Argspec oder Arität einer Bytecode-Funktion in Emacs 24

8

Ich habe Code, der die Arität einer Funktion testet. Ich verwende es, um festzustellen, ob optionale Argumente vorhanden sind, die in neueren Versionen eines Pakets hinzugefügt wurden. Es subr-arityerfordert integrierte Funktionen und analysiert die Liste der Bytecode-Objekte und 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)))
  ))

Dies hat bis Emacs 23 gut funktioniert. In Emacs 24.3 unter Ubuntu 14.04 funktioniert es für einige Funktionen gut, für andere jedoch nicht.

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

Offensichtlich hat sich das Bytecode-Format auf eine Weise geändert, die im Handbuch nicht berücksichtigt wird .

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

Wie kann ich zuverlässig auf die Argumentliste eines Bytecode-Objekts zugreifen? Nur zu wissen, dass die Arität ausreichen würde, interessieren mich die Argumentnamen nicht. Genauer gesagt möchte ich wissen, wie viele Argumente obligatorisch und wie viele Argumente optional sind, oder mit anderen Worten, ich möchte dieselben Informationen, von denen ich erhalte subr-arity. Natürlich muss mein Code sowohl mit Bytecode im alten als auch im neuen Stil umgehen, daher muss ich nicht nur wissen, wo ich graben muss, sondern auch, wann ich wo graben muss.

Gilles 'SO - hör auf böse zu sein'
quelle
Tangential: Vielleicht haben Sie diesen Teil gerade entfernt, um das Beispiel zu verkleinern, aber Sie möchten möglicherweise Unterstützung für Verschlüsse in Ihrem hinzufügen function-argspec.
Malabarba
Gilles, haben Sie function-argspecirgendwo eine endgültige Version Ihrer Funktion, einschließlich Bytecode-Funktionen und Schließungen?
Jordon Biondo
@JordonBiondo Ich habe es hier als Antwort hinzugefügt.
Gilles 'SO - hör auf böse zu sein'

Antworten:

8

Edit: Woo! Ich habe eine Funktion gefunden, die entweder die normale Argumentliste oder die Ganzzahlversion verwendet und eine Art Signatur byte-compile-arglist-signaturezurückgibt : in bytecomp.el!

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

Erste Antwort:

Ich hoffe, jemand anderes kann sich einschalten, ob dies irgendwo dokumentiert ist oder nicht, aber das habe ich gelernt, indem ich exec_byte_codein bytecode.c in der Emacs-Quelle gelesen habe .

Die Zahl, die Sie sehen, wird verwendet, um die Argspec zu berechnen, wenn der Bytecode tatsächlich ausgeführt wird. Ich gehe davon aus, dass dies für die Leistung ziemlich clever ist.

Ich habe diesen Code geschrieben, um Ihnen zu zeigen, wie Sie die Arität einer Funktion mit dieser Zahl berechnen können:

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

Wir können hier sehen, dass wir arity-infomit 1283 Folgendes erhalten:

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

Was Sie sehen können, beschreibt die Arität von vc-print-log-internalperfekt, 5 Gesamtargumente, 3 erforderlich, 2 optional, erlaubt & ruhen nicht.

(vc-print-log-internal BACKEND FILES WORKING-REVISION &optional IS-START-REVISION LIMIT)
Jordon Biondo
quelle
Gut gemacht. [Füllzeichen]
Drew
2

Auf Anfrage hier meine Implementierung von function-argspecund function-arity. Ich habe Jordon Biondos ursprüngliche Lösung für Emacs 24-Bytecode verwendet.

(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 - hör auf böse zu sein'
quelle