| 1 | # -*-org-*- |
| 2 | #+TITLE: ~runlisp~ -- run scripts written in Common Lisp |
| 3 | #+AUTHOR: Mark Wooding |
| 4 | #+LaTeX_CLASS: strayman |
| 5 | #+LaTeX_HEADER: \usepackage{tikz, gnuplot-lua-tikz} |
| 6 | #+EXPORT_FILE_NAME: doc/README.pdf |
| 7 | |
| 8 | ~runlisp~ is a small C program intended to be run from a script ~#!~ |
| 9 | line. It selects and invokes a Common Lisp implementation, so as to run |
| 10 | the script. In this sense, ~runlisp~ is a partial replacement for |
| 11 | ~cl-launch~. |
| 12 | |
| 13 | Currently, the following Lisp implementations are supported: |
| 14 | |
| 15 | + Armed Bear Common Lisp (~abcl~), |
| 16 | + Clozure Common Lisp (~ccl~), |
| 17 | + GNU CLisp (~clisp~), |
| 18 | + Carnegie--Mellon Univerity Common Lisp (~cmucl~), |
| 19 | + Embeddable Common Lisp (~ecl~), and |
| 20 | + Steel Bank Common Lisp (~sbcl~). |
| 21 | |
| 22 | Adding more Lisps is simply a matter of writing the necessary runes in a |
| 23 | configuration file. Of course, there's a benefit to having a collection |
| 24 | of high-quality configuration runes curated centrally, so I'm happy to |
| 25 | accept submissions in support of any free[fn:free] Lisp implementations. |
| 26 | |
| 27 | [fn:free] Here I mean free as in freedom. |
| 28 | |
| 29 | |
| 30 | * Writing scripts in Common Lisp |
| 31 | |
| 32 | ** Basic use |
| 33 | |
| 34 | The obvious way to use ~runlisp~ is in a shebang (~#!~) line at the top |
| 35 | of a script. For example: |
| 36 | |
| 37 | : #! /usr/local/bin/runlisp |
| 38 | : (format t "Hello from Lisp!~%") |
| 39 | |
| 40 | Script interpreters must be named with absolute pathnames in shebang |
| 41 | lines; if your ~runlisp~ is installed somewhere other than |
| 42 | ~/usr/local/bin/~ then you'll need to write something different. |
| 43 | Alternatively, a common hack involves abusing the ~env~ program as a |
| 44 | script interpreter, because it will do a path search for the program |
| 45 | it's supposed to run: |
| 46 | |
| 47 | : #! /usr/bin/env runlisp |
| 48 | : (format t "Hello from Lisp!~%") |
| 49 | |
| 50 | ** Specific Lisps |
| 51 | |
| 52 | Lisp implementations are not created equal -- for good reason. If your |
| 53 | script depends on the features of some particular Lisp implementation, |
| 54 | then you can tell ~runlisp~ that it must use that implementation to run |
| 55 | your script using the ~-L~ option; for example: |
| 56 | |
| 57 | : #! /usr/local/bin/runlisp -Lsbcl |
| 58 | : (format t "Hello from Steel Bank Common Lisp!~%") |
| 59 | |
| 60 | If your script supports several Lisps, but not all, then list them all |
| 61 | in the ~-L~ option, separated by commas: |
| 62 | |
| 63 | : #! /usr/local/bin/runlisp -Lsbcl,ccl |
| 64 | : (format t #.(concatenate 'string |
| 65 | : "Hello from " |
| 66 | : #+sbcl "Steel Bank" |
| 67 | : #+ccl "Clozure" |
| 68 | : #-(or sbcl ccl) "an unexpected" |
| 69 | : " Common Lisp!~%")) |
| 70 | |
| 71 | ** Embedded options |
| 72 | |
| 73 | If your script requires features of particular Lisp implementations |
| 74 | /and/ you don't want to hardcode an absolute path to ~runlisp~, then you |
| 75 | have a problem. Most Unix-like operating systems will parse a shebang |
| 76 | line into the initial ~#!~, the pathname to the interpreter program, |
| 77 | and a /single/ optional argument: any further spaces don't separate |
| 78 | further arguments: they just get included in the first argument, all the |
| 79 | way up to the end of the line. So |
| 80 | |
| 81 | : #! /usr/bin/env runlisp -Lsbcl |
| 82 | : (format t "Hello from Steel Bank Common Lisp!~%") |
| 83 | |
| 84 | won't work: it'll just try to run a program named ~runlisp -Lsbcl~, with |
| 85 | a space in the middle of its name, and that's quite unlikely to exist. |
| 86 | |
| 87 | To help with this situation, ~runlisp~ reads /embedded options/ from |
| 88 | your script. Specifically, if the script's second line contains the |
| 89 | token ~@RUNLISP:~ then ~runlisp~ will parse additional options from this |
| 90 | line. So the following will work properly. |
| 91 | |
| 92 | : #! /usr/bin/env runlisp |
| 93 | : ;;; @RUNLISP: -Lsbcl |
| 94 | : (format t "Hello from Steel Bank Common Lisp!~%") |
| 95 | |
| 96 | Embedded options are split at spaces properly. Spaces can be escaped or |
| 97 | quoted in (an approximation to) the usual shell manner, should that be |
| 98 | necessary. See the manpage for the gory details. |
| 99 | |
| 100 | ** Common environment |
| 101 | |
| 102 | ~runlisp~ puts some effort into making sure that Lisp scripts get the |
| 103 | same view of the world regardless of which implementation is running |
| 104 | them. |
| 105 | |
| 106 | For example: |
| 107 | |
| 108 | + The ~asdf~ and ~uiop~ systems are loaded and ready for use. |
| 109 | |
| 110 | + The script's command-line arguments are available in |
| 111 | ~uiop:*command-line-arguments*~. Its name can be found by calling |
| 112 | ~(uiop:argv0)~ -- though it's probably also in ~*load-pathname*~. |
| 113 | |
| 114 | + The prevailing Unix standard input, output, and error files are |
| 115 | available through the Lisp ~*standard-input*~, ~*standard-output*~, |
| 116 | and ~*error-ouptut*~ streams, respectively. (This is, alas, not a |
| 117 | foregone conclusion.) |
| 118 | |
| 119 | + The keyword ~:runlisp-script~ is added to the ~*features*~ list. |
| 120 | This means that your script can tell whether it's being run from the |
| 121 | command line, and should therefore do its thing and then quit; or |
| 122 | merely being loaded into a Lisp system, e.g., for debugging or |
| 123 | development, and should sit still and not do anything until it's |
| 124 | asked. |
| 125 | |
| 126 | See the manual for the complete list of guarantees. |
| 127 | |
| 128 | |
| 129 | * Invoking Lisp implementations |
| 130 | |
| 131 | ** Basic use |
| 132 | |
| 133 | A secondary use of ~runlisp~ is in build scripts for Lisp programs. If |
| 134 | the entire project is just a Lisp library, then it's possibly acceptable |
| 135 | to just provide an ASDF system definition and expect users to type |
| 136 | ~(asdf:load-system "mumble")~ to use it. If it's a program, or there |
| 137 | are things other than Lisp which ASDF can't or shouldn't handle -- |
| 138 | significant pieces in other languages, or a Lisp executable image to |
| 139 | make and install -- then it seems sensible to make the project's main |
| 140 | build system be something language-agnostic, say Unix ~make~, and |
| 141 | arrange for that to invoke ASDF at the appropriate time. |
| 142 | |
| 143 | But how should that be arranged? It's relatively easy for a project' |
| 144 | Lisp code to support multiple Lisp implementation; but each |
| 145 | implementation wants different runes for evaluating Lisp forms from the |
| 146 | command line, and some of them don't provide an ideal environment for |
| 147 | integrating into a build system. So ~runlisp~ provides a simple common |
| 148 | command-line interface for evaluating Lisp forms. For example: |
| 149 | |
| 150 | : $ runlisp -e '(format t "~A~%" (+ 1 2))' |
| 151 | : 3 |
| 152 | |
| 153 | If your build script needs to get information out of Lisp, then wrapping |
| 154 | ~format~, or even ~prin1~, around forms is annoying; so ~runlisp~ has a |
| 155 | ~-p~ option which prints the values of the forms it evaluates. |
| 156 | |
| 157 | : $ runlisp -e '(+ 1 2)' |
| 158 | : 3 |
| 159 | |
| 160 | If a form produces multiple values, then ~-p~ will print all of them |
| 161 | separated by spaces, on a single line: |
| 162 | |
| 163 | : $ runlisp -p '(floor 5 2)' |
| 164 | : 2 1 |
| 165 | |
| 166 | In addition to evaluating forms with ~-e~, and printing their values |
| 167 | with ~-p~, you can also load a file of Lisp code using ~-l~. |
| 168 | |
| 169 | When ~runlisp~ is acting on ~-e~, ~-p~, and/or ~-l~ options, it's said |
| 170 | to be running in /eval/ mode, rather than its usual /script/ mode. In |
| 171 | eval mode, it /doesn't/ set ~:runlisp-script~ in ~*features*~. |
| 172 | |
| 173 | You can still insist that ~runlisp~ use a particular Lisp |
| 174 | implementation, or one of a subset of implementations, using the ~-L~ |
| 175 | option mentioned above. |
| 176 | |
| 177 | : $ runlisp -Lsbcl -p "(lisp-implementation-type)" |
| 178 | : "SBCL" |
| 179 | |
| 180 | ** Command-line processing |
| 181 | |
| 182 | When scripting a Lisp -- as opposed to running a Lisp script -- it's not |
| 183 | necessarily the case that your script knows in advance exactly what it |
| 184 | needs to ask Lisp to do. For example, it might need to tell Lisp to |
| 185 | install a program in a particular directory, determined by Autoconf. |
| 186 | While it's certainly /possible/ to quote such data and splice them into |
| 187 | Lisp forms, it's more convenient to pass them in separately. So |
| 188 | ~runlisp~ ensures that the command-line options are available to Lisp |
| 189 | forms via ~uiop:*command-line-arguments*~, as they are to a Lisp script. |
| 190 | |
| 191 | : $ runlisp -p "uiop:*command-line-arguments*" one two three |
| 192 | : ("one" "two" "three") |
| 193 | |
| 194 | When running Lisp forms like this, ~(uiop:argv0)~ isn't very |
| 195 | meaningful. (Currently, it reveals the name of the script which |
| 196 | ~runlisp~ uses to implement this feature.) |
| 197 | |
| 198 | |
| 199 | * Configuring =runlisp= |
| 200 | |
| 201 | ** Where =runlisp= looks for configuration |
| 202 | |
| 203 | You can influence which Lisp implementations are chosen by ~runlisp~ by |
| 204 | writing configuration files, and/or setting environment variables. |
| 205 | |
| 206 | The ~runlisp~ program looks for configuration in a number of places. |
| 207 | |
| 208 | + There's a system-global directory ~SYSCONFDIR/runlisp/runlisp.d/~. |
| 209 | All of the files in this directory named ~SOMETHING.conf~ are read, |
| 210 | in increasing lexicographical order by name. The package comes with |
| 211 | a file ~0base.conf~ intended to be read first, so that it can be |
| 212 | overridden if necessar. This sets up basic definitions, and defines |
| 213 | the necessary runes for those Lisp implementations which are |
| 214 | supported `out of the box'. New Lisp packages might come with |
| 215 | additional files to drop into this directory. |
| 216 | |
| 217 | + There's a system-global file ~SYSCONFDIR/runlisp/runlisp.conf~ which |
| 218 | is intended to be edited by the system administrator to account for |
| 219 | any local quirks. This is read /after/ the directory, which is |
| 220 | intended to be used by distribution packages, so that the system |
| 221 | administrator can override them. |
| 222 | |
| 223 | + Users can create files ~$HOME/.runlisp.conf~ and/or |
| 224 | ~$HOME/.config/runlisp.conf~[fn:xdg-config] in their home |
| 225 | directories to add support for privately installed Lisp systems, or |
| 226 | to override settings made by earlier configuration files. |
| 227 | |
| 228 | The configuration syntax is complicated, and explained in detail in the |
| 229 | *runlisp.conf* manpage. |
| 230 | |
| 231 | Configuration options can also be set on the command line, though the |
| 232 | effects are subtly different. Again, see the manual pages for details. |
| 233 | |
| 234 | [fn:xdg-config] More properly, in ~$XDG_CONFIG_HOME/runlisp.conf~, if |
| 235 | you set that. |
| 236 | |
| 237 | |
| 238 | ** Deciding which Lisp implementation to use |
| 239 | |
| 240 | The ~prefer~ option specifies a /preference list/ of Lisp |
| 241 | implementations. The value is a list of Lisp implementation names, as |
| 242 | you'd give to ~-L~, separated by commas and/or spaces. If the |
| 243 | environment variable ~RUNLISP_PREFER~ is set, then this overrides any |
| 244 | value found in the configuration files. |
| 245 | |
| 246 | When deciding which Lisp implementation to use, ~runlisp~ works as |
| 247 | follows. It builds a list of /acceptable/ Lisp implementations from the |
| 248 | ~-L~ command-line option, and a list of /preferred/ Lisp implementations |
| 249 | from the ~prefer~ configuration option (or environment variable). If |
| 250 | there aren't any ~-L~ options, then it assumes that /all/ Lisp |
| 251 | implementations are acceptable; if no ~prefer~ option is set then it |
| 252 | assumes that /no/ Lisp implementations are preferred. It then works |
| 253 | through the preferred list in order: if it finds an implementation which |
| 254 | is installed and acceptable, then it uses that one. If that doesn't |
| 255 | work, then it works through the acceptable implementations that it |
| 256 | hasn't tried yet, in order, and if it finds one of those that's |
| 257 | installed, then it runs that one. Otherwise it reports an error and |
| 258 | gives up. |
| 259 | |
| 260 | |
| 261 | ** Supporting new Lisp implementations |
| 262 | |
| 263 | ~runlisp~ tries hard to make adding support for a new Lisp as painless |
| 264 | as possible. An awkward Lisp will of course cause trouble, but |
| 265 | ~runlisp~ itself is easy. |
| 266 | |
| 267 | As a simple example, let's add support for the 32-bit version of |
| 268 | Clozure\nbsp{}CL. The source code for Clozure\nbsp{}CL easily builds |
| 269 | both 32- and 64-bit binaries in either 32- or 64-bit userlands, and one |
| 270 | might reasonably want to use the 32-bit CCL for some reason. The |
| 271 | following configuration stanza is sufficient |
| 272 | |
| 273 | : [ccl32] |
| 274 | : @PARENTS = ccl |
| 275 | : command = ${@ENV:CCL32?ccl32} |
| 276 | |
| 277 | + The first line heads a configuration section, providing the name |
| 278 | which will be used for this Lisp implementation, e.g., in ~-L~ |
| 279 | options or ~prefer~ lists. |
| 280 | |
| 281 | + The second line tells ~runlisp~ that configuration settings not |
| 282 | found in this section should be looked up in the ~ccl~ section |
| 283 | instead. |
| 284 | |
| 285 | + The third line defines the command to be used to invoke the Lisp |
| 286 | system. It tries to find an environment variable named ~CCL32~, |
| 287 | falling back to looking up ~ccl32~ in the path otherwise. |
| 288 | |
| 289 | And, err..., that's it. The ~@PARENTS~ setting uses the detailed |
| 290 | command-line runes for ~ccl~, so they don't need to be written out |
| 291 | again. |
| 292 | |
| 293 | That was rather anticlimactic, because all of the work got done |
| 294 | somewhere else. So let's look at a complete example: Steel Bank Common |
| 295 | Lisp. (SBCL's command-line interface is well thought-out, so this is an |
| 296 | ideal opportunity to explain how ~runlisp~ configuration works, without |
| 297 | getting bogged down in the details of fighting less amenable Lisps.) |
| 298 | |
| 299 | The provided ~0base.conf~ file defines SBCL as follows. |
| 300 | |
| 301 | : [sbcl] |
| 302 | : |
| 303 | : command = ${@ENV:SBCL?sbcl} |
| 304 | : image-file = ${@NAME}+asdf.core |
| 305 | : |
| 306 | : run-script = |
| 307 | : ${command} --noinform |
| 308 | : $?@IMAGE{--core "${image-path}" --eval "${image-restore}" | |
| 309 | : --eval "${run-script-prelude}"} |
| 310 | : --script "${@SCRIPT}" |
| 311 | : |
| 312 | : dump-image = |
| 313 | : ${command} --noinform --no-userinit --no-sysinit --disable-debugger |
| 314 | : --eval "${dump-image-prelude}" |
| 315 | : --eval "(sb-ext:save-lisp-and-die \"${@IMAGENEW|q}\")" |
| 316 | |
| 317 | Let's take this in slightly larger pieces. |
| 318 | |
| 319 | + We see the ~[sbcl]~ section heading, and the ~command~ setting |
| 320 | again. These should now be unsurprising. |
| 321 | |
| 322 | + There's no ~@PARENTS~ setting, so by default the ~sbcl~ section |
| 323 | inherits settings from the ~@COMMON~ section, defined in |
| 324 | ~0base.conf~. We shall use a number of definitions from this |
| 325 | section. |
| 326 | |
| 327 | + The ~image-file~ gives the name of the custom image file to look for |
| 328 | when trying to start SBCL, but not the directory. (The directory is |
| 329 | named by the ~image-dir~ configuration setting.) The image file |
| 330 | will be named ~sbcl+asdf.core~, but this isn't what's written. |
| 331 | Instead, it uses ~${@NAME}~, which is replaced by the name of the |
| 332 | section being processed. When we're running SBCL, this does the |
| 333 | same thing; but if someone wants to configure a new ~foo~ Lisp and |
| 334 | set ~@PARENTS~ to ~sbcl~, then the image file for ~foo~ will be |
| 335 | named ~foo+asdf.core~ by default. You needn't take such care when |
| 336 | configuring Lisp implementations for your own purposes, but it's |
| 337 | important for configurations which will be widely used. |
| 338 | |
| 339 | + The ~run-script~ setting explains how to get SBCL to run a script. |
| 340 | This string is broken into words at (unquoted) spaces. |
| 341 | |
| 342 | The syntax ~$?VAR{CONSEQ|ALT}~ means: if a configuration setting |
| 343 | ~VAR~ is defined, then expand to ~CONSEQ~; otherwise, expand to |
| 344 | ~ALT~. In this case, if the magic setting ~@IMAGE~ is defined, then |
| 345 | we add the tokens ~--core "${image-path}" --eval "${image-restore}"~ |
| 346 | to the SBCL command line; otherwise, we add ~--eval |
| 347 | "${run-script-prelude}"~. The ~@IMAGE~ setting is defined by |
| 348 | ~runlisp~ only if (a)\nbsp{}a custom image was found in the correct |
| 349 | place, and (b)\nbsp{}use of custom images isn't disabled on its |
| 350 | command line. |
| 351 | |
| 352 | The ~${image-path}~ token expands to the full pathname to the custom |
| 353 | image file; ~image-restore~ is a predefined Lisp expression to be |
| 354 | run when starting from a dumped image (e.g., to get ASDF to refresh |
| 355 | its idea of which systems are available). |
| 356 | |
| 357 | The ~run-script-prelude~ is another (somewhat involved) Lisp |
| 358 | expression which sets up a Lisp environment suitable for running |
| 359 | scripts -- e.g., by arranging to ignore ~#!~ lines, and pushing |
| 360 | ~:runlisp-script~ onto ~*features*~. |
| 361 | |
| 362 | Finally, regardless of whether we're using a custom or vanilla |
| 363 | image, we add the tokens ~--script "${@SCRIPT}"~ to the command |
| 364 | line. The ~${@SCRIPT}~ token is replaced by the actual script |
| 365 | pathname. ~runlisp~ then appends further arguments from its own |
| 366 | command line and runs the command. (For most Lisps, ~uiop~ needs a |
| 367 | ~--~ marker before the user arguments, but not for SBCL.) |
| 368 | |
| 369 | + Finally, ~dump-image~ defines a command line for dumping a custom |
| 370 | images. The ~dump-image-prelude~ setting is a Lisp expression for |
| 371 | setting up a Lisp so that it will be in a useful state when dumped: |
| 372 | it's very similar to ~run-script-prelude~, and is built out of many |
| 373 | of the same pieces. |
| 374 | |
| 375 | The thing we haven't seen before is ~${@IMAGENEW|q}~. The |
| 376 | ~@IMAGENEW~ setting is defined by the ~dump-runlisp-image~ program |
| 377 | to name the file in which the new image should be |
| 378 | saved.[fn:image-rename] The ~|q~ `filter' is new: it means that the |
| 379 | filename should be escaped suitable for inclusion in a Lisp quoted |
| 380 | string, by prefixing each ~\~ or ~"~ with a ~\~. |
| 381 | |
| 382 | That's more or less all there is. SBCL is a particularly simple |
| 383 | example, but mostly because other Lisp implementations require fancier |
| 384 | stunts /at the Lisp level/. The ~runlisp~-level configuration isn't any |
| 385 | more complicated than SBCL. |
| 386 | |
| 387 | [fn:image-rename] ~dump-runlisp-image~ wants to avoid clobbering an |
| 388 | existing image with a half-finished one, so it tries to arrange for the |
| 389 | new image to be written to a different file, and then renames it once |
| 390 | it's been created successfully.) |
| 391 | |
| 392 | |
| 393 | * What's wrong with =cl-launch=? |
| 394 | |
| 395 | The short version is that ~cl-launch~ is slow and inconvenient. |
| 396 | ~cl-launch~ is a big, complicated Common Lisp/Bourne shell polyglot |
| 397 | which tries to do everything but doesn't quite succeed. |
| 398 | |
| 399 | ** It's slow. |
| 400 | |
| 401 | I took a trivial Lisp script: |
| 402 | |
| 403 | : (format t "Hello from ~A!~%~ |
| 404 | : Script = `~A'~%~ |
| 405 | : Arguments = (~{`~A'~^, ~})~%" |
| 406 | : (lisp-implementation-type) |
| 407 | : (uiop:argv0) |
| 408 | : uiop:*command-line-arguments*) |
| 409 | |
| 410 | I timed how long it took to run on all of ~runlisp~'s supported Lisp |
| 411 | implementations, and compared them to how long ~cl-launch~ took: the |
| 412 | results are shown in table [[tab:runlisp-vanilla]]. ~runlisp~ is /at least/ |
| 413 | two and half times faster at running this script than ~cl-launch~ on all |
| 414 | implementations except Clozure\nbsp{}CL[fn:slow-ccl], and approaching |
| 415 | four and a half times faster on SBCL. |
| 416 | |
| 417 | #+CAPTION: ~cl-launch~ vs ~runlisp~ (with vanilla images) |
| 418 | #+NAME: tab:runlisp-vanilla |
| 419 | #+ATTR_LATEX: :float t :placement [tbp] |
| 420 | |------------------+-------------------+-----------------+----------------------| |
| 421 | | *Implementation* | *~cl-launch~ (s)* | *~runlisp~ (s)* | *~runlisp~ (factor)* | |
| 422 | |------------------+-------------------+-----------------+----------------------| |
| 423 | | ABCL | 7.3378 | 2.6474 | 2.772 | |
| 424 | | Clozure CL | 1.2888 | 0.9742 | 1.323 | |
| 425 | | GNU CLisp | 1.2405 | 0.2703 | 4.589 | |
| 426 | | CMU CL | 0.9521 | 0.3097 | 3.074 | |
| 427 | | ECL | 0.8020 | 0.3236 | 2.478 | |
| 428 | | SBCL | 0.3205 | 0.0874 | 3.667 | |
| 429 | |------------------+-------------------+-----------------+----------------------| |
| 430 | #+TBLFM: $4=$2/$3;%.3f |
| 431 | |
| 432 | But this is using the `vanilla' Lisp images installed with the |
| 433 | implementations. ~runlisp~ by default builds custom images for most |
| 434 | Lisp implementations, which improves startup performance significantly; |
| 435 | see table [[tab:runlisp-custom]]. (I don't currently know how to build a |
| 436 | useful custom image for ABCL. ~runlisp~ does build a custom image for |
| 437 | ECL, but it doesn't help significantly.) These results are summarized |
| 438 | in figure [[fig:lisp-graph]]. |
| 439 | |
| 440 | #+CAPTION: ~cl-launch~ vs ~runlisp~ (with custom images) |
| 441 | #+NAME: tab:runlisp-custom |
| 442 | #+ATTR_LATEX: :float t :placement [tbp] |
| 443 | |------------------+-------------------+-----------------+----------------------| |
| 444 | | *Implementation* | *~cl-launch~ (s)* | *~runlisp~ (s)* | *~runlisp~ (factor)* | |
| 445 | |------------------+-------------------+-----------------+----------------------| |
| 446 | | ABCL | 7.3378 | 2.7023 | 2.715 | |
| 447 | | Clozure CL | 1.2888 | 0.0371 | 34.739 | |
| 448 | | GNU CLisp | 1.2405 | 0.0191 | 64.948 | |
| 449 | | CMU CL | 0.9521 | 0.0060 | 158.683 | |
| 450 | | ECL | 0.8020 | 0.3275 | 2.449 | |
| 451 | | SBCL | 0.3205 | 0.0064 | 50.078 | |
| 452 | |------------------+-------------------+-----------------+----------------------| |
| 453 | #+TBLFM: $4=$2/$3;%.3f |
| 454 | |
| 455 | #+CAPTION: Comparison of ~runlisp~ and ~cl-launch~ times |
| 456 | #+NAME: fig:lisp-graph |
| 457 | #+ATTR_LATEX: :float t :placement [tbp] |
| 458 | [[file:doc/lisp-graph.tikz]] |
| 459 | |
| 460 | Unlike ~cl-launch~, with some Lisp implementations at least, ~runlisp~ |
| 461 | startup performance is usefully comparable to other popular scripting |
| 462 | language implementations. I wrote similarly trivial scripts in a number |
| 463 | of other languages, and timed them; the results are tabulated in table |
| 464 | [[tab:runlisp-interp]] and graphed in figure [[fig:interp-graph]]. |
| 465 | |
| 466 | #+CAPTION: ~runlisp~ vs other interpreters |
| 467 | #+NAME: tab:runlisp-interp |
| 468 | #+ATTR_LATEX: :float t :placement [tbp] |
| 469 | |------------------------------+-------------| |
| 470 | | *Implementation* | *Time (ms)* | |
| 471 | |------------------------------+-------------| |
| 472 | | Clozure CL | 37.1 | |
| 473 | | GNU CLisp | 19.1 | |
| 474 | | CMU CL | 6.0 | |
| 475 | | SBCL | 6.4 | |
| 476 | |------------------------------+-------------| |
| 477 | | Perl | 1.1 | |
| 478 | | Python | 6.8 | |
| 479 | |------------------------------+-------------| |
| 480 | | Debian Almquist shell (dash) | 1.2 | |
| 481 | | GNU Bash | 1.5 | |
| 482 | | Z Shell | 3.1 | |
| 483 | |------------------------------+-------------| |
| 484 | | Tiny C (compile & run) | 1.6 | |
| 485 | | GCC (precompiled) | 0.6 | |
| 486 | |------------------------------+-------------| |
| 487 | |
| 488 | #+CAPTION: Comparison of ~runlisp~ and other script interpreters |
| 489 | #+NAME: fig:interp-graph |
| 490 | #+Attr_latex: :float t :placement [tbp] |
| 491 | [[file:doc/interp-graph.tikz]] |
| 492 | |
| 493 | (All the timings in this section were performed on the same 2020 Dell |
| 494 | XPS13 laptop running Debian `buster'. The tools used to make the |
| 495 | measurements are included in the source distribution, in the ~bench/~ |
| 496 | subdirectory.) |
| 497 | |
| 498 | [fn:slow-ccl] I don't know why Clozure\nbsp{}CL shows such a small |
| 499 | difference here. |
| 500 | |
| 501 | ** It's inconvenient |
| 502 | |
| 503 | ~cl-launch~ has this elaborate machinery which reads shell script |
| 504 | fragments from various places and sets variables like ~$LISPS~, but it |
| 505 | doesn't quite work. |
| 506 | |
| 507 | Unlike other scripting languages such as Perl or Python, Common Lisp has |
| 508 | lots of implementations, and they all have various unique features (and |
| 509 | bugs) which a script might rely on (or need to avoid). Also, a user |
| 510 | might have preferences about which Lisps to use. ~cl-launch~'s approach |
| 511 | to this problem is a ~system_preferred_lisps~ shell function which can |
| 512 | be used in ~~/.cl-launchrc~ to select a Lisp system for a particular |
| 513 | `software system', though this notion doesn't appear to be well-defined, |
| 514 | but this all works by editing a single ~$LISPS~ shell variable. By |
| 515 | contrast, ~runlisp~ has a ~-L~ option with which scripts can specify the |
| 516 | Lisp systems they support (in a preference order), and a ~prefer~ |
| 517 | configuration setting with which users can express their own |
| 518 | preferences: ~runlisp~ will never choose a Lisp system which the script |
| 519 | can't deal with, but it will respect the user's relative preferences. |
| 520 | |
| 521 | Also, ~cl-launch~ is a monolith. Adding a new Lisp implementation to |
| 522 | it, or changing how a particular implementation is invoked, is rather |
| 523 | involved. By contrast, ~runlisp~ makes this remarkably easy, as |
| 524 | described in [[Supporting new Lisp implementations]]. |
| 525 | |
| 526 | ** It doesn't establish a (useful) common environment |
| 527 | |
| 528 | A number of Lisp systems are annoyingly deficient in their handling of |
| 529 | scripts. |
| 530 | |
| 531 | For example, when GNU CLisp's ~-x~ option is used, it rebinds |
| 532 | ~*standard-input*~ to an internal string stream holding the expression |
| 533 | passed in on the command line, leaving the process's actual stdin nearly |
| 534 | impossible to access. |
| 535 | |
| 536 | : $ date | cl-launch -l sbcl -i "(princ (read-line nil nil))" # expected |
| 537 | : Sun 9 Aug 14:39:10 BST 2020 |
| 538 | : $ date | cl-launch -l clisp -i "(princ (read-line nil nil))" # bug! |
| 539 | : NIL |
| 540 | |
| 541 | As another example, Armed Bear Common Lisp doesn't seem to believe in |
| 542 | the stderr stream: when it starts up, ~*error-ouptut*~ is bound to the |
| 543 | standard output, just like ~*standard-output*~. Also, ~cl-launch~ |
| 544 | loading ASDF causes a huge number of ~style-warning~ messages to be |
| 545 | written to stdout, making ABCL pretty much useless for writing filter |
| 546 | scripts. |
| 547 | |
| 548 | : $ cl-launch -l sbcl -i '(progn |
| 549 | : (format *standard-output* "output~%") |
| 550 | : (format *error-output* "error~%"))' \ |
| 551 | : > >(sed 's/^/stdout: /') 2> >(sed 's/^/stderr: /') |
| 552 | : stdout: output |
| 553 | : stderr: error |
| 554 | : $ cl-launch -l abcl -i '(progn |
| 555 | : (format *standard-output* "output~%") |
| 556 | : (format *error-output* "error~%"))' \ |
| 557 | : > >(sed 's/^/stdout: /') 2> >(sed 's/^/stderr: /') |
| 558 | : [1813 lines of compiler warnings tagged `stdout:'] |
| 559 | : stdout: output |
| 560 | : stdout: error |
| 561 | |
| 562 | ~runlisp~ takes care of all of this, providing a basic but useful common |
| 563 | level of shell integration for all its supported Lisp implementations. |
| 564 | In particular: |
| 565 | |
| 566 | + It ensures that the standard Unix `stdin', `stdout', and `stderr' |
| 567 | file descriptors are hooked up to the Lisp ~*standard-input*~, |
| 568 | ~*standard-output*~, and ~*error-output*~ streams. |
| 569 | |
| 570 | + It ensures that starting a script doesn't write a deluge of |
| 571 | diagnostic drivel. |
| 572 | |
| 573 | The complete details are given in ~runlisp~'s manpage. |
| 574 | |
| 575 | ** Why might one prefer =cl-launch= anyway? |
| 576 | |
| 577 | On the other hand, ~cl-launch~ is well established and full-featured. |
| 578 | |
| 579 | ~cl-launch~ compiles scripts before trying to run them, so they'll run |
| 580 | faster on Lisps which use an interpreter by default. It has a caching |
| 581 | feature so running a script a second time doesn't need to recompile it. |
| 582 | If your scripts are compute-intensive and benefit from ahead-of-time |
| 583 | compilation then maybe ~cl-launch~ is preferable. |
| 584 | |
| 585 | ~cl-launch~ supports more Lisp systems. I only have six installed on my |
| 586 | development machine at the moment, so those are the ones that ~runlisp~ |
| 587 | supports. If you want your scripts to be able to run on other Lisps, |
| 588 | then ~cl-launch~ is the way to do that. Of course, I welcome patches to |
| 589 | help ~runlisp~ support other free Lisp implementations. ~cl-launch~ |
| 590 | also supports proprietary Lisps: I have very little interest in these, |
| 591 | so if you want to run scripts using Allegro or LispWorks then |
| 592 | ~cl-launch~ is your only choice. |