wiki:GettingAroundInOpenMcl

Version 3 (modified by rme, 6 years ago) (diff)

de-spam

Getting Around in OpenMCL

Although most Lisp toplevel Read-Eval-Print-Loop (REPL) interfaces are similar, a quick overview is helpful for newcomers (and forgetful longer-term users).

OpenMCL Break Options

When code is typed in at the interactive ? prompt, or if an executing program fails, OpenMCL enters a debugging mode with specific syntax and semantics.

:? (help)

At either a ? (toplevel) prompt or a n > (break) prompt, typing :? will print one-line syntax summaries of available colon-commands (so called because the command names are all keyword symbols, which are usually printed and read with a leading colon, not because of any scatological meaning.) As the summaries show, some colon commands take arguments. In 1.0, it's necesssary to surround any colon command that takes arguments and those arguments with parens; in 1.1, the parens will be optional as long as the keyword and arguments are typed on the same line.

:q (exit all nested break loops)

Knowing the syntax of available break loop commands is only helpful if you have some higher-level notion of what your options are. One option that's always available from a break loop is to exit all pending break loops and return to the toplevel loop:

? (break)		; enter a break loop deliberately
Break in process listener(1)
...
Type :? for other options
1> (break)              ; enter another break loop
Break ...
...
2> :q			; exit all nested break loops
?			; and return to "toplevel"

[At some point around the 1.0 release, the :Q command was broken. No one remembers whether it was broken in 1.0 or not; however, it had been broken for a while before anyone reported it. This probably means that most people use SLIME or some similar environment these days, and rarely interact with the REPL directly.]

Depending on how the break loop was entered, it may be possible to continue execution at the point right after the (implicit or implicit) call that invoked the break loop. A SIGINT (^C interrupt) usually causes the lisp listener to enter a break loop.

Here's a contrived example:

? (defun countdown-slowly (n)
     (unless (zerop n)
       (sleep 1)
       (countdown-slowly (1- n))))
COUNTDOWN-SLOWLY
? (countdown-slowly 1000)
;;; Wait a few seconds, then hit ^C
Break in process listener(1):
While executing: #<Anonymous Function #x8107DB6>
Type :GO to continue, :POP to abort.
If continued: Return from BREAK.
Type :? for other options.

It'd have been more meaningful if the message that was printed said that we were executing COUNTDOWN-SLOWLY or 1- or SLEEP when the interrupt happened; hopefully 1.1 will be better about that sort of thing.

:b (backtrace)

Unless there was a bug in the definition of COUNTDOWN-SLOWLY, we should find that it's (slowly) counting down from 1000 to 0. We can verify this (at least verify that it's made some progress) by looking at a stack backtrace. By just using the :b break loop command with no arguments, we'll get some indication of what functions are awaiting return on the stack. (The 1.0 backtrace is pretty messy and often shows uninteresting internal frames; the SLIME Emacs environment and 1.1 both try to make the call history a little more intelligible.)

In 1.0, we'd see something like:

(F01351A0) : 0 "Anonymous Function #x8107DB6" 120
(F01351C0) : 1 NIL NIL
(F01351D0) : 2 "APPLY" 76
(F01351E0) : 3 "THREAD-HANDLE-INTERRUPTS" 420
(F0135200) : 4 NIL NIL
(F0135210) : 5 "FUNCALL-WITH-XP-STACK-FRAMES" 248
(F0135220) : 6 "XCMAIN" 1600
(F0135230) : 7 NIL NIL
(F0135240) : 8 "%PASCAL-FUNCTIONS%" 140
(F0135260) : 9 NIL NIL
(F0135B90) : 10 "%NANOSLEEP" 264
(F0135B90) : 11 "COUNTDOWN-SLOWLY" 52

:f (inspect frame contents)

In this case, COUNTDOWN-SLOWLY is in the 11th stack frame (counting from the most recent and including a lot of junk that we really don't care about.) We can use the :F command to look at the contents of that frame:

1 > (:f 11)
(F0135B90) : 11 "COUNTDOWN-SLOWLY" 52
   0 N: 998 ("required")

That's trying to say that the value of the required argument N to the function COUNTDOWN-SLOWLY is 998 at this point in time.

:GO (continue execution)

Satisfied that we are indeed counting down (slowly ...), we can continue the interrupted program. The easiest way to do so is to use the :GO break loop command:

1 > :go

and at this point we've resumed the not-very-interesting process of decrementing a small integer and sleeping. (Mostly sleeping, of course.) If we wanted to wait another 998 seconds or so, we'd eventually return to the toplevel read-eval-print loop. If we didn't want to wait, we could use ^C to interrupt the "computation" (probably, to interrupt a call to SLEEP) and use :Q in the resulting break loop to abort out of the not-very-interesting ongoing computation.

The :GO break loop command actually calls a CL function called CONTINUE; the standard CONTINUE function "invokes the most recently established restart named CONTINUE." A restart is basically a function that can be used to transfer control - often in response to an error or other exceptional situation - and possibly return values or have other side-effects along the way.

:r (see available restarts)

Suppose you want to access the value of the special variable *FOO*, and mistakenly forget to type the last "*". This will be interpreted as an attempt to access the value of *FOO, and *FOO is probably unbound:

? *foo
Error in process listener(1): Unbound variable: *FOO
While executing: "Unknown"
Type :GO to continue, :POP to abort.
If continued: Retry getting the value of *FOO.
Type :? for other options.
1>

:GO at this point isn't very useful; we'd just return to the attempt to access *FOO's value and be right back in the break loop. We can, however, see if other restarts (besides the CONTINUE restart that :GO invokes) are available:

1> :r
0. Return to break level 1.
1. #<RESTART ABORT-BREAK #x294976>
2. Retry getting the value of *FOO.
3. Specify a value of *FOO to use this time.
4. Specify a value of *FOO to store and use.
5. Return to toplevel.
6. #<RESTART ABORT-BREAK #x294CBE>
7. Reset this process
8. Kill this process

This is another contrived example; the simplest response to a typo at the toplevel is usually to return to return to toplevel and try again. If we'd gotten this sort of error when debugging a long-running application, it might be simpler to try to resume execution after invoking restart 3 or 4 in the list above:

1 > (:c 3)
Invoking restart: Specify a value of *FOO to use this time.
New value for *FOO :  nil
NIL
?

Loading Lisp Files

LOAD is the standard way of loading a (source or binary) file into a running lisp. It takes a PATHNAME (or a string or something else that designates a PATHNAME) as a required argument.

? (load "/Users/gb/fact.lisp")
#P"/Users/gb/fact.lisp"

REQUIRE is a different thing; it can often be used to load files, but what it actually tries to do is to ensure that some named piece of functionality (a "MODULE") has been made available ("PROVIDEd"). The exact mechanism that REQUIRE uses to make these modules available is implementation-dependent. In OpenMCL, the default implementation is to search a list of directories (the value of the variable CCL:*MODULE-SEARCH-PATH*) for a file whose (lowercased) name matches the module name and, if successful, to try to LOAD that file. (More generally, any function on the list which is the value of CCL:*MODULE-PROVIDER-FUNCTIONS* can perform arbitrary actions - like downloading and installing code from the net - in order to provide a module; the function that searches CCL:*MODULE-SEARCH-PATH* is, by default, the only function on the CCL:*MODULE-PROVIDER-FUNCTIONS* list.)

REQUIRE and PROVIDE are often used as system-construction tools: in order to compile X, it may be necessary that functionality defined in some other system component (file or set of files). It's not quite the same thing (it doesn't involve text substitution), but one might think of the C idiom

#include <foo.h>	/* Find foo.h in the filesystem somewhere.  We don't
                            care where (too much); we just want the functionality
                            that it offers */

and

(require "FOO")	         ;; If FOO hasn't already been provided, go find it
                         ;; and make the functionality that it offers available

as being sort of distant cousins.

Note that REQUIRE's first argument is a module name, not a pathname.