为 Common Lisp 中的宏获取堆栈跟踪模拟
Obtaining Stack-trace Analog for Macros in Common Lisp
我可能要求不可能,但我仍然想知道。
是否可以获得宏堆栈跟踪的模拟?也就是说,如果在某个函数内设置断点,宏堆栈跟踪将列出所有被宏扩展以达到代码中该级别的宏(可能及其输入)。
据我了解,目前这是不可能的,但可能是我认识浅薄所致。 Allegro 或 SBCL 是否允许或跟踪此类信息?看来这对调试宏非常有用。
如有任何帮助或建议,我们将不胜感激。
因为 SBCL 是 compiler-only implementation,这意味着所有代码都是自动编译的(与 "interpreted" 相反)。对宏的调用作为编译的一部分进行扩展,因此某些东西是宏调用的事实丢失了。
(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:
Incidentally, too, back to CL, you should know that when I write these
WITH-xxx macros, I almost always accompany them with a CALL-WITH-xxx
so that I can do either kind of call. But I find I almost never use
the CALL-WITH-xxx even when I was the one to provide it as an option.
The main reason I write them is not to use them but to make
redefinition easier, since I can redefine the CALL-WITH-xxx without
redefining the macro, and so I don't have to recompile the callers if
the definition changes.
是的,AllegroCl 支持宏的跟踪和一般调试。相当大的努力不知道有多少好处,但 Franz 倾向于做好事以使 CL 更可行。专业提示:有一个选项可以关闭我认为他们所谓的宏的源代码级调试,如果您的代码大量使用宏或编译时间可能变得疯狂,您将想要这样做。当您认为需要源代码调试时,只需将其重新打开即可。
我可能要求不可能,但我仍然想知道。
是否可以获得宏堆栈跟踪的模拟?也就是说,如果在某个函数内设置断点,宏堆栈跟踪将列出所有被宏扩展以达到代码中该级别的宏(可能及其输入)。
据我了解,目前这是不可能的,但可能是我认识浅薄所致。 Allegro 或 SBCL 是否允许或跟踪此类信息?看来这对调试宏非常有用。
如有任何帮助或建议,我们将不胜感激。
因为 SBCL 是 compiler-only implementation,这意味着所有代码都是自动编译的(与 "interpreted" 相反)。对宏的调用作为编译的一部分进行扩展,因此某些东西是宏调用的事实丢失了。
(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:
Incidentally, too, back to CL, you should know that when I write these WITH-xxx macros, I almost always accompany them with a CALL-WITH-xxx so that I can do either kind of call. But I find I almost never use the CALL-WITH-xxx even when I was the one to provide it as an option. The main reason I write them is not to use them but to make redefinition easier, since I can redefine the CALL-WITH-xxx without redefining the macro, and so I don't have to recompile the callers if the definition changes.
是的,AllegroCl 支持宏的跟踪和一般调试。相当大的努力不知道有多少好处,但 Franz 倾向于做好事以使 CL 更可行。专业提示:有一个选项可以关闭我认为他们所谓的宏的源代码级调试,如果您的代码大量使用宏或编译时间可能变得疯狂,您将想要这样做。当您认为需要源代码调试时,只需将其重新打开即可。