Skip to content

Roswell as a Scripting Environment

Masataro Asai edited this page Oct 24, 2021 · 10 revisions

Although Roswell is a unified interface to Common Lisp implementations, it also encourages writing scripts with it. A "Roswell script" is an implementation-independent script which can be invoked from a shell command line, launched by Roswell and run under standard CL environment.

To relatively new users of common lisp: Roswell scripts are just like shell scripts, but for lisp. Most tutorials you will find on the internet will be assuming a REPL (read-eval-print-loop), but you can do the same thing in a file.

To advanced users: Compared to writing a normal lisp script (e.g. by #!/usr/bin/sbcl -l), Roswell scripts have the following advantages:

  • A roswell script can be distributed using quicklisp's infrastructure (if you are a library author), and installed into ~/.roswell/bin with ros install [system]. By adding the directory to $PATH, those scripts are made available from the shell.
  • Also, the script can be dumped into an executable binary for the distribution purpose or for reducing the startup time. Each subsection below describes each feature.

Writing a Roswell Script

To start, run ros init in your terminal:

$ ros init
Usage: ros init [template] name [options...]

$ ros init fact
Successfully generated: fact.ros

The content of the file looks like this:

#!/bin/sh
#|-*- mode:lisp -*-|#
#|
exec ros -Q -- $0 "$@"
|#
(defun main (&rest argv)
  (declare (ignorable argv)))

Something magical seems to be happening in the beginning of the script. It actually is a shell script which exec Roswell immediately. Roswell runs the same script, skips multi-line comments, reads the rest of the file as a Common Lisp program, and finally invokes a function main with command-line arguments.

Here's an example program which takes the exact one argument and prints its factorial:

#!/bin/sh
#|-*- mode:lisp -*-|#
#|
exec ros -Q -- $0 "$@"
|#

(defun fact (n)
  (if (zerop n)
      1
      (* n (fact (1- n)))))

(defun main (n &rest argv)
  (declare (ignore argv))
  (format t "~&Factorial ~D = ~D~%" n (fact (parse-integer n))))
$ fact.ros 3
Factorial 3 = 6

$ fact.ros 10
Factorial 10 = 3628800

Though it falls through CL debugger if there's no arguments or non-integer value, I assume it's okay for now as a simple example ;) For further details, see this article.

Another thing you may notice is the speed --- why it takes so long to just compute the factorial? It is due to the slow startup time, and you can easily reduce it. See Reducing Startup Time.

Bundling scripts with an ASDF system / Distributing scripts via Quicklisp

When you write a script which would make other people happy, let's think about sharing it. The easiest way to achieve this is to just send the .ros file to the others. However, it is impractical when your script is more like a "library" containing multiple separated source files.

If you're an author of the library, then consider adding the ros file to the repository, which automatically provides a roswell-installable command-line interface to it. The .ros files should be located in roswell/ subdirectory in the repository. Once added, ros install recognizes the ros scripts in the subdirectory.

Let's make our factorial into an asdf system for example. We separate the library part and the "main script" part of the factorial into two files, where the former goes to the main source tree recognized by ASDF, and the latter goes to a roswell script under roswell/.

An example directory looks like:

+ factorial/
  + factorial.asd
  + roswell/
    + fact.ros
  + src/
    + factorial.lisp

src/factorial.lisp

(defpackage :factorial
  (:use :cl)
  (:export :fact))
(in-package :factorial)
(defun fact (n)
  (if (zerop n)
      1
      (* n (fact (1- n)))))

roswell/fact.ros

#!/bin/sh
#|-*- mode:lisp -*-|#
#|
exec ros -Q -- $0 "$@"
|#

(progn ;;init forms
  (ros:ensure-asdf)
  #+quicklisp (ql:quickload '(:factorial) :silent t)
  )

(defun main (n &rest argv)
  (declare (ignore argv))
  (format t "~&Factorial ~D = ~D~%" n (factorial:fact (parse-integer n))))

Installing a script(s) bundled with an ASDF system / distributed via Quicklisp

When ros install <library name> is invoked, it downloads the specified library from Quicklisp and then copies the script files to ~/.roswell/bin.

These scripts can be launched via ros exec <script name>. Also, when the PATH to ~/.roswell/bin is resolved, they are available from the command line.

$ ros install <library name>

For instance, try ros install factorial:

$ ros install factorial
found system factorial.
Attempting install scripts...
/path/to/.roswell/bin/fact
$ ros exec fact 10       # Note that the name should match the script name (fact.ros), not the ASDF system name.
Factorial 10 = 3628800

To avoid ros exec, add bin/ to the PATH variable:

$ echo 'export PATH="$HOME/.roswell/bin:$PATH"' >> ~/.zshrc
$ fact 10
Factorial 10 = 3628800

As another instance, try ros install clack:

$ ros install clack
found system clack.
Attempting install scripts...
/Users/nitro_idiot/.roswell/bin/clackup
$ ros exec clackup                                                ### launch the web server!
$ clackup                                                         ### launch the web server!
Clone this wiki locally