This minitutorial discusses several topics:

Debugging without print statements and breaks

On your way to becoming a skilled Lisp "Hacker" you have the feeling that there must be a better way to debug you programs than by placing (break) and (print .. ) scattered about through your code. After all if MCL can provide all the functionality it does for ~$500.00 ( including trace ) , surely there should be some more functionality I can eek out of the trace facility in HLW ( ~$1500 ) . If not. I might as well use DTRACE from Touretzky's book, right? [ Or else maybe by a Mactinosh JUST to write Lisp programs ... don't laugh this has happened. ]

Alas the trace macro in HLW does have a bunch of "Whiz Bang" options. Shamelessly, extracting the Macro description from the HLW documentation.


Purpose:  The macro trace allows you to trace one or more function and to
          perform certain actions at the time a function is called or at 
          the time it returns.  It returns as its value a list of 
          names of all the functions it has traced.

Syntax:   trace { trace-spec}*					[Macro ]

          trace-spec::= function-name | 
                        ( {function-name | ( {function-name}+)}
				{keyword form}* )
	   keyword::= 		:after
		     	|	:before
			|	:break
			| 	:break-on-exit
			| 	:entrycond
			| 	:exitcond
			|	:process
			|	:trace-output
			|	:eval-before
			|	:eval-after
			|	:allocation
			|	:step


Remarks    You can specify the following keyword-values pairs to trace:


 
:after (form)*
Allows the user to supply a list of forms to be evaluated upon returning from the function. The forms are evaluated after printing out the results of the functions call, and there values are also printed out by the tracer.
:before (form)*
Allows the user to supply a list of forms to be evaluated upon entering from the function. The forms are evaluated after printing out the arguments of the function, and there values are also printed out by the tracer.
:break form
Allows the user to enter the debugger directly from the tracing facility. It takes a form which is evaluated after printing the standard information caused by entering the function, and after excuting any :before forms; if it returns nil then tracing continues normally, otherwise break is called.
:break-on-exit form
Allows the user to enter the debugger directly from the tracing facility as above, except that the form is evaluated after printing the standard information caused by returning from the function, and before excuting any :after forms ( so that the debugger is entered after the function call, rather than before ).
:entrycond form
Controls whether or not the standard entry message ( including the function's arguments) is printed. It takes a form and displays the entry message if and only if on entry to the function the form evaluates to a non-nil value.
:exitcond form
Controls whether or not the standard exit message ( including the functions's results) is printed, in an analogous fashion to the above.
:process
may be used to restrict the tracing of a funcdtion to a particular process.
:trace-output stream
May be used to direct all output from the tracer to a specified stream. By using this the user can arrange to dispatch traced output from different functions to different places.
:eval-before (forms*)
Allows the user to supply a list of forms for evaluation upon entering the function. The form are evaluated after printing out the arguments to the function, but unlike :before no output is given.
:eval-after (forms*)
Allows the user to supply a list of forms for evaluation upon leaving the function. The form are evaluated after printing out the results of the function, but unlike :after there is no output.
:allocation
Prints out the amount of memory allocated to the funcdtion, allowing for such things as the amount of consing involed in the function.
:step
causes the function to enter the stepper, thus allowing the user to step through interpreted or compiled code one step at a time.
If a function is already being traced , trace calls untrace before starting the new trace. Calling untrace restores all functions to their normal state.

Tracing through an example function

First we'll need a function to trace. How about our old friend fact?
       CL-USER 3 > (defun fact (num )
              (declare (notinline fact))
              (if (= num 0 )
                  1
              (* num (fact (- num 1 )))))
       FACT
       CL-USER 4 > (trace fact)
       (FACT)
Now if you enter (fact 4 ) you'll see the familiar trace of fact.

Using the various tracing options

However we would like to try out some of the more fancy options. For instance:
      CL-USER 5 > (trace  (fact :exitcond nil  ))
      CL-USER 6 > (fact 4 )

or
      CL-USER 7 > (trace ( fact :break t ))
      CL-USER 8 > (fact 4 )

or
      CL-USER 9 > (trace ( fact  :before (*traced-arglist*)))

or how about

      CL-USER 10 > (trace (fact :break (evenp (first *traced-arglist*))))
       
      CL-USER 11 > (fact 3 )

The variable *traced-arglist* always contains the arguments to the function being traced.

Using tracing and the stepper at the same time.

From the trace specification you can either turn stepping on full time. Or if the form provided to step is a predicate then the stepper will only turn on when the predicate is true.
	CL-USER 12 > (trace (fact :step (= 2 (first *traced-arglist*))))
Will only drop into the stepper once you reach (fact 2 ).

What else can I do?

You think of more examples of your own. If you haven't already you should try out the minitutorial on Stepping and the debugger.


Back to HLW and Tool Time

Last modified: by Lyman S. Taylor(lyman@cc.gatech.edu)
(c) copyright Lyman S. Taylor 1995, All rights reserved