by Per Bothner (SRFI 163), Peter McGoron (design), John Cowan (editor and steward)
This SRFI is currently in draft status. Here is an explanation of each status that a SRFI can hold. To provide input on this SRFI, please send email to srfi-268@nospamsrfi.schemers.org. To subscribe to the list, follow these instructions. You can access previous messages via the mailing list archive.
This is a specification of a lexical syntax
for multi-dimensional arrays. Textually it is an alteration of
SRFI 163, which
is an extension of the
Common Lisp array reader syntax
to handle non-zero lower bounds and optional uniform element types
(compatibly with SRFI 4
and SRFI 160).
It can be used in conjunction with
SRFI 25,
SRFI 122,
or SRFI 231.
There are recommendations for output formatting,
read-array and write-array
procedures, and a suggested
format-array procedure.
It is desirable to have a lexical syntax for reading and writing multi-dimensional arrays, so they can not only appear as literal values in code (where they are especially useful for testing and noodling at the REPL) but also be read from and written to data files. Basing it on the Common Lisp syntax makes sense, and is what has been done by all known existing implementations.
However, Common Lisp arrays do not support non-zero lower bounds, and Common Lisp's handling of specialized (uniform) arrays is very different from that of known Scheme implementations. Various Scheme extensions have been proposed. SRFI 58 is one proposal, but it does not handle non-zero lower bounds, and its type-specifier syntax is stylistically incompatible with the literals proposed for uniform vectors in SRFIs 4 and 160.
Unlike this SRFI, SRFI 163 is based on lower bound and length rather than lower bound and (exclusive) upper bound, like the rest of Scheme. It also requires the tag and bounds to be a single token, which is problematic for high-dimensional arrays, and uses a special notation for the bounds rather than a list.
An array literal consists of the following parts:
an #a
or #A
immediately followed by an optional tag
that specifies the type of the elements of the array.
An implementation that supports the literal syntax of
SRFI 4
or SRFI 160
should allow the TAG or @
from those specifications as a tag
case-insensitively. These tags are:
u8,
u16,
u32,
s8,
s16,
s32,
s64,
f32,
f64,
and (in SRFI 160 only)
c64 and
c128.
The tags f16 and char
for 16-bit float arrays and character arrays
may also be understood. If the tag is omitted,
the array may contain elements of any type.
An unknown tag has implementation-specified behavior.
After the tag comes the bounds, a list each of whose elements is either an integer literal giving the upper bound of a dimension (in which case the lower bound is 0), or a list of two integer literals giving the lower and upper bounds. This is followed by a datum containing array elements organized into dimensions using parentheses. The datum contains the elements in a nested-list format: a one-dimensional array uses a single list, a two-dimensional array uses a list of lists, and so on. The elements are in lexicographic order. A zero-dimensional array is followed by a datum of any type that the tag supports.
The number of dimensions is inferred
from the length of bounds, as
it would be impossible from the
datum alone to tell if
(1 2)
represents a one-dimensional array with integer elements,
or a zero-dimensional array whose sole element is a list.
Whitespace is permitted before either the lower bounds or
the list of array elements or both,
but not between "#a" and the tag.
A single space before the datum
is recommended style, even if the datum starts
with a delimiter.
(Compatibility note: Common Lisp does not require a space after
#0a.)
Here is an EBNF grammar for array literals (ignoring case and whitespace):
array-literal ::= "#a" tag bounds datum
tag ::= letter (letter | digit)*
bounds = "(" bound* ")"
bound := (number | "(" number number ")")
Note that the numbers must represent exact integers.
A 2x2 array of 32-bit unsigned integers:
#au32(2 2) ((10 11) (20 21))
Another way to write the same array:
#au32((0 2) (0 2)) ((10 11) (20 21))
A different u32 array with index ranges 2..3 and 3..4 inclusive:
#au32((2 4) (3 5)) ((a b) (c d))
Some zero-dimensional arrays:
#a() sym #af32() 237.0
Some empty arrays:
#a(1 0) () #a((2 1) (2 3)) (() ())
Here is a (hopefully correct) array of the 4D Levi-Civita symbol, which is defined where ε_ABCD = 1 if (A,B,C,D) is an even permutation of (1,2,3,4), -1 if it is odd, and zero if it is neither:
(define ε #ai32 ((1 5) (1 5) (1 5) (1 5)) ((((0 0 0 0) (0 0 0 0) (0 0 0 0) (0 0 0 0)) ((0 0 0 0) (0 0 0 0) (0 0 0 1) (0 0 -1 0)) ((0 0 0 0) (0 0 0 -1) (0 0 0 0) (0 1 0 0)) ((0 0 0 0) (0 0 1 0) (0 -1 0 0) (0 0 0 0))) (((0 0 0 0) (0 0 0 0) (0 0 0 -1) (0 0 1 0)) ((0 0 0 0) (0 0 0 0) (0 0 0 0) (0 0 0 0)) ((0 0 0 1) (0 0 0 0) (0 0 0 0) (-1 0 0 0)) ((0 0 -1 0) (0 0 0 0) (1 0 0 0) (0 0 0 0))) (((0 0 0 0) (0 0 0 1) (0 0 0 0) (0 -1 0 0)) ((0 0 0 -1) (0 0 0 0) (0 0 0 0) (1 0 0 0)) ((0 0 0 0) (0 0 0 0) (0 0 0 0) (0 0 0 0)) ((0 1 0 0) (-1 0 0 0) (0 0 0 0) (0 0 0 0))) (((0 0 0 0) (0 0 -1 0) (0 1 0 0) (0 0 0 0)) ((0 0 1 0) (0 0 0 0) (-1 0 0 0) (0 0 0 0)) ((0 -1 0 0) (1 0 0 0) (0 0 0 0) (0 0 0 0)) ((0 0 0 0) (0 0 0 0) (0 0 0 0) (0 0 0 0))))
read-array [ input-port ]Reads an array literal from input-port
(by default the value of (current-input-port))
and returns the corresponding array.
Possible extension:
It might make source code more readable if it could contain
array literals in the form produced by format-array.
An implementation has not been attempted, but it seems doable:
If the first non-whitespace character after the header is a
box drawing character, then use those box-drawing characters to figure out
each element cell
. (Nested arrays make this a bit more complicated.)
If a cell has multiple lines,
convert it to a string, with a newline between each line.
Then recursively read each element.
An attempt to read a literal with too many or too few elements in the datum has undefined effect.
write-array array [ output-port ]Writes the array as an array literal to output-port
(by default the value of (current-output-port)).
When the write, write-shared,
or write-simple procedures are asked
to write an array, the output is the same as write-array,
A single space should be printed
before the datum.
Printing with display may format the array in the same way as write (except using display for each element),
or in some more readable way, perhaps by
using the format-array procedure.
format-array array [ port ]This is an optional procedure that produces a niceprettydisplay forarray. Using Unicode "box drawing" characters is suggested but not required.
If port is an output port, the formatted output is written into
that port.
Otherwise, port must be a boolean
(one of #t or #f).
If the port is #t, output is to
the value of (current-output-port).
If the port is #f or no port is specified,
the output is returned as a string.
If the port is specified and is #t or an output-port,
the result of the format-array procedure is unspecified.
(This convention matches that of SRFI 48 format.)
Zero-dimensional arrays are written without boxing; empty arrays are written using empty boxes.
The top line includes the tag and the bounds if there is room for them possibly in abbreviated form.
If the number of dimensions is more than 2, then each "layer" is printed separated by double lines.
The literal #a((0 2) (0 3)) ((12 13) (21 22 23))
could be formatted as:
#a═══════╗ ║11│12│13║ ╟──┼──┼──╢ ║21│22│23║ ╚══╧══╧══╝
and this literal for a three-dimensional array:
#af32(2 3 4) (((1 2 3 4) (5 6 7 8)) ((9 10 11 12) (13 14 15 16)) ((17 18 19 20) (21 22 23 24)))
could be formatted as
#f32(2 3 4)═════════╗ ║ 1.0│ 2.0│ 3.0│ 4.0║ ╟────┼────┼────┼────╢ ║ 5.0│ 6.0│ 7.0│ 8.0║ ╠════╪════╪════╪════╣ ║ 9.0│10.0│11.0│12.0║ ╟────┼────┼────┼────╢ ║13.0│14.0│15.0│16.0║ ╠═──═╪══──╪═──═╪═──═╣ ║17.0│18.0│19.0│20.0║ ╟══──┼─══─┼──══┼─══─╢ ║21.0│22.0│23.0│24.0║ ╚════╧════╧════╧════╝
A sample implementation in R7RS Scheme (with SRFIs 1 and 231) can be found in the SRFI repository. It has been developed for Chibi Scheme, but should be portable to any R7RS implementation. SRFI 64 is required to run the included test suite.
format-array is not yet implemented.
© Per Bothner 2019, John Cowan, Wolfgang Corcoran-Mathe 2026.
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.