193: Command line

by Lassi Kortela

Status

This SRFI is currently in final status. Here is an explanation of each status that a SRFI can hold. To provide input on this SRFI, please send email to srfi-193@nospamsrfi.schemers.org. To subscribe to the list, follow these instructions. You can access previous messages via the mailing list archive.

Abstract

R6RS and R7RS define a command-line procedure. While a useful baseline, the specification is not detailed enough to cover all practical situations. This SRFI clarifies the definition of command-line and adds a few related procedures. Scheme scripts, standalone executables, compilation and REPL use are accounted for. Option parsing is out of scope.

Table of contents

Rationale

OS executables

When a Scheme implementation is run on a conventional operating system, the OS runs an executable. An executable is a file that contains a program in a format suitable for the OS. In the case of Scheme, the executable can represent:

OS command lines

The OS gives the executable a command line. The command line is a vector of strings. There is a ubiquitous convention that the first string represents the command name. Any remaining strings are command arguments interpreted differently by each program.

Command lines are extremely portable. Practically all general-purpose operating systems provide them. Command names cannot be reliably interpreted as filenames.

Program command lines

The command-line procedure specified in R6RS and R7RS returns a list of one or more strings. As with OS command lines, the first string is by convention the command name. However, (command-line) is generally not the same as the OS command line.

When an implementation is started for the purpose of running a Scheme program as a batch job, (command-line) is most often a tail of the OS command line such that (car (command-line)) is the name of the Scheme program and (cdr (command-line)) are the arguments given to that program.

For example, if the OS command line is:

fantastic-scheme --script program.scm foo bar baz

the value of (command-line) generally is:

("program.scm" "foo" "bar" "baz")

Gambit Scheme supports running more than one program in the same batch job, with the ability to insert a read-eval-print loop in between any pair of programs. In that case the command line is given to the first program in the batch that starts with the Unix-style #! script indicator. Since it consumes all remaining arguments, that program also becomes the last program in the batch.

When a Scheme program is turned into a standalone executable, (command-line) and the OS command line are generally equivalent. However, the Scheme implementation’s runtime system may omit command line options that belong to it. For example, Gambit omits the -: option.

In a read-eval-print loop the value of (command-line) is not well specified. A reasonable convention from Chez Scheme is to use a zero-length string as the command name in this case, with no command line arguments after the name.

Another ambiguous situation arises when using load or import to load some code into an implementation. When the implementation loads or imports a Scheme library or module to use as a main program, it should perhaps set the command name to the name of the module and give it any remaining command line arguments. If a library or module is loaded or imported to help with another program, and not as a main program in its own right, the command line should be '("") as in the REPL.

Taming the complexity

The interface defined by this SRFI is divided into two layers:

The command layer applies whenever we are running a Scheme program that is given its own command line (in the sense that the RnRS command-line procedure would return a useful command line concerning that program specifically). Such a command line is normally a tail of the OS command line, but may come from other places as well. Kawa offers a particularly large number of alternative ways to set it.

The script layer applies whenever we are running a Scheme program from a source file. It does not apply to standalone, pre-compiled programs or libraries. The idea is that when running simple Scheme scripts, it is often convenient to have them read and write data files kept in the same directory as the script itself.

A third layer to access the unabridged command line of the OS process is omitted. The process command line can be difficult to access from hosted environments such as the Java Virtual Machine, which would make this SRFI more difficult to adopt.

Specification

Terminology

Top-level programs

For the purposes of this SRFI, the following are considered top-level programs:

The following are not considered top-level programs:

However, implementation-defined global options or optional arguments are permitted that control whether or not load treats files as top-level programs.

Commands and scripts

For the purposes of this SRFI, we classify a running Scheme program as follows:

A running program can be either a command, a script, both at once, or neither.

Command procedures

(command-line) ⇒ string-list

This procedure is equivalent to the R6RS and R7RS command-line procedure, but specified in more detail.

R6RS definition: "Returns a nonempty list of strings. The first element is an implementation-specific name for the running top-level program. The remaining elements are command-line arguments according to the operating system’s conventions."

R7RS definition: "Returns the command line passed to the process as a list of strings. The first string corresponds to the command name, and is implementation-dependent. It is an error to mutate any of these strings."

Additional stipulations by this SRFI:

(command-name) ⇒ string?

Returns a friendly version of (car (command-line)) evaluated in the current lexical environment.

If (car (command-line)) is a zero-length string, #f is returned to indicate "not a command".

Otherwise a friendly command name is typically derived from a filename as follows:

For example, both the Windows filename C:\Program Files\Fantastic Scheme\fantastic-scheme-1.0.EXE and the Unix filename /usr/local/bin/fantastic-scheme-1.0 would be typically shortened to fantastic-scheme-1.0.

(command-args) ⇒ string-list

Returns (cdr (command-line)) evaluated in the current lexical environment.

Script procedures

(script-file) ⇒ string?

Returns an absolute pathname pointing to the calling script. Symbolic links are not resolved.

(The script may or may not be a command; use command-name to find out.)

If the calling program is not a script, #f is returned.

Implementations must resolve the absolute pathname of a script before running that script. The script may change the working directory, thereby changing the interpretation of relative pathnames.

(script-directory) ⇒ string?

Returns only the non-filename part of script-file as a string. As with script-file, this is an absolute pathname.

The string should end with a directory separator (a forward slash on Unix; a backslash on Windows; an appropriate character on other operating systems) so that string-append can be easily used to build pathnames based on it: for example, (string-append (script-directory) "my-data-file"). However, if appending such a separator would make the pathname invalid on the underlying operating system, the separator is not added.

If the calling program is not a script, #f is returned.

Implementation

Due to the highly environment-dependent nature of the SRFI, there is almost nothing that can go into a portable sample implementation. Implementations have been done for Gauche (src/execenv.c and lib/srfi-193.scm) as well as Chibi-Scheme (main.c and lib/srfi/193.sld).

It is not possible to implement this SRFI based on the RnRS command-line procedure. However, the command-line procedure from this SRFI can serve as a conforming implementation of RnRS command-line, and implementations are encouraged to merge these procedures where possible.

Acknowledgements

Thanks to Shiro Kawai for the initial implementation in Gauche, for patiently reviewing iterations of the design and for valuable feedback that solved key problems in it. The interplay of command-line, command-name and command-args was especially tricky to figure out and I had all but given up hope of a satisfying solution. With Shiro’s insight a simple and natural approach was finally found.

References

The Kawa Scheme language. Section 21.5 System inquiry. Corresponds to Kawa version 3.1.1. Link

The Gambit Scheme manual. Section 2.5 Scheme scripts. Corresponds to Gambit version 4.9.3. Link

Copyright

© 2020 Lassi Kortela.

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice (including the next paragraph) shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.


Editor: Arthur A. Gleckler