Some useful CL debugging code
Thursday, June 16, 2005
I have posted about debugging in CL in the past (see here and here). Well, Wade Humeniuck recently posted on c.l.l. some useful code that he developed for debugging in CL. Here it is:
(defvar *debug-output* t)
(define-condition progn-debug-output (simple-condition) ((form :initarg :form) (multiple-value-list :initarg :multiple-value-list)))
(defun progn-debugger (condition value) (declare (ignorable value)) (typecase condition (progn-debug-output (with-slots (form multiple-value-list) condition (when *debug-output* (cond ((and (= (length multiple-value-list) 1) (eql form (first multiple-value-list))) (format *debug-output* " ~S~%" form)) (t (format *debug-output* " ~S -> ~{~S~^,~}~%" form multiple-value-list)))) (signal condition)))))
(defmacro progn-debug (&body body) (let ((completedp (gensym "completed")) (values (gensym "values"))) `(let ((*debugger-hook* #'progn-debugger) (,completedp nil)) (unwind-protect (progn (when *debug-output* (format *debug-output* "(PROGN-DEBUG~%")) (let ((,values (progn ,@(mapcar (lambda (form) `(handler-case (invoke-debugger (make-condition 'progn-debug-output :form ',form :multiple-value-list (multiple-value-list ,form))) (progn-debug-output (condition) (slot-value condition 'multiple-value-list)))) body)))) (setf ,completedp t) (apply #'values ,values))) (when *debug-output* (unless ,completedp (format *debug-output* " #<Abnormal termination of PROGN-DEBUG>~%")) (format *debug-output* ")~%"))))))
Here's an example session using Wade's code:
CL-USER> (progn-debug (+ 2 3) (format t "this is a test") (defparameter x 10) (values (setf x (/ x 10)) (incf x))) (PROGN-DEBUG (+ 2 3) -> 5 this is a test (FORMAT T "this is a test") -> NIL (DEFPARAMETER X 10) -> X (VALUES (SETF X (/ X 10)) (INCF X)) -> 1,2 ) 1 2 CL-USER>
Another example of a useful debugging utility is the following code snippet which is based on a macro that Kenny Tilton posted on c.l.l. at one stage (unfortunately, I didn't keep a record of the URL of his post and I have made a few minor mods to his code):
(defmacro debug-prt (tag &rest sexps) `(progn ,@(progn (format t "~&debug-prt> start ~a" tag) (mapcar (lambda (s) (format t "~&debug-prt> eval: ~a=~a" s (eval s))) sexps)) (format t "~&debug-prt> end ~a" ,tag)))
Here's an example session using the above code:
CL-USER> (debug-prt :test (+ 2 3) (format t "this is a test") (defparameter x 10) (values (setf x (/ x 10)) (incf x))) debug-prt> start TEST debug-prt> eval: (+ 2 3)=5this is a test debug-prt> eval: (FORMAT T this is a test)=NIL debug-prt> eval: (DEFPARAMETER X 10)=X debug-prt> eval: (VALUES (SETF X (/ X 10)) (INCF X))=1 debug-prt> end TESTKenny's macro is nice and concise and provides a mechanism for tagging different blocks of debug code, however, it doesn't handle the display of multiple values that are returned by a lisp form. Both Wade and Kenny's code are useful code snippets to keep in your toolbox and good examples to look at if you're interested in creating your own debugging code!NIL CL-USER>

