Search code examples
debuggingemacscommon-lispslime

How Can I Make Sly's Backtrace Be Sensible?


Whenever I get an error, Sly displays restarts and backtrace. I've seen people on the internet who have readable backtraces that are sensible function calls. When I enter (fun duf) in the repl, I end up with something like this:

Backtrace:
 0: ((LAMBDA ()))
 1: (SB-INT:SIMPLE-EVAL-IN-LEXENV (FUN DUF) #<NULL-LEXENV>)
 2: (EVAL (FUN DUF))
 3: ((LAMBDA NIL :IN SLYNK-MREPL::MREPL-EVAL-1))
 4: (SLYNK::CALL-WITH-RETRY-RESTART "Retry SLY mREPL evaluation request." #<CLOSURE (LAMBDA NIL :IN SLYNK-MREPL::MREPL-EVAL-1) {100308AA1B}>)
 5: ((LAMBDA NIL :IN SLYNK-MREPL::MREPL-EVAL-1))
 6: ((LAMBDA NIL :IN SLYNK::CALL-WITH-LISTENER))
 7: (SLYNK::CALL-WITH-BINDINGS ((*PACKAGE* . #<PACKAGE "COMMON-LISP-USER">) (* . 13) (** . 10) (***) (/ 13) (// 10) ...) #<CLOSURE (LAMBDA NIL :IN SLYNK::CALL-WITH-LISTENER) {100308A68B}>)
 8: (SLYNK-MREPL::MREPL-EVAL-1 #<SLYNK-MREPL::MREPL mrepl-2-2> "(fun duf)")
 9: (SLYNK-MREPL::MREPL-EVAL #<SLYNK-MREPL::MREPL mrepl-2-2> "(fun duf)")
...

There is a lot more. This makes it impossible for me to debug my code. I have tried: (declaim (optimize (debug 3))), but it makes no difference. Also, even if the call is an actual function, the output is no better. I have tried with both gnuclisp and sbcl.


Solution

  • I think that you feel that a lot of what you are seeing is “garbage” just because you do not yet know what it is.

    • In line 9 and 8, you see the functions that the REPL is using to handle your input. At this stage, the input is a string, as you can see.
    • In line 7, there are some bindings put on the stack, so that the right package is used, and * to *** are bound to the last results (a handy feature for a REPL).
    • In line 6, call-with-listener is probably establishing that output should go to Sly.
    • In line 5, this anonymous function is the one contained in the closure that has been an argument on line 6. In other words, an implementation detail.
    • In line 4, the retry restart is established. This frame is where control would be transferred if you invoked the Retry restart in the debugger.
    • In line 3, you again see a function argument being executed. Note the call-with-… naming convention.
    • In line 2, you see that your input has been read, i. e. transformed into a lisp datastructure. It is now evaluated with the standard function eval.
    • In line 1, you see how SBCL does that in this case — an implementation detail.
    • In line 0, you see some anonymous function, again something that comes from evals implementation.

    There is not much to see here, because the first thing eval does is to evaluate duf, which fails.

    The computer has no idea which part of the current stack context you are trying to debug. It therefore must show you the entire backtrace, or suffer the wailing of people that got the information withheld they were interested in.

    What you need to learn is to recognize where the stuff you are interested in starts. Here, all that Slynk stuff (obviously?) is below your context, so you really only need to look at the part until the first eval line. In this case, that's just the first line, which makes sense, because the line you entered simply fails at the first lookup. For a Sly developer, those lower stack frames might be interesting.

    Of course, in more realistic scenarios, there are some implementations that have more legible backtraces than others. That's not something Sly can do something about. What it does do is to give you the option to jump from any line in the backtrace to the corresponding source file position, if available (in SLIME, it's v (view), don't know about Sly). There is a lot more that you can do, e. g. inspect local bindings/variables/arguments, and invoking restarts. Take a look at the manual.