Argspec o arity de una función de bytecode en Emacs 24

8

Tengo un código que prueba la aridad de una función. Lo uso para determinar si los argumentos opcionales agregados en las versiones recientes de un paquete están presentes. Se llama subr-aritya las funciones incorporadas y analiza el arglist de objetos de código de bytes y lambdas.

(defun function-argspec (func)
  (if (symbolp func) (setq func (indirect-function func)))
  (cond
   ((byte-code-function-p func)
    (aref func 0))
   ((and (consp func)
         (eq (car func) 'lambda)
         (consp (cdr func)))
    (car (cdr func)))
  ))

Esto ha funcionado bien hasta Emacs 23. En Emacs 24.3 en Ubuntu 14.04, funciona bien para algunas funciones, pero no para otras.

(function-argspec 'revert-buffer)
(&optional ignore-auto noconfirm preserve-modes)
(require 'vc)
vc
(function-argspec 'vc-print-log-internal)
1283

Evidentemente, el formato del código de bytes ha cambiado de una manera que no se refleja en el manual .

(symbol-function 'vc-print-log-internal)
#[1283 \301\211\302\301\211\203\211@\303!\203\304\262A\266\202\202\210\203'\305>\202*\306>??\262\2036\307\2027\310\262\311
\312\313\314\315\316
$\317"\320\321%\312\322\323\315\316#\324"\325\326%\312\327\330\315\316!\331"\332\333%\312\334\335\315\316%\336"\325\337%&\262\207 [vc-log-short-style nil *vc-change-log* file-directory-p t directory file short long vc-log-internal-common make-byte-code 1028 \304\305\303\301\205\300\302&\207 vconcat vector [vc-call-backend print-log] 12 

(fn BK BUF TYPE-ARG FILES-ARG) 771 \303\300\301\302$\207 [vc-print-log-setup-buttons] 8 

(fn BK FILES-ARG RET) 257 \301\302\300#\207 [vc-call-backend show-log-entry] 5 

(fn BK) 514 \305\300\301\302\303\304%\207 [vc-print-log-internal] 

(fn IGNORE-AUTO NOCONFIRM)] 28 

(fn BACKEND FILES WORKING-REVISION &optional IS-START-REVISION LIMIT)]

¿Cómo puedo acceder de manera confiable a la lista de argumentos de un objeto bytecode? Solo sabiendo que el arity funcionaría, no me importan los nombres de los argumentos. Más precisamente, quiero saber cuántos argumentos son obligatorios y cuántos argumentos son opcionales, o en otros términos, quiero la misma información que obtengo subr-arity. Por supuesto, mi código debe hacer frente tanto al bytecode antiguo como al nuevo, por lo que necesito saber no solo dónde cavar sino también cuándo cavar dónde.

Gilles 'SO- deja de ser malvado'
fuente
Tangencial: tal vez acaba de eliminar esta parte para recortar el ejemplo, pero es posible que desee agregar soporte para los cierres en su function-argspec.
Malabarba
Gilles, ¿tienes una versión finalizada de tu function-argspecfunción en algún lugar, incluidas las funciones de bytecode y los cierres?
Jordon Biondo
@JordonBiondo Lo agregué como respuesta aquí.
Gilles 'SO- deja de ser malvado'

Respuestas:

8

Editar: Woo! Encontré una función que tomará la lista de argumentos normal o la versión entera y devolverá algo de una firma: ¡ byte-compile-arglist-signatureen bytecomp.el!

(byte-compile-arglist-signature 1283) ;; => (3 . 5)

Respuesta inicial

Espero que alguien más pueda comentar si esto está documentado o no en alguna parte, pero esto es lo que aprendí al leer exec_byte_codebytecode.c en la fuente de Emacs.

El número que ve se usa para calcular el argspec cuando se está ejecutando el código de bytes, supongo que para el rendimiento, en realidad es bastante inteligente.

Escribí este código para mostrarle cómo calcular la aridad de una función dado ese número:

(defun arity-info (byte-code-int)
  (let* ((required  (logand byte-code-int 127))
         (total-named  (lsh byte-code-int -8))
         (optional (- total-named required))
         (allow-rest  (if (not (zerop (logand byte-code-int 128))) "yes" "no")))
    (list
     (cons 'required required)
     (cons 'total-named total-named)
     (cons 'optional optional)
     (cons 'allow-rest allow-rest))))

Podemos ver aquí que si corremos arity-infocon 1283 obtenemos lo siguiente:

((required . 3) (total-named . 5) (optional . 2) (allow-rest . "no"))

que puede ver describe la aridad de vc-print-log-internalperfectamente, 5 args totales, 3 requeridos, 2 opcionales, no permite y descansa.

(vc-print-log-internal BACKEND FILES WORKING-REVISION &optional IS-START-REVISION LIMIT)
Jordon Biondo
fuente
Buen trabajo. [caracteres de relleno]
Drew
2

A pedido, aquí está mi implementación de function-argspecy function-arity. Utilicé la solución original de Jordon Biondo para Emacs 24 bytecode.

(cond
 ;; XEmacs
 ((fboundp 'compiled-function-arglist)
  (defalias 'emacsen-compiled-function-arglist 'compiled-function-arglist))
 ;; GNU Emacs
 (t
  (defun emacsen-make-up-number-arglist (start end tail)
    (while (< start end)
      (setq end (1- end))
      (setq tail (cons (intern (format "a%d" end)) tail)))
    tail)
  (defun emacsen-compiled-function-arglist (func)
    (let ((a (aref func 0)))
      (if (integerp a)
          ;; An integer encoding the arity. Encountered in Emacs 24.3.
          ;; /emacs/971/argspec-or-arity-of-a-bytecode-function-in-emacs-24/973#973
          (let ((arglist (if (zerop (logand a 128))
                             nil
                           '(&rest rest)))
                (mandatory (logand a 127))
                (nonrest (lsh a -8)))
            (if (> nonrest mandatory)
                (setq arglist (cons '&optional (emacsen-make-up-number-arglist mandatory nonrest arglist))))
            (emacsen-make-up-number-arglist 0 mandatory arglist))
        ;; Otherwise: this is the arglist. The only format I've seen up to GNU 23.
        a)))))

(defun function-argspec (func)
  "Return a function's argument list.
For byte-compiled functions in Emacs >=24, some information may be lost as the
byte compiler sometimes erases argument names. In this case, fake argument names
are reconstructed."
  (if (symbolp func) (setq func (indirect-function func)))
  (cond
   ((subrp func)
    (let ((docstring (documentation func)))
      (save-match-data
        (if (string-match "\n.*\\'" docstring)
            (let ((form (read (match-string 0 docstring))))
              (cdr form))
          nil))))
   ((byte-code-function-p func)
    (emacsen-compiled-function-arglist func))
   ((and (consp func)
         (eq (car func) 'lambda)
         (consp (cdr func)))
    (car (cdr func)))
   ((and (consp func)
         (eq (car func) 'closure)
         (consp (cdr func))
         (consp (cdr (cdr func))))
    (car (cdr (cdr func))))
   (t (signal 'wrong-type-argument
              (list 'functionp func)))))

(defun function-arity (func)
  "Return a function's arity as (MIN . MAX).
Return minimum and maximum number of args allowed for SUBR.
The returned value is a pair (MIN . MAX).  MIN is the minimum number
of args.  MAX is the maximum number or the symbol `many', for a
function with `&rest' args, or `unevalled' for a special form.

This function is like `subr-arity', but also works with user-defined
and byte-code functions. Symbols are dereferenced through
`indirect-function'."
  ;; TODO: keyword support
  (if (symbolp func) (setq func (indirect-function func)))
  (cond
   ((subrp func)
    (subr-arity func))
   (t
    (let ((mandatory 0) (optional 0) (rest nil)
          (where 'mandatory))
      (when (and (consp func) (eq 'macro (car func)))
        (setq func (cdr func))
        (setq rest 'unevalled))
      (let ((argspec (function-argspec func)))
        (dolist (arg argspec)
          (cond
           ((eq arg '&optional) (setq where 'optional))
           ((eq arg '&rest) (unless rest (setq rest 'many)))
           (t (set where (+ (symbol-value where) 1)))))
        (cons mandatory (or rest (+ mandatory optional))))))))
Gilles 'SO- deja de ser malvado'
fuente