Argspec ou arity d'une fonction de bytecode dans Emacs 24

8

J'ai du code qui teste l'arité d'une fonction. Je l'utilise pour déterminer si des arguments facultatifs ajoutés dans des versions récentes d'un package sont présents. Il appelle subr-aritydes fonctions intégrées et analyse l'arglist des objets bytecode et des 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)))
  ))

Cela a bien fonctionné jusqu'à Emacs 23. Dans Emacs 24.3 sur Ubuntu 14.04, cela fonctionne bien pour certaines fonctions, mais pas pour d'autres.

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

Évidemment, le format du bytecode a changé d'une manière qui n'est pas reflétée dans le manuel .

(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)]

Comment accéder de manière fiable à la liste d'arguments d'un objet bytecode? Tout en sachant que l'arité ferait l'affaire, je me fiche des noms des arguments. Plus précisément, je veux savoir combien d'arguments sont obligatoires et combien d'arguments sont facultatifs, ou en d'autres termes, je veux les mêmes informations que j'obtiens subr-arity. Bien sûr, mon code doit gérer à la fois l'ancien et le nouveau bytecode, j'ai donc besoin de savoir non seulement où creuser, mais aussi quand creuser où.

Gilles 'SO- arrête d'être méchant'
la source
Tangentiel: vous venez peut-être de supprimer cette partie pour réduire l'exemple, mais vous voudrez peut-être ajouter un support pour les fermetures dans votre function-argspec.
Malabarba
Gilles, avez-vous une version finalisée de votre function-argspecfonction quelque part, y compris les fonctions de bytecode et les fermetures?
Jordon Biondo
@JordonBiondo Je l'ai ajouté comme réponse ici.
Gilles 'SO- arrête d'être méchant'

Réponses:

8

Edit: Woo! J'ai trouvé une fonction qui prendra soit la liste d'arguments normale, soit la version entière et retournera une sorte de signature: byte-compile-arglist-signaturedans bytecomp.el!

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

Réponse initiale:

J'espère que quelqu'un d'autre pourra sonner si cela est documenté quelque part mais c'est ce que j'ai appris en lisant l' exec_byte_codeintérieur de bytecode.c dans la source Emacs.

Le nombre que vous voyez est utilisé pour calculer l'argspec lorsque le code d'octet est réellement exécuté, je suppose que pour les performances, c'est en fait assez intelligent.

J'ai écrit ce code pour vous montrer comment calculer l'arité d'une fonction étant donné ce nombre:

(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))))

Nous pouvons voir ici que si nous exécutons arity-infoavec 1283, nous obtenons ce qui suit:

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

que vous pouvez voir décrit l'arité de vc-print-log-internalparfaitement, 5 arguments totaux, 3 requis, 2 facultatifs, ne permet pas & reste.

(vc-print-log-internal BACKEND FILES WORKING-REVISION &optional IS-START-REVISION LIMIT)
Jordon Biondo
la source
Bon travail. [caractères de remplissage]
Drew
2

Sur demande, voici mon implémentation de function-argspecet function-arity. J'ai utilisé la solution originale de Jordon Biondo pour le bytecode Emacs 24.

(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- arrête d'être méchant'
la source