Most CL implementations provide convenient toplevel commands, like
;; ECL command to load a Lisp file
:ld file.lisp
These commands tend to start with a colon and end with a newline, possibly including space-separated arguments. Such commands are too similar to not want a portable way to define them. Trivial Toplevel Commands is such a portability layer. It allows to define and remove toplevel commands on SBCL, CCL, ECL, ABCL, CLISP, and Allegro CL.
Couple of caveats:
- There are no toplevel commands on SBCL
- there are only debugger
commands. Trivial Toplevel Commands only defines debugger command to
not interfere with default SBCL REPL. If you want to access defined
commands, enter the debugger by signaling some condition (
(break)
, at least) or contribute a more involved REPL to SBCL directly.- You can also
(require "sb-aclrepl")
to enable an Allegro-like REPL. Trivial Toplevel Commands supports it.
- You can also
- CCL commands only accept evaluated arguments
- macro-like commands
are thus not possible. If you want to have maximum portability, only
use
define-toplevel-command/eval
or constant arguments. Contribute a new REPL to CCL if you want to change that. - ECL
/read
and/string
commands remove a layer of quotes from strings - I.e. “”hello”
hello” will be read as “hello hello”. Plan accordingly.
/eval
commands are unaffected. - Help with making it work on CLASP, CMUCL, and LispWorks
- I don’t have access to these implementations. Like, at all. Thanks.
Clone the Git repository:
git clone --recursive https://github.com/aartaka/trivial-toplevel-commands ~/common-lisp/
And then load :trivial-toplevel-commands
in the REPL:
(asdf:load-system :trivial-toplevel-commands)
;; or, if you use Quicklisp
(ql:quickload :trivial-toplevel-commands)
You can also install Trivial Toplevel Commands via Guix, using the
bundled guix.scm
file:
guix package -f guix.scm
A simple shell-invoking command mimicking ruricolist/cmd. Obviously quite primitive:
(tpl-cmds:define-command/eval (:cmd :!) (command)
"Shell invocation shortcut."
(uiop:launch-program command))
;; Used like:
:! "touch ~/cmd-test.txt"
;; With an apparent result
(uiop:file-exists-p #p"~/cmd-test.txt")
;; => #P"~/cmd-test.txt"
A much more involved command processing raw string argument, say, for your MP3 player setup:
(tpl-cmds:define-command/string (:list-mp3s :lm) (path)
"Recursively list all MP3 files in PATH, be it directory or file."
(labels ((ls (file)
(cond
((uiop:directory-pathname-p file)
(format t "~&Directory ~a..." file)
(mapc #'ls (uiop:subdirectories file))
(mapc #'ls (uiop:directory-files file)))
((equalp "mp3" (pathname-type file))
(format t "~&File ~a..." file)))))
(ls (uiop:merge-pathnames* (uiop:parse-native-namestring path) (uiop:getcwd)))))
;; Used like:
:list-mp3s /path/to/your/mp3/files
;; or
:lm /path/to/your/mp3/files
;; Directory ...
;; Directory ...
;; File ...
;; Directory ...
;; Directory ...
;; File ...
;; File ...
;; File ...
;; File ...
You can also undefine previously defined commands with remove-command
:
;; By alias:
(tpl-cmds:remove-command :lm)
;; Or by full name:
(tpl-cmds:remove-command :list-mp3s)
Most of the information for command is accessible too, via helper functions:
- Command char for implementation (setf-able)
command-char
.- Command alias by name
command-alias
.- And vice versa
command-name
.- The handler function for the command
command-handler
.- Documentation for the command can be fetched with
(documentation (command-handler :command) 'function)
.
- Documentation for the command can be fetched with
You can also see the practical uses for commands in my config.
- Peder Klingenberg’s commands.lisp
- CMUCL-specific, could possibly work on SBCL.
- Paul Foley’s Toplevel
- CMUCL-specific.