【问题标题】:Obtaining Stack-trace Analog for Macros in Common Lisp在 Common Lisp 中获取宏的堆栈跟踪模拟
【发布时间】:2018-11-08 21:21:28
【问题描述】:

我可能会要求不可能的事情,但我仍然想知道。

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

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

感谢任何帮助或建议。

【问题讨论】:

    标签: macros common-lisp stack-trace trace


    【解决方案1】:

    由于 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 而无需 重新定义宏,所以我不必重新编译调用者如果 定义改变了。

    【讨论】:

    • 请注意,SBCL 现在还有一个可选的解释器:sbcl.org/manual/#Interpreter
    • @RainerJoswig 您是否建议人们可以使用可选解释器来实现目标?
    【解决方案2】:

    是的,AllegroCl 支持宏的跟踪和一般调试。不确定有多少好处是相当努力的,但弗朗茨倾向于做一些好事来使 CL 更可行。专业提示:有一个选项可以关闭我认为他们所谓的宏的源代码级调试,如果您的代码大量使用宏或编译时间会变得疯狂,您会想要这样做。当您认为需要进行源代码调试时,只需将其重新打开即可。

    【讨论】:

    • 您想到了哪些 Allegro 功能?我几乎只与 Allegro 合作,这对我来说是个新闻。
    • franz.com/products/allegrocl/stepper_dialog.lhtml 请注意,这只是谈论步进。我只是假设它们可以被跟踪,等等。我自己从不使用源调试器。我认为我在堆栈跟踪中看到了宏。
    猜你喜欢
    • 2012-08-29
    • 2010-10-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-09-11
    • 2011-01-22
    • 2011-06-01
    相关资源
    最近更新 更多