CMUCL Hints
A collection of miscellaneous hints and tips for CMUCL users - some also apply to the CMUCL spin-off, SBCL.

Informative top-level prompt

Courtesy of Paul Foley: FWIW, I like to add

(in-package "COMMON-LISP") (defvar *last-package* nil) (defvar *cached-prompt*) (defun tpl-prompt () (unless (eq *last-package* *package*) (setf *cached-prompt* (concatenate 'string (or (first (package-nicknames *package*)) (package-name *package*)) "> ")) (setf *last-package* *package*)) *cached-prompt*) (setf *prompt* #'tpl-prompt)

to my lisp.core, so I get a "USER> " prompt that changes as I change packages instead of the horrible default "* " prompt.

Correct package name for CMUCL 19a changes

(in-package "LISP")

To achieve the same effect in SBCL, use:

(defvar *last-package* nil) (defvar *cached-prompt* nil) (defun package-prompt (stream) (unless (eq *last-package* *package*) (setf *cached-prompt* (concatenate 'string (or (first (package-nicknames *package*)) (package-name *package*)) "> ")) (setf *last-package* *package*)) (terpri) (princ *cached-prompt* stream)) (setf sb-int:*repl-prompt-fun* #'package-prompt)

Using the prompt with ilisp

If you want to use the prompt above in a ilisp buffer, add the following to you emacs startup-file (.emacs):

(add-hook 'cmulisp-hook (lambda () (setq comint-prompt-regexp "^\\([0-9]+\\]+\\|\\*\\|[-a-zA-Z0-9]*\\[[0-9]+\\]:\\|[-A-Z0-9]*>\\) ") ))

Turning off GC messages

... is as easy as setting ext:*gc-verbose* to NIL.

Behaviour of setq at top-level

When

(setq foo "bar")
is entered at top-level, foo is declared special automagically. The variable ext:*top-level-auto-declare* can be used to change this behaviour; see
(describe 'ext:*top-level-auto-declare*)
for details.

Undoing a special declaration

If you find that you've accidentally created a special, you can undo it. CMUCL keeps this information in a database you can query/modify using ext:info.

* (setf x nil)
Warning:  Declaring X special.

NIL

;; Uh oh
* (ext:info :variable :kind 'x)
:SPECIAL
T

;; This changes X back to a lexical
* (setf (ext:info :variable :kind 'x) :global)
:GLOBAL

This works on SBCL too, using sb-ext:info

Using the MOP

The MOP in CMUCL is based on PCL and works pretty much like AMOP describes. There is however an oddity with it that has to do with ``wrapper'' classes, the details of which I (dan) unfortunately can't remember (so, somebody please edit this). Anyway, a reasonable fix is to work in a package that shadows a bunch of symbols from PCL instead of picking up the default versions in COMMON-LISP :

(defpackage "MY-PACKAGE" (:shadowing-import-from PCL FIND-CLASS CLASS-NAME BUILT-IN-CLASS CLASS-OF) ... )

Running Lisp programs from the shell prompt

Note that this works on Linux systems only (tested on Slackware 7.0 by the original poster). Depends on having a kernel compiled with the binfmt_misc module enabled, and root access to setup binfmt_misc. Any user can run the scripts.

From: "Vladimir V. Zolotych" <gsmith@eurocom.od.ua>
Subject: Re: echo program
Newsgroups: comp.lang.lisp
Date: Wed, 28 Feb 2001 20:19:54 +0200
Organization: DCS-EuroCom ISP

Here is the summary (Thanks to Paul Foley and
Rudolf Schlatte)

Using Slackware 7.0

The CL echo program (file ECHO.lisp)

(loop for arg in *command-line-strings* as i upfrom 0 do
      (format t "~&arg[~D] = ~S" i arg))
(terpri)

Shell script (file /usr/local/lib/batch)
#!/bin/sh
exec /usr/local/bin/lisp -batch -load $*

Settings for binfmt_misc
  # cd /proc/sys/fs/binfmt_misc/
  # echo ':CMUCL:M::FASL FILE::/usr/local/lib/batch:' >register

Running ECHO.x86f

$ echo.x86f 1 2 3
; Loading #p"/home/vlz/.cmucl-init.lisp".
; Loading #p"/home/vlz/cmucl/echo/echo.x86f".
arg[0] = "/usr/local/bin/lisp"
arg[1] = "-batch"
arg[2] = "-load"
arg[3] = "./echo.x86f"
arg[4] = "1"
arg[5] = "2"
arg[6] = "3"
$ 

-- 
Vladimir Zolotych                         gsmith@eurocom.od.ua

(also see this page)


Note that it'd be even better to use ${1+"$@"} instead of $* in the shell script to pass through all the parameters untouched.

Explanation:

$* first concatenates all parameters, separating them with space (the first character of $IFS, in fact), and after that, splits them again according to $IFS. My suggestion does NOT do this. This matters when some parameters contain whitespace, such as /some/program -name 'Hannah Schroeter'

-- Hannah Schröter hannah@schlund.de, 22 May 2001


Note that debian provides a wrapper ``cmucl-run'' with the cmucl package, which can be used with binfmt-support for binfmt_misc goodness.


CMUCL shell scripts

This is an alternative method to the linux-specific binfmt_misc method above, which should work on all Unix platforms. It consists of using a small trampoline program which invokes the CMUCL runtime with a rewritten argv. Your shell script looks something like

#!/usr/bin/cmucl-trampoline \ -quiet -batch -noinit !# (format t "~&Hello, world!~%")

You need to download the cmucl-trampoline program, and your image must contain a reader macro which treats #!..!# as comments (there is example lisp in the code).

Another way relies on your shell automatically interpreting executable files not marked with #! itself:

":" ; exec lisp -load "$0" -eval '(quit)'
or, to compile it first:
":" ; exec lisp -eval "(compile-file \"$0\")" -load `basename $0 .lisp`.x86f -eval '(quit)'

Here is what I (Erik Enge) use to write Lisp shellscripts:

#!/bin/sh
# -*- mode: sh -*-

# $Id: runlisp,v 1.3 2003/04/22 02:33:53 erik Exp $
# $Source: /home/cvsd/repo/bin/runlisp,v $

# A handy script to run Lisp as shellscripts.  In the top of your
# file, put:
#
#  #!/usr/bin/env runlisp
#
# and stick runlisp (this script) somewhere in your path.  Then you
# can have regular-looking shellscripts that are written in Lisp.

# If you have another Lisp implementation and know what arguments to
# pass to it for this to work, feel free to add it and send me
# (erik@nittin.net) the patch.

if [ -f /usr/bin/sbcl ]; then
   /usr/bin/sbcl --noinform --disable-debugger --eval '(set-dispatch-macro-character #\# #\! (lambda (stream bang number) (declare (ignore bang number)) (read-line stream) t))' --load $1 --eval '(quit)'
elif [ -f /usr/bin/lisp ]; then
   /usr/bin/lisp -quiet -batch -noinit -eval '(set-dispatch-macro-character #\# #\! (lambda (stream bang number) (declare (ignore bang number)) (read-line stream) t))' -load $1 -eval '(quit)'
else
   echo "$0: could not find a Lisp I know how to call"
fi


CLX over ssh-forwarded displays

The standard CLX function OPEN-DISPLAY doesn't correctly extract the display number from the DISPLAY environment variable. This is a problem you might run into when using CLX on an ssh-forwarded X11 session. Indeed, ssh typically sets $DISPLAY to remotehost:10, and forwards port 6010 on the remote host to port 6000 (or whatever port the local X11 server is running on) on the local host. CLX will unsuccessfully try to connect to remotehost:0.

The solution is to use the CMUCL-specific function EXT:OPEN-CLX-DISPLAY instead of XLIB:OPEN-DISPLAY. This function will parse the DISPLAY environment variable to extract the display and screen numbers.

Note that the version of CLX distributed with CMUCL includes a number of extensions to the original code. It is able to extract X11 authorization cookies from the file mentioned by the XAUTHORITY environment variable (defaulting to ~/.Xauthority), and it includes hooks into CMUCL's SERVE-EVENT facility.

NB: Many people seem to find that this does not work for them, as the question of how to use CLX locally without turning off access control is a frequent question on c.l.lisp and the CMUCL mailing list.


SERVE-EVENT example

An example of CMUCL's SERVE-EVENT facility, that allows you to handle multiple concurrent network connections and file handles, without using multithreading. The program is a port forwarder, or redirector. It redirects TCP connections to another port on another machine. Get it from http://emarsden.chez.com/downloads/.


Adding commandline switches

Certain implementations such as CLISP have a -c commandline switch that allows you to invoke the file compiler from the shell. CMUCL's commandline switches are user-extensible, so you can emulate this behaviour with code such as the following, in your site-init.lisp or ~/.cmucl-init initialization files.

(macrolet ((with-batch-processing (&body body) `(handler-case (prog1 t ,@body) (serious-condition (condition) (format *error-output* "Error in batch processing:~%~A~%" condition) (finish-output *error-output*) (throw 'lisp::%end-of-the-world 1)) (:no-error (value) (declare (ignore value)) (throw 'lisp::%end-of-the-world 0))))) (ext:defswitch "compile" #'(lambda (switch) (with-batch-processing (mapc #'compile-file (ext:cmd-switch-words switch))))) (ext:defswitch "compile-and-load" #'(lambda (switch) (with-batch-processing (mapc #'(lambda (file) (compile-file file :load t)) (ext:cmd-switch-words switch))))) ) ; macrolet

Now you can use the following to compile (and load) from the command line:

  lisp -compile-and-load demo.lisp demo2.lisp

If errors are encountered during processing, CMUCL is aborted, with a return value of 1, otherwise it returns 0 (i.e. success). This can be combined with the -quiet flag (put it at the start of the commandline) if wanted.

An alternative to this form of interaction with the file compiler is to load the files and compile them from a stream, so you don't have any FASL files hanging around on disk (thus avoiding problems with binary compatibility between different CMUCL releases), yet still benefit from compiled performance. You can do this with code such as:

(defun process-switch-demon (switch) (let ((files (copy-list (ext:cmd-switch-words switch)))) (push #'(lambda () (dolist (file files) (format *terminal-io* "~&;;; Processing compiled code from ~A.~%" file) (with-open-file (s file) (ext:compile-from-stream s)))) *run-actions*))) (ext:defswitch "process" #'process-switch-demon)

Adapted from article 87g06vubxi.fsf@orion.bln.pmsf.de posted to comp.lang.lisp by Pierre Mai, on 2001-11-30.


"Advise" facilities

In CMUCL 18e and later, use fwrappers; the encapsulation mechanism described below is deprecated.

from the release notes for CMUCL 17f:

 -- The encapsulation mechanism (similar to advise facilities) has been
    revamped to work on SETF function names as well as symbols.

    EXT:ENCAPSULATED-DEFINITION
       Returns whatever definition is stored for name, regardless of whether
       it is encapsulated.  Unlike FDEFINITION, this does not strip off the
       encapsulation.  This is SETF'able.
       
    EXT:ENCAPSULATE
       Replaces the definition of name with a function that binds name's
       arguments a variable named argument-list, binds name's definition to a
       variable named basic-definition, and EVAL's body in that context.  Type
       is whatever you would like to associate with this encapsulation for
       identification in case you need multiple encapsuations of the same
       name.
       
    EXT:UNENCAPSULATE
       Removes name's most recent encapsulation of the specified type.
       
    EXT:ENCAPSULATED-P
       Returns t if name has an encapsulation of the given type, otherwise
       nil.
       
    EXT:*SETF-FDEFINITION-HOOK*
       A list of functions invoked by (SETF FDEFINITION) before storing the
       new value.  Each hook function must take the function name and the
       new-value.

also see: section 3.10.1 of the CMUCL Users' Manual; (describe 'ext:encapsulate)


Command line editing and auto completion with CMUCL

For the rare cases when you don't use ILISP but invoke CMUCL directly from the command line:

  1. Get rlwrap and build it.
  2. Put something like
    alias cmucl="rlwrap cmucl"
    into your ~/.bashrc (or ~/.profile or whatever).
  3. Put this file into your home directory, call it ~/.cmucl_completions.
  4. Optionally adjust your ~/.inputrc file (actually the file where $INPUTRC points to). I added the line
    TAB: complete
    because rlwrap binds this key to menu-complete.

Note that recent readline versions include an example program rlfe that does almost the same thing as rlwrap. I think rlwrap is nicer, though.

For those of you on Debian, cle that does pretty much the same thing (but no tab-completion) is packaged on testing.


Allegro-like REPL commands

To get top-level commands like :load "foo.lisp" either use Paul Foley's Toplevel or Peder O. Klingenberg's http://heim.ifi.uio.no/~pok/download/commands.lisp.


programming tips