Skip to content

Instantly share code, notes, and snippets.

@vindarel
Last active December 23, 2023 08:55
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save vindarel/107ed869399a548e6ef41dc324df3099 to your computer and use it in GitHub Desktop.
Save vindarel/107ed869399a548e6ef41dc324df3099 to your computer and use it in GitHub Desktop.
Awesome example on Common Lisp's interactive debugger (Slime) advanced interactivity

Awesome comment: https://www.reddit.com/r/programming/comments/65ct5j/a_pythonist_finds_a_new_home_at_clojure_land/dg9p8rk/

That's not an interpreter. A REPL is not the same as a Lisp interpreter. REPL means read eval print loop. EVAL can be implemented by a compiler or an interpreter. Common Lisp has both and mixed implementations with both compiler and interpreter.

A Lisp interpreter is executing Lisp code directly. Clojure does not have an Interpreter.

https://clojure.org/reference/evaluation

Clojure has no interpreter.

Example in LispWorks, which uses the Interpreter in the REPL.

CL-USER 29 > (let ((f (lambda (a b)
                         (+ (prog1 2 (break))  ; we have a break here
                            (* a b)))))
              (funcall f 2 3))

Break.
  1 (continue) Return from break.
  2 (abort) Return to level 0.
  3 Return to top loop level 0.

Type :b for backtrace or :c <option number> to proceed.
Type :bug-form "<subject>" for a bug report template or :? for other options.

As you see Lisp comes with a sub-repl in the break. The sub-repl is just another repl, but in the context of the break. The break could be done by the debugger when it sees an error or by user code - as above.

Now we ask the interpreter for the current lambda expression:

CL-USER 30 : 1 > :lambda
(LAMBDA (A B) (+ (PROG1 2 (BREAK)) (* A B)))

Above is actually Lisp data. Code as data.

Now I'm changing the + function in the code to be expt, exponentiation. To be clear: I'm changing in the debugger the current executed Lisp function on the Lisp level. We take the third element of the list, and then the first one of that. This is the + symbol. We change it to be expt. * holds the last evaluation result of the REPL.

CL-USER 31 : 1 > (setf (first (third *)) 'expt)
EXPT

Then I'm restarting the current stack frame:

CL-USER 32 : 1 > :res

We get another break, which we just continue from:

Break.
  1 (continue) Return from break.
  2 (abort) Return to level 0.
  3 Return to top loop level 0.

Type :b for backtrace or :c <option number> to proceed.
Type :bug-form "<subject>" for a bug report template or :? for other options.

CL-USER 33 : 1 > :c 1
64                                   ; we computed 2^(2*3)  instead of 2+(2*3)

What did we see? We saw that the interpreter uses actual Lisp code. Lisp code we can change with Lisp code in the debugger.

A second example.

What can we do with that for debugging? Well, we can for example write our own evaluation tracer. The Evaluator prints each expression and its result nicely indented, while walking the expression tree and evaluating subexpressions. Remember: this is now user-level code. The example is from CLtL2. You will also see that LispWorks can freely mix compiled and interpreted functions. The function COMPILE takes a function name and compiles its Lisp code to machine code.

CL-USER 1 > (defvar *hooklevel* 0)
*HOOKLEVEL*

CL-USER 2 > (defun hook (x) 
              (let ((*evalhook* 'eval-hook-function)) 
                (eval x)))
HOOK

CL-USER 3 > (compile 'hook)
HOOK
NIL
NIL

CL-USER 4 > (defun eval-hook-function (form &rest env) 
              (let ((*hooklevel* (+ *hooklevel* 1))) 
                (format *trace-output* "~%~V@TForm:  ~S" 
                        (* *hooklevel* 2) form) 
                (let ((values (multiple-value-list 
                               (evalhook form 
                                         #'eval-hook-function 
                                         nil 
                                         env)))) 
                  (format *trace-output* "~%~V@TValue:~{ ~S~}" 
                          (* *hooklevel* 2) values) 
                  (values-list values))))
EVAL-HOOK-FUNCTION

CL-USER 5 > (compile 'eval-hook-function)
EVAL-HOOK-FUNCTION
NIL
NIL

Now we can trace the evaluation of expressions on the Lisp level:

CL-USER 6 > (hook '(cons (floor *print-base* 2) 'b))

  Form:  (CONS (FLOOR *PRINT-BASE* 2) (QUOTE B))
    Form:  (FLOOR *PRINT-BASE* 2)
      Form:  *PRINT-BASE*
      Value: 10
      Form:  2
      Value: 2
    Value: 5 0
    Form:  (QUOTE B)
    Value: B
  Value: (5 . B)
(5 . B)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment