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

时间:2018-11-08 21:21:29

标签: macros common-lisp stack-trace trace

我可能在问不可能的事,但我仍在想。

是否可以获得宏的堆栈跟踪的模拟?也就是说,如果在某个函数中设置了一个断点,则宏堆栈跟踪将列出所有已宏扩展到代码中该级别的宏(可能包括其输入)。

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

任何帮助或建议都值得赞赏。

2 个答案:

答案 0 :(得分:6)

因为SBCL是compiler-only implementation,这意味着所有代码都是自动编译的(与“解释”相反)。对宏的调用会在编译过程中扩展,因此丢失了宏调用这一事实。

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

(defun foo (x) (m x))

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)
[...]

一些实现,例如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))
   [...]

zoom command包含许多选项,使它们更详细,显示形式为(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))

当您(compile 'foo)时,宏调用将被展开(如SBCL),并且不再显示在回溯中(但是Allegro的source-level debugging可以提供帮助)。

通常,在定义宏时,要帮助调试尝试扩展为函数调用,而不是大型代码。例如。代替:

(defmacro with-foo ((var-x var-y thing) &body body)
   `(let ((,var-x (..derive from ,thing ..))
          (,var-y (..derive from ,thing ..)))
       ,@body))

我会这样写:

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

因此它最终出现在堆栈跟踪中,并且易于重新定义。 参见此great post by Kent Pitman

  

顺便说一句,回到CL,您应该知道,当我编写这些内容时       WITH-xxx宏,我几乎总是伴随着CALL-WITH-xxx       这样我就可以打电话。但是我发现我几乎从不使用       CALL-WITH-xxx,即使我是提供它的一种选择。       我写它们的主要原因不是使用它们而是       重新定义更加容易,因为我可以重新定义CALL-WITH-xxx,而无需       重新定义了宏,因此,如果出现以下情况,我不必重新编译调用方       定义会改变。

答案 1 :(得分:2)

是的,AllegroCl支持跟踪和一般的宏调试。尽管不确定付出多少努力,但弗朗兹(Franz)往往会做一些好事以使CL更加可行。专家提示:有一个选项可以关闭我认为它们称为宏的源代码级调试的选项,并且如果您的代码大量使用宏或者编译时间变得很疯狂,您将希望这样做。认为需要调试源代码时,只需重新打开即可。