関数の挙動を調べたい

xyzzyにはデバッグのためにstack-traceとstepが用意されている。ところが、これらの関数・マクロの使い方がよくわからない。

(stack-trace)
>CALL STACK  9: (stack-trace)
>CALL STACK  8: (eval (stack-trace))
>CALL STACK  7: (system:*byte-code ...)
>CALL STACK  6: (eval-region 8502 8515 #<buffer stream 51254204>)
>CALL STACK  5: (system:*byte-code ...)
>CALL STACK  4: (eval-last-sexp #<buffer stream 51254204>)
>CALL STACK  3: (system:*byte-code ...)
>CALL STACK  2: (#<lexical-closure: eval-print-last-sexp>)
>CALL STACK  1: (command-execute eval-print-last-sexp)

これは*scratch*上でC-jで実行した例で、多分stack-traceが呼び出されたときのstackの状態を表示するものなのだろう。でも、関数の動作を追うにはどうやって使えばいいのだろう。試しに再帰関数に組み込んでみると、

(defun f (num)
;  "特に意味のない再帰をする関数"
  (stack-trace)
  (if (plusp num)
      (f (1- num))
    0))

(f 3)
|
>CALL STACK 11: (stack-trace)
>CALL STACK 10: (block (f (stack-trace) (if (plusp num) (f (1- num)) ...)))
>CALL STACK  9: (f 3)
>CALL STACK  8: (eval (f 3))
>CALL STACK  7: (system:*byte-code ...)
>CALL STACK  6: (eval-region 9033 9038 #<buffer stream 51256572>)
>CALL STACK  5: (system:*byte-code ...)
>CALL STACK  4: (eval-last-sexp #<buffer stream 51256572>)
>CALL STACK  3: (system:*byte-code ...)
>CALL STACK  2: (#<lexical-closure: eval-print-last-sexp>)
>CALL STACK  1: (command-execute eval-print-last-sexp)

>CALL STACK 14: (stack-trace)
>CALL STACK 13: (block (f (stack-trace) (if (plusp num) (f (1- num)) ...)))
>CALL STACK 12: (f 2)
>CALL STACK 11: (if ((plusp num) (f (1- num)) 0))
>CALL STACK 10: (block (f (stack-trace) (if (plusp num) (f (1- num)) ...)))
>CALL STACK  9: (f 3)
>CALL STACK  8: (eval (f 3))
>CALL STACK  7: (system:*byte-code ...)
>CALL STACK  6: (eval-region 9033 9038 #<buffer stream 51256572>)
>CALL STACK  5: (system:*byte-code ...)
>CALL STACK  4: (eval-last-sexp #<buffer stream 51256572>)
>CALL STACK  3: (system:*byte-code ...)
>CALL STACK  2: (#<lexical-closure: eval-print-last-sexp>)
>CALL STACK  1: (command-execute eval-print-last-sexp)

>CALL STACK 17: (stack-trace)
>CALL STACK 16: (block (f (stack-trace) (if (plusp num) (f (1- num)) ...)))
>CALL STACK 15: (f 1)
>CALL STACK 14: (if ((plusp num) (f (1- num)) 0))
>CALL STACK 13: (block (f (stack-trace) (if (plusp num) (f (1- num)) ...)))
>CALL STACK 12: (f 2)
>CALL STACK 11: (if ((plusp num) (f (1- num)) 0))
>CALL STACK 10: (block (f (stack-trace) (if (plusp num) (f (1- num)) ...)))
>CALL STACK  9: (f 3)
>CALL STACK  8: (eval (f 3))
>CALL STACK  7: (system:*byte-code ...)
>CALL STACK  6: (eval-region 9033 9038 #<buffer stream 51256572>)
>CALL STACK  5: (system:*byte-code ...)
>CALL STACK  4: (eval-last-sexp #<buffer stream 51256572>)
>CALL STACK  3: (system:*byte-code ...)
>CALL STACK  2: (#<lexical-closure: eval-print-last-sexp>)
>CALL STACK  1: (command-execute eval-print-last-sexp)

>CALL STACK 20: (stack-trace)
>CALL STACK 19: (block (f (stack-trace) (if (plusp num) (f (1- num)) ...)))
>CALL STACK 18: (f 0)
>CALL STACK 17: (if ((plusp num) (f (1- num)) 0))
>CALL STACK 16: (block (f (stack-trace) (if (plusp num) (f (1- num)) ...)))
>CALL STACK 15: (f 1)
>CALL STACK 14: (if ((plusp num) (f (1- num)) 0))
>CALL STACK 13: (block (f (stack-trace) (if (plusp num) (f (1- num)) ...)))
>CALL STACK 12: (f 2)
>CALL STACK 11: (if ((plusp num) (f (1- num)) 0))
>CALL STACK 10: (block (f (stack-trace) (if (plusp num) (f (1- num)) ...)))
>CALL STACK  9: (f 3)
>CALL STACK  8: (eval (f 3))
>CALL STACK  7: (system:*byte-code ...)
>CALL STACK  6: (eval-region 9033 9038 #<buffer stream 51256572>)
>CALL STACK  5: (system:*byte-code ...)
>CALL STACK  4: (eval-last-sexp #<buffer stream 51256572>)
>CALL STACK  3: (system:*byte-code ...)
>CALL STACK  2: (#<lexical-closure: eval-print-last-sexp>)
>CALL STACK  1: (command-execute eval-print-last-sexp)

0

なんだか、とても冗長である。stackの7番までと最深層の(stack-trace)は必要ない。それを除けば、見たいところ−どのように関数が展開されているか−はわかるので、この使い方であってるのだろうか。


もう一つのstepだが、これは現在実行している関数と、その引数、結果を逐次ダイアログで表示するもののようだ。ダイアログで表示されるので、1回や2回なら良いのだが、何重にもネストして実行される関数を追うにはやかましくて使えない。なぜこのようになっているのだろう。


使いづらいので、ダイアログではなくバッファに書き出すようにしてみたが、それでいいのだろうか・・・

; misc.l(Tetsuya Kamei) より、step step-apply-hookを改変。

(defmacro step-trace (form &environment env)
  `(let ((lisp::*bypass-step* nil))
     (evalhook ',form nil #'step-trace-apply-hook ',env)))

(defun step-trace-apply-hook (fn args)
  (let ((values (multiple-value-list
         (applyhook fn args nil (and (not lisp::*bypass-step*) #'step-trace-apply-hook)))))
    (when (and (not lisp::*bypass-step*)
               (step-trace-show fn args values))
      (setq lisp::*bypass-step* t))
    (values-list values)))

(defun step-trace-show (fn args values)
  (save-excursion
    (with-output-to-buffer ((get-buffer-create "*step*"))
      (format t "Function: ~S~%Args:~{ ~S~}~%Value:~{ ~S~}~%~%"
              fn args values))))