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