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