SRFI 176

Title

Version flag

Author

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-176@nospamsrfi.schemers.org. To subscribe to the list, follow these instructions. You can access previous messages via the mailing list archive.

Abstract

This SRFI defines a standard command-line flag to get version information from a Scheme implementation. The output is Line-oriented S-expressions which are easy to parse from Scheme, C, and shell scripts and can co-exist with non-S-expression output. A standard vocabulary is defined; extensions are easy to make.

Table of contents

Rationale

The implementation of this SRFI boils down to a few write calls. But careful planning has gone into the details of those calls.

Introduction

There is a long tradition of complex command line programs having a version flag. This flag skips the normal operation of the program; instead, it writes version information to standard output and immediately exits. The flag is useful for:

Part 1: Which flag

Survey of existing version flags

Implementation -V -v -version --version
Bigloo (bigloo) (verbose) x
Chez Scheme (scheme) x
Chibi-Scheme (chibi-scheme) x
Chicken (csc) (verbose) x
Chicken (csi) x
Cyclone (cyclone) x
Cyclone (icyc) x
Dfsch (dfsch-repl) x
Foment (foment) x
Gambit (gsc) x
Gambit (gsi) x
Gauche (gosh) x (run another version)
Guile (guile) x x
Kawa (kawa) x
KSi (ksi) x x x
Larceny (larceny) x x
MIT Scheme (scheme) x
Mosh (mosh) x x
Mosh (nmosh) x x
Oaklisp (oaklisp) x x x
Owl Lisp (ol) x x
Racket (racket) x x x
Sagittarius (sagittarius) x x x
Sagittarius (sash) x x x
Schemik (schemik) x x
SCM (scm) x
Shoe (shoe) x x
STklos (stklos) x x x
Vicare (vicare) x (verbose) x
Ypsilon (ypsilon) x

Cyclone and Vicare have a "version number only" flag (cyclone -vn and vicare --version-only, respectively). Gauche has a flag to load another version of itself. These features are not addressed by this SRFI.

Which flag to choose

The first problem is that long options (-version and --version) do not have a universally agreed-upon syntax. Perhaps most programs now adhere to the GNU-style two-dash syntax. However, many prominent ones such as C compilers and fundamental X Window System utilities continue to use the one-dash syntax. There are many Schemes supporting only one of those variants, and many that do not have long options at all.

One-letter options are much more standardized; so much in fact, that almost all Unix programs have supported them ever since Unix was first published. While programs disagree on whether to interpret a group of letters after one dash as a single one-word option or as multiple one-letter options, there is no ambiguity when only one letter follows the dash. The convention is especially strong when the flag is the only argument after the program name.

Lower-case -v is the most popular one-letter flag in the survey. However, it is commonly used by programs for their "verbose" flag — including a few Scheme implementations.

Upper-case -V is less popular but has no known conflicting uses. (Racket uses -V as an alias for --no-yield but makes an exception and treats racket -V with no other arguments as a version flag.) It is a strong standard for a version flag among Unix programs in general. Hence we choose the upper-case -V flag for this SRFI.

Parser-friendly output format

Many Scheme/Lisp implementations and other Unix tools output version information in a format that is quite stable. The idea is that the information can be parsed by other programs and scripts. Often the output format is almost regular but not quite. Some of the more complex formats, while stable, are not self-consistent since they evolved over time from an ad hoc syntax; outside of Scheme, clisp --version and clang --version are good examples.

This SRFI mandates a very simple S-expression syntax that is a subset of Scheme’s standard syntax. Implementations can easily write out the information using the standard write procedure as long as the expressions given to write are suitably constrained. Version output is naturally represented as an association list of properties and their values. Each association shall be written as a separate S-expression; the full list is implicit. The precise output format is slightly unconventional and is presented in the next part.

Part 2: Line-oriented S-expressions (LOSE)

Background

The Lisp tradition offers easy handling of nested data. But we sometimes have to interoperate with languages that don't.

In particular, Unix shell scripts generally parse their input using the traditional tools awk, grep, and sed that are based around regular expressions. Regexps are notoriously unable to handle nested structure. Perhaps for that reason, Unix shells also make it difficult to store nested data in variables. Even string list handling is clumsy and error-prone.

The other classic Unix programming environment, the C language, makes it easy to represent nested data. But handling it is difficult due to the lack of standard tools. Almost all of the data processing functions in the standard library are string functions. Thus we come back to the same situation as with shell scripts: only strings are easy to process from standard C.

Example

The following S-expression:

(version "1.2.3")

Can be easily parsed even with ancient versions of grep and sed:

grep '^(version ".*".*)$'      \
    | sed -e 's/^(version "//' \
          -e 's/".*//'

The sed command s/whatever// replaces whatever with the empty string. whatever is a regular expression.

An equivalent parser can be written in awk. It’s a bit more verbose but avoids using a shell pipeline:

awk -F '[()]' \
    '/^\(version .*\)$/ {
        sub(/^[a-z-]+ /, "", $2);
        gsub(/"/, "", $2);
        print $2
    }'

Again, the Awk command sub(/whatever/, "", $variable) replaces the regular expression whatever with the empty string. gsub replaces all occurrences on the line whereas sub replaces only the first. Awk splits each line into numbered input fields at the separator [()].

These commands work with the POSIX standard versions of these tools. They have been tested with the bare-bones versions in BusyBox, meaning they are quite conservative.

Parsing from C is surprisingly easy as well:

#include <stdio.h>
#include <string.h>

static char output[1024];

static const char *parse_version(void)
{
    const char prefix[] = "\n(version \"";
    char *start;
    char *limit;

    if ((start = strstr(output, prefix)) == NULL) {
        return "";
    }
    start += strlen(prefix);
    for (limit = start; *limit != '"'; limit++) {
        if ((*limit == '\0') || (*limit == '\n') || (*limit == '\\')) {
            return "";
        }
    }
    *limit = '\0';
    return start;
}

int main(void)
{
    output[0] = '\n';
    fread(&output[1], 1, sizeof(output) - 2, stdin);
    printf("%s\n", parse_version());
    return 0;
}

When reading this code, note that C implicitly initializes the output buffer to all all zero bytes. In order to find (version "1.2.3") when it is the first line of output, a newline character \n is artificially prepended to the real output. When reading we additionally leave one zero byte at the end of the output buffer to ensure the buffer is null-terminated. The parse_version function mutates the output buffer by replacing the closing double-quote in (version "1.2.3") with a null byte. Then we can easily handle the substring 1.2.3 as a null-terminated string.

Assumptions made

In order for the above S-expression parsing to be robust, it relies on some concessions from the program writing the output:

At first glance, this looks like a big list of restrictions. But in practice, they are not hard to conform to for simple data.

Easy forms

The following LOSE forms are quite easy to parse reliably. This SRFI uses them exclusively, and the sample implementation offers ready-made parsers for them.

(symbol string…​)

(symbol symbol/integer…​)

(symbol (symbol symbol/integer) …​)

Part 3: Backward compatibility

To let implementations keep their existing version output for backward-compatibility, LOSE parsing starts at the first line with a left parenthesis ( at the first column. This means that any amount of other text can come before the S-expression part. Some Unix programs write a multi-paragraph copyright, warranty and version message; all of that can be preserved if desired.

This simple arrangement makes all of the following work naturally:

Conclusion

The best argument for using S-expressions generally is that people keep re-inventing them in less consistent and flexible formats without an objective reason. We will save time and effort by using time-tested syntax from the beginning. Easy interoperability with Scheme/Lisp is an obvious plus.

The main arguments against S-expressions is that they look foreign to non-Lisp programmers and require too many parentheses. The nesting implied by the parentheses makes them a poor fit for line-oriented tools. All the classic Unix text processing utilities are line-oriented. To interoperate with these tools we need a compromise. The easiest compromise is to write each association list entry on its own line, which leaves most lines with only one pair parentheses and no nesting. Nested lists in the output will be rare.

We could take this even further by using implied parentheses around each line of text, so that no parentheses are needed for most output lines. This should make the output completely un-scary even for Unix programmers who know nothing about Lisp. Unfortunately this syntax would make it hard to provide backward-compatibility with the many existing output formats for version info. Having a left parenthesis in the first column is a simple and unambiguous rule. If there are no such syntactic markers, parsing will be a lot harder. Most natural candidates for a syntactic marker are also more ambiguous than a left parenthesis. For example, property: value pairs are harder to detect and easier to confuse with other things.

Specification

Character set and encoding

The version output should be in an ASCII superset character encoding, so that bytes #x01..#x7e correspond to those ASCII codepoints. The encoding of bytes outside this range is unspecified; UTF-8 is recommended where possible.

ASCII space (#x20), carriage return (#x0d) and line feed (#x0a) characters are recognized as whitespace.

On Unix, only a single line feed character (LF) is recognized as a newline.

On Windows, either a single line feed character (LF) or a carriage return followed by a line feed (CR/LF) can be used as a newline.

Line-oriented S-expressions (LOSE)

The version output shall conform to the following subset of Scheme syntax:

LOSE symbols are case-sensitive. Letter case is preserved when reading them in.

Top-level S-expressions must all be lists. A top-level open parenthesis must fall on the first column of a line, and there must be a newline immediately after a top-level close parenthesis. There must be a newline even after the last top-level list in the output, i.e. the output must end with a final newline, Unix style.

Multi-line S-expressions are not recognized by line-oriented tools and should hence be avoided; if required, then continuation lines of nested expressions must start with one space.

Missing features:

Suggested workarounds:

Use of color and other display attributes

ANSI, HTML or other in-band color and text attribute markup shall not be used in the S-expression part of the output since it will confuse parsers.

An exception is if the implementation can be sure that the output is going to a terminal (instead of a file, pipe, etc.) On Unix, isatty() is an adequate check.

Out-of-band markup (e.g. Windows console character attributes) may be used.

Version flag

For a Scheme invoked as fantastic-scheme, the command line fantastic-scheme -V (i.e. upper-case V preceded by one dash) must conform to the version output format in this SRFI.

Specifically, LOSE parsing starts at the first output line that has a left parenthesis ( in the first column with no preceding whitespace characters. Parsing continues from that line until the end of the output. The parser collects every top-level S-expression into one big association list, preserving the order. If there is no line starting with a left parenthesis, an empty association list is returned.

This SRFI guarantees only that the above simple command invocation, with -V as the first command-line argument and no other arguments, has the intended effect. The implementation should also support the -V flag in other argument positions if it makes sense, and it should have the same output format as when it is the only command line argument, but neither of those is strictly required.

When the -V flag is used as above, the command shall exit with a success exit code if it:

Otherwise it shall exit with a failure exit code. On Unix and Windows, exit code 0 means success and codes 1..100 are safe to use for indicating failure.

Other version flags and multiple commands

All interpreter and compiler commands supplied by the implementation must support the -V flag as the only argument.

The implementation may additionally support -v, -version, --version and/or other version flags, but none of these are required. To present a simple interface to users, all version flags should ideally give the same output, but that is not required either.

Besides the compiler and interpreter commands, any other commands supplied by the implementation are also encouraged to support the same version flags, but are not required to.

The version output is allowed to differ between commands.

Effect of non-version flags on version information

The -V output may change if other flags are also given on the command line. For example, fantastic-scheme -V -r r6rs and fantastic-scheme -V -r r7rs could give different output describing the R6RS and R7RS modes of Fantastic Scheme, and fantastic-scheme -V could give yet different output describing both of them or have less information.

-:version runtime option

If the Scheme system has a -: flag for runtime options (as do Chicken and Gambit), it shall recognize -:version and behave compatibly with the -V flag. If the version runtime option comes from an environment variable or some other place than command line arguments, it is permitted to cause an error.

Accessing from Scheme

Implementations shall provide a (version-alist) procedure that can be called from Scheme and returns an association list of zero or more properties according to this specification. Ideally it should return roughly the same information as the -V command line flag, but this is not required. It is an error for the caller to modify the list returned.

Implementations supporting the R7RS module system shall provide a (srfi 176) library with the procedure.

Implementations supporting the R6RS module system shall provide a (srfi :176) library with the procedure.

The procedure may also be exported by other libraries. It's implementation-defined whether or not it's imported into the default interaction environment.

Standalone executables

If the Scheme implementation can generate standalone executables or bundles of user programs, those executables:

Date and time

In standard properties, dates and times are written in RFC 3339 format. It is a restricted form of the international standard date format specified in ISO 8601.

We always use upper-case T for the date/time separator and upper-case Z for the UTC time zone marker. The UTC offset -00:00 (i.e. negative zero) indicates an unknown time zone.

Examples:

Unix shell commands to output the current date and time in these formats:

The %z conversion specification is the only non-POSIX feature in these commands. %z gives a ±HHMM time zone offset under every modern Unix-like operating system. The sed command converts that to ±HH:MM. The ISO C99 strftime() function standardizes %z with the same format. C code to output the above date formats is included with the sample implementation of this SRFI. Most popular programming languages have a ready-made library function to parse RFC 3339 timestamps.

Standard properties

Below is a large set of proposed standard properties. This set was designed based on actual information currently reported by various Scheme implementations in their version output.

All properties are optional. That implies any Scheme implementation with a -V version flag writing only to standard output, no output lines starting with ( already conforms to this SRFI.

The order in which the properties are given does not matter. Writers should not write properties with duplicate names; readers should pick the first property matching a given property name.

General properties

(command string…​)

The command names for some Schemes differ on different operating systems and installations. Implementors typically desire a canonical command name for each command shipping with their implementation, but compromises sometimes need to be made due to name conflicts or multiple versions of the same command that need to be able to coexist. This property gives the canonical name suggested by the implementor without any optional version number.

If the executable being invoked is a multi-call binary (i.e. it can behave like more than one program depending on which argv[0] is given) or otherwise is known by more than one canonical name, then more than one string may be given.

Examples:

(command "csi")
(command "gosh")
(command "gsc")
(command "isc")
(command "scheme" "mit-scheme")

(website string)

The URL for the Scheme implementation's website.

Examples:

(website "https://practical-scheme.net/gauche")

(install-dir string)

Root directory of the Scheme installation, if it has one. Typically, this is the directory that has bin and lib subdirectories, but the meaning is implementation-dependent.

This directory is often set by a build-time option called prefix in the implementation's configure script or makefile.

(languages symbol…​)

The set of programming languages supported by the implementation. Symbols denote set membership.

The distinctions between language, language standard, language family and dialect are muddy. For the purposes of this property, they are all equivalent, and any of them may be represented by a symbol in this property. For example, r7rs is a member of scheme and both should be given. Non-Scheme languages could also be listed. If a unified Scheme and Common Lisp implementation is ever made, it would list both languages.

This property means that the implementation aspires to conform to these languages to a useful degree, and if it does not, you can open issues in the issue tracker to discuss it. Guarantees about conformance and pedantry about language definitions are not the point. In particular, any executable usefully characterized as a Scheme implementation should list scheme even if it does not fully conform to any RnRS report.

Standard symbols include r3rs, r4rs, r5rs, r6rs, r7rs, r7rs-large, scheme. Please coordinate with other implementors about coining symbols for other Scheme derivatives and non-Scheme languages.

Examples:

(languages scheme r6rs r7rs)

(encodings symbol…)

Each symbol is the lowercase IANA name of a character encoding known to be supported by this build of the Scheme implementation.

All encoding names in the IANA list can be represented as LOSE symbols except for names with a year suffix: e.g. ISO_8859-1:1987.

It is permitted to list more than one alias for the same encoding.

The default encoding selected at start-up time should come first in the list. The default may change based on command line options, environment variables or other environmental factors.

The implementation may also support other encodings not listed here, either natively or via extensions.

Examples:

(encodings utf-8 iso-8859-1)

(version string)

A free-form version string in the native format preferred by the implementor. No portable information can be reliably parsed from the string, but version strings should be sortable in order from oldest to newest using typical "version sort" algorithms.

In practice, most Scheme implementations use release version numbers in major.minor.patch format. Other information such as distributor patchlevel or version control commit may be appended.

Examples:

(version "1.2.3")
(version "1.11.6")
(version "0.9.9_pre1")
(version "1.0.188-a4a79d5")
(version "4.3f")

Release properties

(release string)

The most recent released version of the implementation based on which this build was made. If this is that release version, then version is identical to this. If this has patches on top, then the two versions are different.

(release.date iso-date-string)

The date on which the release was made.

Examples:

(release.date "2019-08-06")

(release.name string)

A codename for the release.

Examples:

(release.name "Grain Alcohol and Rainwater")
(release.name "oxygen")

Build properties

(build.date iso-date-string)

The date and time when this executable was built.

It is implementation-dependent whether the timestamp is nearer to the start or end of the build.

Examples:

(build.date "2018-09-30")
(build.date "2018-09-30T02:07Z")
(build.date "2018-09-30T02:07+02:00")
(build.date "2018-09-30T02:07-05:30")

(build.platform string)

A free-form string identifying the computer architecture, operating system, and/or other aspects of the computing platform for which the executable was built. This is the platform string in the implementation’s native format; there is no portable information that can be reliably parsed. Often this is a GNU-style computer-kernel-userland triple; just as often it is not.

Examples:

(build.platform "DarwinX8664")
(build.platform "x86_64-apple-darwin18.7.0")
(build.platform "macosx-unix-clang-x86-64")

(build.configure string…​)

Command line arguments given to the configure script before building this Scheme implementation. A configure script is a common means for build-time configuration of programs on Unix-like operating systems. It is useful to save the options given to that script for run time: knowing them helps replicate builds and debug problems with the implementation.

Each command line argument is given as one string. S-expression string escapes are used; since the double-quoted string syntax used with S-expressions is largely compatible with Unix shells, the resulting syntax can generally be pasted to a shell with no changes.

The name of the configure script is not given. It is almost always configure, though that is not required.

Examples:

(build.configure "--enable-single-host")
(build.configure "--prefix=/home/wiley/.local" "CC=gcc-9")

(build.git.tag string)
(build.git.branch string)
(build.git.commit string)
(build.git.modified string…​)

The state of the Git version control repository from which the build was made.

These can be got from the following Unix shell command:

Tag:

git describe --tags --abbrev=0 2>/dev/null || true

Branch:

git rev-parse --abbrev-ref HEAD 2>/dev/null || true

Commit:

git rev-parse --short HEAD 2>/dev/null || true

List of files with uncommitted changes:

printf '(build.git.modified';
git diff-index --name-only HEAD 2>/dev/null | head | xargs -n 1 printf ' "%s"';
echo ')'

Examples:

(build.git.tag "3.1.1")
(build.git.branch "master")
(build.git.commit "8e62f718")
(build.git.modified "c/char.c" "c/env.c" "c/env_unix.c" "c/scheme.h")

Image properties

(image.date iso-date-string)

If this is an image-based Scheme system, the date and time when the active boot image was saved.

This may vary by command line options and environment variables if those can be used to select a different boot image.

Examples:

(image.date "2018-09-30")
(image.date "2018-09-30T02:07Z")
(image.date "2018-09-30T02:07+02:00")
(image.date "2018-09-30T02:07-05:30")

(image.file string)

If images can be loaded by filename, string gives the filename that is used to load the active boot image.

Scheme properties

These properties deal with things directly related to the Scheme programming language.

(scheme.id symbol)

A symbol identifying which Scheme implementation provides this executable. Together with command this can be used to figure out which command of which implementation was invoked, even in cases where two implementations use the same command name.

At the time of writing, there is no central registry for Scheme IDs. The Scheme Registry tracks IDs.

Examples:

(scheme.id fantastic)
(scheme.id gauche)

(scheme.srfi integer…)

Each integer gives the number of a SRFI supported natively by the implementation. The integer 0 stands for Feature-based conditional expansion construct, 1 stands for List Library, 176 stands for Version flag, etc.

The integers shall be listed in order from smallest to largest.

If the implementation supports a particular SRFI via a library or extension that is not built or installed, that SRFI shall not be listed.

A partially supported SRFI should not be listed unless the implementor has plans for full support and users can report missing features as bugs.

The implementation does not need to list every SRFI it supports, as there may be situations where that is hard to define. The absence of a number does not conclusively prove that SRFI is not supported.

Examples:

(scheme.srfi)
(scheme.srfi 0 1 13 14 176)

(scheme.features symbol…​)

The symbols should correspond to the feature list for cond-expand.

Note: Only symbols conforming to the LOSE symbol syntax can be listed for compatibility reasons. Other symbols from the feature list need to be filtered out. In particular, Scheme symbols cannot portably start with digits so LOSE does not allow it either. This is not generally a problem since Scheme implementations do not tend to have such feature identifiers, but some of them may.

Examples:

(scheme.features)
(scheme.features dload ptables)
(scheme.features utf-8 pthreads linux r7rs)
(scheme.features chibi r7rs ratios complex uvector threads full-unicode)
(scheme.features modules dynamic-loading darwin bsd macosx little-endian)

(scheme.path string…​)

List of directories to search for imported libraries. Listed in order of decreasing priority: highest priority first.

C properties

If the Scheme implementation is written in the C programming language and/or has a C foreign function interface, it can provide these properties to give information about the C side of things.

(c.version string)

A free-form version string describing the C compiler or toolchain being used.

Expect this to be difficult to parse reliably, like HTTP user-agent strings.

Pre-defined Compiler Macros is a site listing the C preprocessor macros set by lots of compilers to reveal their version information.

Examples:

(c.version "GCC 9.2.0")
(c.version "GCC 4.2.1 Compatible Apple LLVM 10.0.1 (clang-1001.0.46.4)")
(c.version "Open Watcom 1.9")

(c.compile string…)
(c.link string…)

The command name and any fixed command line arguments for the C compiler/linker used to build the Scheme implementation.

If same command is used for compiling and linking, both c.compile and c.link may be given with the same values, or only c.compile may be given and c.link omitted.

Each command line argument is given as one string. S-expression string escapes are used; since the double-quoted string syntax used with S-expressions is largely compatible with Unix shells, the resulting syntax can generally be pasted to a shell with no changes.

Examples:

(c.compile "clang" "-Wall" "-Werror" "-Wextra" "-O2" "-D" "NDEBUG")
(c.link "clang" "-lm")

(c.type-bits (symbol integer) …​)

Tells the sizes of C data types. Each symbol is the name of a C type and integer gives giving the width of the type in bits. The size of type t can be determined in C using:

#include <limits.h>  // For CHAR_BIT
#define BITSIZEOF(t) (sizeof(t) * CHAR_BIT)
printf("%zu\n", BITSIZEOF(void *));

It's completely up to the implementation to decide which types are listed. The list can include non-standard types from the operating system and third-party libraries as well as custom types used only by the implementation. Types do not need to be sorted by symbol or integer.

Each C type name is converted to a symbol as follows:

While qualifiers like const and unsigned are permitted, they are generally superfluous since the unqualified type is the same size. All pointer types are generally the same size as a void pointer, but the C standard does not guarantee that.

Examples (for completeness, some silly ones are included):

(c.type-bits)
(c.type-bits (pointer 64))
(c.type-bits (int 32) (long 64) (float 32) (double 64) (pointer 64) (size_t 64))
(c.type-bits (struct-timespec 128) (struct-timespec-pointer 64) (time_t 64))
(c.type-bits (long 64) (long-long 64) (intmax_t 64) (intmax_t-pointer 64))
(c.type-bits (long-double 128) (long-double-pointer 64))
(c.type-bits (char-pointer-pointer 32) (jmp_buf 416))
(c.type-bits (TCHAR 8) (DWORD 32) (WIN32_FIND_DATA 2560))
(c.type-bits (struct-__Foo_BAR__12_-pointer-pointer 64))

Java virtual machine properties

(jvm.symbol string)

This property can be used to expose one or more Java system properties.

The value of this property is determined at run time, not at build time.

Note: symbol is part of the property name, not a separate symbol.

Note: System properties whose names are not valid LOSE symbols cannot be included.

Note: A missing system property can either be omitted from the output or show a null string "" as its value.

(jvm.java.awt.graphicsenv "sun.awt.X11GraphicsEnvironment")
(jvm.java.class.path "/usr/local/share/kawa/lib/kawa.jar:")
(jvm.java.version "13.0.2")
(jvm.java.vm.name "OpenJDK 64-Bit Server VM")
(jvm.kawa.home "/usr/local/share/kawa")
(jvm.sun.jnu.encoding "UTF-8")

Operating system properties

(os.env.symbol string)

This property can be used to expose one or more environment variables from the operating system.

The value of this property is determined at run time, not at build time.

On Windows, environment variable names are case-insensitive and should be normalized to uppercase for this SRFI.

Note: symbol is part of the property name, not a separate symbol.

Note: Environment variables whose names are not valid LOSE symbols cannot be included.

Note: A missing environment variable can either be omitted from the output or show a null string "" as its value.

Examples:

(os.env.LANG "en_US.UTF-8")
(os.env.TERM "xterm-256color")

(os.stdio symbol symbol symbol)

Three symbols in this order:

Each symbol shall give the type of the port in the current environment. The following choices are standard:

Knowing the types can be useful for debugging unusual I/O behavior in a particular environment.

Examples:

(os.stdio device pipe pipe)
(os.stdio socket terminal file)

(os.uname string string string)

Three strings from the Unix utsname structure in this order:

The value of this property is determined at run time, not at build time.

Examples:

(os.uname "Linux" "5.4.11_1" "x86_64")
(os.uname "Darwin" "18.7.0" "x86_64")
(os.uname "Haiku" "1" "BePC")
(os.uname "FreeBSD" "12.0-RELEASE-p6" "amd64")
(os.uname "MSYS_NT-10.0-18362" "3.0.7-338.i686" "i686")

Implementation-defined properties

Implementations are free to have any number of custom properties.

The names of implementation-defined properties shall start with the implementation’s Scheme ID and a dot.

For example, if Fantastic Scheme builds varied by the phase of the moon, it could have:

(fantastic.phase-of-the-moon waxing-crescent)

Complete example

If fantastic-scheme -V gives the following output:

Fantastic Scheme version 2.95
Copyright (C) 2003 Pyrrhic Ventures
This is free software; always read the label. There is NO warranty;
not even for buoyancy or fitness for high-velocity landings at sea.

"Shoot for the moon. Even if you miss, you'll crash on impact."

(command "fantastic-scheme")
(website "https://example.com/scheme")
(languages scheme r5rs r6rs r7rs)
(encodings utf-8 iso-8859-1)
(install-dir "/home/wiley/.local")
(scheme.id fantastic)
(scheme.srfi 0 1 2 176)
(scheme.features bits-64 little-endian r7rs ratios exact-complex full-unicode little-endian fantastic-scheme)
(scheme.path "/home/wiley/.local" "/usr/local/share/fantastic-scheme/1.x")
(c.version "GCC 4.2.1 Compatible Apple LLVM 10.0.1 (clang-1001.0.46.4)")
(c.compile "cc" "-Wall" "-O2" "-D" "SCHEME_UNIX")
(c.link "cc" "-lm")
(c.type-bits (int 32) (long 64) (pointer 64) (float 32) (double 64) (size_t 64))
(release "2.95")
(release.date "2003-06-24")
(release.name "Sheer lunacy")
(build.date "2020-02-14T22:25+02:00")
(build.platform "Darwin-x86_64-cc")
(build.configure "--prefix=/home/wiley/.local")
(build.git.tag "draft-2")
(build.git.branch "for-draft-3")
(build.git.commit "96d6322")
(build.git.modified "implementation/scheme.c")
(os.stdio terminal terminal terminal)
(os.uname "Darwin" "18.7.0" "x86_64")
(os.env.LANG "en_FI.UTF-8")
(os.env.TERM "dumb")
(fantastic.phase-of-the-moon waxing-crescent)

It is parsed into the following association list in Scheme:

((command "fantastic-scheme")
 (website "https://example.com/scheme")
 (languages scheme r5rs r6rs r7rs)
 (encodings utf-8 iso-8859-1)
 (install-dir "/home/wiley/.local")
 (scheme.id fantastic)
 (scheme.srfi 0 1 2 176)
 (scheme.features bits-64 little-endian r7rs ratios exact-complex full-unicode little-endian fantastic-scheme)
 (scheme.path "/home/wiley/.local" "/usr/local/share/fantastic-scheme/1.x")
 (c.version "GCC 4.2.1 Compatible Apple LLVM 10.0.1 (clang-1001.0.46.4)")
 (c.compile "cc" "-Wall" "-O2" "-D" "SCHEME_UNIX")
 (c.link "cc" "-lm")
 (c.type-bits (int 32) (long 64) (pointer 64) (float 32) (double 64) (size_t 64))
 (release "2.95")
 (release.date "2003-06-24")
 (release.name "Sheer lunacy")
 (build.date "2020-02-14T22:25+02:00")
 (build.platform "Darwin-x86_64-cc")
 (build.configure "--prefix=/home/wiley/.local")
 (build.git.tag "draft-2")
 (build.git.branch "for-draft-3")
 (build.git.commit "96d6322")
 (build.git.modified "implementation/scheme.c")
 (os.stdio terminal terminal terminal)
 (os.uname "Darwin" "18.7.0" "x86_64")
 (os.env.LANG "en_FI.UTF-8")
 (os.env.TERM "dumb")
 (fantastic.phase-of-the-moon waxing-crescent))

Implementation

Writing the version information can be as simple as calling write or equivalent with suitable input.

The following is a fully functional parser.

(define (read-version-alist in)
  (let skip-until-paren ((line-start? #t))
    (let ((c (peek-char in)))
      (if (not (or (eof-object? c) (and line-start? (char=? c #\())))
          (skip-until-paren (eqv? #\newline (read-char in)))
          (let read-all ((xs '()))
            (let ((x (read in)))
              (if (eof-object? x) (reverse xs) (read-all (cons x xs))))))))))

A full sample implementation is available at:

github.com/scheme-requests-for-implementation/srfi-176

It has plenty of code to gather, read and write version information in common situations:

Acknowledgements

Thanks to John Cowan for detailed feedback on all aspects of this SRFI.

Thanks to Marc Feeley and John for detailed discussions of S-expressions that can be parsed from portable shell scripts.

Thanks to Arthur Gleckler for questioning why the output of existing version flags is extended instead of making a new flag exclusively for machine-parseable output.

This SRFI started off as one of those "what if we made this simple tweak" hunches. It has now reached a ludicrous length considering the triviality of the topic. I am grateful to anyone who may want to use LOSE for another application. It will do at least a little to justify the effort spent.

Copyright

Copyright © Lassi Kortela (2019)

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