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