Slimv Tutorial - Part Three
This is the third part of the Slimv Tutorial series. In the first two parts we introduced Lisp source code editing concepts, basic REPL operations and the SLIME debugger. In this part we follow Marco Barringer's SLIME tutorial movie to its end.
This part of the tutorial assumes that you have followed Part One and Part Two and your REPL is loaded with all the definitions added there. So if you haven't yet visited the previous parts, please do so before continuing with this page.
Tracing
So far we have installed package split-sequence, so we can now write morse-to-string.
This is the initial version:
(defun morse-to-string (string) (with-output-to-string (character-stream) (loop for morse-char in (split-sequence:split-sequence #\Space string) do (write-char (morse-to-character morse-char) character-stream))))
Just for fun after compiling morse.lisp we test our new function with an extra space added at the end of the morse string:
MORSE> (string-to-morse "marco") "-- .- .-. -.-. ---" MORSE> (morse-to-string "-- .- .-. -.-. --- ") Slimv.REPL.lisp 75,0-1 Bot The value NIL is not of type CHARACTER. [Condition of type TYPE-ERROR] Restarts: 0: [RETRY] Retry SLIME REPL evaluation request. 1: [*ABORT] Return to SLIME's top level. 2: [TERMINATE-THREAD] Terminate this thread (#<THREAD "repl-thread" RUNNING {B617D49}>) Backtrace: 0: (SB-IMPL::STRING-OUCH #<SB-IMPL::STRING-OUTPUT-STREAM {AE0A679}> NIL) 1: (WRITE-CHAR NIL #<SB-IMPL::STRING-OUTPUT-STREAM {AE0A679}>) 2: (MORSE-TO-STRING "-- .- .-. -.-. --- ") ... Slimv.SLDB [RO]^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^4,1^^^^^^^^^^^^Top
In the backtrace we can see that nil was passed to write-char (look at frame #1), but it's not immediately clear why. We can use the editor buffer for testing function calls "in place": let's quit the debugger, replace argument string in the split-sequence:split-sequence call and evaluate the containing form with ,e:
(defun morse-to-string (string) (with-output-to-string (character-stream) (loop for morse-char in (split-sequence:split-sequence #\Space "-- .- .-. -.-. --- ") do (write-char (morse-to-character morse-char) character-stream))))
MORSE> (split-sequence:split-sequence #\Space "-- .- .-. -.-. --- ") ("--" ".-" ".-." "-.-." "---" "")
From the result it is now clear that the problem is caused by the last empty string. Let's fix it by removing empty subsequences in split-sequence:split-sequence (and don't forget to restore the original string argument):
(defun morse-to-string (string) (with-output-to-string (character-stream) (loop for morse-char in (split-sequence:split-sequence #\Space string :remove-empty-subseqs t) do (write-char (morse-to-character morse-char) character-stream))))
After compiling and testing, our new function seems to work as intended (the characters are converted to upper case because our morse mapping contains capital letters only):
MORSE> (morse-to-string (string-to-morse "marco")) "MARCO"
But how can we make sure that this is not just a strange coincidence? In order to answer this, we're going to trace the functions involved. Slimv toggles tracing by placing the cursor on a function name and pressing ,t (or by selecting Debugging/Toggle-Trace from the Slimv menu). So let's switch on tracing for string-to-morse and morse-to-string.
With tracing enabled we re-evaluate our last test form:
MORSE> ;; Pressing ,t on symbol string-to-morse STRING-TO-MORSE is now traced. MORSE> ;; Pressing ,t on symbol morse-to-string MORSE-TO-STRING is now traced. MORSE> (morse-to-string (string-to-morse "marco")) 0: (STRING-TO-MORSE "marco") 0: STRING-TO-MORSE returned "-- .- .-. -.-. ---" 0: (MORSE-TO-STRING "-- .- .-. -.-. ---") 0: MORSE-TO-STRING returned "MARCO" "MARCO" MORSE> ;; Pressing ,T to untrace all Untracing: morse::string-to-morse morse::morse-to-string
Now we also got the tracing output, showing what functions were called, the arguments passed to them, and their return values. We can be fairly confident that our functions work as we want them to.
Inspecting objects
The SLIME Inspector has been introduced in section Inspecting a package. This section describes some more advanced uses of the Inspector. Let's begin with evaulating the defective form (morse-to-string (string-to-morse 42)), dropping us into the debugger. If we open the frame(s) we can see the local variable bindings and the source location of the frame. In the example below frame #5 has one argument, presented as SB-DEBUG::ARG-0.
MORSE> (morse-to-string (string-to-morse 42)) Slimv.REPL.lisp 145,0-1 Bot The value 42 is not of type ARRAY. [Condition of type TYPE-ERROR] Restarts: 0: [RETRY] Retry SLIME REPL evaluation request. 1: [*ABORT] Return to SLIME's top level. 2: [TERMINATE-THREAD] Terminate this thread (#<THREAD "repl-thread" RUNNING {B618699}>) Backtrace: 0: (STRING-TO-MORSE 42) 1: (SB-INT:SIMPLE-EVAL-IN-LEXENV (STRING-TO-MORSE 42) #<NULL-LEXENV>) 2: (SB-INT:SIMPLE-EVAL-IN-LEXENV (MORSE-TO-STRING (STRING-TO-MORSE 42)) #<NULL-LEXENV>) 3: (SWANK::EVAL-REGION "(morse-to-string (string-to-morse 42))\n") 4: ((LAMBDA ())) 5: (SWANK::TRACK-PACKAGE #<CLOSURE (LAMBDA #) {B1272AD}>) in "~/.vim/slime/swank.lisp" line 2258 Locals: SB-DEBUG::ARG-0 = #<CLOSURE (LAMBDA ()) {AD4BE6D}> 6: (SWANK::CALL-WITH-RETRY-RESTART "Retry SLIME REPL evaluation request." #<CLOSURE ... 7: (SWANK::CALL-WITH-BUFFER-SYNTAX NIL #<CLOSURE (LAMBDA #) {B127225}>) Slimv.SLDB [RO]^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^15,1^^^^^^^^^^^^Top
If we press Enter on in "~/.vim/slime/swank.lisp" line 2258 then Slimv opens the associated source file in a buffer and locates line 2258.
If we select Debugging/Inspect while the cursor is on the frame line, then a special "Inspect in Frame" function is executed, which inspects the result of evaluating an expression in the given frame. Choose it for examining the value of the function argument SB-DEBUG::ARG-0 that has a kind of useless representation in the frame box (#<CLOSURE (LAMBDA ()) {AD4BE6D}>).
4: ((LAMBDA ())) 5: (SWANK::TRACK-PACKAGE #<CLOSURE (LAMBDA #) {B1272AD}>) in "~/.vim/slime/swank.lisp" line 2258 Locals: SB-DEBUG::ARG-0 = #<CLOSURE (LAMBDA ()) {AD4BE6D}> 6: (SWANK::CALL-WITH-RETRY-RESTART "Retry SLIME REPL evaluation request." #<CLOSURE ... Slimv.SLDB [RO]^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^15,1^^^^^^^^^^^^Top Inspect in frame 5: sb-debug::arg-0
Inspecting #<FUNCTION {AD4BE6D}> -------------------- [1] FUNCTION: #<FUNCTION (LAMBDA ()) {A20DB3D}> Closed over values: [2] 0: "(morse-to-string (string-to-morse 42)) .. [<<] Slimv.INSPECT [RO]^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^1,1^^^^^^^^^^^^Top
The Inspector output is much more descriptive, isn't it?
We can also use the "Inspect in Frame" facility for examining other variable bindings. Take for instance *package*...:
16: ((FLET SWANK-BACKEND:CALL-WITH-DEBUGGER-HOOK) #<FUNCTION SWANK:SWANK-DEBUGGER-HOOK.. in "~/.vim/slime/swank-sbcl.lisp" line 1014 Locals: *DEBUGGER-HOOK* = :<NOT-AVAILABLE> SB-KERNEL:*HANDLER-CLUSTERS* = :<NOT-AVAILABLE> SWANK-BACKEND::FUN = #<CLOSURE (LAMBDA ()) {BCC93ED}> SWANK-BACKEND::HOOK = #<FUNCTION SWANK:SWANK-DEBUGGER-HOOK> Slimv.SLDB [RO]^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^30,1^^^^^^^^^^^^Top Inspect in frame 16: *package*
Inspecting #<PACKAGE {B65E011}> -------------------- [1] Name: "MORSE" Nick names: [2] Use list: COMMON-LISP Used by list: [3] 26 present symbols. 0 external symbols. [4] 26 internal symbols. [5] 978 inherited symbols. 0 shadowed symbols. [<<] Slimv.INSPECT [RO]^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^1,1^^^^^^^^^^^^Top
... or *standard-output* as another example:
16: ((FLET SWANK-BACKEND:CALL-WITH-DEBUGGER-HOOK) #<FUNCTION SWANK:SWANK-DEBUGGER-HOOK.. in "~/.vim/slime/swank-sbcl.lisp" line 1014 Locals: *DEBUGGER-HOOK* = :<NOT-AVAILABLE> Slimv.SLDB [RO]^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^30,1^^^^^^^^^^^^Top Inspect in frame 16: *standard-output*
Inspecting #<SWANK-BACKEND::SLIME-OUTPUT-STREAM {B60A699}> -------------------- [1] Class: #<STANDARD-CLASS SWANK-BACKEND::SLIME-OUTPUT-STREAM> -------------------- <0> Group slots by inheritance [ ] <1> Sort slots alphabetically [X] All Slots: <2> [ ] [2] BUFFER = Slimv.INSPECT [RO]^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^1,1^^^^^^^^^^^^Top
One can even inspect the result of a compound expression, like in the following example:
1: (SB-INT:SIMPLE-EVAL-IN-LEXENV (STRING-TO-MORSE 42) #<NULL-LEXENV>) No source line information Locals: SB-DEBUG::ARG-0 = (STRING-TO-MORSE 42) SB-DEBUG::ARG-1 = #<NULL-LEXENV> Slimv.SLDB [RO]^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^11,1^^^^^^^^^^^^Top Inspect in frame 1: (make-hash-table)
Inspecting #<HASH-TABLE {AED4BD9}> -------------------- [1] Count: 0 [2] Size: 16 [3] Test: EQL [4] Rehash size: 1.5 [5] Rehash threshold: 1.0 [<<] Slimv.INSPECT [RO]^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^1,1^^^^^^^^^^^^Top
Cross reference
The last thing Marco was talking about was the Cross Reference (XRef) facility. Let's assume we're required to add a new parameter to function morse-to-character. In this case we need to find out and update everyone who calls that function. We can use XRef/List Callers from the Slimv menu or press ,xl. It tells us the list of function and file names (if resolved) where morse-to-character is called:
(defun morse-to-character (morse-string) (first (find morse-string *morse-mapping* :test #'string= :key #'cdr))) morse.lisp 52,8 53% List callers: morse-to-character
MORSE> ;; Here follows the result of 'List callers: morse-to-character' MORSE-TO-STRING - no source information Slimv.REPL.lisp 75,0-1 Bot
At the same time we can ask morse-to-character: who do you call?
Use XRef/List callees from the Slimv menu or press ,xe:
(defun morse-to-character (morse-string) (first (find morse-string *morse-mapping* :test #'string= :key #'cdr))) morse.lisp 52,8 53% List callees: morse-to-character
MORSE> ;; Here follows the result of 'List callees: morse-to-character' FIND - no source information SECOND - no source information STRING= - no source information Slimv.REPL.lisp 75,0-1 Bot
If we list callees for morse-to-string then we get a slightly longer list, and - as expected - it contains our morse-to-character and the recently installed split-sequence:split-sequence functions:
(defun morse-to-string (string) (with-output-to-string (character-stream) (loop for morse-char in (split-sequence:split-sequence #\Space string) do (write-char (morse-to-character morse-char) character-stream)))) morse.lisp 63,8 92% List callees: morse-to-string
MORSE> ;; Here follows the result of 'List callees: morse-to-string' (LAMBDA (SB-PCL::.ARG0. SB-INT:&MORE SB-PCL::.MORE-CONTEXT. SB-PCL::.MORE-COUNT.)) - no sourc WRITE-CHAR - no source information MORSE-TO-CHARACTER - no source information GET-OUTPUT-STREAM-STRING - no source information SPLIT-SEQUENCE:SPLIT-SEQUENCE - /home/kovisoft/.sbcl/site/split-sequence/split-sequence.l MAKE-STRING-OUTPUT-STREAM - no source information Slimv.REPL.lisp 75,0-1 Bot
At that point the Slime introductory movie ends.
Once again, let me reuse Marco's words: I hope you think Slimv is cool. :)
Written by Tamas Kovacs
Last updated on Aug 28, 2020