5

THE LISP IDE

I've already introduced some essential functions and keybindings for coding Lisp in Emacs. Now that you have a basic knowledge of both Lisp and Emacs commands for coding in Lisp, it will be useful to gain a deeper knowledge of the Lisp IDE in Emacs.

Common Lisp includes debugging functions like trace, break, etc. that allow you to do debugging directly in the REPL.

However, there are Common Lisp IDEs that help improve the ergonomics of using the debugger. One of those IDEs is Sly, which was installed when we configured the init.el file to include common-lisp language support.

SLY BACKTRACE NAVIGATION

There are some functions within the Sly debugger that we can use for simple navigation. They all are prefixed with sly-db- and are discoverable if you run embark-bindings with SPC h b b or C-h b b. We'll look at a few of them here.

Move the cursor up or down the stack in the backtrace with C-k (sly-db-up) and C-j (sly-db-down). If the cursor is not highlighting any frames in the stack, the cursor will first move to the top frame if you run sly-db-down.

To show any local variables and bindings, arguments passed, etc.: With the cursor over a stack frame in the backtrace, type t (sly-db-toggle-details).

If you bring the cursor over a stack frame in the backtrace and press the v (sly-db-show-frame-source) key, Sly will open the source code file for that line in the backtrace and briefly highlight the exact form that executed in that frame. Useful for navigating straight to possible sources of error.

These will do all three functions above: move the cursor to a stack from in the backtrace, toggle open/close the details for that frame, open the source for the frame in a different window, and highlight the form in that source. The bindings are M-p (sly-db-details-up) and M-n (sly-db-details-down) (or M-k and M-j if you prefer more Evilly bindings consistent with the ones above for the normal up and down commands).

TRACING

Beyond simple navigation within the debugger is actual debugging methods and tools.

Tracing is a debugging method that shows a form as it is called and then what it returns. Tracing is typically used for recursive functions.

Let's say you have this function:

(defun factorial (n)
  "Compute factorial using linear recursion."
  (if (= n 1)
      1
      (* n (factorial (- n 1)))))

You can run trace on a function in Emacs like this:

(trace factorial)

…or by highlighting a function name symbol and typing C-c M-t for sly-fancy-trace.

If you use fancy tracing on factorial above, you will get output like this in the REPL:

0: (FACTORIAL 5)
  1: (FACTORIAL 4)
    2: (FACTORIAL 3)
      3: (FACTORIAL 2)
	4: (FACTORIAL 1)
	4: FACTORIAL returned 1
      3: FACTORIAL returned 2
    2: FACTORIAL returned 6
  1: FACTORIAL returned 24
0: FACTORIAL returned 120

Alternatively, you can trace using sly-trace-dialog-toggle-trace via C-c C-t or SPC m T T. The difference is that fancy trace will show the trace in the REPL, whereas the trace dialog toggle requires you to open the trace dialog with C-c T.

If you use the trace dialog, you get output like this:

0 - factorial
> 5 (3 bits, #x5, #o5, #b101)< 120 (7 bits, #x78, #o170, #b1111000)1 `-- factorial
> 4 (3 bits, #x4, #o4, #b100)< 24 (5 bits, #x18, #o30, #b11000)2    `-- factorial
> 3 (2 bits, #x3, #o3, #b11)< 6 (3 bits, #x6, #o6, #b110)3       `-- factorial
> 2 (2 bits, #x2, #o2, #b10)< 2 (2 bits, #x2, #o2, #b10)4          `-- factorial
		> 1 (1 bit, #x1, #o1, #b1)
		< 1 (1 bit, #x1, #o1, #b1)

You can untrace the function when you're done either with the Lisp function or by using the same commands above in Emacs.

STICKERS

Where Sly's debugging capabilities really shine is with stickers. Stickers are basically a replacement for print and break functions

Let's say we have the following code:

(defun do-some-math (num)
  (/ (+ num (* num num num) (- num 10 num)) 7))
(do-some-math 9)

And you're thinking, "Hmm, I wonder what the result of that addition was?"

You could wrap it in print. Or you could use a sticker. Highlight the opening parenthesis of the addition form then use sly-stickers-dwim via C-c C-s C-s or SPC m s s to place a sticker on it. After placing the sticker, you need to recompile the function (C-c C-c) to "arm" the sticker.

With the sticker armed, when you run the function, the return value of the form you placed the sticker on will be recorded. You can view the recording with sly-stickers-replay using C-c C-s C-r or SPC m s r.

You can also configure Sly to break when computation reaches the sticker using sly-stickers-toggle-break-on-stickers via SPC m s b. During computation, when a sticker is reached, the debugger will open with a message like this:

#<JUST BEFORE #<STICKER id=145 hit-count=1>>
   [Condition of type SLYNK-STICKERS::JUST-BEFORE-STICKER]

The sticker will also flash in the source code file window. Useful for when you have multiple stickers and need to know which one you're looking at.

If you choose the CONTINUE restart (either via c or 0 or navigating to [CONTINUE] and typing Enter) or you use sly-db-step via s in the debugger window, you will see a message like this:

#<RIGHT-AFTER #<STICKER id=255 hit-count=2> (recorded #<RECORDING 1 values>)>
   [Condition of type SLYNK-STICKERS::RIGHT-AFTER-STICKER]

…and the return value of the form where the sticker is placed.

If you are having trouble tracking down the exact location of your error, you can perform a comprehensive sweep using sly-stepper with C-c C-s P. That will apply stickers to every form inside a function. After running sly-stepper, compile the function to arm the stickers.

sly-stickers-replay is especially useful because, like with the breaking option, it will open the source code file and flash the exact sticker whose value it's displaying. You can also navigate to the next sticker with n, or the previous one with p. Every time you move forward or backward in the replay, the stickers will flash. This makes a bit easier to follow the control flow of the code.