This is a complete rewrite, and rather more competently done.
--- /dev/null
+Makefile.in
+*.tex
+*.pdf
+/_inst/
+/aclocal.m4
+/autom4te.cache/
+/config/compile
+/config/config.h.in
+/config/depcomp
+/config/install-sh
+/config/missing
+/configure
+/t/testsuite
--- /dev/null
+;;; -*-emacs-lisp-*-
+
+(setq skel-alist
+ (append
+ '((author . "Mark Wooding")
+ (full-title . "Runlisp, a tool for invoking Common Lisp scripts")
+ (program . "Runlisp")
+ (licence-text . "[[gpl-3]]"))
+ skel-alist))
--- /dev/null
+.ext/cfd/licence/GPL-3
\ No newline at end of file
--- /dev/null
+# -*-org-*-
+#+TITLE: Hacking on =runlisp=
+#+AUTHOR: Mark Wooding
+#+LaTeX_CLASS: strayman
+
+* Adding a new Lisp implementation
+
+When a program needs to know about a bunch of /things/, I generally try
+to arrange that there's exactly one place where you put all of the
+knowledge about each particular /thing/. In the case of ~runlisp~, I've
+failed rather abjectly. Sorry.
+
+So, here's the list of places which need to be modified in order to
+teach ~runlisp~ about a new Lisp system.
+
+ + The main C source file ~runlisp.c~ has a master list macro named
+ ~LISP_SYSTEMS~, which just contains an entry ~_(foo)~ for each Lisp
+ system. Add a new entry for your new system here. This list
+ ordered according to my personal preference -- the /opinionated
+ order/.
+
+ + There's also a function ~run_foo~ defined in ~runlisp.c~ for each
+ Lisp system ~foo~. These are defined in a section headed `Invoking
+ Lisp systems', in the opinionated order.
+
+ + The manual page ~runlisp.1~ lists each supported Lisp system by name
+ in the section `Supported Common Lisp implementations'. These are
+ listed in alphabetical order by command name (so GNU CLisp is
+ ~clisp~, and therefore comes before ~ecl~) -- the /command order/.
+
+ + The ~README.org~ file also has a list of supported Lisp systems,
+ again in command order.
+
+ + In ~configure.ac~, there's a line ~mdw_CHECK_LISP([FOO], [foo])~ for
+ each known Lisp system in the `Checking for Lisp implementations'
+ section, in opinionated order.
+
+ + If the Lisp system needs any additional configure-time hacking, then
+ that goes at the end of the section. Currently only ECL needs
+ special treatment here, but these are notionally in opinionated
+ order.
+
+ + The file ~vars.am~ builds a list ~LISPS~ of the supported Lisp
+ systems in opinionated order.
+
+ + For each Lisp system that can have a custom image dumped, there's a
+ paragraph in the `Image dumping' section of ~Makefile.am~, which
+ says
+
+ : if DUMP_FOO
+ : image_DATA += foo+asdf.dump
+ : CLEANFILES += foo+asdf.dump
+ : foo+asdf.dump: dump-runlisp-image
+ : (v_dump)./dump-runlisp-image -o$@ foo
+ : endif
+
+ The ~DUMP_FOO~ conditional is already set up by ~mdw_CHECK_LISP~.
+ The ~.dump~ suffix should be whatever extension your Lisp system
+ usually uses to mark its image files. These paragraphs are in
+ opinionated order.
+
+ + For each Lisp system that can be dumped, there's a section in
+ ~dump-runlisp-image.in~ which goes
+
+ : ## Foo Common Lisp.
+ : deflisp foo foo+asdf.dump
+ : dump_foo () {
+ : ## ...
+ : }
+
+ These sections are in opinionated order.
+
+ + The ~tests.at~ file has /five/ lists of Lisp systems.
+
+ - The first, named ~LISP_SYSTEMS~ has a pair of entries, ~foo~,
+ ~foo/noimage~ for each Lisp system, in opinionated order.
+
+ - The second is in the macro ~WHICH_LISP~, which contains an entry
+ ~#+foo "foo"~ for each system, in opinionated order. The former
+ symbol is the Lisp system's (preferred) ~*features*~ keyword
+ name, which is usually the same as its command name, but, for
+ example, is ~cmu~ rather than ~cmucl~ for CMU CL.
+
+ - The third is a ~case~ block in the ~smoke~ test, which contains
+ an entry
+
+ : foo) initfile=.foorc ;;
+
+ naming the system's user initialization file, relative to the
+ user's home directory. (If your Lisp doesn't have one of these,
+ then this can be anything you like.)
+
+ - The fourth is another ~case~ block in the ~smoke~ test, which
+ contains an entry
+
+ : foo) impl="Foo Common Lisp" ;;
+
+ giving the Lisp system's ~lisp-implementation-type~ string.
+
+ - The fifth is in the ~preferences~ test: there's a ~set~ line
+ which simply lists the Lisp systems' command names. This is in
+ order of increasing startup time, because the test will be
+ running lots of trivial scripts, simply checking that the right
+ Lisp system is being run, so it's valuable to choose fast Lisps.
+
+ + The script ~bench/massage-benchmarks~ has a hash ~%LISP~ mapping
+ Lisp command names to short labels to use in graphs, in opinionated
+ order. Add an entry
+
+ : "foo" => "Foo CL",
+
+ to this hash.
+
+And now the actual pain: the benchmarks need to be run again, and the
+data and graphs in ~README.org~ need to be updated. Leave this to me.
+
--- /dev/null
+### -*-makefile-*-
+###
+### Build script for `runlisp'
+###
+### (c) 2020 Mark Wooding
+###
+
+###----- Licensing notice ---------------------------------------------------
+###
+### This file is part of Runlisp, a tool for invoking Common Lisp scripts.
+###
+### Runlisp is free software: you can redistribute it and/or modify it
+### under the terms of the GNU General Public License as published by the
+### Free Software Foundation; either version 3 of the License, or (at your
+### option) any later version.
+###
+### Runlisp is distributed in the hope that it will be useful, but WITHOUT
+### ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+### FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+### for more details.
+###
+### You should have received a copy of the GNU General Public License
+### along with Runlisp. If not, see <https://www.gnu.org/licenses/>.
+
+include $(top_srcdir)/vars.am
+
+SUBDIRS =
+
+pkgdata_DATA =
+image_DATA =
+image_SCRIPTS =
+
+SUBDIRS += .
+
+ACLOCAL_AMFLAGS = -Im4
+
+###--------------------------------------------------------------------------
+### The main driver program.
+
+bin_PROGRAMS += runlisp
+runlisp_SOURCES = runlisp.c
+man_MANS += runlisp.1
+
+###--------------------------------------------------------------------------
+### Additional machinery.
+
+pkgdata_DATA += eval.lisp
+EXTRA_DIST += eval.lisp
+
+###--------------------------------------------------------------------------
+### Image dumping.
+
+nodist_bin_SCRIPTS += dump-runlisp-image
+man_MANS += dump-runlisp-image.1
+
+v_dump = $(v_dump_@AM_V@)
+v_dump_ = $(v_dump_@AM_DEFAULT_V@)
+v_dump_0 = @echo " DUMP $@";
+
+EXTRA_DIST += dump-runlisp-image.in
+CLEANFILES += dump-runlisp-image
+dump-runlisp-image: dump-runlisp-image.in
+ $(SUBST) $(srcdir)/dump-runlisp-image.in >$@.new \
+ $(SUBSTITUTIONS) && \
+ chmod +x $@.new && mv $@.new $@
+
+if DUMP_SBCL
+image_DATA += sbcl+asdf.core
+CLEANFILES += sbcl+asdf.core
+sbcl+asdf.core: dump-runlisp-image
+ $(v_dump)./dump-runlisp-image -o$@ sbcl
+endif
+
+if DUMP_CCL
+image_DATA += ccl+asdf.image
+CLEANFILES += ccl+asdf.image
+ccl+asdf.image: dump-runlisp-image
+ $(v_dump)./dump-runlisp-image -o$@ ccl
+endif
+
+if DUMP_CLISP
+image_DATA += clisp+asdf.mem
+CLEANFILES += clisp+asdf.mem
+clisp+asdf.mem: dump-runlisp-image
+ $(v_dump)./dump-runlisp-image -o$@ clisp
+endif
+
+if DUMP_ECL
+image_SCRIPTS += ecl+asdf
+CLEANFILES += ecl+asdf
+ecl+asdf: dump-runlisp-image
+ $(v_dump)./dump-runlisp-image -o$@ ecl
+endif
+
+if DUMP_CMUCL
+image_DATA += cmucl+asdf.core
+CLEANFILES += cmucl+asdf.core
+cmucl+asdf.core: dump-runlisp-image
+ $(v_dump)./dump-runlisp-image -o$@ cmucl
+endif
+
+###--------------------------------------------------------------------------
+### Benchmarking and testing.
+
+if BENCHMARK
+SUBDIRS += bench
+endif
+
+SUBDIRS += t
+
+###--------------------------------------------------------------------------
+### Distribution.
+
+## Release number.
+dist-hook::
+ echo $(VERSION) >$(distdir)/RELEASE
+
+## Additional build tools.
+EXTRA_DIST += config/auto-version
+
+###----- That's all, folks --------------------------------------------------
--- /dev/null
+# -*-org-*-
+#+TITLE: ~runlisp~ -- run scripts written in Common Lisp
+#+AUTHOR: Mark Wooding
+#+LaTeX_CLASS: strayman
+#+LaTeX_HEADER: \usepackage{tikz, gnuplot-lua-tikz}
+
+~runlisp~ is a small C program intended to be run from a script ~#!~
+line. It selects and invokes a Common Lisp implementation, so as to run
+the script. In this sense, ~runlisp~ is a partial replacement for
+~cl-launch~.
+
+Currently, the following Lisp implementations are supported:
+
+ + Armed Bear Common Lisp (~abcl~),
+ + Clozure Common Lisp (~ccl~),
+ + GNU CLisp (~clisp~),
+ + Carnegie--Mellon Univerity Common Lisp (~cmucl~), and
+ + Embeddable Common Lisp (~ecl~), and
+ + Steel Bank Common Lisp (~sbcl~).
+
+I'm happy to take patches to support additional free Lisp
+implementations. I'm not interested in supporting non-free Lisp
+systems.
+
+
+* Writing scripts in Lisp
+
+** Basic use
+
+The obvious way to use ~runlisp~ is in a shebang (~#!~) line at the top
+of a script. For example:
+
+: #! /usr/local/bin/runlisp
+: (format t "Hello from Lisp!~%")
+
+Script interpreters must be named with absolute pathnames in shebang
+lines; if your ~runlisp~ is installed somewhere other than
+~/usr/local/bin/~ then you'll need to write something different.
+Alternatively, a common hack involves abusing the ~env~ program as a
+script interpreter, because it will do a path search for the program
+it's supposed to run:
+
+: #! /usr/bin/env runlisp
+: (format t "Hello from Lisp!~%")
+
+** Specific Lisps
+
+Lisp implementations are not created equal -- for good reason. If your
+script depends on the features of some particular Lisp implementation,
+then you can tell ~runlisp~ that it must use that implementation to run
+your script using the ~-L~ option; for example:
+
+: #! /usr/local/bin/runlisp -Lsbcl
+: (format t "Hello from Steel Bank Common Lisp!~%")
+
+If your script supports several Lisps, but not all, then list them all
+in the ~-L~ option, separated by commas:
+
+: #! /usr/local/bin/runlisp -Lsbcl,ccl
+: (format t #.(concatenate 'string
+: "Hello from "
+: #+sbcl "Steel Bank"
+: #+ccl "Clozure"
+: #-(or sbcl ccl) "an unexpected"
+: " Common Lisp!~%"))
+
+** Embedded options
+
+If your script requires features of particular Lisp implementations
+/and/ you don't want to hardcode an absolute path to ~runlisp~, then you
+have a problem. Most Unix-like operating systems will parse a shebang
+line into the initial ~#!~, the pathname to the interpreter program,
+and a /single/ optional argument: any further spaces don't separate
+further arguments: they just get included in the first argument, all the
+way up to the end of the line. So
+
+: #! /usr/bin/env runlisp -Lsbcl
+: (format t "Hello from Steel Bank Common Lisp!~%")
+
+won't work: it'll just try to run a program named ~runlisp -Lsbcl~, with
+a space in the middle of its name, and that's quite unlikely to exist.
+
+To help with this situation, ~runlisp~ reads /embedded options/ from
+your script. Specifically, if the script's second line contains the
+token ~@RUNLISP:~ then ~runlisp~ will parse additional options from this
+line. So the following will work properly.
+
+: #! /usr/bin/env runlisp
+: ;;; @RUNLISP: -Lsbcl
+: (format t "Hello from Steel Bank Common Lisp!~%")
+
+Embedded options are split at spaces properly. Spaces can be escaped or
+quoted in (an approximation to) the usual shell manner, should that be
+necessary. See the manpage for the gory details.
+
+** Common environment
+
+~runlisp~ puts some effort into making sure that Lisp scripts get the
+same view of the world regardless of which implementation is running
+them.
+
+For example:
+
+ + The ~asdf~ and ~uiop~ systems are loaded and ready for use.
+
+ + The script's command-line arguments are available in
+ ~uiop:*command-line-arguments*~. Its name can be found by calling
+ ~(uiop:argv0)~ -- though it's probably also in ~*load-pathname*~.
+
+ + The prevailing Unix standard input, output, and error files are
+ available through the Lisp ~*standard-input*~, ~*standard-output*~,
+ and ~*error-ouptut*~ streams, respectively. (This is, alas, not a
+ foregone conclusion.)
+
+ + The keyword ~:runlisp-script~ is added to the ~*features*~ list.
+ This means that your script can tell whether it's being run from the
+ command line, and should therefore do its thing and then quit; or
+ merely being loaded into a Lisp system, e.g., for debugging or
+ development, and should sit still and not do anything until it's
+ asked.
+
+See the manual for the complete list of guarantees.
+
+
+* Invoking Lisp implementations
+
+** Basic use
+
+A secondary use of ~runlisp~ is in build scripts for Lisp programs. If
+the entire project is just a Lisp library, then it's possibly acceptable
+to just provide an ASDF system definition and expect users to type
+~(asdf:load-system "mumble")~ to use it. If it's a program, or there
+are things other than Lisp which ASDF can't or shouldn't handle --
+significant pieces in other languages, or a Lisp executable image to
+make and install -- then it seems sensible to make the project's main
+build system be something language-agnostic, say Unix ~make~, and
+arrange for that to invoke ASDF at the appropriate time.
+
+But how should that be arranged? It's relatively easy for a project'
+Lisp code to support multiple Lisp implementation; but each
+implementation wants different runes for evaluating Lisp forms from the
+command line, and some of them don't provide an ideal environment for
+integrating into a build system. So ~runlisp~ provides a simple common
+command-line interface for evaluating Lisp forms. For example:
+
+: $ runlisp -e '(format t "~A~%" (+ 1 2))'
+: 3
+
+If your build script needs to get information out of Lisp, then wrapping
+~format~, or even ~prin1~, around forms is annoying; so ~runlisp~ has a
+~-p~ option which prints the values of the forms it evaluates.
+
+: $ runlisp -e '(+ 1 2)'
+: 3
+
+If a form produces multiple values, then ~-p~ will print all of them
+separated by spaces, on a single line:
+
+: $ runlisp -p '(floor 5 2)'
+: 2 1
+
+In addition to evaluating forms with ~-e~, and printing their values
+with ~-p~, you can also load a file of Lisp code using ~-l~.
+
+When ~runlisp~ is acting on ~-e~, ~-p~, and/or ~-l~ options, it's said
+to be running in /eval/ mode, rather than its usual /script/ mode. In
+script mode, it /doesn't/ set ~:runlisp-script~ in ~*features*~.
+
+You can still insist that ~runlisp~ use a particular Lisp
+implementation, or one of a subset of implementations, using the ~-L~
+option mentioned above.
+
+: $ runlisp -Lsbcl -p "(lisp-implementation-type)"
+: "SBCL"
+
+** Command-line processing
+
+When scripting a Lisp -- as opposed to running a Lisp script -- it's not
+necessarily the case that your script knows in advance exactly what it
+needs to ask Lisp to do. For example, it might need to tell Lisp to
+install a program in a particular directory, determined by Autoconf.
+While it's certainly /possible/ to quote such data and splice them into
+Lisp forms, it's more convenient to pass them in separately. So
+~runlisp~ ensures that the command-line options are available to Lisp
+forms via ~uiop:*command-line-arguments*~, as they are to a Lisp script.
+
+: $ runlisp -p "uiop:*command-line-arguments*" one two three
+: ("one" "two" "three")
+
+When running Lisp forms like this, ~(uiop:argv0)~ isn't very
+meaningful. (Currently, it reveals the name of the script which
+~runlisp~ uses to implement this feature.)
+
+
+* Configuring =runlisp=
+
+** Where =runlisp= looks for configuration
+
+You can influence which Lisp implementations are chosen by ~runlisp~ by
+writing a configuration file, and/or setting an environment variable.
+
+~runlisp~ looks for configuration in ~~/.runlisprc~, and in
+~~/.config/runlisprc~. You could put configuration in both, but that
+doesn't seem like a great idea. A configuration file just contains
+blank lines, comments, and command-line options, just as you'd write
+them to the shell. Simple quoting and escaping is provided: see the
+manual page for the full details. Each line is processed independently,
+so it doesn't work to write an option on one line and then its argument
+on the next.
+
+The environment variable ~RUNLISP_OPTIONS~ is processed /after/ reading
+the configuration file(s), if any. Again, it should contain
+command-line options, as you'd write them to the shell.
+
+** Deciding which Lisp implementation to use
+
+The most useful option to use here is ~-P~, which builds up a
+/preference list/, in order. The argument to ~-P~ is a comma-separated
+list of Lisp implementation names, just like you'd give to ~-L~.
+
+If you provide multiple ~-P~ options (e.g., on different lines of your
+configuration file, or separately in the configuration file and
+environment variable, then the lists are concatenated. Since the
+environment variable is processed after the configuration file, this
+means that
+
+When deciding which Lisp implementation to use, ~runlisp~ works as
+follows. It builds a list of /acceptable/ Lisp implementations from the
+~-L~ options, and a list of /preferred/ Lisp implementations from the
+~-P~ options. If there aren't any ~-L~ options, then it assumes that
+/all/ Lisp implementations are acceptable; but if there are no ~-P~
+options then it assumes that /no/ Lisp implementations are preferred.
+It then works through the preferred list in order: if it finds an
+implementation which is installed and acceptable, then it uses that one.
+If that doesn't work, then it works through the acceptable
+implementations that it hasn't tried yet, in order, and if it finds one
+of those that's installed, then it runs that one. Otherwise it reports
+an error and gives up.
+
+** Clearing the preferred list
+
+Since the environment variable is processed after the configuration
+files, it can only append more Lisp implementations to the end of the
+preferred list, which may well not be so helpful. There's an additional
+option ~-C~, which completely clears the preferred list. The idea is
+that you can write ~-C~ at the start of your ~RUNLISP_OPTIONS~
+environment variable to temporarily override your usual configuration
+for some special effect.
+
+
+* What's wrong with =cl-launch=?
+
+The short version is that ~cl-launch~ is slow and inconvenient.
+~cl-launch~ is a big, complicated Common Lisp/Bourne shell polyglot
+which tries to do everything but doesn't quite succeed.
+
+** It's slow.
+
+I took a trivial Lisp script:
+
+: (format t "Hello from ~A!~%~
+: Script = `~A'~%~
+: Arguments = (~{`~A'~^, ~})~%"
+: (lisp-implementation-type)
+: (uiop:argv0)
+: uiop:*command-line-arguments*)
+
+I timed how long it took to run on all of ~runlisp~'s supported Lisp
+implementations, and compared them to how long ~cl-launch~ took: the
+results are shown in table [[tab:runlisp-vanilla]]. ~runlisp~ is /at least/
+two and half times faster at running this script than ~cl-launch~ on all
+implementations except Clozure CL[fn:slow-ccl], and approaching four and
+a half times faster on SBCL.
+
+#+CAPTION: ~cl-launch~ vs ~runlisp~ (with vanilla images)
+#+NAME: tab:runlisp-vanilla
+#+ATTR_LATEX: :float t :placement [tbp]
+|------------------+-------------------+-----------------+----------------------|
+| *Implementation* | *~cl-launch~ (s)* | *~runlisp~ (s)* | *~runlisp~ (factor)* |
+|------------------+-------------------+-----------------+----------------------|
+| ABCL | 7.3036 | 2.6027 | 2.806 |
+| Clozure CL | 1.2769 | 0.9678 | 1.319 |
+| GNU CLisp | 1.2498 | 0.2659 | 4.700 |
+| CMU CL | 0.9665 | 0.3065 | 3.153 |
+| ECL | 0.8025 | 0.3173 | 2.529 |
+| SBCL | 0.3266 | 0.0739 | 4.419 |
+|------------------+-------------------+-----------------+----------------------|
+#+TBLFM: $4=$2/$3;%.3f
+
+But this is using the `vanilla' Lisp images installed with the
+implementations. ~runlisp~ by default builds custom images for most
+Lisp implementations, which improves startup performance significantly;
+see table [[tab:runlisp-custom]]. (I don't currently know how to build a
+useful custom image for ABCL. ~runlisp~ does build a custom image for
+ECL, but it doesn't help significantly.) These results are summarized
+in figure [[fig:lisp-graph]].
+
+#+CAPTION: ~cl-launch~ vs ~runlisp~ (with custom images)
+#+NAME: tab:runlisp-custom
+#+ATTR_LATEX: :float t :placement [tbp]
+|------------------+-------------------+-----------------+----------------------|
+| *Implementation* | *~cl-launch~ (s)* | *~runlisp~ (s)* | *~runlisp~ (factor)* |
+|------------------+-------------------+-----------------+----------------------|
+| ABCL | 7.3036 | 2.5873 | 2.823 |
+| Clozure CL | 1.2769 | 0.0088 | 145.102 |
+| GNU CLisp | 1.2498 | 0.0146 | 85.603 |
+| CMU CL | 0.9665 | 0.0063 | 153.413 |
+| ECL | 0.8025 | 0.3185 | 2.520 |
+| SBCL | 0.3266 | 0.0077 | 42.416 |
+|------------------+-------------------+-----------------+----------------------|
+#+TBLFM: $4=$2/$3;%.3f
+
+#+CAPTION: Comparison of ~runlisp~ and ~cl-launch~ times
+#+NAME: fig:lisp-graph
+#+ATTR_LATEX: :float t :placement [tbp]
+[[file:doc/lisp-graph.tikz]]
+
+Unlike ~cl-launch~, with some Lisp implementations at least, ~runlisp~
+startup performance is usefully comparable to other popular scripting
+language implementations. I wrote similarly trivial scripts in a number
+of other languages, and timed them; the results are tabulated in table
+[[tab:runlisp-interp]] and graphed in figure [[fig:interp-graph]].
+
+#+CAPTION: ~runlisp~ vs other interpreters
+#+NAME: tab:runlisp-interp
+#+ATTR_LATEX: :float t :placement [tbp]
+|------------------------------+-------------|
+| *Implementation* | *Time (ms)* |
+|------------------------------+-------------|
+| Clozure CL | 8.8 |
+| GNU CLisp | 14.6 |
+| CMU CL | 6.3 |
+| SBCL | 7.7 |
+|------------------------------+-------------|
+| Perl | 1.2 |
+| Python | 10.3 |
+|------------------------------+-------------|
+| Debian Almquist shell (dash) | 1.4 |
+| GNU Bash | 2.0 |
+| Z Shell | 4.1 |
+|------------------------------+-------------|
+| Tiny C (compile & run) | 1.2 |
+| GCC (precompiled) | 0.5 |
+|------------------------------+-------------|
+
+#+CAPTION: Comparison of ~runlisp~ and other script interpreters
+#+NAME: fig:interp-graph
+#+Attr_latex: :float t :placement [tbp]
+[[file:doc/interp-graph.tikz]]
+
+(All the timings in this section were performed on the same 2020 Dell
+XPS13 laptop running Debian `buster'. The tools used to make the
+measurements are included in the source distribution, in the ~bench/~
+subdirectory.)
+
+[fn:slow-ccl] I don't know why Clozure CL shows such a small difference
+here.
+
+** It's inconvenient
+
+~cl-launch~ has this elaborate machinery which reads shell script
+fragments from various places and sets variables like ~$LISPS~, but it
+doesn't quite work.
+
+Unlike other scripting languages such as Perl or Python, Common Lisp has
+lots of implementations, and they all have various unique features (and
+bugs) which a script might rely on (or need to avoid). Also, a user
+might have preferences about which Lisps to use. ~cl-launch~'s approach
+to this problem is a ~system_preferred_lisps~ shell function which can
+be used in ~~/.cl-launchrc~ to select a Lisp system for a particular
+`software system', though this notion doesn't appear to be well-defined,
+but this all works by editing a single ~$LISPS~ shell variable. By
+contrast, ~runlisp~ has a ~-L~ option with which scripts can specify the
+Lisp systems they support (in a preference order), and a ~-P~ option
+with which users can express their own preferences (e.g., in the
+environment or a configuration file): ~runlisp~ will never choose a Lisp
+system which the script can't deal with, but it will respect the user's
+relative preferences.
+
+** It doesn't establish a (useful) common environment
+
+A number of Lisp systems are annoyingly deficient in their handling of
+scripts.
+
+For example, when GNU CLisp's ~-x~ option is used, it rebinds
+~*standard-input*~ to an internal string stream holding the expression
+passed in on the command line, leaving the process's actual stdin nearly
+impossible to access.
+
+: $ date | cl-launch -l sbcl -i "(princ (read-line nil nil))" # expected
+: Sun 9 Aug 14:39:10 BST 2020
+: $ date | cl-launch -l clisp -i "(princ (read-line nil nil))" # bug!
+: NIL
+
+As another example, Armed Bear Common Lisp doesn't seem to believe in
+the stderr stream: when it starts up, ~*error-ouptut*~ is bound to the
+standard output, just like ~*standard-output*~. Also, ~cl-launch~
+loading ASDF causes a huge number of ~style-warning~ messages to be
+written to stdout, making ABCL pretty much useless for writing filter
+scripts.
+
+: $ cl-launch -l sbcl -i '(progn
+: (format *standard-output* "output~%")
+: (format *error-output* "error~%"))' \
+: > >(sed 's/^/stdout: /') 2> >(sed 's/^/stderr: /')
+: stdout: output
+: stderr: error
+: $ cl-launch -l abcl -i '(progn
+: (format *standard-output* "output~%")
+: (format *error-output* "error~%"))' \
+: > >(sed 's/^/stdout: /') 2> >(sed 's/^/stderr: /')
+: [1813 lines of compiler warnings tagged `stdout:']
+: stdout: output
+: stdout: error
+
+~runlisp~ takes care of all of this, providing a basic but useful common
+level of shell integration for all its supported Lisp implementations.
+In particular:
+
+ + It ensures that the standard Unix `stdin', `stdout', and `stdarr'
+ file descriptors are hooked up to the Lisp ~*standard-input*~,
+ ~*standard-output*~, and ~*error-output*~ streams.
+
+ + It ensures that starting a script doesn't write a deluge of
+ diagnostic drivel.
+
+The complete details are given in ~runlisp~'s manpage.
+
+** Why might one prefer =cl-launch= anyway?
+
+On the other hand, ~cl-launch~ is well established and full-featured.
+
+~cl-launch~ compiles scripts before trying to run them, so they'll run
+faster on Lisps which use an interpreter by default. It has a caching
+feature so running a script a second time doesn't need to recompile it.
+If your scripts are compute-intensive and benefit from ahead-of-time
+compilation then maybe ~cl-launch~ is preferable.
+
+~cl-launch~ supports more Lisp systems. I only have six installed on my
+development machine at the moment, so those are the ones that ~runlisp~
+supports. If you want your scripts to be able to run on other Lisps,
+then ~cl-launch~ is the way to do that. Of course, I welcome patches to
+help ~runlisp~ support other free Lisp implementations. ~cl-launch~
+also supports proprietary Lisps: I have very little interest in these,
+so if you want to run scripts using Allegro or LispWorks then
+~cl-launch~ is your only choice.
--- /dev/null
+### -*-makefile-*-
+###
+### Build script for start-up benchmarks
+###
+### (c) 2020 Mark Wooding
+###
+
+###----- Licensing notice ---------------------------------------------------
+###
+### This file is part of Runlisp, a tool for invoking Common Lisp scripts.
+###
+### Runlisp is free software: you can redistribute it and/or modify it
+### under the terms of the GNU General Public License as published by the
+### Free Software Foundation; either version 3 of the License, or (at your
+### option) any later version.
+###
+### Runlisp is distributed in the hope that it will be useful, but WITHOUT
+### ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+### FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+### for more details.
+###
+### You should have received a copy of the GNU General Public License
+### along with Runlisp. If not, see <https://www.gnu.org/licenses/>.
+
+include $(top_srcdir)/vars.am
+
+GNUPLOT = gnuplot
+
+FORCE =
+FORCE:
+.PHONY: FORCE
+
+###--------------------------------------------------------------------------
+### Preliminaries.
+
+v_bench = $(v_bench_@AM_V@)
+v_bench_ = $(v_bench_@AM_DEFAULT_V@)
+v_bench_0 = @echo " BENCH $@";
+
+BENCHES =
+bench: $(BENCHES)
+
+noinst_PROGRAMS += timeit
+timeit_SOURCES = timeit.c
+
+CLEANFILES += *.out *.bench
+
+###--------------------------------------------------------------------------
+### Lisp systems using `runlisp'.
+
+RUNLISP = $(top_builddir)/runlisp -I$(top_builddir)/
+EXTRA_DIST += t.lisp
+
+RUNLISP_BENCHES = $(foreach l,$(LISPS), runlisp.$l.bench)
+BENCHES += $(RUNLISP_BENCHES)
+$(RUNLISP_BENCHES): runlisp.%.bench: timeit $(FORCE)
+ $(v_bench)./timeit $(RUNLISP) -L$* -- $(srcdir)/t.lisp a b c >runlisp.$*.out 2>$@
+
+RUNLISP_NOIMAGE_BENCHES = $(foreach l,$(LISPS), runlisp-noimage.$l.bench)
+BENCHES += $(RUNLISP_NOIMAGE_BENCHES)
+$(RUNLISP_NOIMAGE_BENCHES): runlisp-noimage.%.bench: timeit $(FORCE)
+ $(v_bench)./timeit $(RUNLISP) -D -L$* -- $(srcdir)/t.lisp a b c >runlisp-noimage.$*.out 2>$@
+
+###--------------------------------------------------------------------------
+### Lisp systems using `cl-launch'.
+
+CL_LAUNCH_BENCHES = $(foreach l,$(LISPS), cl-launch.$l.bench)
+BENCHES += $(CL_LAUNCH_BENCHES)
+$(CL_LAUNCH_BENCHES): cl-launch.%.bench: timeit $(FORCE)
+ $(v_bench)./timeit cl-launch -X -l $* -- $(srcdir)/t.lisp a b c >cl-launch.$*.out 2>$@
+
+###--------------------------------------------------------------------------
+### C programs (as a baseline).
+
+BENCHES += c.tcc.bench
+c.tcc.bench: timeit $(FORCE)
+ $(v_bench)./timeit tcc -run $(srcdir)/t.c a b c >c.tcc.out 2>$@
+
+BENCHES += c.gcc.bench
+noinst_PROGRAMS += t.c.gcc
+t_c_gcc_SOURCES = t.c
+c.gcc.bench: t.c.gcc timeit $(FORCE)
+ $(v_bench)./timeit ./t.c.gcc a b c >c.gcc.out 2>$@
+
+###--------------------------------------------------------------------------
+### Other scripting languages.
+
+BENCHES += perl.bench
+EXTRA_DIST += t.pl
+perl.bench: timeit $(FORCE)
+ $(v_bench)./timeit perl -- $(srcdir)/t.pl a b c >perl.out 2>$@
+
+BENCHES += python.bench
+EXTRA_DIST += t.py
+python.bench: timeit $(FORCE)
+ $(v_bench)./timeit python -- $(srcdir)/t.py a b c >python.out 2>$@
+
+SHELLS = dash bash zsh
+EXTRA_DIST += t.sh
+SHELL_BENCHES = $(foreach s,$(SHELLS), shell.$s.bench)
+BENCHES += $(SHELL_BENCHES)
+$(SHELL_BENCHES): shell.%.bench: timeit $(FORCE)
+ $(v_bench)TEST_SHELL=$* ./timeit $* -- $(srcdir)/t.sh a b c >shell.$*.out 2>$@
+
+###--------------------------------------------------------------------------
+### Reporting.
+
+GRAPHS =
+noinst_DATA += $(GRAPHS)
+CLEANFILES += $(GRAPHS)
+
+v_massage = $(v_massage_@AM_V@)
+v_massage_ = $(v_massage_@AM_DEFAULT_V@)
+v_massage_0 = @echo " MASSAGE $@";
+
+v_gnuplot = $(v_gnuplot_@AM_V@)
+v_gnuplot_ = $(v_gnuplot_@AM_DEFAULT_V@)
+v_gnuplot_0 = @echo " GNUPLOT $@";
+
+CLEANFILES += bench.data
+bench.data: $(BENCHES) massage-benchmarks
+ $(v_massage)$(srcdir)/massage-benchmarks >$@.new && mv $@.new $@
+
+GRAPHS += lisp-graph.tikz
+lisp-graph.tikz: lisp-graph.gp bench.data
+ $(v_gnuplot)$(GNUPLOT) $< >$@.new && mv $@.new $@
+
+GRAPHS += interp-graph.tikz
+interp-graph.tikz: interp-graph.gp bench.data
+ $(v_gnuplot)$(GNUPLOT) $< >$@.new && mv $@.new $@
+
+graphs: $(GRAPHS)
+.PHONY: graphs
+
+###----- That's all, folks --------------------------------------------------
--- /dev/null
+### -*-gnuplot-*-
+
+set terminal tikz
+
+set style data histogram
+set xtic rotate by -35 offset -1, 0 scale 0
+set style fill solid
+set style histogram cluster gap 1
+
+unset key
+set border 3
+set tics nomirror
+set ylabel "Time (ms) to run trivial script"
+
+plot "bench.data" index "> interp" using (1000*$2):xtic(1)
--- /dev/null
+### -*-gnuplot-*-
+
+set terminal tikz
+
+set style data histogram
+set xtics rotate by -35 scale 0
+set style fill solid
+set style histogram cluster
+
+set border 3
+set tics nomirror
+set ylabel "Time (s) to run trivial script"
+
+plot for [i = 2:4] "bench.data" index "> lisp" using i:xtic(1) \
+ title columnheader(i)
--- /dev/null
+#! /usr/bin/perl
+
+use autodie;
+
+my %LISP =
+ ("sbcl" => "SBCL",
+ "ccl" => "Clozure CL",
+ "ecl" => "ECL",
+ "clisp" => "GNU CLisp",
+ "cmucl" => "CMU CL",
+ "abcl" => "ABCL");
+my %LABEL =
+ ("perl" => "Perl",
+ "python" => "Python",
+ "c.tcc" => "Tiny C",
+ "c.gcc" => "GCC",
+ "shell.dash" => "dash",
+ "shell.bash" => "GNU Bash",
+ "shell.zsh" => "Z Shell");
+
+for my $l (keys %LISP) { $LABEL{"runlisp.$l"} = $LISP{$l}; }
+
+{
+ my %d;
+
+ sub timing ($) {
+ my ($f) = @_;
+ return $d{$f} if exists $d{$f};
+ open my $fh, "<", "$f.bench";
+ (my $data = readline $fh) =~ s/^.* elapsed = ([0-9.]+)s.*$/$1/;
+ return $d{$f} = $data;
+ }
+}
+
+print <<EOF;
+#> lisp
+"Lisp system" "\\\\texttt{cl-launch}" "\\\\texttt{runlisp} (vanilla image)" "\\\\texttt{runlisp} (custom image)"
+EOF
+for my $l (sort keys %LISP) {
+ printf "\"%s\" %.4f %.4f %.4f\n",
+ $LISP{$l},
+ timing("cl-launch.$l"),
+ timing("runlisp-noimage.$l"),
+ timing("runlisp.$l");
+}
+print "\n\n";
+
+print <<EOF;
+#> interp
+EOF
+for my $i
+ ("runlisp.ccl", "runlisp.clisp", "runlisp.cmucl", "runlisp.sbcl",
+ "perl", "python",
+ "shell.dash", "shell.bash", "shell.zsh",
+ "c.tcc", "c.gcc")
+ { printf "\"%s\" %.4f\n", $LABEL{$i}, timing $i; }
--- /dev/null
+#include <stdio.h>
+
+#if __clang__
+# define IMPL "Clang"
+#elif __TINYC__
+# define IMPL "TCC"
+#elif __GNUC__
+# define IMPL "GCC"
+#else
+# define IMPL "an unknown C implementation"
+#endif
+
+int main(int argc, char *argv[])
+{
+ int i;
+
+ puts("Hello from " IMPL "!");
+ printf("Script = `%s'\n", argv[0]);
+ fputs("Arguments = (", stdout);
+ for (i = 1; i < argc; i++) {
+ if (i > 1) fputs(", ", stdout);
+ printf("`%s'", argv[i]);
+ }
+ putchar(')'); putchar('\n');
+ return (0);
+}
--- /dev/null
+#! /usr/bin/runlisp
+
+(format t "Hello from ~A!~%~
+ Script = `~A'~%~
+ Arguments = (~{`~A'~^, ~})~%"
+ (lisp-implementation-type)
+ (uiop:argv0)
+ uiop:*command-line-arguments*)
--- /dev/null
+#! /usr/bin/perl
+
+printf <<EOF, $0, join ", ", map "`$_'", @ARGV;
+Hello from Perl!
+Script = `%s'
+Arguments = (%s)
+EOF
--- /dev/null
+#! /usr/bin/python
+
+import sys as SYS
+
+print("""\
+Hello from Python!
+Script = `%s'
+Arguments = (%s)""" %
+ (SYS.argv[0], ", ".join("`%s'" % arg for arg in SYS.argv[1:])))
--- /dev/null
+#! /bin/sh
+
+cat <<EOF
+Hello from ${TEST_SHELL-an unknown shell}!
+Script = $0
+Arguments = ($*)
+EOF
--- /dev/null
+#include <errno.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+#include <unistd.h>
+#include <sys/resource.h>
+#include <sys/time.h>
+#include <sys/wait.h>
+
+static const char *progname = "timeit";
+
+static void set_progname(const char *prog)
+{
+ const char *p;
+
+ p = strrchr(prog, '/');
+ progname = p ? p + 1 : progname;
+}
+
+static void lose(const char *msg, ...)
+{
+ va_list ap;
+
+ va_start(ap, msg);
+ fprintf(stderr, "%s: ", progname);
+ vfprintf(stderr, msg, ap);
+ fputc('\n', stderr);
+ va_end(ap);
+ exit(127);
+}
+
+static double timeval_to_float(const struct timeval *tv)
+ { return (tv->tv_sec + tv->tv_usec*1e-6); }
+
+int main(int argc, char *argv[])
+{
+ struct rusage ru;
+ struct timeval t0, t1, t;
+ pid_t kid;
+ int i, st;
+
+ set_progname(argv[0]);
+ gettimeofday(&t0, 0);
+ kid = fork(); if (kid < 0) lose("fork failed: %s", strerror(errno));
+ if (!kid) {
+ execvp(argv[1], argv + 1);
+ lose("exec (`%s') failed: %s", argv[1], strerror(errno));
+ }
+ if (wait4(kid, &st, 0, &ru) < 0) lose("wait failed: %s", strerror(errno));
+ gettimeofday(&t1, 0);
+ if (st) {
+ if (WIFSIGNALED(st))
+ lose("program killed by signal %d\n", WTERMSIG(st));
+ else if (WIFEXITED(st))
+ lose("program failed with status %d\n", WEXITSTATUS(st));
+ else
+ lose("program exited with incomprehensible status 0x%04x\n", st);
+ }
+
+ if (t0.tv_usec > t1.tv_usec) {
+ t.tv_sec = t1.tv_sec - t0.tv_sec - 1;
+ t.tv_usec = t1.tv_usec + 1000000 - t0.tv_usec;
+ } else {
+ t.tv_sec = t1.tv_sec - t0.tv_sec;
+ t.tv_usec = t1.tv_usec - t0.tv_usec;
+ }
+
+ for (i = 1; i < argc; i++) {
+ if (i > 1) fputc(' ', stderr);
+ fputs(argv[i], stderr);
+ }
+ fprintf(stderr, ": elapsed = %.4fs; user = %.4fs; system = %.4fs\n",
+ timeval_to_float(&t),
+ timeval_to_float(&ru.ru_utime),
+ timeval_to_float(&ru.ru_stime));
+ return (0);
+}
--- /dev/null
+../.ext/cfd/build/auto-version
\ No newline at end of file
--- /dev/null
+../.ext/cfd/build/confsubst
\ No newline at end of file
--- /dev/null
+dnl -*-autoconf-*-
+dnl
+dnl Configuration script for `runlisp'
+dnl
+dnl (c) 2020 Mark Wooding
+dnl
+
+dnl----- Licensing notice ---------------------------------------------------
+dnl
+dnl This file is part of Runlisp, a tool for invoking Common Lisp scripts.
+dnl
+dnl Runlisp is free software: you can redistribute it and/or modify it
+dnl under the terms of the GNU General Public License as published by the
+dnl Free Software Foundation; either version 3 of the License, or (at your
+dnl option) any later version.
+dnl
+dnl Runlisp is distributed in the hope that it will be useful, but WITHOUT
+dnl ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+dnl FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+dnl for more details.
+dnl
+dnl You should have received a copy of the GNU General Public License
+dnl along with Runlisp. If not, see <https://www.gnu.org/licenses/>.
+
+dnl--------------------------------------------------------------------------
+dnl Initialization.
+
+mdw_AUTO_VERSION
+AC_INIT([runlisp], AUTO_VERSION, [mdw@distorted.org.uk])
+AC_CONFIG_SRCDIR([runlisp.c])
+AC_CONFIG_AUX_DIR([config])
+AM_INIT_AUTOMAKE([foreign])
+mdw_SILENT_RULES
+
+AC_PROG_CC
+AX_CFLAGS_WARN_ALL
+
+AC_CHECK_PROGS([AUTOM4TE], [autom4te])
+
+dnl--------------------------------------------------------------------------
+dnl Checking for Lisp implementations.
+
+imagedir=$localstatedir/$PACKAGE_NAME; AC_SUBST(imagedir)
+mdw_DEFINE_PATHS([
+ mdw_DEFINE_PATH([IMAGEDIR], [$imagedir])
+ mdw_DEFINE_PATH([DATADIR], [$datadir/$PACKAGE_NAME])])
+
+AC_ARG_ENABLE([imagedump],
+ [AS_HELP_STRING([--enable-imagedump[=SYSTEMS]],
+ [make dumps of Lisp SYSTEMS with ASDF etc. preloaded;
+ SYSTEMS is `yes', `no', or a comma-separated list of
+ system names])],
+ [], [enable_imagedump=yes])
+
+AC_DEFUN([mdw_CHECK_LISP],
+[AC_CHECK_PROGS([$1], [$2])
+AC_ARG_VAR([$1], [Path to the $1 Lisp system.])
+case ,$enable_imagedump, in
+ ,yes, | *,$2,*) dump=t ;;
+ *) dump=nil ;;
+esac
+AM_CONDITIONAL([DUMP_$1], [test $dump = t])])
+
+mdw_CHECK_LISP([SBCL], [sbcl])
+mdw_CHECK_LISP([CCL], [ccl])
+mdw_CHECK_LISP([CLISP], [clisp])
+mdw_CHECK_LISP([ECL], [ecl])
+mdw_CHECK_LISP([CMUCL], [cmucl])
+mdw_CHECK_LISP([ABCL], [abcl])
+
+dnl ECL is changing its command-line option syntax, because that will make
+dnl things much better or something. So we need to figure out which version
+dnl of the syntax to use.
+mdw_ecl_opts=hunoz
+if test "x$ECL" != x; then
+ AC_MSG_CHECKING([ECL command-line option flavour])
+ ver=$($ECL --version)
+ case $ver in
+ [ECL\ [0-9].*] | [ECL\ 1[0-5].*]) mdw_ecl_opts=trad ;;
+ [ECL\ 1[6-9].*] | [ECL\ [2-9][0-9].*]) mdw_ecl_opts=gnu ;;
+ *) AC_MSG_ERROR([unsupported ECL version \`$ver']) ;;
+ esac
+ AC_MSG_RESULT([$mdw_ecl_opts])
+ case $mdw_ecl_opts in
+ gnu) AC_DEFINE([ECL_OPTIONS_GNU], [1],
+ [Define 1 if ECL uses GNU-style `--FOO' options]) ;;
+ esac
+fi
+case $mdw_ecl_opts in
+ gnu) ECLOPT=-- ;;
+ trad) ECLOPT=- ;;
+ *) AC_MSG_ERROR([internal error: unexpected value for `$mdw_ecl_opts']) ;;
+esac
+AC_SUBST([ECLOPT])
+
+dnl--------------------------------------------------------------------------
+dnl Benchmarking support.
+
+dnl This has lots of random dependencies, and isn't really very useful. Turn
+dnl it off unless the user is very keen.
+AC_ARG_ENABLE([benchmark],
+ [AS_HELP_STRING([--enable-benchmark],
+ [turn on script-startup benchmark machinery])],
+ [mdw_bench=$enableval], [mdw_bench=no])
+AM_CONDITIONAL([BENCHMARK], [test "$mdw_bench" = yes])
+
+dnl--------------------------------------------------------------------------
+dnl Produce output.
+
+AC_CONFIG_HEADER([config/config.h])
+AC_CONFIG_TESTDIR([t])
+
+AC_CONFIG_FILES([Makefile]
+ [bench/Makefile doc/Makefile]
+ [t/Makefile t/atlocal])
+AC_OUTPUT
+
+dnl----- That's all, folks --------------------------------------------------
--- /dev/null
+### -*-makefile-*-
+###
+### Additional documentation files
+###
+### (c) 2020 Mark Wooding
+###
+
+###----- Licensing notice ---------------------------------------------------
+###
+### This file is part of Runlisp, a tool for invoking Common Lisp scripts.
+###
+### Runlisp is free software: you can redistribute it and/or modify it
+### under the terms of the GNU General Public License as published by the
+### Free Software Foundation; either version 3 of the License, or (at your
+### option) any later version.
+###
+### Runlisp is distributed in the hope that it will be useful, but WITHOUT
+### ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+### FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+### for more details.
+###
+### You should have received a copy of the GNU General Public License
+### along with Runlisp. If not, see <https://www.gnu.org/licenses/>.
+
+include $(top_srcdir)/vars.am
+
+EXTRA_DIST += bench.data
+
+EXTRA_DIST += lisp-graph.tikz
+EXTRA_DIST += interp-graph.tikz
+
+###----- That's all, folks --------------------------------------------------
--- /dev/null
+#> lisp
+"Lisp system" "\\texttt{cl-launch}" "\\texttt{runlisp} (vanilla image)" "\\texttt{runlisp} (custom image)"
+"ABCL" 7.3036 2.6027 2.5873
+"Clozure CL" 1.2769 0.9678 0.0088
+"GNU CLisp" 1.2498 0.2659 0.0146
+"CMU CL" 0.9665 0.3065 0.0063
+"ECL" 0.8025 0.3173 0.3185
+"SBCL" 0.3266 0.0739 0.0077
+
+
+#> interp
+"Clozure CL" 0.0088
+"GNU CLisp" 0.0146
+"CMU CL" 0.0063
+"SBCL" 0.0077
+"Perl" 0.0012
+"Python" 0.0103
+"dash" 0.0014
+"GNU Bash" 0.0020
+"Z Shell" 0.0041
+"Tiny C" 0.0012
+"GCC" 0.0005
--- /dev/null
+\begin{tikzpicture}[gnuplot]
+%% generated with GNUPLOT 5.2p6 (Lua 5.3; terminal rev. Nov 2018, script rev. 107)
+%% Sat 15 Aug 2020 14:07:28 BST
+\path (0.000,0.000) rectangle (12.500,8.750);
+\gpcolor{color=gp lt color border}
+\gpsetlinetype{gp lt border}
+\gpsetdashtype{gp dt solid}
+\gpsetlinewidth{1.00}
+\draw[gp path] (1.136,1.363)--(1.316,1.363);
+\node[gp node right] at (0.952,1.363) {$0$};
+\draw[gp path] (1.136,2.248)--(1.316,2.248);
+\node[gp node right] at (0.952,2.248) {$2$};
+\draw[gp path] (1.136,3.133)--(1.316,3.133);
+\node[gp node right] at (0.952,3.133) {$4$};
+\draw[gp path] (1.136,4.017)--(1.316,4.017);
+\node[gp node right] at (0.952,4.017) {$6$};
+\draw[gp path] (1.136,4.902)--(1.316,4.902);
+\node[gp node right] at (0.952,4.902) {$8$};
+\draw[gp path] (1.136,5.787)--(1.316,5.787);
+\node[gp node right] at (0.952,5.787) {$10$};
+\draw[gp path] (1.136,6.672)--(1.316,6.672);
+\node[gp node right] at (0.952,6.672) {$12$};
+\draw[gp path] (1.136,7.556)--(1.316,7.556);
+\node[gp node right] at (0.952,7.556) {$14$};
+\draw[gp path] (1.136,8.441)--(1.316,8.441);
+\node[gp node right] at (0.952,8.441) {$16$};
+\node[gp node left,rotate=-35] at (1.853,1.179) {Clozure CL};
+\node[gp node left,rotate=-35] at (2.754,1.179) {GNU CLisp};
+\node[gp node left,rotate=-35] at (3.655,1.179) {CMU CL};
+\node[gp node left,rotate=-35] at (4.556,1.179) {SBCL};
+\node[gp node left,rotate=-35] at (5.457,1.179) {Perl};
+\node[gp node left,rotate=-35] at (6.358,1.179) {Python};
+\node[gp node left,rotate=-35] at (7.258,1.179) {dash};
+\node[gp node left,rotate=-35] at (8.159,1.179) {GNU Bash};
+\node[gp node left,rotate=-35] at (9.060,1.179) {Z Shell};
+\node[gp node left,rotate=-35] at (9.961,1.179) {Tiny C};
+\node[gp node left,rotate=-35] at (10.862,1.179) {GCC};
+\draw[gp path] (1.136,8.441)--(1.136,1.363)--(11.947,1.363);
+\node[gp node center,rotate=-270] at (0.276,4.902) {Time (ms) to run trivial script};
+\gpfill{rgb color={0.580,0.000,0.827}} (1.812,1.363)--(2.263,1.363)--(2.263,5.257)--(1.812,5.257)--cycle;
+\gpcolor{rgb color={0.580,0.000,0.827}}
+\draw[gp path] (1.812,1.363)--(1.812,5.256)--(2.262,5.256)--(2.262,1.363)--cycle;
+\gpfill{rgb color={0.580,0.000,0.827}} (2.713,1.363)--(3.164,1.363)--(3.164,7.823)--(2.713,7.823)--cycle;
+\draw[gp path] (2.713,1.363)--(2.713,7.822)--(3.163,7.822)--(3.163,1.363)--cycle;
+\gpfill{rgb color={0.580,0.000,0.827}} (3.614,1.363)--(4.065,1.363)--(4.065,4.151)--(3.614,4.151)--cycle;
+\draw[gp path] (3.614,1.363)--(3.614,4.150)--(4.064,4.150)--(4.064,1.363)--cycle;
+\gpfill{rgb color={0.580,0.000,0.827}} (4.514,1.363)--(4.966,1.363)--(4.966,4.770)--(4.514,4.770)--cycle;
+\draw[gp path] (4.514,1.363)--(4.514,4.769)--(4.965,4.769)--(4.965,1.363)--cycle;
+\gpfill{rgb color={0.580,0.000,0.827}} (5.415,1.363)--(5.867,1.363)--(5.867,1.895)--(5.415,1.895)--cycle;
+\draw[gp path] (5.415,1.363)--(5.415,1.894)--(5.866,1.894)--(5.866,1.363)--cycle;
+\gpfill{rgb color={0.580,0.000,0.827}} (6.316,1.363)--(6.768,1.363)--(6.768,5.920)--(6.316,5.920)--cycle;
+\draw[gp path] (6.316,1.363)--(6.316,5.919)--(6.767,5.919)--(6.767,1.363)--cycle;
+\gpfill{rgb color={0.580,0.000,0.827}} (7.217,1.363)--(7.669,1.363)--(7.669,1.983)--(7.217,1.983)--cycle;
+\draw[gp path] (7.217,1.363)--(7.217,1.982)--(7.668,1.982)--(7.668,1.363)--cycle;
+\gpfill{rgb color={0.580,0.000,0.827}} (8.118,1.363)--(8.570,1.363)--(8.570,2.249)--(8.118,2.249)--cycle;
+\draw[gp path] (8.118,1.363)--(8.118,2.248)--(8.569,2.248)--(8.569,1.363)--cycle;
+\gpfill{rgb color={0.580,0.000,0.827}} (9.019,1.363)--(9.470,1.363)--(9.470,3.178)--(9.019,3.178)--cycle;
+\draw[gp path] (9.019,1.363)--(9.019,3.177)--(9.469,3.177)--(9.469,1.363)--cycle;
+\gpfill{rgb color={0.580,0.000,0.827}} (9.920,1.363)--(10.371,1.363)--(10.371,1.895)--(9.920,1.895)--cycle;
+\draw[gp path] (9.920,1.363)--(9.920,1.894)--(10.370,1.894)--(10.370,1.363)--cycle;
+\gpfill{rgb color={0.580,0.000,0.827}} (10.821,1.363)--(11.272,1.363)--(11.272,1.585)--(10.821,1.585)--cycle;
+\draw[gp path] (10.821,1.363)--(10.821,1.584)--(11.271,1.584)--(11.271,1.363)--cycle;
+\gpcolor{color=gp lt color border}
+\draw[gp path] (1.136,8.441)--(1.136,1.363)--(11.947,1.363);
+%% coordinates of the plot area
+\gpdefrectangularnode{gp plot 1}{\pgfpoint{1.136cm}{1.363cm}}{\pgfpoint{11.947cm}{8.441cm}}
+\end{tikzpicture}
+%% gnuplot variables
--- /dev/null
+\begin{tikzpicture}[gnuplot]
+%% generated with GNUPLOT 5.2p6 (Lua 5.3; terminal rev. Nov 2018, script rev. 107)
+%% Sat 15 Aug 2020 14:07:24 BST
+\path (0.000,0.000) rectangle (12.500,8.750);
+\gpcolor{color=gp lt color border}
+\gpsetlinetype{gp lt border}
+\gpsetdashtype{gp dt solid}
+\gpsetlinewidth{1.00}
+\draw[gp path] (0.952,1.363)--(1.132,1.363);
+\node[gp node right] at (0.768,1.363) {$0$};
+\draw[gp path] (0.952,2.248)--(1.132,2.248);
+\node[gp node right] at (0.768,2.248) {$1$};
+\draw[gp path] (0.952,3.133)--(1.132,3.133);
+\node[gp node right] at (0.768,3.133) {$2$};
+\draw[gp path] (0.952,4.017)--(1.132,4.017);
+\node[gp node right] at (0.768,4.017) {$3$};
+\draw[gp path] (0.952,4.902)--(1.132,4.902);
+\node[gp node right] at (0.768,4.902) {$4$};
+\draw[gp path] (0.952,5.787)--(1.132,5.787);
+\node[gp node right] at (0.768,5.787) {$5$};
+\draw[gp path] (0.952,6.672)--(1.132,6.672);
+\node[gp node right] at (0.768,6.672) {$6$};
+\draw[gp path] (0.952,7.556)--(1.132,7.556);
+\node[gp node right] at (0.768,7.556) {$7$};
+\draw[gp path] (0.952,8.441)--(1.132,8.441);
+\node[gp node right] at (0.768,8.441) {$8$};
+\node[gp node left,rotate=-35] at (2.523,1.179) {ABCL};
+\node[gp node left,rotate=-35] at (4.093,1.179) {Clozure CL};
+\node[gp node left,rotate=-35] at (5.664,1.179) {GNU CLisp};
+\node[gp node left,rotate=-35] at (7.235,1.179) {CMU CL};
+\node[gp node left,rotate=-35] at (8.806,1.179) {ECL};
+\node[gp node left,rotate=-35] at (10.376,1.179) {SBCL};
+\draw[gp path] (0.952,8.441)--(0.952,1.363)--(11.947,1.363);
+\node[gp node center,rotate=-270] at (0.276,4.902) {Time (s) to run trivial script};
+\node[gp node right] at (10.479,8.107) {\texttt{cl-launch}};
+\gpfill{rgb color={0.580,0.000,0.827}} (10.663,8.030)--(11.579,8.030)--(11.579,8.184)--(10.663,8.184)--cycle;
+\gpcolor{rgb color={0.580,0.000,0.827}}
+\draw[gp path] (10.663,8.030)--(11.579,8.030)--(11.579,8.184)--(10.663,8.184)--cycle;
+\gpfill{rgb color={0.580,0.000,0.827}} (2.209,1.363)--(2.524,1.363)--(2.524,7.826)--(2.209,7.826)--cycle;
+\draw[gp path] (2.209,1.363)--(2.209,7.825)--(2.523,7.825)--(2.523,1.363)--cycle;
+\gpfill{rgb color={0.580,0.000,0.827}} (3.779,1.363)--(4.094,1.363)--(4.094,2.494)--(3.779,2.494)--cycle;
+\draw[gp path] (3.779,1.363)--(3.779,2.493)--(4.093,2.493)--(4.093,1.363)--cycle;
+\gpfill{rgb color={0.580,0.000,0.827}} (5.350,1.363)--(5.665,1.363)--(5.665,2.470)--(5.350,2.470)--cycle;
+\draw[gp path] (5.350,1.363)--(5.350,2.469)--(5.664,2.469)--(5.664,1.363)--cycle;
+\gpfill{rgb color={0.580,0.000,0.827}} (6.921,1.363)--(7.236,1.363)--(7.236,2.219)--(6.921,2.219)--cycle;
+\draw[gp path] (6.921,1.363)--(6.921,2.218)--(7.235,2.218)--(7.235,1.363)--cycle;
+\gpfill{rgb color={0.580,0.000,0.827}} (8.491,1.363)--(8.807,1.363)--(8.807,2.074)--(8.491,2.074)--cycle;
+\draw[gp path] (8.491,1.363)--(8.491,2.073)--(8.806,2.073)--(8.806,1.363)--cycle;
+\gpfill{rgb color={0.580,0.000,0.827}} (10.062,1.363)--(10.377,1.363)--(10.377,1.653)--(10.062,1.653)--cycle;
+\draw[gp path] (10.062,1.363)--(10.062,1.652)--(10.376,1.652)--(10.376,1.363)--cycle;
+\gpcolor{color=gp lt color border}
+\node[gp node right] at (10.479,7.799) {\texttt{runlisp} (vanilla image)};
+\gpfill{rgb color={0.000,0.620,0.451}} (10.663,7.722)--(11.579,7.722)--(11.579,7.876)--(10.663,7.876)--cycle;
+\gpcolor{rgb color={0.000,0.620,0.451}}
+\draw[gp path] (10.663,7.722)--(11.579,7.722)--(11.579,7.876)--(10.663,7.876)--cycle;
+\gpfill{rgb color={0.000,0.620,0.451}} (2.523,1.363)--(2.838,1.363)--(2.838,3.667)--(2.523,3.667)--cycle;
+\draw[gp path] (2.523,1.363)--(2.523,3.666)--(2.837,3.666)--(2.837,1.363)--cycle;
+\gpfill{rgb color={0.000,0.620,0.451}} (4.093,1.363)--(4.409,1.363)--(4.409,2.220)--(4.093,2.220)--cycle;
+\draw[gp path] (4.093,1.363)--(4.093,2.219)--(4.408,2.219)--(4.408,1.363)--cycle;
+\gpfill{rgb color={0.000,0.620,0.451}} (5.664,1.363)--(5.979,1.363)--(5.979,1.599)--(5.664,1.599)--cycle;
+\draw[gp path] (5.664,1.363)--(5.664,1.598)--(5.978,1.598)--(5.978,1.363)--cycle;
+\gpfill{rgb color={0.000,0.620,0.451}} (7.235,1.363)--(7.550,1.363)--(7.550,1.635)--(7.235,1.635)--cycle;
+\draw[gp path] (7.235,1.363)--(7.235,1.634)--(7.549,1.634)--(7.549,1.363)--cycle;
+\gpfill{rgb color={0.000,0.620,0.451}} (8.806,1.363)--(9.121,1.363)--(9.121,1.645)--(8.806,1.645)--cycle;
+\draw[gp path] (8.806,1.363)--(8.806,1.644)--(9.120,1.644)--(9.120,1.363)--cycle;
+\gpfill{rgb color={0.000,0.620,0.451}} (10.376,1.363)--(10.691,1.363)--(10.691,1.429)--(10.376,1.429)--cycle;
+\draw[gp path] (10.376,1.363)--(10.376,1.428)--(10.690,1.428)--(10.690,1.363)--cycle;
+\gpcolor{color=gp lt color border}
+\node[gp node right] at (10.479,7.491) {\texttt{runlisp} (custom image)};
+\gpfill{rgb color={0.337,0.706,0.914}} (10.663,7.414)--(11.579,7.414)--(11.579,7.568)--(10.663,7.568)--cycle;
+\gpcolor{rgb color={0.337,0.706,0.914}}
+\draw[gp path] (10.663,7.414)--(11.579,7.414)--(11.579,7.568)--(10.663,7.568)--cycle;
+\gpfill{rgb color={0.337,0.706,0.914}} (2.837,1.363)--(3.152,1.363)--(3.152,3.653)--(2.837,3.653)--cycle;
+\draw[gp path] (2.837,1.363)--(2.837,3.652)--(3.151,3.652)--(3.151,1.363)--cycle;
+\gpfill{rgb color={0.337,0.706,0.914}} (4.408,1.363)--(4.723,1.363)--(4.723,1.372)--(4.408,1.372)--cycle;
+\draw[gp path] (4.408,1.363)--(4.408,1.371)--(4.722,1.371)--(4.722,1.363)--cycle;
+\gpfill{rgb color={0.337,0.706,0.914}} (5.978,1.363)--(6.293,1.363)--(6.293,1.377)--(5.978,1.377)--cycle;
+\draw[gp path] (5.978,1.363)--(5.978,1.376)--(6.292,1.376)--(6.292,1.363)--cycle;
+\gpfill{rgb color={0.337,0.706,0.914}} (7.549,1.363)--(7.864,1.363)--(7.864,1.370)--(7.549,1.370)--cycle;
+\draw[gp path] (7.549,1.363)--(7.549,1.369)--(7.863,1.369)--(7.863,1.363)--cycle;
+\gpfill{rgb color={0.337,0.706,0.914}} (9.120,1.363)--(9.435,1.363)--(9.435,1.646)--(9.120,1.646)--cycle;
+\draw[gp path] (9.120,1.363)--(9.120,1.645)--(9.434,1.645)--(9.434,1.363)--cycle;
+\gpfill{rgb color={0.337,0.706,0.914}} (10.690,1.363)--(11.006,1.363)--(11.006,1.371)--(10.690,1.371)--cycle;
+\draw[gp path] (10.690,1.363)--(10.690,1.370)--(11.005,1.370)--(11.005,1.363)--cycle;
+\gpcolor{color=gp lt color border}
+\draw[gp path] (0.952,8.441)--(0.952,1.363)--(11.947,1.363);
+%% coordinates of the plot area
+\gpdefrectangularnode{gp plot 1}{\pgfpoint{0.952cm}{1.363cm}}{\pgfpoint{11.947cm}{8.441cm}}
+\end{tikzpicture}
+%% gnuplot variables
--- /dev/null
+.\" -*-nroff-*-
+.\"
+.\" Manual for `dump-runlisp-image'
+.\"
+.\" (c) 2020 Mark Wooding
+.\"
+.
+.\"----- Licensing notice ---------------------------------------------------
+.\"
+.\" This file is part of Runlisp, a tool for invoking Common Lisp scripts.
+.\"
+.\" Runlisp is free software: you can redistribute it and/or modify it
+.\" under the terms of the GNU General Public License as published by the
+.\" Free Software Foundation; either version 3 of the License, or (at your
+.\" option) any later version.
+.\"
+.\" Runlisp is distributed in the hope that it will be useful, but WITHOUT
+.\" ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+.\" FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+.\" for more details.
+.\"
+.\" You should have received a copy of the GNU General Public License
+.\" along with Runlisp. If not, see <https://www.gnu.org/licenses/>.
+.
+.ie t \{\
+. ds o \(bu
+. if \n(.g \{\
+. fam P
+. ev an-1
+. fam P
+. ev
+. \}
+.\}
+.el \{\
+. ds o o
+.\}
+.
+.de hP
+.IP
+\h'-\w'\fB\\$1\ \fP'u'\fB\\$1\ \fP\c
+..
+.ds , \h'.16667m'
+.
+.\"--------------------------------------------------------------------------
+.TH runlisp 1 "12 August 2020" "Mark Wooding"
+.SH NAME
+dump-runlisp-image \- dump Lisp images for faster script execution
+.
+.\"--------------------------------------------------------------------------
+.SH SYNOPSIS
+.
+.B dump-runlisp-image
+.RB [ \-acluv ]
+.RB [ \-o
+.IR output ]
+.RI [ lisp
+\&...]
+.
+.\"--------------------------------------------------------------------------
+.SH DESCRIPTION
+.
+The
+.B dump-runlisp-image
+program builds custom images for use by
+.BR runlisp (1).
+For many Lisp implementation,
+a custom image,
+with ASDF already loaded,
+can start scripts much more quickly
+than the `vanilla' images installed by deafult.
+The downside is that custom images may be rather large.
+.
+.SS "Supperted Common Lisp implementations"
+The following Lisp implementations are currently supported.
+.TP
+.B "ccl"
+Clozure Common Lisp.
+The default image name is
+.BR ccl+asdf.image ;
+a typical image can be 20\(en30\*,MB in size.
+.TP
+.B "clisp"
+GNU CLisp.
+The default image name is
+.BR clisp+asdf.mem ;
+a typical image is about 10\*,MB in size.
+.TP
+.B "cmucl"
+Carnegie\(enMellon University Common Lisp.
+The default image name is
+.BR cmucl+asdf.core ;
+a typical image is about 35\*,MB in size.
+.TP
+.B "ecl"
+Embeddable Common Lisp.
+The default image name is
+.BR ecl+asdf ;
+images comparatively very small
+\(en about 4\*,MB \(en
+but, sadly, not very effective.
+.TP
+.B "sbcl"
+Steel Bank Common Lisp.
+The default image name is
+.BR sbcl+asdf.core ;
+a typical image is nearly 45\*,MB in size.
+.PP
+(Although
+.BR runlisp (3)
+also supports Armed Bear Common Lisp,
+.B dump-runlisp-image
+currently doesn't.)
+.
+.SS "Options"
+The following options are accepted on the command line.
+.
+.TP
+.B "\-h"
+Write a synopsis of
+.BR dump-runlisp-image 's
+command-line syntax
+and a description of the command-line options
+to standard output
+and immediately exit with status 0.
+.
+.TP
+.B "\-v"
+Write
+.BR dump-runlisp-image 's
+version number
+to standard output
+and immediately exit with status 0.
+.
+.TP
+.B "\-a"
+Dump images for all Lisp supported implementations
+which are installed .
+This implies
+.BR \-c ,
+described below.
+You can't set
+.B \-a
+and also list implementations explicitly on the command line.
+.
+.TP
+.B "\-c"
+Only dump images for Lisp implementations
+which are actually installed
+(and can be found).
+.
+.TP
+.B "\-l"
+List the supported implementations
+and the names of the image files for each
+to standard output,
+and immediately exit with status 0.
+.
+.TP
+.BI "\-o " output
+If
+.I output
+names a directory,
+then write images to that directory
+with their default names
+(as listed above).
+Otherwise,
+exactly one Lisp implementation may be named, and
+the image is written to a file named
+.IR output .
+By default,
+images are written to the directory which
+.BR runlisp (1)
+will look in when checking for custom images
+(shown in
+.B "runlisp \-\-help"
+or
+.BR "dump-runlisp-image \-h" ),
+unless overridden by the
+.B RUNLISP_IMAGEDIR
+environment variable.
+.
+.TP
+.BI "\-u"
+Don't create Lisp images
+if a file with the appropriate name
+already exists.
+.
+.TP
+.BI "\-v"
+Be more verbose about the process of creating images.
+Lisp implementations can be rather noisy:
+by default,
+.B dump-runlisp-image
+runs silently unless something goes wrong,
+in which case it prints the failed Lisp command line
+and its output.
+If you set
+.B \-v
+then
+.B dump-runlisp-image
+will show Lisp implementation's noise immediately,
+without waiting to see whether it succeeds or fails.
+.PP
+The
+.B dump-runlisp-image
+program will dump an image for each of the named
+.I lisp
+implementations in turn,
+or all Lisp implementations, if
+.B \-a
+is set.
+.PP
+This involves invoking the Lisp implementations.
+The
+.B dump-runlisp-image
+program expects, by default,
+to be able to run a Lisp system
+as a program with the same name,
+found by searching as directed by the
+.B PATH
+environment variable.
+This can be overridden by setting an environment variable,
+with the same name but in
+.IR "upper case" ,
+to the actual name \(en
+either a bare filename to be searched for on the
+.BR PATH ,
+or a pathname containing a
+.RB ` / ',
+relative to the working directory or absolute,
+to the program.
+Note that the entire variable value is used as the program name:
+it's not possible to provide custom arguments to a Lisp system
+using this mechanism.
+If you want to do that,
+you must write a shell script to do the necessary work,
+and point the environment variable
+(or the
+.BR PATH )
+at your script.
+(This is the same convention as
+.BR runlisp (1).)
+.PP
+If
+.B \-a
+or
+.B \-c
+is set,
+then
+.B dump-runlisp-image
+will skip Lisp implementations
+which can't actually be found
+(by searching the
+.B PATH
+for its command name).
+.
+.\"--------------------------------------------------------------------------
+.
+.SH "BUGS"
+.hP \*o
+There's no support for making images for ABCL.
+I don't really know what this would look like,
+but I suspect it wouldn't help very much.
+ABCL is terribly slow to start up anyway.
+.
+.SH "SEE ALSO"
+.BR runlisp (1).
+.
+.SH "AUTHOR"
+Mark Wooding, <mdw@distorted.org.uk>
+.
+.\"----- That's all, folks --------------------------------------------------
--- /dev/null
+#! /bin/sh -e
+###
+### Dump Lisp images for faster script execution
+###
+### (c) 2020 Mark Wooding
+###
+
+###----- Licensing notice ---------------------------------------------------
+###
+### This file is part of Runlisp, a tool for invoking Common Lisp scripts.
+###
+### Runlisp is free software: you can redistribute it and/or modify it
+### under the terms of the GNU General Public License as published by the
+### Free Software Foundation; either version 3 of the License, or (at your
+### option) any later version.
+###
+### Runlisp is distributed in the hope that it will be useful, but WITHOUT
+### ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+### FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+### for more details.
+###
+### You should have received a copy of the GNU General Public License
+### along with Runlisp. If not, see <https://www.gnu.org/licenses/>.
+
+###--------------------------------------------------------------------------
+### Build-time configuration.
+
+VERSION=@VERSION@
+imagedir=@imagedir@
+eclopt=@ECLOPT@
+
+###--------------------------------------------------------------------------
+### Random utilities.
+
+prog=${0##*/}
+
+## Report a fatal error.
+lose () { echo >&2 "$prog: $*"; exit 2; }
+
+## Quote a string so that Lisp will understand it.
+lisp_quote () { printf "%s\n" "$1" | sed 's/[\\"]/\\&/g'; }
+
+## Mention that we're running a program.
+run () { echo "$*"; $lbuf "$@"; }
+
+## Figure out whether we can force line-buffering.
+if stdbuf --version >/dev/null 2>&1; then lbuf="stdbuf -oL --"
+else lbuf=""; fi
+
+## Copy stdin to stdout, one line at a time. This is important in the shell
+## game below, to prevent lines from two incoming streams being interleaved
+## in the log file.
+copy () { while IFS= read -r line; do printf "%s %s\n" "$1" "$line"; done; }
+
+###--------------------------------------------------------------------------
+### Lisp runes.
+
+## Load and upgrade ASDF.
+load_asdf_rune="(require \"asdf\")"
+upgrade_asdf_rune="(asdf:upgrade-asdf)"
+
+## Ignore `#!' lines. (We force this so as to provide a uniform environment,
+## even though some Lisp implementations take special action when they know
+## they're running scripts.)
+ignore_shebang_rune="\
+(set-dispatch-macro-character
+ #\\# #\\!
+ (lambda (stream #1=#:char #2=#:arg)
+ (declare (ignore #1# #2#))
+ (values (read-line stream))))"
+
+## Push `:runlisp-script' into the `*features*' list.
+set_script_feature_rune="(pushnew :runlisp-script *features*)"
+
+## All of the above.
+common_prelude_rune="\
+(progn
+ $upgrade_asdf_rune
+ $ignore_shebang_rune
+ $set_script_feature_rune)"
+
+###--------------------------------------------------------------------------
+### Explain how to dump the various Lisp systems.
+
+## Maintain the master tables.
+unset lisps
+deflisp () { lisps=${lisps+$lisps }$1; eval ${1}_image=\$2; }
+
+## Steel Bank Common Lisp.
+deflisp sbcl sbcl+asdf.core
+dump_sbcl () {
+ image=$(lisp_quote "$1")
+ run "${SBCL-sbcl}" --noinform --no-userinit --no-sysinit \
+ --disable-debugger \
+ --eval "$load_asdf_rune" \
+ --eval "$common_prelude_rune" \
+ --eval "(sb-ext:save-lisp-and-die \"$image\")"
+}
+
+## Clozure Common Lisp.
+deflisp ccl ccl+asdf.image
+dump_ccl () {
+ image=$(lisp_quote "$1")
+ ## A snaglet occurs here. CCL wants to use the image name as a clue to
+ ## where the rest of its installation is; but in fact the image is
+ ## nowhere near its installation. So we must hack...
+
+ run "${CCL-ccl}" -b -n -Q \
+ -e "$load_asdf_rune" \
+ -e "$common_prelude_rune" \
+ -e "(ccl::in-development-mode
+ (let ((#1=#:real-ccl-dir (ccl::ccl-directory)))
+ (defun ccl::ccl-directory ()
+ (let* ((#2=#:dirpath (ccl:getenv \"CCL_DEFAULT_DIRECTORY\")))
+ (if (and #2# (plusp (length (namestring #2#))))
+ (ccl::native-to-directory-pathname #2#)
+ #1#))))
+ (compile 'ccl::ccl-directory))" \
+ -e "(ccl:save-application \"$image\"
+ :init-file nil
+ :error-handler :quit)"
+}
+
+## GNU CLisp.
+deflisp clisp clisp+asdf.mem
+dump_clisp () {
+ image=$(lisp_quote "$1")
+ run "${CLISP-clisp}" -norc -q -q \
+ -x "$load_asdf_rune" \
+ -x "$common_prelude_rune" \
+ -x "(ext:saveinitmem \"$image\"
+ :norc t
+ :script t)" \
+ -- wrong arguments
+}
+
+## Embeddable Common Lisp.
+deflisp ecl ecl+asdf
+dump_ecl () {
+ image=$1
+ set -e
+
+ ## Start by compiling a copy of ASDF.
+ cat >"$tmp/ecl-build.lisp" <<EOF
+(require "asdf")
+
+(defparameter *asdf* (asdf:find-system "asdf"))
+
+(defun right-here (pathname pattern)
+ (declare (ignore pattern))
+ (merge-pathnames
+ (make-pathname :name (concatenate 'string
+ (string-downcase
+ (lisp-implementation-type))
+ "-"
+ (pathname-name pathname))
+ :type nil
+ :version nil
+ :defaults *default-pathname-defaults*)
+ pathname))
+(asdf:initialize-output-translations '(:output-translations
+ ((#p"/" :**/ :*.*.*)
+ (:function right-here))
+ :ignore-inherited-configuration))
+
+(asdf:operate 'asdf:lib-op *asdf*)
+(si:quit 0)
+EOF
+ (cd "$tmp" && run "${ECL-ecl}" ${eclopt}norc ${eclopt}load "ecl-build.lisp")
+
+ ## And now compile our driver code.
+ cat >"$tmp/ecl-run.lisp" <<EOF
+(cl:defpackage #:runlisp
+ (:use #:common-lisp))
+(cl:in-package #:runlisp)
+
+(defun main ()
+ $ignore_shebang_rune
+ (asdf:register-immutable-system "asdf")
+ (let ((pkg (find-package "COMMON-LISP-USER")))
+ (with-package-iterator (next pkg :internal)
+ (loop (multiple-value-bind (anyp sym how) (next)
+ (declare (ignore how))
+ (unless anyp (return))
+ (unintern sym pkg)))))
+ $set_script_feature_rune
+ (let ((winning t) (script nil) (marker nil)
+ (prog (file-namestring (si:argv 0))) (i 1) (argc (si:argc)))
+ (labels ((lose (msg &rest args)
+ (format *error-output* "~&~A: ~?~%" prog msg args)
+ (setf winning nil))
+ (quit (rc)
+ (si:quit rc))
+ (usage (stream)
+ (format stream "~&usage: ~A -s SCRIPT -- ARGS~%"
+ prog))
+ (getarg ()
+ (and (< i argc) (prog1 (si:argv i) (incf i)))))
+ (loop (let ((arg (getarg)))
+ (cond ((null arg) (return))
+ ((string= arg "--") (setf marker t) (return))
+ ((string= arg "-s") (setf script (getarg)))
+ ((string= arg "-h") (usage *standard-output*) (quit 0))
+ (t (lose "unrecognized option \`~A'" arg)))))
+ (unless script (lose "nothing to do"))
+ (unless marker (lose "unexpected end of options (missing \`--'?)"))
+ (unless winning (usage *error-output*) (quit 255))
+ (handler-case
+ (let ((*package* (find-package "COMMON-LISP-USER")))
+ (load script :verbose nil :print nil))
+ (error (err)
+ (format *error-output* "~&~A (uncaught error): ~A~%" prog err)
+ (quit 255)))
+ (quit 0))))
+(main)
+EOF
+ (cd "$tmp" && run "${ECL-ecl}" ${eclopt}norc ${eclopt}load "ecl-asdf.fas" \
+ -s -o "ecl-run.o" ${eclopt}compile "ecl-run.lisp")
+
+ ## Finally link everything together.
+ run "${ECL-ecl}" ${eclopt}norc -o "$image"\
+ ${eclopt}link "$tmp/ecl-asdf.o" "$tmp/ecl-run.o"
+}
+
+## Carnegie--Mellon University Common Lisp.
+deflisp cmucl cmucl+asdf.core
+dump_cmucl () {
+ image=$(lisp_quote "$1")
+ run "${CMUCL-cmucl}" -batch -noinit -nositeinit -quiet \
+ -eval "$load_asdf_rune" \
+ -eval "$common_prelude_rune" \
+ -eval "(ext:save-lisp \"$image\"
+ :batch-mode t :print-herald nil
+ :site-init nil :load-init-file nil)"
+}
+
+###--------------------------------------------------------------------------
+### Command-line processing.
+
+usage () { echo "usage: $prog [-acluv] [-o FILE] [LISP ...]"; }
+version () { echo "$prog, runlisp version $VERSION"; }
+help () {
+ version; echo; usage; cat <<EOF
+
+Options:
+ -h Show this help text and exit successfully.
+ -V Show the version number and exit successfully.
+ -a Dump all installed Lisp implementations.
+ -c Check that Lisp systems are installed before
+ trying to dump.
+ -l List known Lisp systems and default image filenames.
+ -o OUT Store images in OUT (file or directory); default
+ is \`\$RUNLISP_IMAGEDIR' or \`$imagedir'
+ -u Only dump images which don't exist already.
+ -v Be verbose, even if things go well.
+EOF
+}
+
+unset outfile; dir=${RUNLISP_IMAGEDIR-$imagedir}; dir=${dir%/}/
+all=nil checkinst=nil bogus=nil out=nil update=nil verbose=nil
+
+## Parse the options.
+while getopts "hVaclo:uv" opt; do
+ case $opt in
+ h) help; exit 0 ;;
+ V) version; exit 0 ;;
+ a) all=t checkinst=t ;;
+ l)
+ for i in $lisps; do
+ eval out=\$${i}_image
+ echo "$i -> $out"
+ done
+ exit 0
+ ;;
+ o) outfile=$OPTARG out=t; dir= ;;
+ u) update=t ;;
+ v) verbose=t ;;
+ *) bogus=t ;;
+ esac
+done
+shift $(( $OPTIND - 1 ))
+
+## If the destination is a directory then notice this.
+case $out in
+ t) if [ -d "$outfile" ]; then dir=${outfile%/}/; out=nil; fi ;;
+esac
+
+## Check that everything matches.
+case $#,$all,$out in
+ 0,nil,*) lose "no Lisp systems to dump" ;;
+ 0,t,nil) set -- $lisps ;;
+ *,t,*) lose "\`-a' makes no sense with explicit list" ;;
+ 1,nil,t) ;;
+ *,*,t) lose "can't name explicit output file for multiple Lisp systems" ;;
+esac
+
+## Check that the Lisp systems named are actually known.
+for lisp in "$@"; do
+ case " $lisps " in
+ *" $lisp "*) ;;
+ *) echo >&2 "$prog: unknown Lisp \`$lisp'"; exit 2 ;;
+ esac
+done
+
+## Complain if there were problems.
+case $bogus in t) usage >&2; exit 2 ;; esac
+
+###--------------------------------------------------------------------------
+### Dump the images.
+
+## Establish a temporary directory to work in.
+i=0
+while :; do
+ tmp=${TMPDIR-/tmp}/runlisp-tmp.$$.
+ if mkdir "$tmp" >/dev/null 2>&1; then break; fi
+ case $i in 64) lose "failed to create temporary directory" ;; esac
+ i=$(expr $i + 1)
+done
+trap 'rm -rf "$tmp"' EXIT INT TERM HUP
+
+## Send stdout to stderr or the log, depending on verbosity.
+output () {
+ case $verbose in
+ nil) $lbuf cat -u >"$tmp/log" ;;
+ t) $lbuf cat >&2 ;;
+ esac
+}
+
+## Work through each requested Lisp system.
+exit=0
+for lisp in "$@"; do
+
+ ## Figure out the output file to use.
+ case $out in nil) eval outfile=\$dir\$${lisp}_image ;; esac
+
+ ## Maybe we skip this one if the output already exists.
+ case $update in
+ t)
+ if [ -f "$outfile" ]; then
+ case $verbose in
+ t)
+ echo >&2 "$prog: \`$outfile' already exists: skipping \`$lisp'"
+ ;;
+ esac
+ continue
+ fi
+ ;;
+ esac
+
+ ## If we're doing all the Lisps, then skip systems which aren't actually
+ ## installed.
+ case $checkinst in
+ t)
+ LISP=$(echo $lisp | tr a-z A-Z)
+ eval lispprog=\${$LISP-$lisp}
+ if ! type >/dev/null 2>&1 $lispprog; then
+ case $verbose in
+ t)
+ echo >&2 "$prog: command \`$LISP' not found: skipping \`$lisp'"
+ ;;
+ esac
+ continue
+ fi
+ ;;
+ esac
+
+ ## Dump the Lisp, capturing its potentially drivellous output in a log
+ ## (unless we're being verbose). Be careful to keep stdout and stderr
+ ## separate.
+ rc=$(
+ { { { { echo "dumping $lisp to \`$outfile'..."
+ set +e; dump_$lisp "$outfile" 4>&- 5>&-
+ echo $? >&5; } |
+ copy "|" >&4; } 2>&1 |
+ copy "*" >&4; } 4>&1 |
+ output; } 5>&1 </dev/null
+ )
+
+ ## If it failed, and we didn't already spray the output to the terminal,
+ ## then do that now; also record that we encountered a problem.
+ case $rc in
+ 0) ;;
+ *) case $verbose in nil) cat >&2 "$tmp/log" ;; esac; exit=2 ;;
+ esac
+done
+
+## All done.
+exit $exit
+
+###----- That's all, folks --------------------------------------------------
--- /dev/null
+;;; -*-lisp-*-
+;;;
+;;; Evaluate expressions and run scripts
+;;;
+;;; (c) 2020 Mark Wooding
+;;;
+
+;;;----- Licensing notice ---------------------------------------------------
+;;;
+;;; This file is part of Runlisp, a tool for invoking Common Lisp scripts.
+;;;
+;;; Runlisp is free software: you can redistribute it and/or modify it
+;;; under the terms of the GNU General Public License as published by the
+;;; Free Software Foundation; either version 3 of the License, or (at your
+;;; option) any later version.
+;;;
+;;; Runlisp is distributed in the hope that it will be useful, but WITHOUT
+;;; ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+;;; FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+;;; for more details.
+;;;
+;;; You should have received a copy of the GNU General Public License
+;;; along with Runlisp. If not, see <https://www.gnu.org/licenses/>.
+
+(cl:defpackage #:runlisp
+ (:use #:common-lisp))
+(cl:in-package #:runlisp)
+
+(setf *features* (remove :runlisp-script *features*))
+
+(let ((*package* (find-package "COMMON-LISP-USER")))
+ (let ((token (cons 'token nil))
+ (args uiop:*command-line-arguments*)
+ (list nil))
+ (flet ((foreach-form (func arg)
+ (with-input-from-string (in arg)
+ (loop (let ((form (read in nil token)))
+ (when (eq form token) (return))
+ (funcall func form)))))
+ (print-form (form)
+ (format t "~@[~{~S~^ ~}~%~]"
+ (multiple-value-list (eval form)))))
+ (loop (let ((arg (pop args)))
+ (when (or (null arg) (string= arg "--")) (return))
+ (when (zerop (length arg))
+ (error "empty argument (no indicator)"))
+ (let ((rest (subseq arg 1)))
+ (ecase (char arg 0)
+ (#\! (push (lambda ()
+ (foreach-form #'eval rest))
+ list))
+ (#\? (push (lambda ()
+ (foreach-form #'print-form rest))
+ list))
+ (#\< (push (lambda ()
+ (load rest))
+ list)))))))
+ (let ((uiop:*command-line-arguments* args))
+ (mapc #'funcall (nreverse list)))))
--- /dev/null
+../.ext/cfd/m4/mdw-auto-version.m4
\ No newline at end of file
--- /dev/null
+../.ext/cfd/m4/mdw-decl-environ.m4
\ No newline at end of file
--- /dev/null
+../.ext/cfd/m4/mdw-define-paths.m4
\ No newline at end of file
--- /dev/null
+../.ext/cfd/m4/mdw-dir-texmf.m4
\ No newline at end of file
--- /dev/null
+../.ext/cfd/m4/mdw-libtool-version-info.m4
\ No newline at end of file
--- /dev/null
+../.ext/cfd/m4/mdw-manext.m4
\ No newline at end of file
--- /dev/null
+../.ext/cfd/m4/mdw-silent-rules.m4
\ No newline at end of file
--- /dev/null
+.\" -*-nroff-*-
+.\"
+.\" Manual for `runlisp'
+.\"
+.\" (c) 2020 Mark Wooding
+.\"
+.
+.\"----- Licensing notice ---------------------------------------------------
+.\"
+.\" This file is part of Runlisp, a tool for invoking Common Lisp scripts.
+.\"
+.\" Runlisp is free software: you can redistribute it and/or modify it
+.\" under the terms of the GNU General Public License as published by the
+.\" Free Software Foundation; either version 3 of the License, or (at your
+.\" option) any later version.
+.\"
+.\" Runlisp is distributed in the hope that it will be useful, but WITHOUT
+.\" ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+.\" FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+.\" for more details.
+.\"
+.\" You should have received a copy of the GNU General Public License
+.\" along with Runlisp. If not, see <https://www.gnu.org/licenses/>.
+.
+.ie t \{\
+. ds o \(bu
+. if \n(.g \{\
+. fam P
+. ev an-1
+. fam P
+. ev
+. \}
+.\}
+.el \{\
+. ds o o
+.\}
+.
+.de hP
+.IP
+\h'-\w'\fB\\$1\ \fP'u'\fB\\$1\ \fP\c
+..
+.
+.\"--------------------------------------------------------------------------
+.TH runlisp 1 "2 August 2020" "Mark Wooding"
+.SH NAME
+runlisp \- run Common Lisp programs as scripts
+.
+.\"--------------------------------------------------------------------------
+.SH SYNOPSIS
+.
+.B runlisp
+.RB [ \-CDEnqv ]
+.RB [ \-I
+.IR imagedir ]
+.RB [ \-L
+.IB sys , sys , \fR...]
+.RB [ \-P
+.IB sys , sys , \fR...]
+.br
+\h'8n'
+.RB [ \-e
+.IR form ]
+.RB [ \-l
+.IR file ]
+.RB [ \-p
+.IR form ]
+.RB [ \-\- ]
+.RI [ script ]
+.RI [ arguments
+\&...]
+.
+.\"--------------------------------------------------------------------------
+.SH DESCRIPTION
+.
+The
+.B runlisp
+program has two main functions.
+.hP 1.
+It can be used in a script's
+.RB ` #! '
+line to run a Common Lisp script.
+.hP 2.
+It can be used in build scripts
+to invoke a Common Lisp system,
+e.g., to build a standalone program.
+.
+.SS "Supported Common Lisp implementations"
+The following Lisp implementations are currently supported.
+.TP
+.B "abcl"
+Armed Bear Common Lisp.
+.TP
+.B "ccl"
+Clozure Common Lisp.
+.TP
+.B "clisp"
+GNU CLisp.
+.TP
+.B "cmucl"
+Carnegie\(enMellon University Common Lisp.
+.TP
+.B "ecl"
+Embeddable Common Lisp.
+.TP
+.B "sbcl"
+Steel Bank Common Lisp.
+.PP
+The
+.B runlisp
+program expects, by default,
+to be able to run a Lisp system
+as a program with the same name,
+found by searching as directed by the
+.B PATH
+environment variable.
+This can be overridden by setting an environment variable,
+with the same name but in
+.IR "upper case" ,
+to the actual name \(en
+either a bare filename to be searched for on the
+.BR PATH ,
+or a pathname containing a
+.RB ` / ',
+relative to the working directory or absolute,
+to the program.
+Note that the entire variable value is used as the program name:
+it's not possible to provide custom arguments to a Lisp system
+using this mechanism.
+If you want to do that,
+you must write a shell script to do the necessary work,
+and point the environment variable
+(or the
+.BR PATH )
+at your script.
+.
+.SS "Options"
+Options are read from the command line, as usual,
+but also from a number of other sources;
+these are, in order:
+.hP \*o
+If a
+.I script
+is named,
+and the script's second line contains a
+.RB ` @RUNLISP: '
+marker,
+then text following the marker is parsed as options.
+.hP \*o
+If files named
+.B ~/.runlisprc
+and/or
+.B ~/.config/runlisprc
+exist,
+then their contents are parsed as options.
+.hP \*o
+If an environment variable
+.B RUNLISP_OPTIONS
+is defined,
+then its contents is parsed as options.
+.PP
+A simple quoting and escaping system is implemented
+to allow spaces and other special characters
+to be included in argument words
+in the script, configuration files, and environment variable.
+The details of all of this are given in the section
+.B Operation
+below.
+.
+.PP
+The options accepted are as follows.
+.
+.TP
+.B "\-\-help"
+Write a synopsis of
+.BR runlisp 's
+command-line syntax
+and a description of the command-line options
+to standard output
+and immediately exit with status 0.
+.
+.TP
+.B "\-\-version"
+Write
+.BR runlisp 's
+version number
+to standard output
+and immediately exit with status 0.
+.
+.TP
+.B "\-C"
+Clear the list of preferred Lisp implementations.
+.
+.TP
+.B "\-D"
+Don't check for a custom Lisp image.
+Usually,
+.B runlisp
+tries to start Lisp systems using a custom image,
+so that they'll start more quickly;
+the
+.RB ` \-D '
+option forces the use of the default `vanilla' image
+provided with the system.
+There's not usually any good reason to prefer the vanilla image,
+except for performance comparisons, or debugging
+.B runlisp
+itself.
+.
+.TP
+.B "\-E"
+Don't read embedded options from the
+second line of the
+.I script
+file.
+This has no effect in eval mode.
+.
+.TP
+.BI "\-I " imagedir
+Look in
+.I imagedir
+for custom Lisp images.
+This option overrides the default image directory,
+which is set at compile time.
+.
+.TP
+.BI "\-L " sys , sys ,\fR...
+Use one of the named Lisp systems.
+Each
+.I sys
+must name a supported Lisp system.
+This option may be given more than once:
+the effect is the same as a single option
+listing all of the systems named, in the same order.
+If a system is named more than once,
+a warning is issued (at verbosity level 1 or higher),
+and all but the first occurrence is ignored.
+.
+.TP
+.BI "\-P " sys , sys ,\fR...
+Set the relative preference order of Lisp systems:
+systems listed earlier are more preferred.
+Each
+.I sys
+must name a supported Lisp system.
+This option may be given more than once:
+the effect is the same as a single option
+listing all of the systems named, in the same order.
+If a system is named more than once,
+a warning is issued (at verbosity level 1 or higher),
+and all but the first occurrence is ignored.
+Unmentioned systems are assigned lowest preference:
+if a
+.RB ` \-L '
+option is given,
+then this provides a default preference ordering;
+otherwise, an ordering hardcoded into the program is used.
+The first acceptable Lisp system,
+according to the preference order just described,
+which actually exists,
+is the one selected.
+.
+.TP
+.BI "\-e " expr
+Evaluate the expression(s)
+.I expr
+and discard the resulting values.
+This option causes
+.B runlisp
+to execute in
+.I eval
+mode.
+.
+.TP
+.BI "\-l " file
+Read and evaluate forms from the
+.IR file .
+This option causes
+.B runlisp
+to execute in
+.I eval
+mode.
+.
+.TP
+.B "\-n"
+Don't actually start the Lisp environment.
+This may be helpful for the curious,
+in conjunction with
+.RB ` \-v '
+to increase the verbosity.
+.
+.TP
+.BI "\-p " expr
+Evaluate the expression(s)
+.I expr
+and print the resulting value(s)
+to standard output
+(as if by
+.BR prin1 ).
+If a form produces multiple values,
+they are printed on a single line,
+separated by a single space character;
+if a form produces no values at all,
+then nothing is printed \(en not even a newline character.
+This option causes
+.B runlisp
+to execute in
+.I eval
+mode.
+.
+.TP
+.B "\-q"
+Don't print warning messages.
+This option may be repeated:
+each use reduces verbosity by one step,
+counteracting one
+.RB ` \-v '
+option.
+The default verbosity level is 1,
+which prints only warning measages.
+.
+.TP
+.B "\-v"
+Print informational or debugging messages.
+This option may be repeated:
+each use increases verbosity by one step,
+counteracting one
+.RB ` \-q '
+option.
+The default verbosity level is 1,
+which prints only warning measages.
+Higher verbosity levels print informational and debugging messages.
+.
+.PP
+The
+.RB ` \-e ',
+.RB ` \-l ',
+and
+.RB ` \-p '
+options may only be given on the command-line itself,
+not following a
+.RB `@ RUNLISP: '
+marker in a script,
+in a configuration file,
+or in the
+.B RUNLISP_OPTIONS
+environment variable.
+These options may be given multiple times:
+they will be processed in the order given.
+If any of these options is given, then no
+.I script
+name will be parsed;
+instead, use
+.RB ` \-l '
+to load code from files.
+The
+.IR arguments ,
+if any,
+are still made available to the evaluated forms and loaded files.
+.
+.SS "Operation"
+The
+.B runlisp
+program behaves as follows.
+.PP
+The first thing it does is parse its command line.
+Options must precede positional arguments,
+though the boundary may be marked explicitly using
+.RB ` \-\- '
+if desired.
+If the command line contains any of
+.RB ` \-e ',
+.RB ` \-l ',
+or
+.RB ` \-p ',
+then
+.B runlisp
+treats all of its positional arguments as
+.I arguments
+to provide to the given forms and files,
+and runs in
+.I eval
+mode;
+otherwise, the first positional argument becomes the
+.I script
+name, the remaining ones become
+.IR arguments ,
+and
+.B runlisp
+runs in
+.I script
+mode.
+.PP
+In
+.I script
+mode,
+.B runlisp
+reads the second line of the script file,
+and checks to see if it contains the string
+.RB ` @RUNLISP: '.
+If so, then the following text is parsed
+for
+.IR "embedded options" ,
+as follows.
+The text is split into words
+separated by sequences of whitespace characters.
+Whitespace,
+and other special characters,
+can be included in a word by
+.I quoting
+or
+.IR escaping .
+Text between single quotes
+.BR ' ... '
+is included literally, without any further interpretation;
+text between double quotes
+.BR """" ... """"
+is treated literally,
+except that escaping can still be used
+to escape (e.g.) double quotes and the escape character itself.
+Outside of single quotes, a backslash
+.RB ` \e '
+causes the following character to be included in a word
+regardless of its usual meaning.
+(None of this allows a newline character
+to be included in a word:
+this is simply not possible.)
+A word which is
+.RB ` \-\- '
+before processing quoting and escaping
+marks the end of embedded options.
+As a concession to Emacs users,
+if the sequence
+.RB ` \-*\- '
+appears at the start of a word
+before processing quoting and escaping,
+then everything up to and including the next occurrence of
+.RB ` \-*\- '
+is ignored.
+The resulting list of words
+is processed as if it held further command-line options.
+However,
+.B runlisp
+is now committed to
+.I script
+mode, so
+.RB ` \-e ',
+.RB ` \-l ',
+and
+.RB ` \-p '
+options may not appear in a script file's embedded options list.
+(This feature allows scripts to provide options even if they use
+.BR env (1)
+to find
+.B runlisp
+on the
+.BR PATH ,
+or to provide more than one option,
+since many operating systems pass the text following
+the interpreter name on a
+.RB ` #! '
+line as a single argument, without further splitting it at spaces.)
+.PP
+If a file named
+.B .runlisprc
+exists in the user's home directory,
+then this file is read to discover more options.
+(If the variable
+.B HOME
+is set in the environment,
+then its value is assumed to name the user's home directory;
+otherwise, the home directory is determined by looking up
+the process's real uid in the password database.)
+Lines consisting entirely of whitespace,
+and lines whose first whitespace character is either
+.RB ` # '
+or
+.RB ` ; '
+are ignored in this file.
+Other lines are split into words,
+and processed as additional command-line options,
+as described for embedded options above,
+except that:
+a
+.RB ` \-\- '
+marker does not terminate word splitting; and
+Emacs-style
+.RB ` \-*\- ... \-*\- '
+local variable lists are not ignored.
+Each line is processed separately,
+so an option and its argument must be written on the same line.
+By this point
+.B runlisp
+will have committed to
+.I script
+or
+.I eval
+mode,
+so
+.RB ` \-e ',
+.RB ` \-l ',
+and
+.RB ` \-p '
+options may not appear in a configuration file.
+.PP
+If a file
+.B runlisprc
+exists in the user's
+.I "configuration home"
+directory,
+then it is processed as for
+.B .runlisprc
+above.
+If a variable
+.B XDG_CONFIG_HOME
+is set in the environment,
+then its value is assumed to name the configuration home;
+otherwise, the configuration home is the directory
+.B .config
+in the user's home directory, as determined above.
+.PP
+If the variable
+.B RUNLISP_OPTIONS
+is set in the environment,
+then its value is split into words
+and processed as additional command-line options,
+as for a line of a configuration file as described above.
+.PP
+The list of
+.I "acceptable Lisp implementations"
+is determined.
+If any
+.RB ` \-L '
+options have been issued,
+then the list of acceptable implementations
+consists of all of the implementations mentioned in
+.RB ` -L '
+options
+in any of the places
+.B runlisp
+looked for options,
+in the order of their first occurrence.
+(If an implementation is named more than once,
+then
+.B runlisp
+prints a warning to stderr
+and ignores all but the first occurrence.)
+If no
+.RB ` \-L '
+option is given, then
+.B runlisp
+uses a default list,
+which consists of all of the supported Lisp implementations
+in an hardcoded order which reflects
+the author's arbitrary preferences.
+.PP
+The list of
+.I "preferred Lisp implementations"
+is determined.
+If any
+.RB ` \-P '
+options have been issued
+.I "since the last"
+.IB ` \-C '
+.IR "option" ,
+then the list of preferred implementations
+consists of all of the implementations mentioned in
+.RB ` \-P '
+options after the last occurrence of
+.RB ` \-C ',
+in the order of their first occurrences.
+(If an implementation is named more than once,
+then
+.B runlisp
+prints a warning to stderr
+and ignores all but the first occurrence.)
+If no
+.RB ` \-P '
+option is given,
+or a
+.RB ` \-C '
+option appears after all of the
+.RB ` \-P '
+options,
+then the list of preferred implementations is empty.
+.PP
+Acceptable Lisp implementations are tried in turn.
+First, the preferred implementations
+which are also listed as acceptable implementations
+are tried, in the order in which they appear
+in the preferred implementations list;
+then, the remaining acceptable implementations are tried
+in the order in which they appear
+in the acceptable implementations list.
+To
+.I try
+a Lisp implementation means to construct a command line
+(whose effect will be described below)
+and pass it to the
+.BR execvp (3)
+function.
+If that succeeds, the Lisp implementation runs;
+if it fails with
+.B ENOENT
+then other Lisp systems are tried;
+if it fails with some other error, then
+.B runlisp
+reports an error message to stderr
+and exits unsuccessfully
+(with code 127).
+If the
+.RB ` \-n '
+option was given, then
+.B runlisp
+just simulates the behaviour of
+.BR execvp (3),
+printing messages to stderr
+if the verbosity level is sufficiently high,
+and exits.
+.PP
+In
+.I script
+mode,
+the script is invoked.
+In
+.I eval
+mode,
+the instructions given in
+.RB ` \-e ',
+.RB ` \-l ',
+and
+.RB ` \-p '
+options are carried out,
+in the order in which the appeared in the command line.
+The details of the environment
+in which Lisp code is executed
+are described next.
+.
+.SS "Script environment"
+Code in scripts and forms invoked by
+.B runlisp
+may assume the following facts about their environment.
+.hP \*o
+The keyword
+.B :runlisp-script
+is added to the
+.B *features*
+list if
+.B runlisp
+is running in
+.I script
+mode.
+.hP \*o
+Most Lisp systems support a user initialization file
+which they load before entering the REPL;
+some also have a system initialization file.
+The
+.B runlisp
+program arranges
+.I not
+to read these files,
+so that the Lisp environment is reasonably predictable,
+and to avoid slowing down script startup
+with things which are convenient for use in an interactive session,
+but can't be relied upon by a script anyway.
+.hP \*o
+The Unix standard input, standard output, and standard error files
+are available through the Lisp
+.BR *standard-input* ,
+.BR *standard-output* ,
+and
+.BR *error-output*
+streams, respectively.
+.hP \*o
+Both
+.B *compile-verbose*
+and
+.B *load-verbose*
+are set to nil.
+On CMU\ CL,
+.B ext:*require-verbose*
+is also nil.
+Alas, this is insufficient to muffle noise while loading add-on systems
+on some implementations.
+.hP \*o
+If an error is signalled, and not caught by user code,
+then the process will print a message to stderr
+and exit with a nonzero status.
+The reported message may be a long, ugly backtrace,
+or a terse error report.
+If no error is signalled but not caught,
+then the process will exit with status 0.
+.hP \*o
+The initial package is
+.BR COMMON-LISP-USER ,
+which has no symbols `present' (i.e., imported or interned).
+.hP \*o
+The
+.B asdf
+and
+.B uiop
+systems are already loaded.
+Further systems can be loaded using
+.B asdf:load-system
+as usual.
+The script name
+(which is only meaningful if
+.B runlisp
+is in
+.I script
+mode, obviously)
+and arguments are available through the
+.B uiop:argv0
+function and
+.B uiop:*command-line-arguments*
+variable, respectively.
+.
+.\"--------------------------------------------------------------------------
+.
+.SH "BUGS"
+.hP \*o
+Loading ASDF systems is irritatingly noisy
+with some Lisp implementations.
+Suggestions for how to improve this are welcome.
+.hP \*o
+More Lisp implementations should be supported.
+I've supported the ones I have installed.
+I'm not willing to put a great deal of effort into supporting
+non-free Lisp implementations;
+but help supporting free Lisps is much appreciated.
+.hP \*o
+The protocol for passing the script name through to
+.B uiop
+(specifically, through the
+.B __CL_ARGV0
+environment variable)
+is terribly fragile,
+but supporting
+.B uiop
+is obviously a better approach than introducing a
+.BR runlisp -specific
+interface to the same information.
+I don't know how to fix this:
+suggestions are welcome.
+.
+.SH "SEE ALSO"
+.BR dump-runlisp-image (1).
+.
+.SH "AUTHOR"
+Mark Wooding, <mdw@distorted.org.uk>
+.
+.\"----- That's all, folks --------------------------------------------------
--- /dev/null
+/* -*-c-*-
+ *
+ * Invoke a Lisp script
+ *
+ * (c) 2020 Mark Wooding
+ */
+
+/*----- Licensing notice --------------------------------------------------*
+ *
+ * This file is part of Runlisp, a tool for invoking Common Lisp scripts.
+ *
+ * Runlisp is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 3 of the License, or (at your
+ * option) any later version.
+ *
+ * Runlisp is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Runlisp. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+/*----- Header files ------------------------------------------------------*/
+
+#include "config.h"
+
+#include <assert.h>
+#include <ctype.h>
+#include <errno.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <unistd.h>
+#include <sys/stat.h>
+
+#include <pwd.h>
+
+/*----- Common Lisp runes -------------------------------------------------*/
+
+/* A common preamble rune to do the necessary things.
+ *
+ * We need to ensure that `asdf' (and therefore `uiop') is loaded. And we
+ * should arrange for `:runlisp-script' to find its way into the `*features*'
+ * list so that scripts can notice that they're being invoked from the
+ * command line rather than loaded into a resident session, and actually do
+ * something useful.
+ */
+#define COMMON_PRELUDE_RUNE \
+ "(progn " \
+ "(setf *load-verbose* nil *compile-verbose* nil) " \
+ "(require \"asdf\") " \
+ "(funcall (intern \"REGISTER-IMMUTABLE-SYSTEM\" " \
+ "(find-package \"ASDF\")) " \
+ "\"asdf\") " \
+ "(set-dispatch-macro-character " \
+ "#\\# #\\! " \
+ "(lambda (#1=#:stream #2=#:char #3=#:arg) " \
+ "(declare (ignore #2# #3#)) " \
+ "(values (read-line #1#)))) " \
+ "(pushnew :runlisp-script *features*))"
+
+/* Get `uiop' to re-check the command-line arguments following an image
+ * restore.
+ */
+#define IMAGE_RESTORE_RUNE \
+ "(uiop:call-image-restore-hook)"
+
+/* Some Lisps leave crud in the `COMMON-LISP-USER' package. Clear it out. */
+#define CLEAR_CL_USER_RUNE \
+ "(let ((#4=#:pkg (find-package \"COMMON-LISP-USER\"))) " \
+ "(with-package-iterator (#5=#:next #4# :internal) " \
+ "(loop (multiple-value-bind (#6=#:anyp #7=#:sym #8=#:how) " \
+ "(#5#) " \
+ "(declare (ignore #8#)) " \
+ "(unless #6# (return)) " \
+ "(unintern #7# #4#)))))"
+
+/*----- Handy macros ------------------------------------------------------*/
+
+#define N(v) (sizeof(v)/sizeof((v)[0]))
+
+#if defined(__GNUC__)
+# define GCC_VERSION_P(maj, min) \
+ (__GNUC__ > (maj) || (__GNUC__ == (maj) && __GNUC_MINOR__ >= (min)))
+#else
+# define GCC_VERSION_P(maj, min) 0
+#endif
+
+#ifdef __clang__
+# define CLANG_VERSION_P(maj, min) \
+ (__clang_major__ > (maj) || (__clang_major__ == (maj) && \
+ __clang_minor__ >= (min)))
+#else
+# define CLANG_VERSION_P(maj, min) 0
+#endif
+
+#if GCC_VERSION_P(2, 5) || CLANG_VERSION_P(3, 3)
+# define NORETURN __attribute__((__noreturn__))
+# define PRINTF_LIKE(fix, aix) __attribute__((__format__(printf, fix, aix)))
+#endif
+
+#if GCC_VERSION_P(4, 0) || CLANG_VERSION_P(3, 3)
+# define EXECL_LIKE(ntrail) __attribute__((__sentinel__(ntrail)))
+#endif
+
+#define CTYPE_HACK(func, ch) (func((unsigned char)(ch)))
+#define ISSPACE(ch) CTYPE_HACK(isspace, ch)
+
+#define MEMCMP(x, op, y, n) (memcmp((x), (y), (n)) op 0)
+#define STRCMP(x, op, y) (strcmp((x), (y)) op 0)
+#define STRNCMP(x, op, y, n) (strncmp((x), (y), (n)) op 0)
+
+#define END ((const char *)0)
+
+/*----- The Lisp implementation table -------------------------------------*/
+
+/* The systems, in decreasing order of (not quite my personal) preference.
+ * This list is used to initialize various tables and constants.
+ */
+#define LISP_SYSTEMS(_) \
+ _(sbcl) \
+ _(ccl) \
+ _(clisp) \
+ _(ecl) \
+ _(cmucl) \
+ _(abcl)
+
+enum {
+#define DEFSYS(sys) sys##_INDEX,
+ LISP_SYSTEMS(DEFSYS)
+#undef DEFSYS
+ NSYS
+};
+
+enum {
+#define DEFFLAG(sys) sys##_FLAG = 1 << sys##_INDEX,
+ LISP_SYSTEMS(DEFFLAG)
+#undef DEFFLAG
+ ALL_SYSTEMS = 0
+#define SETFLAG(sys) | sys##_FLAG
+ LISP_SYSTEMS(SETFLAG)
+#undef SETFLAG
+};
+
+struct argstate;
+struct argv;
+
+#define DECLENTRY(sys) \
+static void run_##sys(struct argstate *, const char *);
+ LISP_SYSTEMS(DECLENTRY)
+#undef DECLENTRY
+
+static const struct systab {
+ const char *name;
+ unsigned f;
+ void (*run)(struct argstate *, const char *);
+} systab[] = {
+#define SYSENTRY(sys) { #sys, sys##_FLAG, run_##sys },
+ LISP_SYSTEMS(SYSENTRY)
+#undef SYSENTRY
+};
+
+/*----- Diagnostic utilities ----------------------------------------------*/
+
+static const char *progname = "runlisp";
+
+static void set_progname(const char *prog)
+{
+ const char *p;
+
+ p = strrchr(prog, '/');
+ progname = p ? p + 1 : progname;
+}
+
+static void vmoan(const char *msg, va_list ap)
+{
+ fprintf(stderr, "%s: ", progname);
+ vfprintf(stderr, msg, ap);
+ fputc('\n', stderr);
+}
+
+static PRINTF_LIKE(1, 2) void moan(const char *msg, ...)
+ { va_list ap; va_start(ap, msg); vmoan(msg, ap); va_end(ap); }
+
+static NORETURN PRINTF_LIKE(1, 2) void lose(const char *msg, ...)
+ { va_list ap; va_start(ap, msg); vmoan(msg, ap); va_end(ap); exit(127); }
+
+/*----- Memory allocation -------------------------------------------------*/
+
+static void *xmalloc(size_t n)
+{
+ void *p;
+
+ if (!n) return (0);
+ p = malloc(n); if (!p) lose("failed to allocate memory");
+ return (p);
+}
+
+static void *xrealloc(void *p, size_t n)
+{
+ if (!n) { free(p); return (0); }
+ else if (!p) return (xmalloc(n));
+ p = realloc(p, n); if (!p) lose("failed to allocate memory");
+ return (p);
+}
+
+static char *xstrdup(const char *p)
+{
+ size_t n = strlen(p) + 1;
+ char *q = xmalloc(n);
+
+ memcpy(q, p, n);
+ return (q);
+}
+
+/*----- Dynamic strings ---------------------------------------------------*/
+
+struct dstr {
+ char *p;
+ size_t len, sz;
+};
+#define DSTR_INIT { 0, 0, 0 }
+
+/*
+static void dstr_init(struct dstr *d) { d->p = 0; d->len = d->sz = 0; }
+*/
+
+static void dstr_reset(struct dstr *d) { d->len = 0; }
+
+static void dstr_ensure(struct dstr *d, size_t n)
+{
+ size_t need = d->len + n, newsz;
+
+ if (need <= d->sz) return;
+ newsz = d->sz ? 2*d->sz : 16;
+ while (newsz < need) newsz *= 2;
+ d->p = xrealloc(d->p, newsz); d->sz = newsz;
+}
+
+static void dstr_release(struct dstr *d) { free(d->p); }
+
+static void dstr_putm(struct dstr *d, const void *p, size_t n)
+ { dstr_ensure(d, n); memcpy(d->p + d->len, p, n); d->len += n; }
+
+static void dstr_puts(struct dstr *d, const char *p)
+{
+ size_t n = strlen(p);
+
+ dstr_ensure(d, n + 1);
+ memcpy(d->p + d->len, p, n + 1);
+ d->len += n;
+}
+
+static void dstr_putc(struct dstr *d, int ch)
+ { dstr_ensure(d, 1); d->p[d->len++] = ch; }
+
+static void dstr_putz(struct dstr *d)
+ { dstr_ensure(d, 1); d->p[d->len] = 0; }
+
+static int dstr_readline(struct dstr *d, FILE *fp)
+{
+ size_t n;
+ int any = 0;
+
+ for (;;) {
+ dstr_ensure(d, 2);
+ if (!fgets(d->p + d->len, d->sz - d->len, fp)) break;
+ n = strlen(d->p + d->len); assert(n > 0); any = 1;
+ d->len += n;
+ if (d->p[d->len - 1] == '\n') { d->p[--d->len] = 0; break; }
+ }
+
+ if (!any) return (-1);
+ else return (0);
+}
+/*----- Dynamic vectors of strings ----------------------------------------*/
+
+struct argv {
+ const char **v;
+ size_t o, n, sz;
+};
+#define ARGV_INIT { 0, 0, 0, 0 }
+
+/*
+static void argv_init(struct argv *av)
+ { av->v = 0; av->o = av->n = av->sz = 0; }
+*/
+
+/*
+static void argv_reset(struct argv *av) { av->o = av->n = 0; }
+*/
+
+static void argv_ensure(struct argv *av, size_t n)
+{
+ size_t need = av->n + av->o + n, newsz;
+
+ if (need <= av->sz) return;
+ newsz = av->sz ? 2*av->sz : 8;
+ while (newsz < need) newsz *= 2;
+ av->v = xrealloc(av->v, newsz*sizeof(const char *)); av->sz = newsz;
+}
+
+static void argv_ensure_offset(struct argv *av, size_t n)
+{
+ size_t newoff;
+
+ /* Stupid version. We won't, in practice, be prepending lots of stuff, so
+ * avoid the extra bookkeeping involved in trying to make a double-ended
+ * extendable array asymptotically efficient.
+ */
+ if (av->o >= n) return;
+ newoff = 16;
+ while (newoff < n) newoff *= 2;
+ argv_ensure(av, newoff - av->o);
+ memmove(av->v + newoff, av->v + av->o, av->n*sizeof(const char *));
+ av->o = newoff;
+}
+
+static void argv_release(struct argv *av) { free(av->v); }
+
+static void argv_append(struct argv *av, const char *p)
+ { argv_ensure(av, 1); av->v[av->n++ + av->o] = p; }
+
+static void argv_appendz(struct argv *av)
+ { argv_ensure(av, 1); av->v[av->n + av->o] = 0; }
+
+static void argv_appendn(struct argv *av, const char *const *v, size_t n)
+{
+ argv_ensure(av, n);
+ memcpy(av->v + av->n + av->o, v, n*sizeof(const char *));
+ av->n += n;
+}
+
+/*
+static void argv_appendav(struct argv *av, const struct argv *bv)
+ { argv_appendn(av, bv->v + bv->o, bv->n); }
+*/
+
+/*
+static void argv_appendv(struct argv *av, va_list ap)
+{
+ const char *p;
+
+ for (;;)
+ { p = va_arg(ap, const char *); if (!p) break; argv_append(av, p); }
+}
+*/
+
+/*
+static EXECL_LIKE(0) void argv_appendl(struct argv *av, ...)
+ { va_list ap; va_start(ap, av); argv_appendv(av, ap); va_end(ap); }
+*/
+
+static void argv_prepend(struct argv *av, const char *p)
+ { argv_ensure_offset(av, 1); av->v[--av->o] = p; av->n++; }
+
+/*
+static void argv_prependn(struct argv *av, const char *const *v, size_t n)
+{
+ argv_ensure_offset(av, 1);
+ av->o -= n; av->n += n;
+ memcpy(av->v + av->o, v, n*sizeof(const char *));
+}
+*/
+
+/*
+static void argv_prependav(struct argv *av, const struct argv *bv)
+ { argv_prependn(av, bv->v + bv->o, bv->n); }
+*/
+
+static void argv_prependv(struct argv *av, va_list ap)
+{
+ const char *p, **v;
+ size_t n = 0;
+
+ for (;;) {
+ p = va_arg(ap, const char *); if (!p) break;
+ argv_prepend(av, p); n++;
+ }
+ v = av->v + av->o;
+ while (n >= 2) {
+ p = v[0]; v[0] = v[n - 1]; v[n - 1] = p;
+ v++; n -= 2;
+ }
+}
+
+static EXECL_LIKE(0) void argv_prependl(struct argv *av, ...)
+ { va_list ap; va_start(ap, av); argv_prependv(av, ap); va_end(ap); }
+
+/*----- Lisp system table (redux) -----------------------------------------*/
+
+static const struct systab *find_system(const char *name)
+{
+ const struct systab *sys;
+ size_t i;
+
+ for (i = 0; i < NSYS; i++) {
+ sys = &systab[i];
+ if (STRCMP(name, ==, sys->name)) return (sys);
+ }
+ lose("unknown Lisp system `%s'", name);
+}
+
+static void lisp_quote_string(struct dstr *d, const char *p)
+{
+ size_t n;
+
+ for (;;) {
+ n = strcspn(p, "\"\\");
+ if (n) { dstr_putm(d, p, n); p += n; }
+ if (!*p) break;
+ dstr_putc(d, '\\'); dstr_putc(d, *p++);
+ }
+ dstr_putz(d);
+}
+
+static const char *expand_rune(struct dstr *d, const char *rune, ...)
+{
+ const struct argv *av;
+ va_list ap;
+ size_t i, n;
+
+ va_start(ap, rune);
+ for (;;) {
+ n = strcspn(rune, "%");
+ if (n) { dstr_putm(d, rune, n); rune += n; }
+ if (!*rune) break;
+ switch (*++rune) {
+ case '%': dstr_putc(d, '%'); break;
+ case 'e': lisp_quote_string(d, va_arg(ap, const char *)); break;
+ case 'E':
+ av = va_arg(ap, const struct argv *);
+ for (i = 0; i < av->n; i++) {
+ if (i) dstr_putc(d, ' ');
+ dstr_putc(d, '"');
+ lisp_quote_string(d, av->v[i]);
+ dstr_putc(d, '"');
+ }
+ break;
+ default: lose("*** BUG unknown expansion `%%%c'", *rune);
+ }
+ rune++;
+ }
+ dstr_putz(d);
+ return (d->p);
+}
+
+/*----- Argument processing -----------------------------------------------*/
+
+struct syslist {
+ const struct systab *sys[NSYS];
+ size_t n;
+ unsigned f;
+};
+#define SYSLIST_INIT { { 0 }, 0, 0 }
+
+struct argstate {
+ unsigned f;
+#define F_BOGUS 1u
+#define F_NOEMBED 2u
+#define F_NOACT 4u
+#define F_NODUMP 8u
+#define F_AUX 16u
+ int verbose;
+ char *imagedir;
+ struct syslist allow, pref;
+ struct argv av;
+};
+#define ARGSTATE_INIT { 0, 1, 0, SYSLIST_INIT, SYSLIST_INIT, ARGV_INIT }
+
+/*----- Running programs --------------------------------------------------*/
+
+#define FEF_EXEC 1u
+static int file_exists_p(const struct argstate *arg, const char *path,
+ unsigned f)
+{
+ struct stat st;
+
+ if (stat(path, &st)) {
+ if (arg && arg->verbose > 2) moan("file `%s' not found", path);
+ return (0);
+ } else if (!(S_ISREG(st.st_mode))) {
+ if (arg && arg->verbose > 2) moan("`%s' is not a regular file", path);
+ return (0);
+ } else if ((f&FEF_EXEC) && access(path, X_OK)) {
+ if (arg && arg->verbose > 2) moan("file `%s' is not executable", path);
+ return (0);
+ } else {
+ if (arg && arg->verbose > 2) moan("found file `%s'", path);
+ return (1);
+ }
+}
+
+static int found_in_path_p(const struct argstate *arg, const char *prog)
+{
+ struct dstr p = DSTR_INIT, d = DSTR_INIT;
+ const char *path;
+ char *q;
+ size_t n, avail, proglen;
+ int i;
+
+ if (strchr(prog, '/')) return (file_exists_p(arg, prog, 0));
+ path = getenv("PATH");
+ if (path)
+ dstr_puts(&p, path);
+ else {
+ dstr_puts(&p, ".:");
+ i = 0;
+ again:
+ avail = p.sz - p.len;
+ n = confstr(_CS_PATH, p.p + p.len, avail);
+ if (avail > n) { i++; assert(i < 2); dstr_ensure(&p, n); goto again; }
+ }
+
+ q = p.p; proglen = strlen(prog);
+ for (;;) {
+ n = strcspn(q, ":");
+ dstr_reset(&d);
+ if (q[n]) dstr_putm(&d, q, n);
+ else dstr_putc(&d, '.');
+ dstr_putc(&d, '/');
+ dstr_putm(&d, prog, proglen);
+ dstr_putz(&d);
+ if (file_exists_p(arg, d.p, FEF_EXEC)) {
+ if (arg->verbose == 2) moan("found program `%s'", d.p);
+ return (1);
+ }
+ q += n; if (!*q) break; else q++;
+ }
+ return (0);
+}
+
+static void try_exec(const struct argstate *arg, struct argv *av)
+{
+ struct dstr d = DSTR_INIT;
+ size_t i;
+
+ assert(av->n); argv_appendz(av);
+ if (arg->verbose > 1) {
+ for (i = 0; i < av->n; i++) {
+ if (i) { dstr_putc(&d, ','); dstr_putc(&d, ' '); }
+ dstr_putc(&d, '"');
+ lisp_quote_string(&d, av->v[av->o + i]);
+ dstr_putc(&d, '"');
+ }
+ dstr_putz(&d);
+ moan("trying %s...", d.p);
+ }
+ if (arg->f&F_NOACT)
+ { if (found_in_path_p(arg, av->v[av->o])) exit(0); }
+ else {
+ execvp(av->v[av->o], (/*unconst*/ char **)av->v + av->o);
+ if (errno != ENOENT)
+ lose("failed to exec `%s': %s", av->v[av->o], strerror(errno));
+ }
+ if (arg->verbose > 1) moan("`%s' not found", av->v[av->o]);
+ dstr_release(&d);
+}
+
+static const char *getenv_or_default(const char *var, const char *dflt)
+ { const char *p = getenv(var); return (p ? p : dflt); }
+
+/*----- Invoking Lisp systems ---------------------------------------------*/
+
+/* Steel Bank Common Lisp. */
+
+static void run_sbcl(struct argstate *arg, const char *script)
+{
+ struct dstr d = DSTR_INIT;
+
+ argv_prependl(&arg->av, "--script", script, END);
+
+ dstr_puts(&d, arg->imagedir);
+ dstr_putc(&d, '/');
+ dstr_puts(&d, "sbcl+asdf.core");
+ if (!(arg->f&F_NODUMP) && file_exists_p(arg, d.p, 0))
+ argv_prependl(&arg->av,
+ "--core", d.p,
+ "--eval", IMAGE_RESTORE_RUNE,
+ END);
+ else
+ argv_prependl(&arg->av, "--eval", COMMON_PRELUDE_RUNE, END);
+
+ argv_prependl(&arg->av, getenv_or_default("SBCL", "sbcl"),
+ "--noinform",
+ END);
+ try_exec(arg, &arg->av);
+ dstr_release(&d);
+}
+
+/* Clozure Common Lisp. */
+
+#define CCL_QUIT_RUNE \
+ "(ccl:quit)"
+
+static void run_ccl(struct argstate *arg, const char *script)
+{
+ struct dstr d = DSTR_INIT;
+
+ argv_prependl(&arg->av, "-b", "-n", "-Q",
+ "-l", script,
+ "-e", CCL_QUIT_RUNE,
+ "--",
+ END);
+
+ dstr_puts(&d, arg->imagedir);
+ dstr_putc(&d, '/');
+ dstr_puts(&d, "ccl+asdf.image");
+ if (!(arg->f&F_NODUMP) && file_exists_p(arg, d.p, 0))
+ argv_prependl(&arg->av, "-I", d.p, "-e", IMAGE_RESTORE_RUNE, END);
+ else
+ argv_prependl(&arg->av, "-e", COMMON_PRELUDE_RUNE, END);
+
+ argv_prepend(&arg->av, getenv_or_default("CCL", "ccl"));
+ try_exec(arg, &arg->av);
+ dstr_release(&d);
+}
+
+/* GNU CLisp.
+ *
+ * CLisp causes much sadness. Superficially, it's the most sensible of all
+ * of the systems supported here: you just run `clisp SCRIPT -- ARGS ...' and
+ * it works.
+ *
+ * The problems come when you want to do some preparatory work (e.g., load
+ * `asdf') and then run the script. There's a `-x' option to evaluate some
+ * Lisp code, but it has three major deficiencies.
+ *
+ * * It insists on printing the values of the forms it evaluates. It
+ * prints a blank line even if the form goes out of its way to produce no
+ * values at all. So the whole thing has to be a single top-level form
+ * which quits the Lisp rather than returning.
+ *
+ * * For some idiotic reason, you can have /either/ `-x' forms /or/ a
+ * script, but not both. So we have to include the `load' here
+ * explicitly. I suppose that was inevitable because we have to inhibit
+ * printing of the result forms, but it's still a separate source of
+ * annoyance.
+ *
+ * * The icing on the cake: the `-x' forms are collectively concatenated --
+ * without spaces! -- and used to build a string stream, which is then
+ * assigned over the top of `*standard-input*', making the original stdin
+ * somewhat fiddly to track down.
+ *
+ * There's an `-i' option which will load a file without any of this
+ * stupidity, but nothing analogous for immediate expressions.
+ */
+
+#define CLISP_COMMON_STARTUP_RUNES \
+ "(setf *standard-input* (ext:make-stream :input)) " \
+ "(load \"%e\" :verbose nil :print nil) " \
+ "(ext:quit)"
+
+#define CLISP_STARTUP_RUNE \
+ "(progn " \
+ COMMON_PRELUDE_RUNE " " \
+ CLISP_COMMON_STARTUP_RUNES ")"
+
+#define CLISP_STARTUP_IMAGE_RUNE \
+ "(progn " \
+ IMAGE_RESTORE_RUNE " " \
+ CLISP_COMMON_STARTUP_RUNES ")"
+
+static void run_clisp(struct argstate *arg, const char *script)
+{
+ struct dstr d = DSTR_INIT, dd = DSTR_INIT;
+
+ dstr_puts(&d, arg->imagedir);
+ dstr_putc(&d, '/');
+ dstr_puts(&d, "clisp+asdf.mem");
+ if (!(arg->f&F_NODUMP) && file_exists_p(arg, d.p, 0))
+ argv_prependl(&arg->av, "-M", d.p, "-q",
+ "-x", expand_rune(&dd, CLISP_STARTUP_IMAGE_RUNE, script),
+ "--",
+ END);
+ else
+ argv_prependl(&arg->av, "-norc", "-q",
+ "-x", expand_rune(&dd, CLISP_STARTUP_RUNE, script),
+ "--",
+ END);
+
+ argv_prepend(&arg->av, getenv_or_default("CLISP", "clisp"));
+ try_exec(arg, &arg->av);
+ dstr_release(&d);
+ dstr_release(&dd);
+
+#undef f
+}
+
+/* Embeddable Common Lisp. *
+ *
+ * ECL is changing its command-line option syntax in version 16. I have no
+ * idea why they think the result can ever be worth the pain of a transition.
+ */
+
+#if ECL_OPTIONS_GNU
+# define ECLOPT "--"
+#else
+# define ECLOPT "-"
+#endif
+
+#define ECL_STARTUP_RUNE \
+ "(progn " \
+ COMMON_PRELUDE_RUNE " " \
+ CLEAR_CL_USER_RUNE ")"
+
+static void run_ecl(struct argstate *arg, const char *script)
+{
+ struct dstr d = DSTR_INIT;
+
+ dstr_puts(&d, arg->imagedir);
+ dstr_putc(&d, '/');
+ dstr_puts(&d, "ecl+asdf");
+ if (!(arg->f&F_NODUMP) && file_exists_p(arg, d.p, FEF_EXEC)) {
+ argv_prependl(&arg->av, "-s", script, "--", END);
+ argv_prependl(&arg->av, d.p, END);
+ } else {
+ argv_prependl(&arg->av, ECLOPT "shell", script, "--", END);
+ argv_prependl(&arg->av, getenv_or_default("ECL", "ecl"), ECLOPT "norc",
+ ECLOPT "eval", ECL_STARTUP_RUNE,
+ END);
+ }
+ try_exec(arg, &arg->av);
+}
+
+/* Carnegie--Mellon University Common Lisp. */
+
+#define CMUCL_STARTUP_RUNE \
+ "(progn " \
+ "(setf ext:*require-verbose* nil) " \
+ COMMON_PRELUDE_RUNE ")"
+#define CMUCL_QUIT_RUNE \
+ "(ext:quit)"
+
+static void run_cmucl(struct argstate *arg, const char *script)
+{
+ struct dstr d = DSTR_INIT;
+
+ argv_prependl(&arg->av,
+ "-load", script,
+ "-eval", CMUCL_QUIT_RUNE,
+ "--",
+ END);
+
+ dstr_puts(&d, arg->imagedir);
+ dstr_putc(&d, '/');
+ dstr_puts(&d, "cmucl+asdf.core");
+ if (!(arg->f&F_NODUMP) && file_exists_p(arg, d.p, 0))
+ argv_prependl(&arg->av, "-core", d.p, "-eval", IMAGE_RESTORE_RUNE, END);
+ else
+ argv_prependl(&arg->av, "-batch", "-noinit", "-nositeinit", "-quiet",
+ "-eval", CMUCL_STARTUP_RUNE,
+ END);
+
+ argv_prepend(&arg->av, getenv_or_default("CMUCL", "cmucl"));
+ try_exec(arg, &arg->av);
+ dstr_release(&d);
+}
+
+/* Armed Bear Common Lisp. *
+ *
+ * CLisp made a worthy effort, but ABCL still manages to take the price.
+ *
+ * * ABCL manages to avoid touching the `stderr' stream at all, ever. Its
+ * startup machinery finds `stdout' (as `java.lang.System.out'), wraps it
+ * up in a Lisp stream, and uses the result as `*standard-output*' and
+ * `*error-output*' (and a goodly number of other things too). So we
+ * must manufacture a working `stderr' the hard way.
+ *
+ * * There doesn't appear to be any easy way to prevent toplevel errors
+ * from invoking the interactive debugger. For extra fun, the debugger
+ * reads from `stdin' by default, so an input file which somehow manages
+ * to break the script can then take over its brain by providing Lisp
+ * forms for the debugger to evaluate.
+ */
+
+#define ABCL_STARTUP_RUNE \
+ "(let ((#9=#:script \"%e\")) " \
+ COMMON_PRELUDE_RUNE " " \
+ CLEAR_CL_USER_RUNE " " \
+ \
+ /* Replace the broken `*error-output*' stream with a working \
+ * copy of `stderr'. \
+ */ \
+ "(setf *error-output* " \
+ "(java:jnew \"org.armedbear.lisp.Stream\" " \
+ "'sys::system-stream " \
+ "(java:jfield \"java.lang.System\" \"err\") " \
+ "'character " \
+ "java:+true+)) " \
+ \
+ /* Trap errors signalled by the script and arrange for them \
+ * to actually kill the process rather than ending up in the \
+ * interactive debugger. \
+ */ \
+ "(handler-case (load #9# :verbose nil :print nil) " \
+ "(error (error) " \
+ "(format *error-output* \"~A (unhandled error): ~A~%%\" " \
+ "#9# error) " \
+ "(ext:quit :status 255))))"
+
+static void run_abcl(struct argstate *arg, const char *script)
+{
+ struct dstr d = DSTR_INIT;
+
+ argv_prependl(&arg->av, getenv_or_default("ABCL", "abcl"),
+ "--batch", "--noinform", "--noinit", "--nosystem",
+ "--eval", expand_rune(&d, ABCL_STARTUP_RUNE, script),
+ "--",
+ END);
+ try_exec(arg, &arg->av);
+ dstr_release(&d);
+}
+
+/*----- Main code ---------------------------------------------------------*/
+
+static void version(FILE *fp)
+ { fprintf(fp, "%s, version %s\n", progname, PACKAGE_VERSION); }
+
+static void usage(FILE *fp)
+{
+ fprintf(fp, "usage: %s [-CDEnqv] [-I IMAGEDIR] "
+ "[-L SYS,SYS,...] [-P SYS,SYS,...]\n"
+ "\t[--] SCRIPT [ARGUMENTS ...] |\n"
+ "\t[-e EXPR] [-p EXPR] [-l FILE] [--] [ARGUMENTS ...]\n",
+ progname);
+}
+
+static void help(FILE *fp)
+{
+ version(fp); fputc('\n', fp); usage(fp);
+ fputs("\n\
+Options:\n\
+ --help Show this help text and exit successfully.\n\
+ --version Show the version number and exit successfully.\n\
+ -C Clear the list of preferred Lisp systems.\n\
+ -D Run system Lisp images, rather than custom images.\n\
+ -E Don't read embedded options from the script.\n\
+ -I IMAGEDIR Look for custom images in IMAGEDIR rather than\n\
+ `" IMAGEDIR "'.\n\
+ -L SYS,SYS,... Only use the listed Lisp systems.the script.\n\
+ -P SYS,SYS,... Prefer the listed Lisp systems.\n\
+ -e EXPR Evaluate EXPR (can be repeated).\n\
+ -l FILE Load FILE (can be repeated).\n\
+ -n Don't actually run the script (useful with `-v')\n\
+ -p EXPR Print (`prin1') EXPR (can be repeated).\n\
+ -q Don't print warning messages.\n\
+ -v Print informational messages (repeat for even more).\n",
+ fp);
+}
+
+/* Parse a comma-separated list of system names SPEC, and add the named
+ * systems to LIST.
+ */
+static void parse_syslist(const char *spec, const struct argstate *arg,
+ struct syslist *list, const char *what)
+{
+ char *copy = xstrdup(spec), *p = copy, *q;
+ const struct systab *sys;
+ size_t n;
+
+ for (;;) {
+ n = strcspn(p, ",");
+ if (p[n]) q = p + n + 1;
+ else q = 0;
+ p[n] = 0; sys = find_system(p);
+ if (list->f&sys->f) {
+ if (arg->verbose > 0)
+ moan("ignoring duplicate system `%s' in %s list", p, what);
+ } else {
+ list->sys[list->n++] = sys;
+ list->f |= sys->f;
+ }
+ if (!q) break;
+ p = q;
+ }
+ free(copy);
+}
+
+static void push_eval_op(struct argstate *arg, char op, const char *val)
+{
+ char *p;
+ size_t n;
+
+ if (arg->f&F_AUX) {
+ moan("must use `-e', `-p', or `-l' on command line");
+ arg->f |= F_BOGUS;
+ return;
+ }
+
+ n = strlen(val) + 1;
+ p = xmalloc(n + 1);
+ p[0] = op; memcpy(p + 1, val, n);
+ argv_append(&arg->av, p);
+}
+
+/* Parse a vector ARGS of command-line arguments. Update ARG with the
+ * results. NARG is the number of arguments, and *I_INOUT is the current
+ * index into the vector, to be updated on exit to identify the first
+ * non-option argument (or the end of the vector).
+ */
+static void parse_arguments(struct argstate *arg, const char *const *args,
+ size_t nargs, size_t *i_inout)
+{
+ const char *o, *a;
+ char opt;
+
+ for (;;) {
+ if (*i_inout >= nargs) break;
+ o = args[*i_inout];
+ if (STRCMP(o, ==, "--help")) { help(stdout); exit(0); }
+ else if (STRCMP(o, ==, "--version")) { version(stdout); exit(0); }
+ if (!*o || *o != '-' || !o[1]) break;
+ (*i_inout)++;
+ if (STRCMP(o, ==, "--")) break;
+ o++;
+ while (o && *o) {
+ opt = *o++;
+ switch (opt) {
+
+#define GETARG do { \
+ if (*o) \
+ { a = o; o = 0; } \
+ else { \
+ if (*i_inout >= nargs) goto noarg; \
+ a = args[(*i_inout)++]; \
+ } \
+} while (0)
+
+ case 'C': arg->pref.n = 0; arg->pref.f = 0; break;
+ case 'D': arg->f |= F_NODUMP; break;
+ case 'E': arg->f |= F_NOEMBED; break;
+ case 'e': GETARG; push_eval_op(arg, '!', a); break;
+ case 'p': GETARG; push_eval_op(arg, '?', a); break;
+ case 'l': GETARG; push_eval_op(arg, '<', a); break;
+ case 'n': arg->f |= F_NOACT; break;
+ case 'q': if (arg->verbose) arg->verbose--; break;
+ case 'v': arg->verbose++; break;
+
+ case 'I':
+ free(arg->imagedir);
+ GETARG; arg->imagedir = xstrdup(a);
+ break;
+
+ case 'L':
+ GETARG;
+ parse_syslist(a, arg, &arg->allow, "allowed");
+ break;
+
+ case 'P':
+ GETARG;
+ parse_syslist(a, arg, &arg->pref, "preferred");
+ break;
+
+ default:
+ moan("unknown option `%c'", opt);
+ arg->f |= F_BOGUS;
+ break;
+
+#undef GETARG
+
+ }
+ }
+ }
+ goto end;
+
+noarg:
+ moan("missing argument for `-%c'", opt);
+ arg->f |= F_BOGUS;
+end:
+ return;
+}
+
+/* Parse a string P into words (destructively), and process them as
+ * command-line options, updating ARG. Non-option arguments are not
+ * permitted. If `SOSF_EMACS' is set in FLAGS, then ignore `-*- ... -*-'
+ * editor turds. If `SOSF_ENDOK' is set, then accept `--' and ignore
+ * whatever comes after; otherwise, reject all positional arguments.
+ */
+#define SOSF_EMACS 1u
+#define SOSF_ENDOK 2u
+static void scan_options_from_string(char *p, struct argstate *arg,
+ unsigned flags,
+ const char *what, const char *file)
+{
+ struct argv av = ARGV_INIT;
+ char *q;
+ size_t i;
+ int st = 0;
+ unsigned f = 0;
+#define f_escape 1u
+
+ for (;;) {
+ while (ISSPACE(*p)) p++;
+ if (!*p) break;
+ if ((flags&SOSF_EMACS) && p[0] == '-' && p[1] == '*' && p[2] == '-') {
+ p = strstr(p + 3, "-*-");
+ if (!p) lose("unfinished local-variables list in %s `%s'", what, file);
+ p += 3; continue;
+ }
+ if ((flags&SOSF_ENDOK) &&
+ p[0] == '-' && p[1] == '-' && (!p[2] || ISSPACE(p[2])))
+ break;
+ argv_append(&av, p); q = p;
+ for (;;) {
+ if (!*p) break;
+ else if (f&f_escape) { *q++ = *p; f &= ~f_escape; }
+ else if (st && *p == st) st = 0;
+ else if (st != '\'' && *p == '\\') f |= f_escape;
+ else if (!st && (*p == '"' || *p == '\'')) st = *p;
+ else if (!st && ISSPACE(*p)) break;
+ else *q++ = *p;
+ p++;
+ }
+ if (*p) p++;
+ *q = 0;
+ if (f&f_escape) lose("unfinished escape in %s `%s'", what, file);
+ if (st) lose("unfinished `%c' string in %s `%s'", st, what, file);
+ }
+
+ i = 0; parse_arguments(arg, av.v, av.n, &i);
+ if (i < av.n)
+ lose("positional argument `%s' in %s `%s'", av.v[i], what, file);
+ argv_release(&av);
+
+#undef f_escape
+}
+
+/* Read SCRIPT, and check for a `@RUNLISP:' marker in the second line. If
+ * there is one, parse options from it, and update ARG.
+ */
+static void check_for_embedded_args(const char *script, struct argstate *arg)
+{
+ struct dstr d = DSTR_INIT;
+ char *p;
+ FILE *fp = 0;
+
+ fp = fopen(script, "r");
+ if (!fp) lose("can't read script `%s': %s", script, strerror(errno));
+
+ if (dstr_readline(&d, fp)) goto end;
+ dstr_reset(&d); if (dstr_readline(&d, fp)) goto end;
+
+ p = strstr(d.p, "@RUNLISP:");
+ if (p)
+ scan_options_from_string(p + 9, arg, SOSF_EMACS | SOSF_ENDOK,
+ "embedded options in script", script);
+
+end:
+ if (fp) {
+ if (ferror(fp))
+ lose("error reading script `%s': %s", script, strerror(errno));
+ fclose(fp);
+ }
+ dstr_release(&d);
+}
+
+/* Read the file PATH (if it exists) and update ARG with the arguments parsed
+ * from it. Ignore blank lines and (Unix- or Lisp-style) comments.
+ */
+static void read_config_file(const char *path, struct argstate *arg)
+{
+ FILE *fp = 0;
+ struct dstr d = DSTR_INIT;
+ char *p;
+
+ fp = fopen(path, "r");
+ if (!fp) {
+ if (errno == ENOENT) {
+ if (arg->verbose > 2)
+ moan("ignoring nonexistent configuration file `%s'", path);
+ goto end;
+ }
+ lose("failed to open configuration file `%s': %s",
+ path, strerror(errno));
+ }
+ if (arg->verbose > 1)
+ moan("reading configuration file `%s'", path);
+ for (;;) {
+ dstr_reset(&d);
+ if (dstr_readline(&d, fp)) break;
+ p = d.p;
+ while (ISSPACE(*p)) p++;
+ if (!*p || *p == ';' || *p == '#') continue;
+ scan_options_from_string(p, arg, 0, "configuration file `%s'", path);
+ }
+ if (arg->f&F_BOGUS)
+ lose("invalid options in configuration file `%s'", path);
+
+end:
+ if (fp) {
+ if (ferror(fp))
+ lose("error reading configuration file `%s': %s",
+ path, strerror(errno));
+ fclose(fp);
+ }
+ dstr_release(&d);
+}
+
+int main(int argc, char *argv[])
+{
+ struct dstr d = DSTR_INIT;
+ const char *script, *p;
+ const char *home;
+ struct passwd *pw;
+ char *t;
+ size_t i, n;
+ struct argstate arg = ARGSTATE_INIT;
+
+ /* Scan the command line. This gets low priority, since it's probably
+ * from the script shebang.
+ */
+ set_progname(argv[0]); i = 1;
+ parse_arguments(&arg, (const char *const *)argv, argc, &i);
+ arg.f |= F_AUX;
+ if ((i >= argc && !arg.av.n) || (arg.f&F_BOGUS))
+ { usage(stderr); exit(255); }
+
+ /* Prepare the argument vector. Keep track of the number of arguments
+ * here: we'll need to refer to this later.
+ */
+ if (!arg.av.n) {
+ script = argv[i++];
+ if (!(arg.f&F_NOEMBED)) check_for_embedded_args(script, &arg);
+ if (arg.f&F_BOGUS)
+ lose("invalid options in `%s' embedded option list", script);
+ } else {
+ script = getenv("RUNLISP_EVAL");
+ if (!script) script = DATADIR "/eval.lisp";
+ argv_append(&arg.av, "--");
+ }
+ argv_appendn(&arg.av, (const char *const *)argv + i, argc - i);
+ n = arg.av.n;
+
+ /* Find the user's home directory. (Believe them if they set something
+ * strange.)
+ */
+ home = getenv("HOME");
+ if (!home) {
+ pw = getpwuid(getuid());
+ if (!pw) lose("can't find user in password database");
+ home = pw->pw_dir;
+ }
+
+ /* Check user configuration file `~/.runlisprc'. */
+ dstr_reset(&d);
+ dstr_puts(&d, home); dstr_putc(&d, '/'); dstr_puts(&d, ".runlisprc");
+ read_config_file(d.p, &arg);
+
+ /* Check user configuration file `~/.config/runlisprc'. */
+ dstr_reset(&d);
+ p = getenv("XDG_CONFIG_HOME");
+ if (p)
+ dstr_puts(&d, p);
+ else
+ { dstr_puts(&d, home); dstr_putc(&d, '/'); dstr_puts(&d, ".config"); }
+ dstr_putc(&d, '/'); dstr_puts(&d, "runlisprc");
+ read_config_file(d.p, &arg);
+
+ /* Finally, check the environment variables. */
+ p = getenv("RUNLISP_OPTIONS");
+ if (p) {
+ t = xstrdup(p);
+ scan_options_from_string(t, &arg, 0,
+ "environment variable", "RUNLISP_OPTIONS");
+ free(t);
+ }
+ if (arg.f&F_BOGUS)
+ lose("invalid options in environment variable `RUNLISP_OPTIONS'");
+ if (!arg.imagedir) {
+ arg.imagedir = getenv("RUNLISP_IMAGEDIR");
+ if (!arg.imagedir) arg.imagedir = IMAGEDIR;
+ }
+
+ /* If no systems are listed as acceptable, try them all. */
+ if (!arg.allow.n) {
+ if (arg.verbose > 1)
+ moan("no explicitly allowed implementations: allowing all");
+ for (i = 0; i < NSYS; i++) arg.allow.sys[i] = &systab[i];
+ arg.allow.n = NSYS; arg.allow.f = (1u << NSYS) - 1;
+ }
+
+ /* Print what we're going to do. */
+ if (arg.verbose > 2) {
+ dstr_reset(&d); p = "";
+ for (i = 0; i < arg.allow.n; i++)
+ { dstr_puts(&d, p); p = ", "; dstr_puts(&d, arg.allow.sys[i]->name); }
+ moan("permitted Lisps: %s", d.p);
+
+ dstr_reset(&d); p = "";
+ for (i = 0; i < arg.pref.n; i++)
+ { dstr_puts(&d, p); p = ", "; dstr_puts(&d, arg.pref.sys[i]->name); }
+ moan("preferred Lisps: %s", d.p);
+
+ dstr_reset(&d); p = "";
+ for (i = 0; i < arg.pref.n; i++)
+ if (arg.pref.sys[i]->f&arg.allow.f)
+ { dstr_puts(&d, p); p = ", "; dstr_puts(&d, arg.pref.sys[i]->name); }
+ for (i = 0; i < arg.allow.n; i++)
+ if (!(arg.allow.sys[i]->f&arg.pref.f))
+ { dstr_puts(&d, p); p = ", "; dstr_puts(&d, arg.allow.sys[i]->name); }
+ moan("overall preference order: %s", d.p);
+ }
+
+ /* Inform `uiop' of the script name.
+ *
+ * As an aside, this is a terrible interface. It's too easy to forget to
+ * set it. (To illustrate this, `cl-launch -x' indeed forgets to set it.)
+ * If you're lucky, the script just thinks that its argument is `nil', in
+ * which case maybe it can use `*load-pathname*' as a fallback. If you're
+ * unlucky, your script was invoked (possibly indirectly) by another
+ * script, and now you've accidentally inherited the calling script's name.
+ *
+ * It would have been far better simply to repeat the script name as the
+ * first user argument, if nothing else had come readily to mind.
+ */
+ if (setenv("__CL_ARGV0", script, 1))
+ lose("failed to set script-name environment variable");
+
+ /* Work through the list of preferred Lisp systems, trying the ones which
+ * are allowed.
+ */
+ for (i = 0; i < arg.pref.n; i++)
+ if (arg.pref.sys[i]->f&arg.allow.f) {
+ arg.av.o += arg.av.n - n; arg.av.n = n;
+ arg.pref.sys[i]->run(&arg, script);
+ }
+
+ /* That didn't work. Try the remaining allowed systems, in the given
+ * order.
+ */
+ for (i = 0; i < arg.allow.n; i++)
+ if (!(arg.allow.sys[i]->f&arg.pref.f)) {
+ arg.av.o += arg.av.n - n; arg.av.n = n;
+ arg.allow.sys[i]->run(&arg, script);
+ }
+
+ /* No joy. Give up. */
+ argv_release(&arg.av);
+ lose("no supported Lisp systems found");
+}
+
+/*----- That's all, folks -------------------------------------------------*/
--- /dev/null
+### -*-makefile-*-
+###
+### Build script for tests
+###
+### (c) 2020 Mark Wooding
+###
+
+###----- Licensing notice ---------------------------------------------------
+###
+### This file is part of Runlisp, a tool for invoking Common Lisp scripts.
+###
+### Runlisp is free software: you can redistribute it and/or modify it
+### under the terms of the GNU General Public License as published by the
+### Free Software Foundation; either version 3 of the License, or (at your
+### option) any later version.
+###
+### Runlisp is distributed in the hope that it will be useful, but WITHOUT
+### ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+### FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+### for more details.
+###
+### You should have received a copy of the GNU General Public License
+### along with Runlisp. If not, see <https://www.gnu.org/licenses/>.
+
+include autotest.am
+autotest_TESTS =
+TEST_ARGS = -j8
+
+autotest_TESTS += $(top_srcdir)/tests.at
+
+###----- That's all, folks --------------------------------------------------
--- /dev/null
+### -*-sh-*-
+###
+### Configuration variables interesting to the test suite
+###
+### (c) 2020 Mark Wooding
+###
+
+###----- Licensing notice ---------------------------------------------------
+###
+### This file is part of Runlisp, a tool for invoking Common Lisp scripts.
+###
+### Runlisp is free software: you can redistribute it and/or modify it
+### under the terms of the GNU General Public License as published by the
+### Free Software Foundation; either version 3 of the License, or (at your
+### option) any later version.
+###
+### Runlisp is distributed in the hope that it will be useful, but WITHOUT
+### ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+### FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+### for more details.
+###
+### You should have received a copy of the GNU General Public License
+### along with Runlisp. If not, see <https://www.gnu.org/licenses/>.
+
+###--------------------------------------------------------------------------
+### Configuration snippets.
+
+## Lisp systems.
+SBCL=@SBCL@
+CCL=@CCL@
+CLISP=@CLISP@
+ECL=@ECL@
+CMUCL=@CMUCL@
+ABCL=@ABCL@
+
+###----- That's all, folks --------------------------------------------------
--- /dev/null
+../.ext/cfd/build/autotest.am
\ No newline at end of file
--- /dev/null
+### package information
+m4_define([AT_PACKAGE_NAME], [runlisp])
+m4_define([AT_PACKAGE_TARNAME], [runlisp])
+m4_define([AT_PACKAGE_VERSION], [UNKNOWN])
+m4_define([AT_PACKAGE_STRING], [runlisp UNKNOWN])
+m4_define([AT_PACKAGE_BUGREPORT], [mdw@distorted.org.uk])
--- /dev/null
+TESTS([..], [tests.at])
--- /dev/null
+../.ext/cfd/build/testsuite.at
\ No newline at end of file
--- /dev/null
+### -*-autotest-*-
+###
+### Test script for `runlisp'
+###
+### (c) 2020 Mark Wooding
+###
+
+###----- Licensing notice ---------------------------------------------------
+###
+### This file is part of Runlisp, a tool for invoking Common Lisp scripts.
+###
+### Runlisp is free software: you can redistribute it and/or modify it
+### under the terms of the GNU General Public License as published by the
+### Free Software Foundation; either version 3 of the License, or (at your
+### option) any later version.
+###
+### Runlisp is distributed in the hope that it will be useful, but WITHOUT
+### ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+### FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+### for more details.
+###
+### You should have received a copy of the GNU General Public License
+### along with Runlisp. If not, see <https://www.gnu.org/licenses/>.
+
+m4_define([RUNLISP_PATH], [$abs_top_builddir/runlisp])
+
+m4_define([_FOREACH], [dnl
+m4_if([$#], [1], [_foreach_func($1)],
+ [_foreach_func($1)[]_FOREACH(m4_shift($@))])])
+m4_define([FOREACH], [dnl
+m4_pushdef([_foreach_func], [$2])dnl
+_FOREACH($1)[]dnl
+m4_popdef([_foreach_func])])
+
+m4_define([LISP_SYSTEMS],
+ [sbcl, sbcl/noimage,
+ ccl, ccl/noimage,
+ clisp, clisp/noimage,
+ ecl, ecl/noimage,
+ cmucl, cmucl/noimage,
+ abcl, abcl/noimage])
+
+m4_define([PREPARE_LISP_TEST],
+[lisp=$1
+LISP=$m4_translit(m4_bregexp([$1], [/.*$], []), [a-z], [A-Z])
+AT_SKIP_IF([test "x$LISP" = x])
+case $lisp in
+ */*) opt=${lisp#*/} lisp=${lisp%%/*} ;;
+ *) opt="" ;;
+esac
+case /$opt/ in
+ */noimage/*) RUNLISP_IMAGEDIR=./notexist ;;
+ *) RUNLISP_IMAGEDIR=$abs_top_builddir ;;
+esac
+export RUNLISP_IMAGEDIR])
+
+m4_define([WHICH_LISP],
+[(or #+sbcl "sbcl" #+ccl "ccl" #+clisp "clisp"
+ #+ecl "ecl" #+cmu "cmucl" #+abcl "abcl"
+ "unknown")])
+
+m4_define([NL], [
+])
+
+m4_define([SETUP_RUNLISP_IMAGEDIR],
+[RUNLISP_IMAGEDIR=$abs_top_builddir; export RUNLISP_IMAGEDIR])
+
+m4_define([SETUP_RUNLISP_EVAL],
+[RUNLISP_EVAL=$abs_top_srcdir/eval.lisp; export RUNLISP_EVAL])
+
+###--------------------------------------------------------------------------
+### A basic smoke test.
+
+## Check that the system basically works, by running a trivial test program.
+## Also try to verify that we're not running user or site startup code,
+## though this is hard to do in general.
+FOREACH([LISP_SYSTEMS],
+[AT_SETUP([$1 smoke])
+AT_KEYWORDS([script smoke $1])
+PREPARE_LISP_TEST([$1])
+
+## Prepare a user-init file which will break the test if it's run by printing
+## something unexpected.
+mkdir HOME
+case $lisp in
+ sbcl) initfile=.sbclrc ;;
+ ccl) initfile=.ccl-init.lisp ;;
+ clisp) initfile=.clisprc.lisp ;;
+ ecl) initfile=.eclrc ;;
+ cmucl) initfile=.cmucl-init.lisp ;;
+ abcl) initfile=.abclrc ;;
+esac
+cat >HOME/$initfile <<EOF
+(format t "*** I should not be seen~%")
+EOF
+HOME=$(pwd)/HOME; export HOME
+
+## Prepare the script.
+cat >test-script <<EOF
+[#!] RUNLISP_PATH -L$lisp
+
+;; Print a greeting to \`*standard-output*', identifying the Lisp system, so
+;; that we can tell whether we called the right one.
+(format t "Hello from ~A (~A)!~%" (lisp-implementation-type) WHICH_LISP)
+
+#! this should be a comment everywhere
+
+;; Make sure that \`*error-output*' is hooked up properly.
+(format *error-output* "to stderr~%")
+
+;; Make sure that \`*standard-input*' is hooked up properly, by reading a line
+;; and echoing it.
+(format t "from stdin: ~S~%" (read-line))
+
+;; Check that \`:runlisp-script' is set in \`*features*'. If not, \`assert'
+;; will at least write a complaint to some stream, which will fail the test.
+(assert (member :runlisp-script *features*))
+
+;; Check that there are no symbols present (interned or imported) in the
+;; \`common-lisp-user' package. Obviously, we must avoid interning any
+;; ourselves. Alas, ABCL and ECL pollute \`cl-user' out of the box. (ECL
+;; does this deliberately; ABCL's ``adjoin.lisp' lacks an \`in-package' form.
+(let ((#1=#:syms (sort (loop :for #2=#:s :being :the :present-symbols
+ :of *package*
+ :collect #2#)
+ #'string<)))
+ (format t "package \`~A' [~:[ok~;has unexpected symbols ~:*~S~]]~%"
+ (package-name *package*) #1#))
+
+;; Print the program name and command-line arguments.
+(format t "program name = ~S~%~
+ arguments = ~:S~%"
+ (uiop:argv0)
+ uiop:*command-line-arguments*)
+EOF
+chmod +x test-script
+
+case $lisp in
+ sbcl) impl="SBCL" ;;
+ ccl) impl="Clozure Common Lisp" ;;
+ clisp) impl="CLISP" ;;
+ ecl) impl="ECL" ;;
+ cmucl) impl="CMU Common Lisp" ;;
+ abcl) impl="Armed Bear Common Lisp" ;;
+ *) AT_FAIL_IF([:]) ;;
+esac
+
+## Prepare an input file.
+echo some random text >stdin
+
+## Prepare the reference stdout and stderr.
+cat >stdout.ref <<EOF
+Hello from $impl ($lisp)!
+from stdin: "some random text"
+package \`COMMON-LISP-USER' ok
+program name = "./test-script"
+arguments = ("--eval" "nonsense" "--" "more" "args" "here")
+EOF
+cat >stderr.ref <<EOF
+to stderr
+EOF
+
+AT_CHECK([echo "lisp=$lisp opt=$opt"; env | grep RUNLISP | sort],, [stdout])
+AT_CHECK([./test-script --eval nonsense -- more args here <stdin],,
+ [stdout], [stderr])
+AT_CHECK([diff -u stdout.ref stdout])
+AT_CHECK([diff -u stderr.ref stderr])
+AT_CLEANUP])
+
+###--------------------------------------------------------------------------
+### Check error handling.
+
+FOREACH([LISP_SYSTEMS],
+[AT_SETUP([$1 errors])
+AT_KEYWORDS([script error $1])
+PREPARE_LISP_TEST([$1])
+
+## A simple script which signals an error without catching it.
+cat >test <<EOF
+[#!] RUNLISP_PATH -L$lisp
+(error "just kill me now")
+EOF
+chmod +x test
+
+## As long as it exits with a nonzero status, I'm happy. Some Lisps
+## desperately want to drop the user into an interactive debugger, which is
+## possibly useful for a developer, but an end user is now faced with a
+## confusing internal error message /and/ a confusing prompt which won't go
+## away. The output may still be confusing and (certainly in CCL's case)
+## voluminous, but that's not significantly worse than Tcl or Java.
+./test >out >err; rc=$?
+AT_FAIL_IF([test $rc = 0])
+
+AT_CLEANUP])
+
+###--------------------------------------------------------------------------
+### Check eval mode.
+
+### Eval mode is implemented centrally through a script, so we don't need to
+### test it separately for each Lisp implementation.
+
+AT_SETUP([eval mode])
+AT_KEYWORDS([eval common])
+SETUP_RUNLISP_IMAGEDIR
+SETUP_RUNLISP_EVAL
+
+## A very basic smoke test.
+AT_CHECK([RUNLISP_PATH -e '(format t "Just another Lisp hacker!~%")'],,
+[Just another Lisp hacker!
+])
+
+## The `:runlisp-script' keyword should /not/ be in `*features*'.
+traceon
+AT_CHECK([RUNLISP_PATH -p '(find :runlisp-script *features*)'],, [NIL
+])
+
+## Check a mixture of all the kinds of evaluation. We'll need a stunt script
+## to make this work. Also check that the individual forms are read and
+## evaluated one at a time, so that each one can affect the way the reader
+## interprets the next.
+cat >script.lisp <<EOF
+#! just want to check that Lisp doesn't choke on a shebang line here
+(format t "And we're running the script...~%~
+ Command-line arguments: ~:S~%~
+ Symbols in package \`~A': ~:S~%"
+ uiop:*command-line-arguments*
+ (package-name *package*)
+ (sort (loop :for #2=#:s :being :the :present-symbols :of *package*
+ :collect #2#)
+ #'string<))
+EOF
+AT_CHECK([RUNLISP_PATH \
+ -e '(defpackage [#:]runlisp-test (:export [#:]foo))
+ (defvar runlisp-test:foo 1)' \
+ -p runlisp-test:foo \
+ -e '(incf runlisp-test:foo)' \
+ -l script.lisp \
+ -p runlisp-test:foo \
+ -- -e one two three],,
+[1
+And we're running the script...
+Command-line arguments: ("-e" "one" "two" "three")
+Symbols in package `COMMON-LISP-USER': ()
+2
+])
+
+AT_CLEANUP
+
+###--------------------------------------------------------------------------
+### Check Lisp system selection and preference work.
+
+AT_SETUP([preferences])
+AT_KEYWORDS([prefs common])
+SETUP_RUNLISP_IMAGEDIR
+SETUP_RUNLISP_EVAL
+
+## Before we can make this happen, we need to decide on three Lisp systems,
+## two of which actually work, and one other. These are ordered by startup
+## speed.
+unset lisp0 lisp1 badlisp; win=nil
+set -- cmucl sbcl ccl clisp ecl abcl
+while :; do
+ case $# in 0) break ;; esac
+ lisp=$1; shift
+ if RUNLISP_PATH -L$lisp -enil 2>/dev/null; then good=t; else good=nil; fi
+ case ${lisp0+t},${badlisp+t},$good in
+ ,*,t) lisp0=$lisp ;;
+ t,*,t) lisp1=$lisp win=t; break ;;
+ *,,nil) badlisp=$lisp ;;
+ esac
+done
+AT_CHECK([case $win in nil) exit 77 ;; esac])
+case ${badlisp+t} in t) ;; *) badlisp=$1 ;; esac
+BADLISP=$(echo $badlisp | tr a-z A-Z)
+eval $BADLISP=/notexist/definitely-wrong
+export $BADLISP
+echo Primary Lisp = $lisp0
+echo Secondary Lisp = $lisp1
+echo Bad Lisp = $badlisp
+
+## Check that our selection worked.
+AT_CHECK_UNQUOTED([RUNLISP_PATH -L$lisp0 -p 'WHICH_LISP'],, ["$lisp0"NL])
+AT_CHECK_UNQUOTED([RUNLISP_PATH -L$lisp1 -p 'WHICH_LISP'],, ["$lisp1"NL])
+AT_CHECK([RUNLISP_PATH -L$badlisp -p 'WHICH_LISP'], [127],,
+[runlisp: no supported Lisp systems found[]NL])
+
+## Unset all of the user preference mechanisms.
+unset RUNLISP_OPTIONS
+here=$(pwd)
+mkdir HOME config
+HOME=$here/HOME XDG_CONFIG_HOME=$here/config; export HOME XDG_CONFIG_HOME
+
+## We generally take the first one listed that exists.
+AT_CHECK_UNQUOTED([RUNLISP_PATH -L$lisp0,$lisp1 -p 'WHICH_LISP'],, ["$lisp0"NL])
+AT_CHECK_UNQUOTED([RUNLISP_PATH -L$lisp1,$lisp0 -p 'WHICH_LISP'],, ["$lisp1"NL])
+AT_CHECK_UNQUOTED([RUNLISP_PATH -L$badlisp,$lisp0,$lisp1 -p 'WHICH_LISP'],,
+ ["$lisp0"NL])
+
+## Check parsing of embedded options.
+for i in 0 1; do
+ j=$(( 1 - $i )); eval lisp=\$lisp$i olisp=\$lisp$j
+ cat >script$i <<EOF
+[#!] RUNLISP_PATH
+;;; -z @RUNLISP: -L$lisp -*- -z -*- -L$olisp -- -z
+(prin1 WHICH_LISP) (terpri)
+EOF
+ chmod +x script$i
+ AT_CHECK_UNQUOTED([./script$i],, ["$lisp"NL])
+done
+
+## Preferences will override the order of acceptable implementations.
+AT_CHECK_UNQUOTED([RUNLISP_OPTIONS=-P$badlisp,$lisp0 ./script0],, ["$lisp0"NL])
+AT_CHECK_UNQUOTED([RUNLISP_OPTIONS=-P$badlisp,$lisp0 ./script1],, ["$lisp0"NL])
+
+## But doesn't affect the preference order of unmentioned Lisps.
+AT_CHECK_UNQUOTED([RUNLISP_OPTIONS=-P$badlisp ./script0],, ["$lisp0"NL])
+AT_CHECK_UNQUOTED([RUNLISP_OPTIONS=-P$badlisp ./script1],, ["$lisp1"NL])
+
+## Test configuration files and interactions with the environment.
+for conf in HOME/.runlisprc config/runlisprc; do
+ for i in 0 1; do
+ j=$(( 1 - $i )); eval lisp=\$lisp$i olisp=\$lisp$j
+ cat >$conf <<EOF
+### -*-conf-*-
+-P$lisp
+EOF
+
+ ## Basic check.
+ AT_CHECK_UNQUOTED([./script0],, ["$lisp"NL])
+ AT_CHECK_UNQUOTED([./script1],, ["$lisp"NL])
+
+ ## Environment variable only appends.
+ AT_CHECK_UNQUOTED([RUNLISP_OPTIONS=-P$olisp ./script0],, ["$lisp"NL])
+ AT_CHECK_UNQUOTED([RUNLISP_OPTIONS=-P$olisp ./script1],, ["$lisp"NL])
+
+ ## But we can clear the preferred list.
+ AT_CHECK_UNQUOTED([RUNLISP_OPTIONS="-C -P$olisp" ./script0],, ["$olisp"NL])
+ AT_CHECK_UNQUOTED([RUNLISP_OPTIONS="-C -P$olisp" ./script1],, ["$olisp"NL])
+
+ done
+ rm -f $conf
+done
+
+
+
+AT_CLEANUP
+
+###----- That's all, folks --------------------------------------------------
--- /dev/null
+#! /bin/sh -e
+
+case $# in
+ 0 | 1) echo >&2 "usage: $0 LISP SCRIPT [ARGS ...]"; exit 127 ;;
+esac
+lisp=$1 script=$2; shift 2
+
+__CL_ARGV0=$script; export __CL_ARGV0 # this is stupid
+
+lispscript=$(printf "%s" "$script" | sed 's/[\"]/\\&/g')
+
+load_asdf_rune="\
+(let ((*load-verbose* nil)
+ #+cmu (ext:*require-verbose* nil))
+ (require \"asdf\"))"
+
+ignore_shebang_rune="\
+(set-dispatch-macro-character
+ #\# #\!
+ (lambda (stream char arg)
+ (declare (ignore char arg))
+ (values (read-line stream))))"
+
+clisp_startup_rune="\
+(progn
+ $ignore_shebang_rune
+ $load_asdf_rune
+ (setf *standard-input* (ext:make-stream :input))
+ (load \"$lispscript\" :verbose nil :print nil)
+ (ext:quit))"
+
+abcl_startup_rune="\
+(let ((script \"$lispscript\"))
+ $load_asdf_rune
+ $ignore_shebang_rune
+ (setf *error-output*
+ (java:jnew \"org.armedbear.lisp.Stream\"
+ 'sys::system-stream
+ (java:jfield \"java.lang.System\" \"err\")
+ 'character
+ java:+true+))
+ (handler-case (load script :verbose nil :print nil)
+ (error (error)
+ (format *error-output* \"~A (unhandled error): ~A~%\" script error)
+ (ext:quit :status 255))))"
+
+#set -x
+case $lisp in
+
+ sbcl)
+ exec sbcl --noinform --eval "$load_asdf_rune" --script "$script" "$@"
+ ;;
+
+ ecl)
+ exec ecl --norc --eval "$load_asdf_rune" --shell "$script" -- "$@"
+ ;;
+
+ clisp)
+ exec clisp -norc -q -x "$clisp_startup_rune" -- "$@"
+ ;;
+
+ cmucl)
+ exec cmucl -batch -noinit -nositeinit -quiet \
+ -eval "$load_asdf_rune" \
+ -eval "$ignore_shebang_rune" \
+ -load "$script" -eval "(ext:quit)" -- "$@"
+ ;;
+
+ ccl)
+ exec ccl -b -n -Q \
+ -e "$load_asdf_rune" \
+ -e "$ignore_shebang_rune" \
+ -l "$script" -e "(ccl:quit)" -- "$@"
+ ;;
+
+ abcl)
+ exec abcl --batch --noinform --noinit --nosystem \
+ --eval "$abcl_startup_rune" -- "$@"
+ ;;
+
+ *)
+ echo >&2 "$0: unsupported Lisp \`$lisp'"
+ exit 127
+ ;;
+esac
--- /dev/null
+### -*-makefile-*-
+###
+### Common build-system definitions
+###
+### (c) 2020 Mark Wooding
+###
+
+###----- Licensing notice ---------------------------------------------------
+###
+### This file is part of Runlisp, a tool for invoking Common Lisp scripts.
+###
+### Runlisp is free software: you can redistribute it and/or modify it
+### under the terms of the GNU General Public License as published by the
+### Free Software Foundation; either version 3 of the License, or (at your
+### option) any later version.
+###
+### Runlisp is distributed in the hope that it will be useful, but WITHOUT
+### ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+### FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+### for more details.
+###
+### You should have received a copy of the GNU General Public License
+### along with Runlisp. If not, see <https://www.gnu.org/licenses/>.
+
+###--------------------------------------------------------------------------
+### Initial values for common variables.
+
+EXTRA_DIST =
+CLEANFILES =
+
+bin_PROGRAMS =
+bin_SCRIPTS =
+nodist_bin_SCRIPTS =
+
+man_MANS =
+
+noinst_PROGRAMS =
+noinst_DATA =
+
+###--------------------------------------------------------------------------
+### Standard configuration substitutions.
+
+## Substitute tags in files.
+confsubst = $(top_srcdir)/config/confsubst
+
+SUBSTITUTIONS = \
+ prefix=$(prefix) exec_prefix=$(exec_prefix) \
+ libdir=$(libdir) includedir=$(includedir) \
+ bindir=$(bindir) sbindir=$(sbindir) \
+ imagedir=$(imagedir) \
+ PACKAGE=$(PACKAGE) VERSION=$(VERSION) \
+ ECLOPT=$(ECLOPT)
+
+v_subst = $(v_subst_@AM_V@)
+v_subst_ = $(v_subst_@AM_DEFAULT_V@)
+v_subst_0 = @echo " SUBST $@";
+SUBST = $(v_subst)$(confsubst)
+
+###--------------------------------------------------------------------------
+### List of Lisp systems.
+
+LISPS =
+
+LISPS += sbcl
+LISPS += ccl
+LISPS += clisp
+LISPS += ecl
+LISPS += cmucl
+LISPS += abcl
+
+###----- That's all, folks --------------------------------------------------