debuggingemacscommon-lisp

How to step through some Common Lisp code, inspecting the return value of forms?


Maybe this is noob-ish but I'm losing my mind after this. I discovered with very much surprise that CL's stepper doesn't show the return value of the various forms. What I mean is that the debugger can inspect the frames and the REPL allows to manually inspect the variables, but for instance in a code like

(defun fact (n)
  (if (<= n 0)
      1
      (* n (fact (- n 1)))))

(fact 5)

I cannot instruct the debugger to say "hey, I just stepped over form (<= n 0), which returned nil, then I stepped on (- n 1), which returned value 4". This can be done in Emacs-lisp and in Clojure (I use Emacs, but it's not the interface I'm interested in). I know there exist the form (step), but it is implementation-dependent and for what I could see on both SBCL and CMU it doesn't do what I mean. So my question is, can CL's debugger do this (stepping + printing value just evaluated)? If yes, is it just one implementation? Can you provide a MWE?

Thanks!


Solution

  • Common Lisp is a language specification, not an implementation. As such, expressions like 'CL's stepper' or 'CL's debugger' are category errors: languages do not have steppers or debuggers, implementations do. The language specification provides an interface, step, by which such a thing might be invoked, but says

    step implements a debugging paradigm wherein the programmer is allowed to step through the evaluation of a form. The specific nature of the interaction, including which I/O streams are used and whether the stepping has lexical or dynamic scope, is implementation-defined.

    Thus the answer to your question is that no, 'CL's debugger' can't do this because 'CL's debugger' does not exist.

    Implementations of the language may provide more-or-less support for this. I know, for instance, that LispWorks does, for instance. I don't know whether other implementations do. CL as a language is also reflective enough that a portable stepper could probably be written: sly-stepper might be such a thing but I am not sure.


    Here is an example of LW's non-IDE stepper in use:

    CL-USER 7 > (step (let ((x 0)) (/ (sin x) x)))
    (let ((x 0)) (/ (sin x) x)) -> :su
    
    Error: Division-by-zero caused by / of (0.0 0.0).
      1 (continue) Return a value to use.
      2 Supply new arguments to use.
      3 (abort) Return to debug level 1.
      4 Return to stepper level 1.
      5 Quit from stepper.
      6 Return to debug level 0.
      7 Restart top-level loop.
    
    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 9 : 2 > :a
    (let ((x 0)) (/ (sin x) x)) -> :sr 0.0
    0.0 
    0.0
    
    CL-USER 8 > (step (let ((x 0)) (/ (sin x) x)))
    (let ((x 0)) (/ (sin x) x)) -> :s 2
       0
       0 
       (/ (sin x) x)
          (sin x) -> :si
          0.0 
          x -> :sr 1
          1 
       0.0 
    0.0 
    0.0
    

    In the first interaction I let it run until it got the error, aborted from that back into the stepper, and then told the stepper to return zero. In the second I stepped through until it was about to evaluate x and then told it the result of that was 1 causing it to return zero.

    The non-IDE stepper above is documented here & the IDE stepper is here.