https://lisp-journey.gitlab.io/blog/debugging-lisp-trace-options-break-on-conditions/ Toggle navigation Lisp journey * Posts * Web dev * Python VS Lisp * Good resources * Companies? * Software * About * [ ] Discover my COMMON LISP COURSE in videos with this 40% coupon. Recently added: the Condition System. 1. 2. blog 3. Debugging Lisp: trace options, break on conditions * Dec 2, 2022 * blog Debugging Lisp: trace options, break on conditions Those are useful Common Lisp debugging tricks. Did you know about trace options? We see how trace accepts options. Especially, we see how we can break and invoke the interactive debugger before or after a function call, how we can break on a condition ("this argument equals 0") and how we can enrich the trace output. But we only scratch the surface, more options are documented on their upstream documentation: * SBCL trace * CCL trace * LispWorks trace * Allegro trace INFO: You'd better read this on the Common Lisp Cookbook, that's where it will receive updates. Table of Contents * Trace - basics * Trace options - break and invoke the debugger * break on a condition * Other options + Trace on conditions + Trace if called from another function + Enrich the trace output * Closing remark Trace - basics But let's first see a recap' of the trace macro. Compared to the previous Cookbook content, we just added that (trace) alone returns a list of traced functions. trace allows us to see when a function was called, what arguments it received, and the value it returned. (defun factorial (n) (if (plusp n) (* n (factorial (1- n))) 1)) To start tracing a function, just call trace with the function name (or several function names): (trace factorial) (factorial 2) 0: (FACTORIAL 3) 1: (FACTORIAL 2) 2: (FACTORIAL 1) 3: (FACTORIAL 0) 3: FACTORIAL returned 1 2: FACTORIAL returned 1 1: FACTORIAL returned 2 0: FACTORIAL returned 6 6 (untrace factorial) To untrace all functions, just evaluate (untrace). To get a list of currently traced functions, evaluate (trace) with no arguments. In Slime we have the shortcut C-c M-t to trace or untrace a function. If you don't see recursive calls, that may be because of the compiler's optimizations. Try this before defining the function to be traced: (declaim (optimize (debug 3))) ;; or C-u C-c C-c to compile with maximal debug settings. The output is printed to *trace-output* (see the CLHS). In Slime, we also have an interactive trace dialog with M-x slime-trace-dialog bound to C-c T. But we can do many more things than calling tace with a simple argument. Trace options - break and invoke the debugger trace accepts options. For example, you can use :break t to invoke the debugger at the start of the function, before it is called (more on break below): (trace factorial :break t) (factorial 2) We can define many things in one call to trace. For instance, options that appear before the first function name to trace are global, they affect all traced functions that we add afterwards. Here, :break t is set for every function that follows: factorial, foo and bar: (trace :break t factorial foo bar) On the contrary, if an option comes after a function name, it acts as a local option, only for its preceding function. That's how we first did. Below foo and bar come after, they are not affected by :break: (trace factorial :break t foo bar) But do you actually want to break before the function call or just after it? With :break as with many options, you can choose. These are the options for :break: :break form ;; before :break-after form :break-all form ;; before and after TIP: form can be any form that evaluates to true. You can add any custom logic here. Note that we explained the trace function of SBCL. Other implementations may have the same feature with another syntax and other option names. For example, in LispWorks it is ":break-on-exit" instead of ":break-after", and we write (trace (factorial :break t)). Below are some other options but first, a trick with :break. break on a condition The argument to an option can be any form. Here's a trick, on SBCL, to get the break window when we are about to call factorial with 0. (sb-debug:arg 0) refers to n, the first argument. CL-USER> (trace factorial :break (equal 0 (sb-debug:arg 0))) ;; WARNING: FACTORIAL is already TRACE'd, untracing it first. ;; (FACTORIAL) Running it again: CL-USER> (factorial 3) 0: (FACTORIAL 3) 1: (FACTORIAL 2) 2: (FACTORIAL 1) 3: (FACTORIAL 0) breaking before traced call to FACTORIAL: [Condition of type SIMPLE-CONDITION] Restarts: 0: [CONTINUE] Return from BREAK. 1: [RETRY] Retry SLIME REPL evaluation request. 2: [*ABORT] Return to SLIME's top level. 3: [ABORT] abort thread (#) Backtrace: 0: (FACTORIAL 1) Locals: N = 1 <---------- n is still 1, we break before the call with 0. Other options Trace on conditions :condition enables tracing only if the condition in form evaluates to true. :condition form :condition-after form :condition-all form If :condition is specified, then trace does nothing unless Form evaluates to true at the time of the call. :condition-after is similar, but suppresses the initial printout, and is tested when the function returns. :condition-all tries both before and after. Trace if called from another function :wherein can be super useful: :wherein Names If specified, Names is a function name or list of names. trace does nothing unless a call to one of those functions encloses the call to this function (i.e. it would appear in a backtrace.) Anonymous functions have string names like "DEFUN FOO". Enrich the trace output :report Report-Type If Report-Type is trace (the default) then information is reported by printing immediately. If Report-Type is nil, then the only effect of the trace is to execute other options (e.g. print or break). Otherwise, Report-Type is treated as a function designator and, for each trace event, funcalled with 5 arguments: trace depth (a non-negative integer), a function name or a function object, a keyword (:enter, :exit or :non-local-exit), a stack frame, and a list of values (arguments or return values). See also :print to enrich the trace output: In addition to the usual printout, the result of evaluating Form is printed at the start of the function, at the end of the function, or both, according to the respective option. Multiple print options cause multiple values to be printed. Example: (defparameter *counter* 0) (defun factorial (n) (incf *counter*) (if (plusp n) (* n (factorial (1- n))) 1)) CL-USER> (trace factorial :print *counter*) CL-USER> (factorial 3) (FACTORIAL 3) 0: (FACTORIAL 3) 0: *COUNTER* = 0 1: (FACTORIAL 2) 1: *COUNTER* = 1 2: (FACTORIAL 1) 2: *COUNTER* = 2 3: (FACTORIAL 0) 3: *COUNTER* = 3 3: FACTORIAL returned 1 2: FACTORIAL returned 1 1: FACTORIAL returned 2 0: FACTORIAL returned 6 6 Closing remark As they say: it is expected that implementations extend TRACE with non-standard options. and we didn't list all available options or parameters, so you should check out your implementation's documentation. For more debugging tricks see the Cookbook and the links in it, the Malisper series have nice GIFs. I am also preparing a short screencast to show what we can do inside the debugger, stay tuned! TAGS * debugging * tutorial * tip * sbcl LATESTS Debugging Lisp: fix and resume a program from any point in stack Debugging Lisp: trace options, break on conditions Lisp for the web: building one standalone binary with foreign libraries, templates and static assets Lisp for the web: deploying with Systemd, gotchas and solutions State of Common Lisp Web Development - an overview New video: how to request a REST API in Common Lisp: fetching the GitHub API Video: Create a Common Lisp project from scratch with our project generator Writing an interactive web app in Common Lisp: Hunchentoot then CLOG Resources I Am Creating a Common Lisp Video Course on Udemy (free video previews) TAG libraries tutorial web product companies tip debugging clojure python sbcl database web-scraping abcl google-special-tag testing (c) Lisp journey. Like the content? Want to offer a ko-fi? You can also sponsor me on GitHub, thank you! Discover also my Common Lisp course in videos on the Udemy platform. It is packed with real-world useful content I learned the hard way. Learn Common Lisp now and use it for decades! Powered by Hugo. Beg designed by Daisuke Tsuji.