求语法解释(declare (debug cl-case)) 是什么意思

其实下面代码是转自这里: 性能优化求大神指导 - #12,来自 cireu

(defmacro my/number-case (form &rest clauses)
  (declare (indent 1) (debug cl-case))
  (macroexp-let2 macroexp-copyable-p form form
    `(cond ,@(mapcar (lambda (c)
                       (pcase-exhaustive c
                         (`(,head . ,handlers)
                           (if (memq head '(t otherwise))
                               `(t ,@handlers)
                             `((eq ,head ,form)
                               ,@handlers)))))
                     clauses))))

(my/number-case 1
  (1 (+ 1 2))
  (3 4))

对于defmacro 中的(declare (debug cl-case)) 这种用法读懂具体作用是怎样的? 看elisp文档似乎这个cl-case叫edebug-form-spec, 然后就不明白有什么用了 :crazy_face:

也没有搜索到我能理解的解释 :joy:, 想向大神求一个解析或者是解释的链接

a symbol
The symbol must have an Edebug specification, which is used instead. This indirection is repeated until another kind of specification is found. This allows you to inherit the specification from another macro.

> (require 'cl)
cl

> (get 'cl-case 'edebug-form-spec)
(form &rest (sexp body))

用在这里表示 my/number-case的 debug property 和 cl-case 一样。

也就是写 (declare (indent 1) (debug cl-case)) 等于写 (declare (indent 1) (debug (form &rest (sexp body)))


至于上面 (get 'cl-case 'edebug-form-spec) 怎么来的,

(defmacro def-edebug-spec (symbol spec)
  "Set the `edebug-form-spec' property of SYMBOL according to SPEC.
Both SYMBOL and SPEC are unevaluated.  The SPEC can be:
0 (instrument no arguments); t (instrument all arguments);
a symbol (naming a function with an Edebug specification); or a list.
The elements of the list describe the argument types; see
Info node `(elisp)Specification List' for details."
  `(put (quote ,symbol) 'edebug-form-spec (quote ,spec)))

这个东西是用来让Edebug知道你的宏是怎么组成的,比如let(let虽然是special form,也是一种类似宏的结构)

(let ((VAR VAL) ...) BODY ...)

在let里,VAL是需要eval的,VAR是个不需要eval的symbol, BODY也是需要eval的。因此他的debug spec为(from edebug.el)

(def-edebug-spec let
  ((&rest &or (symbolp &optional form) symbolp)
   body))

为什么需要这个呢?当进行逐步调试时,有了debug spec的宏,Edebug会精确停留到被求值的form上,而忽略掉宏里用来组成语法结构的元素。

(require 'cl-lib)

(defun my/filter (pred lst)
  (cl-loop for x in lst
     if (funcall pred x)
     collect x))

(my/filter #'cl-evenp '(1 2 3 4 5 6 7))

在scratch buffer里用C-u C-c C-c在eval这个defun,会进入调试模式,然后尝试eval(my/filter ...),光标会跳到defun开始处,然后用n进行逐步调试,你会发现光标会“认识”出cl-loop的结构,在lst pred x等地方停留。

2 个赞

文档 (info "(elisp) Specification Examples") 跟代码有点脱节了。

因为 edebug-form-spec 有时候会产生冲突,2021年02月13日 00:28:25 的这个提交 d1be48fdedabb451d5c6cf315fd5f09a632e771f 引入了 edebug-elem-spec,增加新的函数 def-edebug-elem-spec,并且删掉了大量的 (def-edebug-spec xxx 语句。

$ git master d1be48fdedabb451d5c6cf315fd5f09a632e771f~
$ rg '^\(def-edebug-spec' lisp/emacs-lisp/edebug.el | wc -l
      25

$ git checkout master
$ rg '^\(def-edebug-spec' lisp/emacs-lisp/edebug.el | wc -l
$ rg '^\(def-edebug-elem-spec' lisp/emacs-lisp/edebug.el | wc -l
      10

自 28 以后在 edebug.el 里直接搜索 (def-edebug-spec let 是找不到的,而是写在一个 pcase-dolist 里,并且定义也发生了变化:

-      (let ((&rest &or symbolp (gate symbolp &optional form)) body))
+      (let ((&rest &or (symbolp &optional form) symbolp) body))