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