Поскольку SBCL является реализацией только для компилятора , что означает, что весь код автоматически компилируется (в отличие от "интерпретируемого"). Вызовы макросов расширяются как часть компиляции, поэтому тот факт, что что-то было вызовом макроса, теряется.
(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 также предлагает графический интерфейс для установки точек останова, с которыми я не знаком.)
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 принимает много опций, чтобы быть более подробным, это показывает форму (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 отладка на уровне источника может помочь).
В целом, когда дело доходит до определения макросов, чтобы помочь отладке, попробуйте расширить вызовы функций, а не большие объемы кода. Например. вместо:
(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)))
, поэтому он попадает в трассировку стека и его легко переопределить.
Смотрите это великое сообщение Кента Питмана :
Кстати, тоже вернемся к CL, вы должны знать, что когда я пишу эти
Макросы WITH-XXX, я почти всегда сопровождаю их CALL-WITH-XXX
так что я могу сделать любой вид звонка. Но я нахожу, что почти никогда не использую
CALL-WITH-xxx, даже когда я был одним, чтобы предоставить его в качестве опции.
Основная причина, по которой я пишу их, - не использовать их, а
переопределить проще, так как я могу переопределить CALL-WITH-xxx без
переопределение макроса, и поэтому мне не нужно перекомпилировать вызывающие, если
определение меняется.