在Common Lisp中获取宏的堆栈跟踪模拟

Mad*_*ist 3 macros trace common-lisp stack-trace

我可能会要求不可能,但我仍在疑惑.

是否有可能获得宏的堆栈跟踪的模拟?也就是说,如果在某个函数中设置了一个断点,那么宏栈跟踪将列出宏扩展的所有宏(可能带有它们的输入)以达到代码中的那个级别.

据我所知,目前这是不可能的,但可能是由于我的理解浅薄.Allegro或SBCL是否允许或跟踪此类信息?看起来这对于调试宏非常有用.

任何帮助或建议表示赞赏.

zut*_*zut 6

由于SBCL是仅编译器实现,意味着所有代码都是自动编译的(与"解释"相反).作为编译的一部分,对宏的调用被扩展,因此某些东西是宏调用的事实将丢失.

(defmacro m (n)
   `(/ 10 ,n))

(defun foo (x) (m x))
Run Code Online (Sandbox Code Playgroud)

SBCL:

* (foo 0)

debugger invoked on a DIVISION-BY-ZERO in thread
#<THREAD "main thread" RUNNING {1001E06493}>:
  arithmetic error DIVISION-BY-ZERO signalled
Operation was /, operands (10 0).

Type HELP for debugger help, or (SB-EXT:EXIT) to exit from SBCL.

restarts (invokable by number or by possibly-abbreviated name):
  0: [ABORT] Exit debugger, returning to top level.

(SB-KERNEL::INTEGER-/-INTEGER 10 0)
0] backtrace

Backtrace for: #<SB-THREAD:THREAD "main thread" RUNNING {1001E06493}>
0: (SB-KERNEL::INTEGER-/-INTEGER 10 0)
1: (FOO 0)
2: (SB-INT:SIMPLE-EVAL-IN-LEXENV (FOO 0) #<NULL-LEXENV>)
3: (EVAL (FOO 0))
4: (INTERACTIVE-EVAL (FOO 0) :EVAL NIL)
[...]
Run Code Online (Sandbox Code Playgroud)

一些实现,例如Allegro CL,既支持解释代码也支持编译代码,第一个实现有助于调试,第二个提供更好的性能.(我在这里展示了命令行交互.Allegro还提供了一个GUI来设置我不熟悉的断点.)

cl-user(4): (foo 0)
Error: Attempt to divide 10 by zero.
  [condition type: division-by-zero]

Restart actions (select using :continue):
 0: Return to Top Level (an "abort" restart).
 1: Abort entirely from this (lisp) process.

[1] cl-user(5): :zoom
Evaluation stack:

   (error division-by-zero :operation ...)
 ->(/ 10 0)
   (foo 0)
   (eval (foo 0))
   [...]
Run Code Online (Sandbox Code Playgroud)

缩放命令需要许多选项更详细的,这说明形式(block foo (m x)):

[1] cl-user(6): :zoom :all t
Evaluation stack:

... 4 more newer frames ...

   ((:runsys "lisp_apply"))
   [... sys::funcall-tramp ]
   (excl::error-from-code 17 nil ...)
   (sys::..runtime-operation "integer_divide" :unknown-args)
   (excl::/_2op 10 0)
 ->(/ 10 0)
   [... excl::eval-as-progn ]
   (block foo (m x))
   (foo 0)
   (sys::..runtime-operation "comp_to_interp" 0)
   [... excl::%eval ]
   (eval (foo 0))
Run Code Online (Sandbox Code Playgroud)

当您(compile 'foo)的宏调用将被扩展(如SBCL)并且不再显示回溯时(但Allegro的源代码级调试可能有所帮助).

一般来说,在定义宏时,为了帮助调试尝试扩展到函数调用而不是大型代码.例如,而不是:

(defmacro with-foo ((var-x var-y thing) &body body)
   `(let ((,var-x (..derive from ,thing ..))
          (,var-y (..derive from ,thing ..)))
       ,@body))
Run Code Online (Sandbox Code Playgroud)

我会这样写:

(defmacro with-foo ((var-x var-y thing) &body body)
   `(call-with-foo (lambda (,var-x ,var-y) ,@body) ,thing))

(defun call-with-foo (func thing)
  (let ((x (..derive from thing ..)
        (y (..derive from thing ..))
   (funcall func x y)))
Run Code Online (Sandbox Code Playgroud)

所以它最终在堆栈跟踪中,很容易重新定义.看看Kent Pitman的这篇精彩文章:

顺便说一句,回到CL,你应该知道当我写这些WITH-xxx宏时,我几乎总是陪着他们一个CALL-WITH-xxx,这样我就可以做任何一种调用.但我发现即使我是一个提供选项的人,我也几乎从不使用CALL-WITH-xxx.我编写它们的主要原因不是使用它们,而是为了使重定义更容易,因为我可以重新定义CALL-WITH-xxx而无需重新定义宏,因此如果定义更改,我不必重新编译调用者.

  • 请注意,SBCL现在还有一个可选的解释器:http://www.sbcl.org/manual/#Interpreter (2认同)