Commit | Line | Data |
---|---|---|
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 ~#!~ | |
10 | line. It selects and invokes a Common Lisp implementation, so as to run | |
11 | the script. In this sense, ~runlisp~ is a partial replacement for | |
12 | ~cl-launch~. | |
13 | ||
14 | Currently, 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 |
23 | Adding more Lisps is simply a matter of writing the necessary runes in a |
24 | configuration file. Of course, there's a benefit to having a collection | |
25 | of high-quality configuration runes curated centrally, so I'm happy to | |
26 | accept 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 | ||
35 | The obvious way to use ~runlisp~ is in a shebang (~#!~) line at the top | |
36 | of a script. For example: | |
37 | ||
38 | : #! /usr/local/bin/runlisp | |
39 | : (format t "Hello from Lisp!~%") | |
40 | ||
41 | Script interpreters must be named with absolute pathnames in shebang | |
42 | lines; if your ~runlisp~ is installed somewhere other than | |
43 | ~/usr/local/bin/~ then you'll need to write something different. | |
44 | Alternatively, a common hack involves abusing the ~env~ program as a | |
45 | script interpreter, because it will do a path search for the program | |
46 | it's supposed to run: | |
47 | ||
48 | : #! /usr/bin/env runlisp | |
49 | : (format t "Hello from Lisp!~%") | |
50 | ||
51 | ** Specific Lisps | |
52 | ||
53 | Lisp implementations are not created equal -- for good reason. If your | |
54 | script depends on the features of some particular Lisp implementation, | |
55 | then you can tell ~runlisp~ that it must use that implementation to run | |
56 | your script using the ~-L~ option; for example: | |
57 | ||
58 | : #! /usr/local/bin/runlisp -Lsbcl | |
59 | : (format t "Hello from Steel Bank Common Lisp!~%") | |
60 | ||
61 | If your script supports several Lisps, but not all, then list them all | |
62 | in 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 |
72 | It is not an error to include the name of an unrecognized Lisp system in |
73 | the ~-L~ option: such names are simply ignored. This allows a script to | |
74 | declare support for unusual or locally installed Lisp systems without | |
75 | compromising its portability to sites where such systems are unknown, or | |
76 | which are still running older versions of ~runlisp~ which haven't been | |
77 | updated with the necessary configuration for those systems. | |
78 | ||
e29834b8 MW |
79 | ** Embedded options |
80 | ||
81 | If your script requires features of particular Lisp implementations | |
82 | /and/ you don't want to hardcode an absolute path to ~runlisp~, then you | |
83 | have a problem. Most Unix-like operating systems will parse a shebang | |
84 | line into the initial ~#!~, the pathname to the interpreter program, | |
85 | and a /single/ optional argument: any further spaces don't separate | |
86 | further arguments: they just get included in the first argument, all the | |
87 | way 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 | ||
92 | won't work: it'll just try to run a program named ~runlisp -Lsbcl~, with | |
93 | a space in the middle of its name, and that's quite unlikely to exist. | |
94 | ||
95 | To help with this situation, ~runlisp~ reads /embedded options/ from | |
96 | your script. Specifically, if the script's second line contains the | |
97 | token ~@RUNLISP:~ then ~runlisp~ will parse additional options from this | |
98 | line. 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 | ||
104 | Embedded options are split at spaces properly. Spaces can be escaped or | |
105 | quoted in (an approximation to) the usual shell manner, should that be | |
106 | necessary. 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 | |
111 | same view of the world regardless of which implementation is running | |
112 | them. | |
113 | ||
114 | For 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 | ||
134 | See the manual for the complete list of guarantees. | |
135 | ||
136 | ||
137 | * Invoking Lisp implementations | |
138 | ||
139 | ** Basic use | |
140 | ||
141 | A secondary use of ~runlisp~ is in build scripts for Lisp programs. If | |
142 | the entire project is just a Lisp library, then it's possibly acceptable | |
143 | to 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 | |
145 | are things other than Lisp which ASDF can't or shouldn't handle -- | |
146 | significant pieces in other languages, or a Lisp executable image to | |
147 | make and install -- then it seems sensible to make the project's main | |
148 | build system be something language-agnostic, say Unix ~make~, and | |
149 | arrange for that to invoke ASDF at the appropriate time. | |
150 | ||
151 | But how should that be arranged? It's relatively easy for a project' | |
152 | Lisp code to support multiple Lisp implementation; but each | |
153 | implementation wants different runes for evaluating Lisp forms from the | |
154 | command line, and some of them don't provide an ideal environment for | |
155 | integrating into a build system. So ~runlisp~ provides a simple common | |
156 | command-line interface for evaluating Lisp forms. For example: | |
157 | ||
158 | : $ runlisp -e '(format t "~A~%" (+ 1 2))' | |
159 | : 3 | |
160 | ||
161 | If 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 |
168 | If a form produces multiple values, then ~-p~ will print all of them, as |
169 | if by ~princ~, separated by spaces, on a single line: | |
e29834b8 MW |
170 | |
171 | : $ runlisp -p '(floor 5 2)' | |
172 | : 2 1 | |
173 | ||
86ae6147 MW |
174 | There's also a ~-d~ option, which does the same thing as ~-p~, only it |
175 | prints 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 | 182 | In addition to evaluating forms with ~-e~, and printing their values |
86ae6147 | 183 | with ~-d~ and ~-p~, you can also load a file of Lisp code using ~-l~. |
e29834b8 MW |
184 | |
185 | When ~runlisp~ is acting on ~-e~, ~-p~, and/or ~-l~ options, it's said | |
186 | to be running in /eval/ mode, rather than its usual /script/ mode. In | |
44ccabcb | 187 | eval mode, it /doesn't/ set ~:runlisp-script~ in ~*features*~. |
e29834b8 MW |
188 | |
189 | You can still insist that ~runlisp~ use a particular Lisp | |
190 | implementation, or one of a subset of implementations, using the ~-L~ | |
191 | option mentioned above. | |
192 | ||
193 | : $ runlisp -Lsbcl -p "(lisp-implementation-type)" | |
194 | : "SBCL" | |
195 | ||
196 | ** Command-line processing | |
197 | ||
198 | When scripting a Lisp -- as opposed to running a Lisp script -- it's not | |
199 | necessarily the case that your script knows in advance exactly what it | |
200 | needs to ask Lisp to do. For example, it might need to tell Lisp to | |
201 | install a program in a particular directory, determined by Autoconf. | |
202 | While it's certainly /possible/ to quote such data and splice them into | |
203 | Lisp forms, it's more convenient to pass them in separately. So | |
204 | ~runlisp~ ensures that the command-line options are available to Lisp | |
205 | forms 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 | ||
210 | When running Lisp forms like this, ~(uiop:argv0)~ isn't very | |
211 | meaningful. (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 | ||
219 | You can influence which Lisp implementations are chosen by ~runlisp~ by | |
8996f767 | 220 | writing configuration files, and/or setting environment variables. |
e29834b8 | 221 | |
8996f767 | 222 | The ~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 |
244 | But configuration files generally look like =.ini=-style files. A line |
245 | beginning with a semicolon ~;~ is a comment and is ignored. Most lines | |
246 | are assignments, which look like | |
247 | #+BEGIN_QUOTE | |
248 | /name/ ~=~ /value/ | |
249 | #+END_QUOTE | |
250 | and assignments are split into sections by section headers in square | |
251 | brackets: | |
252 | #+BEGIN_QUOTE | |
42ca0522 | 253 | ~[~\relax{}/section/\relax{}~]~ |
bc040e80 MW |
254 | #+END_QUOTE |
255 | The details of the configuration syntax are complicated, and explained | |
256 | in the *runlisp.conf* manpage. | |
e29834b8 | 257 | |
8996f767 MW |
258 | Configuration options can also be set on the command line, though the |
259 | effects are subtly different. Again, see the manual pages for details. | |
260 | ||
261 | [fn:xdg-config] More properly, in ~$XDG_CONFIG_HOME/runlisp.conf~, if | |
262 | you set that. | |
263 | ||
264 | ||
265 | ** Deciding which Lisp implementation to use | |
e29834b8 | 266 | |
8996f767 MW |
267 | The ~prefer~ option specifies a /preference list/ of Lisp |
268 | implementations. The value is a list of Lisp implementation names, as | |
269 | you'd give to ~-L~, separated by commas and/or spaces. If the | |
270 | environment variable ~RUNLISP_PREFER~ is set, then this overrides any | |
bc040e80 MW |
271 | value found in the configuration files. So your ~$HOME/.runlisp.conf~ |
272 | file might look like this: | |
273 | ||
274 | : ;;; -*-conf-*- | |
275 | : | |
276 | : prefer = sbcl, clisp | |
e29834b8 MW |
277 | |
278 | When deciding which Lisp implementation to use, ~runlisp~ works as | |
279 | follows. It builds a list of /acceptable/ Lisp implementations from the | |
8996f767 MW |
280 | ~-L~ command-line option, and a list of /preferred/ Lisp implementations |
281 | from the ~prefer~ configuration option (or environment variable). If | |
282 | there aren't any ~-L~ options, then it assumes that /all/ Lisp | |
283 | implementations are acceptable; if no ~prefer~ option is set then it | |
284 | assumes that /no/ Lisp implementations are preferred. It then works | |
285 | through the preferred list in order: if it finds an implementation which | |
286 | is installed and acceptable, then it uses that one. If that doesn't | |
287 | work, then it works through the acceptable implementations that it | |
288 | hasn't tried yet, in order, and if it finds one of those that's | |
289 | installed, then it runs that one. Otherwise it reports an error and | |
290 | gives up. | |
291 | ||
292 | ||
293 | ** Supporting new Lisp implementations | |
294 | ||
295 | ~runlisp~ tries hard to make adding support for a new Lisp as painless | |
296 | as possible. An awkward Lisp will of course cause trouble, but | |
297 | ~runlisp~ itself is easy. | |
298 | ||
299 | As a simple example, let's add support for the 32-bit version of | |
8beca709 MW |
300 | Clozure\nbsp{}CL. The source code for Clozure\nbsp{}CL easily builds |
301 | both 32- and 64-bit binaries in either 32- or 64-bit userlands, and one | |
302 | might reasonably want to use the 32-bit CCL for some reason. The | |
303 | following 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 | ||
321 | And, err..., that's it. The ~@PARENTS~ setting uses the detailed | |
322 | command-line runes for ~ccl~, so they don't need to be written out | |
323 | again. | |
324 | ||
325 | That was rather anticlimactic, because all of the work got done | |
326 | somewhere else. So let's look at a complete example: Steel Bank Common | |
327 | Lisp. (SBCL's command-line interface is well thought-out, so this is an | |
328 | ideal opportunity to explain how ~runlisp~ configuration works, without | |
329 | getting bogged down in the details of fighting less amenable Lisps.) | |
330 | ||
4ca49125 MW |
331 | The provided ~0base.conf~ file used to define SBCL as follows. (The |
332 | real version now contains a kludge for old versions, which needn't | |
333 | concern 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 | ||
351 | Let'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 | ||
416 | That's more or less all there is. SBCL is a particularly simple | |
417 | example, but mostly because other Lisp implementations require fancier | |
418 | stunts /at the Lisp level/. The ~runlisp~-level configuration isn't any | |
419 | more complicated than SBCL. | |
420 | ||
421 | [fn:image-rename] ~dump-runlisp-image~ wants to avoid clobbering an | |
422 | existing image with a half-finished one, so it tries to arrange for the | |
423 | new image to be written to a different file, and then renames it once | |
424 | it's been created successfully.) | |
e29834b8 MW |
425 | |
426 | ||
427 | * What's wrong with =cl-launch=? | |
428 | ||
429 | The short version is that ~cl-launch~ is slow and inconvenient. | |
430 | ~cl-launch~ is a big, complicated Common Lisp/Bourne shell polyglot | |
431 | which tries to do everything but doesn't quite succeed. | |
432 | ||
433 | ** It's slow. | |
434 | ||
435 | I 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 | ||
444 | I timed how long it took to run on all of ~runlisp~'s supported Lisp | |
445 | implementations, and compared them to how long ~cl-launch~ took: the | |
446 | results are shown in table [[tab:runlisp-vanilla]]. ~runlisp~ is /at least/ | |
447 | two and half times faster at running this script than ~cl-launch~ on all | |
8beca709 MW |
448 | implementations except Clozure\nbsp{}CL[fn:slow-ccl], and approaching |
449 | four 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 | ||
466 | But this is using the `vanilla' Lisp images installed with the | |
467 | implementations. ~runlisp~ by default builds custom images for most | |
468 | Lisp implementations, which improves startup performance significantly; | |
469 | see table [[tab:runlisp-custom]]. (I don't currently know how to build a | |
470 | useful custom image for ABCL. ~runlisp~ does build a custom image for | |
471 | ECL, but it doesn't help significantly.) These results are summarized | |
472 | in 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 | ||
494 | Unlike ~cl-launch~, with some Lisp implementations at least, ~runlisp~ | |
495 | startup performance is usefully comparable to other popular scripting | |
496 | language implementations. I wrote similarly trivial scripts in a number | |
497 | of 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 | |
528 | XPS13 laptop running Debian `buster'. The tools used to make the | |
529 | measurements are included in the source distribution, in the ~bench/~ | |
530 | subdirectory.) | |
531 | ||
8beca709 MW |
532 | [fn:slow-ccl] I don't know why Clozure\nbsp{}CL shows such a small |
533 | difference here. | |
e29834b8 MW |
534 | |
535 | ** It's inconvenient | |
536 | ||
537 | ~cl-launch~ has this elaborate machinery which reads shell script | |
538 | fragments from various places and sets variables like ~$LISPS~, but it | |
539 | doesn't quite work. | |
540 | ||
541 | Unlike other scripting languages such as Perl or Python, Common Lisp has | |
542 | lots of implementations, and they all have various unique features (and | |
543 | bugs) which a script might rely on (or need to avoid). Also, a user | |
544 | might have preferences about which Lisps to use. ~cl-launch~'s approach | |
545 | to this problem is a ~system_preferred_lisps~ shell function which can | |
546 | be 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, | |
548 | but this all works by editing a single ~$LISPS~ shell variable. By | |
549 | contrast, ~runlisp~ has a ~-L~ option with which scripts can specify the | |
8996f767 MW |
550 | Lisp systems they support (in a preference order), and a ~prefer~ |
551 | configuration setting with which users can express their own | |
552 | preferences: ~runlisp~ will never choose a Lisp system which the script | |
553 | can't deal with, but it will respect the user's relative preferences. | |
554 | ||
555 | Also, ~cl-launch~ is a monolith. Adding a new Lisp implementation to | |
556 | it, or changing how a particular implementation is invoked, is rather | |
557 | involved. By contrast, ~runlisp~ makes this remarkably easy, as | |
558 | described in [[Supporting new Lisp implementations]]. | |
e29834b8 MW |
559 | |
560 | ** It doesn't establish a (useful) common environment | |
561 | ||
562 | A number of Lisp systems are annoyingly deficient in their handling of | |
563 | scripts. | |
564 | ||
565 | For example, when GNU CLisp's ~-x~ option is used, it rebinds | |
566 | ~*standard-input*~ to an internal string stream holding the expression | |
567 | passed in on the command line, leaving the process's actual stdin nearly | |
568 | impossible 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 | ||
575 | As another example, Armed Bear Common Lisp doesn't seem to believe in | |
576 | the stderr stream: when it starts up, ~*error-ouptut*~ is bound to the | |
577 | standard output, just like ~*standard-output*~. Also, ~cl-launch~ | |
578 | loading ASDF causes a huge number of ~style-warning~ messages to be | |
579 | written to stdout, making ABCL pretty much useless for writing filter | |
580 | scripts. | |
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 | |
597 | level of shell integration for all its supported Lisp implementations. | |
598 | In 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 | ||
607 | The complete details are given in ~runlisp~'s manpage. | |
608 | ||
609 | ** Why might one prefer =cl-launch= anyway? | |
610 | ||
611 | On 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 | |
614 | faster on Lisps which use an interpreter by default. It has a caching | |
615 | feature so running a script a second time doesn't need to recompile it. | |
616 | If your scripts are compute-intensive and benefit from ahead-of-time | |
617 | compilation then maybe ~cl-launch~ is preferable. | |
618 | ||
619 | ~cl-launch~ supports more Lisp systems. I only have six installed on my | |
620 | development machine at the moment, so those are the ones that ~runlisp~ | |
621 | supports. If you want your scripts to be able to run on other Lisps, | |
622 | then ~cl-launch~ is the way to do that. Of course, I welcome patches to | |
623 | help ~runlisp~ support other free Lisp implementations. ~cl-launch~ | |
624 | also supports proprietary Lisps: I have very little interest in these, | |
625 | so if you want to run scripts using Allegro or LispWorks then | |
626 | ~cl-launch~ is your only choice. |