Skip to content

Instantly share code, notes, and snippets.

@fukamachi
Last active April 10, 2021 16:39
Show Gist options
  • Save fukamachi/94f067a22776bede563c to your computer and use it in GitHub Desktop.
Save fukamachi/94f067a22776bede563c to your computer and use it in GitHub Desktop.
Common Lisp Scripting with Roswell (Draft)

Common Lisp Scripting with Roswell

"Roswell Script" is implementation-independent Common Lisp scripting program which uses Roswell. Although Roswell itself is a unified interface to Common Lisp implementations, it also encourages writing scripts with it.

To start writing it, run ros init in your terminal:

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

$ ros init fact
Successfully generated: fact.ros

It adds a file extension .ros.

Perhaps it's hardly understandable if you have been using only Unix OSes for years like me. Actually, it works well even without .ros on Unix, so renaming it to the name which doesn't have .ros is a usual manner if the script doesn't have to run on Windows. However, it helps Roswell to differentiate it from shell scripts, and Roswell provides some additional features for it like Making Executables.

The content of the file looks like this:

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

This looks a little bit hacky. 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 ;)

Speeding Up

Hey! It's a script. And Common Lisp is pretty fast programming language like C, right?

Well, unfortunately, when scripting in Common Lisp, you have to face its startup time.

$ time fact.ros 10
Factorial 10 = 3628800
fact.ros 10  0.74s user 0.21s system 95% cpu 0.994 total

This very simple program took 0.74s to print the answer. Considering that all Common Lispers can easily compute the factorial of 10 in less than 1 sec, it runs unbearably slow to help us anyway.

One easy solution is skipping loading Quicklisp, as it doesn't require it.

Replace -Q flag by +Q in the ros command:

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

...

Retry time command to see how it worked:

$ time fact.ros 10
Factorial 10 = 3628800
fact.ros 10  0.57s user 0.19s system 97% cpu 0.780 total

It's a little bit better.

Making Executables

Roswell also provides a command to convert a script into an executable, ros build:

$ ros build fact.ros
[undoing binding stack and other enclosing state... done]
[saving current Lisp image into fact:
writing 4976 bytes from the read-only space at 0x20000000
writing 3168 bytes from the static space at 0x20100000
writing 52330496 bytes from the dynamic space at 0x1000000000
done]

An executable version is generated at fact. If your script doesn't have a file extension .ros, Roswell overrides your file and the behaviour is unknown.

Try time for the executable:

$ time fact 10
Factorial 10 = 3628800
fact 10  0.00s user 0.02s system 96% cpu 0.024 total

Well, is it fast enough now? :)

Distributing Scripts via Quicklisp

When you write a script which would make other people happy, think about distributing it.

The easiest way to do it is, just sending the .ros file to others. But, it's hard if your script is more like a "library", so too big to put all stuff into a single file.

Or, if you're a library author, you may think of providing a command-line interface to it.

ros install would be a solution for it. It's known as a command for installing Common Lisp implementation, however,it is also a command for installing Common Lisp libraries.

$ ros install <library name>

It downloads the specified library from Quicklisp, and it installs bundled Roswell scripts if there's any.

For instance, try ros install clack:

$ ros install clack
found system clack.
Attempting install scripts...
/Users/nitro_idiot/.roswell/bin/clackup

It copies script files which located the roswell/ directory of the library into ~/.roswell/bin and gives an executable flag to them.

Don't forget to add ~/.roswell/bin to $PATH to use those scripts:

$ echo "export PATH=\"\$HOME/.roswell/bin:\$PATH\"" >> ~/.zshrc

Real examples

Here're libraries bundling Roswell scripts.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment