Argspec ou aridade de uma função de bytecode no Emacs 24

8

Eu tenho um código que testa a aridade de uma função. Eu o uso para determinar se argumentos opcionais adicionados em versões recentes de um pacote estão presentes. Ele chama subr-arityfunções internas e analisa o arglist de objetos de bytecode e 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)))
  ))

Isso funcionou bem até o Emacs 23. No Emacs 24.3 no Ubuntu 14.04, ele está funcionando bem para algumas funções, mas não para outras.

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

Evidentemente, o formato do bytecode mudou de uma maneira que não é refletida no 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)]

Como posso acessar com segurança a lista de argumentos de um objeto de bytecode? Apenas sabendo que a aridade serviria, não me importo com os nomes dos argumentos. Mais precisamente, quero saber quantos argumentos são obrigatórios e quantos argumentos são opcionais ou, em outros termos, quero as mesmas informações que recebo subr-arity. É claro que meu código deve lidar com os bytecodes do estilo antigo e do novo, então eu preciso saber não apenas onde cavar, mas também quando cavar onde.

Gilles 'SO- parar de ser mau'
fonte
Tangencial: talvez você tenha acabado de remover esta parte para reduzir o exemplo, mas convém adicionar suporte para fechamentos na sua function-argspec.
Malabarba
Gilles, você tem uma versão finalizada de sua function-argspecfunção em algum lugar, incluindo funções e fechamentos de bytecode?
21815 Jordon Biondo
@JordonBiondo Adicionei como resposta aqui.
Gilles 'SO- stop being evil'

Respostas:

8

Editar: Woo! Eu encontrei uma função que pega a lista de argumentos normal ou a versão inteira e retorna um pouco de uma assinatura: byte-compile-arglist-signatureem bytecomp.el!

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

Resposta inicial:

Espero que alguém possa comentar se isso está ou não documentado em algum lugar, mas foi isso que aprendi lendo exec_byte_codedentro do bytecode.c na fonte do Emacs.

O número que você vê é usado para calcular o argspec quando o código de bytes está realmente sendo executado, presumo que para desempenho, é realmente muito inteligente.

Eu escrevi este código para mostrar como calcular a aridade de uma função, dado esse 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 aqui que, se arity-inforodarmos com 1283, obtemos o seguinte:

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

que você pode ver descreve a aridade de vc-print-log-internalperfeitamente, 5 args totais, 3 necessários, 2 opcionais, não permitem e descansam.

(vc-print-log-internal BACKEND FILES WORKING-REVISION &optional IS-START-REVISION LIMIT)
Jordon Biondo
fonte
Bom trabalho. [caracteres de preenchimento]
Drew
2

Por solicitação, aqui está minha implementação de function-argspece function-arity. Usei a solução original de Jordon Biondo para o bymacode 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- parar de ser mau'
fonte