Получение аналога Stack-trace для макросов в Common Lisp - PullRequest
0 голосов
/ 09 ноября 2018

Возможно, я спрашиваю о невозможном, но, тем не менее, мне интересно.

Можно ли получить аналог трассировки стека для макросов? То есть, если установить определенную точку останова внутри определенной функции, трассировка макроса стека выдаст список всех макросов (возможно, с их входами), которые были расширены макросом для достижения этого уровня в коде.

Из того, что я понимаю, в настоящее время это невозможно, но это может быть из-за моего поверхностного понимания. Allegro или SBCL разрешают или отслеживают такую ​​информацию? Похоже, это было бы очень полезно для отладки макросов.

Любая помощь или совет приветствуется.

Ответы [ 2 ]

0 голосов
/ 11 ноября 2018

Да, AllegroCl поддерживает трассировку и общую отладку макросов. Довольно много усилий для того, чтобы не знать, сколько пользы, но Франц стремится делать хорошие вещи, чтобы сделать CL более жизнеспособным. Совет для профессионалов: есть возможность отключить то, что я думаю, они называют отладкой макросов на уровне исходного кода, и вы захотите сделать это, если ваш код интенсивно использует макросы, или время компиляции может сойти с ума. Просто включите его, когда вам понадобится отладка исходного кода.

0 голосов
/ 09 ноября 2018

Поскольку 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 без переопределение макроса, и поэтому мне не нужно перекомпилировать вызывающие, если определение меняется.

...