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