3

I am new to Common Lisp and I am using SBCL, Slime and Emacs to learn.

While reading the book Common Lisp: A Gentle Introduction to Symbolic Computation, the author mentions the STEP tool which is helpful for debugging and able to do this:

enter image description here

It is not 100% clear if the italic text is coming from the author or the tool itself. Probably, just the author's comments.

However, even if I do not consider the italic, I am unable to generate descriptive info like this.

If I use just SBCL's REPL, I get:

* (step (if (oddp 5) 'yes 'no))            
YES

If I use the REPL inside Emacs with Slime on, I get:

CL-USER> (step (if (oddp 5) 'yes 'no))
YES

The author says that:

Each implementation of Common Lisp provides its own version of this tool; only the name has been standardized.

If I try the same thing in Emacs/Slime with a function, I get more info:

(defun my-abs (x)
  (cond ((> x 0) x)
    ((< x 0) (- x))
    (t 0)))

Using the definition above and the command bellow on the REPL:

CL-USER> (step (my-abs 10))

I get:

Evaluating call:
  (MY-ABS 10)
With arguments:
  10
   [Condition of type STEP-FORM-CONDITION]

Restarts:
 0: [STEP-CONTINUE] Resume normal execution
 1: [STEP-OUT] Resume stepping after returning from this function
 2: [STEP-NEXT] Step over call
 3: [STEP-INTO] Step into call
 4: [RETRY] Retry SLIME REPL evaluation request.
 5: [*ABORT] Return to SLIME's top level.
 --more--

Backtrace:
  0: ((LAMBDA ()))
  1: (SB-INT:SIMPLE-EVAL-IN-LEXENV (LET ((SB-IMPL::*STEP-OUT* :MAYBE)) (UNWIND-PROTECT (SB-IMPL::WITH-STEPPING-ENABLED #))) #S(SB-KERNEL:LEXENV :FUNS NIL :VARS NIL :BLOCKS NIL :TAGS NIL :TYPE-RESTRICTIONS ..
  2: (SB-INT:SIMPLE-EVAL-IN-LEXENV (STEP (MY-ABS 10)) #<NULL-LEXENV>)
  3: (EVAL (STEP (MY-ABS 10)))
 --more--

Unfortunately, none of those options seem to give me what I want (which could be an error of interpretation on my side).

I would like to see something like:

enter image description here

SLIME seems to be a thorough tool. I might be missing something.

Is there a way to generate the same output described by the book using SLIME or SBCL?

Pedro Delfino
  • 2,421
  • 1
  • 15
  • 30
  • Since `IF` is a built-in macro, it's implementation-dependent whether `STEP` will drill into it. – Barmar May 18 '21 at 18:13
  • The problem may be that the SBCL compiler is optimizing the code. It detects at compile time that `(oddp 5)` is true. – Barmar May 18 '21 at 18:15
  • Thanks @barmar, but what about my-abs? – Pedro Delfino May 18 '21 at 19:22
  • 1
    I'm not sure what's going on with that. Stepping other functions works, see https://stackoverflow.com/questions/8617064/a-simple-example-of-using-the-stepper-in-sbcl – Barmar May 18 '21 at 19:30
  • 1
    I think it should be made clear that this is a question about SBCL rather than CL in general. How `step` works and how useful it is is very implementation-dependant, and rightly so. I've edited the tags & title to represent that, but feel free to amend it. –  May 19 '21 at 12:58

2 Answers2

3

As someone suggested above, SBCL does optimize away a lot by default, and compiles by default as well. Here's what I did to create an example:

  • I first made up "a bad value" for the "suggested optimization" by running
(declaim (optimize (debug 3) (space 0) (speed 0)))
  • Then, I defined a function with something more than an if condition, since that sort of thing is always inlined, unfortunately (though you might try (declaim (notinline ...)), I haven't. One way is to create a function that calls another one, like:
(defun foo () "hey!")

(defun bar () (foo))
  • Now, when I run (step (bar)), I see the debugger pane that you shared in your question above, and if I now select option #3, step into, I get the same pane but now focussed, as hopefully expected, on the call to foo.

Good luck!

Some references:

agam
  • 5,064
  • 6
  • 31
  • 37
2

It looks like the author is using LispWorks' stepper.

With LispWorks

Here's my step session, using :s to step the current form and all its subforms.

CL-USER 5 > (step (my-abs -5))
(MY-ABS -5) -> :s
   -5 -> :s
   -5 
   (COND ((> X 0) X) ((< X 0) (- X)) (T 0)) <=> (IF (> X 0) (PROGN X) (IF (< X 0) (- X) (PROGN 0)))
   (IF (> X 0) (PROGN X) (IF (< X 0) (- X) (PROGN 0))) -> :s
      (> X 0) -> :s
         X -> :s
         -5 
         0 -> :s
         0 
      NIL 
      (IF (< X 0) (- X) (PROGN 0)) -> :s
         (< X 0) -> :s
            X -> :s
            -5 
            0 -> :s
            0 
         T 
         (- X) -> :s
            X -> :s
            -5 
         5 
      5 
   5 
5 
5

Help is on :?:

 :?

:s       Step this form and all of its subforms (optional +ve integer arg)
:st      Step this form without stepping its subforms
:si      Step this form without stepping its arguments if it is a function call
:su      Step up out of this form without stepping its subforms
:sr      Return a value to use for this form
:sq      Quit from the current stepper level
:bug-form <subject> &key <filename>
         Print out a bug report form, optionally to a file.
:get <variable> <command identifier>
         Get a previous command (found by its number or a symbol/subform within it) and put it in a variable.
:help    Produce this list.
:his &optional <n1> <n2>
         List the command history, optionally the last n1 or range n1 to n2.
:redo &optional <command identifier> 
         Redo a previous command, found by its number or a symbol/subform within it.
:use <new> <old> &optional <command identifier> 
         Do variant of a previous command, replacing old symbol/subform with new symbol/subform.

For compiled code it also has a visual stepper, where you can press a red button to set a breakpoints, see the intermediate variables changing etc. It looks like this:

LispWorks is a proprietary implementation and IDE that has a free but limited version. I just wrote a review that should be merged on the Cookbook.

trace and printv

Do you know trace? printv, an external library, is a trace on steroids. They resemble the output you appreciate.

(defun factorial (n)
  (if (plusp n)
    (* n (factorial (1- n)))
    1))
(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)

printv prints the code and the returned values.

(printv:printv
           (+ 2 3)              
           *print-case*
           *package*
           'symbol
           (let* ((x 0) (y (1+ x)) (z (1+ y)))
             (values x y z)))
;;;   (+ 2 3) => 5
;;;   *PRINT-CASE* => :UPCASE
;;;   *PACKAGE* => #<PACKAGE "ISSR-TEST">
;;;   'SYMBOL => SYMBOL
;;;   (LET* ((X 0) (Y (1+ X)) (Z (1+ Y)))
        (VALUES X Y Z)) =>
           [ [X=0]  [Y=1]  [Z=2] ]
;;;   => 0, 1, 2
Ehvince
  • 17,274
  • 7
  • 58
  • 79