Argspec atau arity dari fungsi bytecode di Emacs 24

8

Saya memiliki kode yang menguji arity dari suatu fungsi. Saya menggunakannya untuk menentukan apakah ada argumen opsional yang ditambahkan dalam versi terbaru dari sebuah paket. Ini membutuhkan subr-arityfungsi bawaan dan mem-parsing daftar objek bytecode dan 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)))
  ))

Ini telah bekerja dengan baik hingga Emacs 23. Di Emacs 24.3 pada Ubuntu 14.04, ini berfungsi dengan baik untuk beberapa fungsi, tetapi tidak untuk yang lain.

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

Jelas format bytecode telah berubah dengan cara yang tidak tercermin dalam 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)]

Bagaimana saya bisa secara andal mengakses daftar argumen objek bytecode? Hanya mengetahui arity akan dilakukan, saya tidak peduli dengan nama argumen. Lebih tepatnya, saya ingin tahu berapa banyak argumen yang wajib dan berapa banyak argumen yang opsional, atau dalam istilah lain, saya ingin informasi yang sama yang saya dapatkan subr-arity. Tentu saja kode saya harus mengatasi bytecode gaya lama dan gaya baru, jadi saya perlu tahu tidak hanya di mana menggali tetapi juga kapan harus menggali di mana.

Gilles 'SANGAT berhenti menjadi jahat'
sumber
Tangensial: mungkin Anda baru saja menghapus bagian ini untuk mengurangi contoh, tetapi Anda mungkin ingin menambahkan dukungan untuk penutupan di Anda function-argspec.
Malabarba
Gilles, apakah Anda memiliki versi final dari function-argspecfungsi Anda di suatu tempat, termasuk fungsi bytecode dan penutupan?
Jordon Biondo
@JordonBiondo saya menambahkannya sebagai jawaban di sini.
Gilles 'SO- berhenti menjadi jahat'

Jawaban:

8

Sunting: Woo! Saya menemukan fungsi yang akan mengambil daftar argumen normal, atau versi integer dan mengembalikan sedikit tanda tangan: byte-compile-arglist-signaturedi bytecomp.el!

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

Jawaban awal:

Saya harap orang lain dapat berpura-pura apakah ini didokumentasikan atau tidak, tetapi inilah yang saya pelajari dengan membaca exec_byte_codebytecode.c di sumber Emacs.

Angka yang Anda lihat digunakan untuk menghitung argspec ketika kode byte sebenarnya dijalankan, saya menganggap ini untuk kinerja, itu sebenarnya cukup pintar.

Saya menulis kode ini untuk menunjukkan kepada Anda bagaimana menghitung arity dari suatu fungsi mengingat nomor itu:

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

Kita dapat melihat di sini bahwa jika kita menjalankan arity-infodengan 1283 kita mendapatkan yang berikut:

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

yang dapat Anda lihat menggambarkan arity vc-print-log-internalsempurna, 5 total args, 3 diperlukan, 2 opsional, tidak memungkinkan & sisanya.

(vc-print-log-internal BACKEND FILES WORKING-REVISION &optional IS-START-REVISION LIMIT)
Jordon Biondo
sumber
Kerja bagus. [filler chars]
Drew
2

Atas permintaan, inilah implementasi saya dari function-argspecdan function-arity. Saya menggunakan solusi asli Jordon Biondo untuk 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 'SANGAT berhenti menjadi jahat'
sumber