# The Sx interface

An `Sx.T` is a symbolic expression represented as a recursive linked list structure, as in Lisp. This interface provides routines for reading and printing symbolic expressions, as well as some convenience procedures for manipulating them. The syntax of an `Sx.T` is as follows:

     Sx = Char | Text | Int | Real | Longreal | Extended
        | Atom | Boolean | "(" List ")".

     List =  {Sx}.

symbolic expression

A `Char` is a Modula-3 character literal; the corresponding `Sx.T` is of type `REF CHAR`.

A `Text` is a Modula-3 text literal. The corresponding `Sx.T` is a `TEXT`.

An `Int` is a Modula-3 integer literal, possibly preceded by a plus sign (`+`) or minus sign (`-`). The corresponding `Sx.T` is of type `REF INTEGER`.

A `Real`, `Longreal`, or `Extended` is a floating-decimal number parsed using the grammar for `Float` specified in the `Lex` interface. The corresponding `Sx.T` is of type `REF REAL`, `REF LONGREAL` or `REF EXTENDED`, depending on whether the letter introducing the exponent is `'e'`, `'d'`, or `'x'`. If there is no exponent, the result will be of type `REF REAL`.

An `Atom` is either (1) a Modula-3 identifier, or (2) a non-empty sequence of characters from the set

      ! # $ % & * + - . / : < = > ? @ [ ] ^ _ { } ~

or (3) a sequence of characters and escape sequences surrounded by vertical bars (`|`s). The escape sequences are the same as those allowed in Modula-3 text literals, with the addition of \| to allow an atom to contain `|`. In all three cases, the corresponding `Sx.T` is an `Atom.T`.

For example, the following are valid atoms:

     A1
     +=
     |1\||

A `Boolean` is either `TRUE` or `FALSE`; the corresponding `Sx.T` is of type `Atom.T`; in other words, this is not a distinct type.

The `Sx.T` corresponding to a `List` is a `RefList.T` containing the items of the list in order.

The tokens of an `Sx.T` can be separated by arbitrary sequences of blanks, tabs, newlines, carriage returns, form feeds, and vertical tabs, which are ignored. (These are the same whitespace characters that are ignored between tokens of a Modula-3 program.) They can also be separated by comments, which begin with a semicolon and end with newline.

The syntax of tokens can be extended with the `SetReadMacro` procedure.

    INTERFACE Sx;

    IMPORT Atom, Rd, RefList, Thread, Wr;

    TYPE T = REFANY;

    EXCEPTION
      ReadError(TEXT);
      PrintError(TEXT);

    PROCEDURE FromChar(c: CHAR): REF CHAR;

Return a `Char` with value `c`.

    PROCEDURE FromInt(i: INTEGER): REF INTEGER;

Return an `Int` with value `i`.

    PROCEDURE FromReal(r: REAL): REF REAL;

Return a `Real` with value `r`.

    PROCEDURE FromLongReal(r: LONGREAL): REF LONGREAL;

Return a `Longreal` with value `r`.

    PROCEDURE FromExtended(r: EXTENDED): REF EXTENDED;

Return an `Extended` with value `r`.

    PROCEDURE FromBool(b: BOOLEAN): Atom.T;

Return a `Boolean`. If `b` is `TRUE`, return `Sx.True`. Otherwise, return `Sx.False`.

The `From...` procedures do not necessarily perform an allocation: if the same value is passed to two calls, the same reference may be returned. As a consequence, clients should not modify the referent of a reference returned by any of these procedures.

Each `REF CHAR`, `REF INTEGER`, `REF REAL`, `REF LONGREAL`, `REF EXTENDED`, `TEXT`, or `Atom.T`, no matter how constructed, is an `Sx.T`.

    VAR (*CONST*) True, False: Atom.T;

`True = Atom.FromText(*TRUE*)`, `False = Atom.FromText(*FALSE*)`.

    PROCEDURE Read(rd: Rd.T; syntax: Syntax := NIL): T
      RAISES {ReadError, Rd.EndOfFile, Thread.Alerted};

Read and return a symbolic expression from `rd`, ignoring whitespace and comments. If `syntax` is `NIL`, use the syntax described above; otherwise use any read macros that have been registered in `syntax`.

    PROCEDURE ReadDelimitedList(
        rd: Rd.T; delim : CHAR; syntax: Syntax := NIL): RefList.T
      RAISES {ReadError, Thread.Alerted};

Repeatedly read symbolic expressions from `rd`, ignoring whitespace and comments, until the next character is `delim`; consume the delimiter and return the list of symbolic expressions that were read. Raise `ReadError` if there is a syntax error, including unexpected end of file.

    PROCEDURE Print(
        wr: Wr.T;
        sx: T;
        maxDepth: CARDINAL := LAST(CARDINAL);
        maxLength: CARDINAL := LAST(CARDINAL))
      RAISES {PrintError, Wr.Failure, Thread.Alerted};

Print the symbolic expression `sx` on the writer `wr`, assuming the standard syntax.

Each sublist will contain no more than `maxLength` elements; extra elements are replaced by an ellipsis (three dots). Any sublist nested at a depth greater than `maxDepth` is also replaced by an ellipsis. `Print` inserts `|` around atoms if necessary to ensure that they are readable. `Print` does not insert line-breaks or indentation to produce a human-readable (``pretty-printed'') format for large symbolic expressions.

`Print` will raise `PrintError` if it tries to print something that is not ``printable'' (as defined below). If a list contains an unprintable element that is beyond the limits established by `maxDepth` and `maxLength`, `PrintError` may or may not be raised.

An object is said to be ``printable'' if it satisfies the following hypothetical predicate:

      PROCEDURE Printable(x: REFANY): BOOLEAN =
        BEGIN
          TYPECASE x OF
          | NULL, REF CHAR, TEXT, REF INTEGER, REF REAL,
            REF LONGREAL, REF EXTENDED, Atom.T =>
              RETURN TRUE
          | RefList.T (list) => RETURN Printable(list.head) AND
                                       Printable(list.tail)
          ELSE
              RETURN FALSE
          END
       END Printable;

`Read(rd,NIL)` is guaranteed to return a printable value unless it raises an exception. Assuming the defaults for `syntax`, `maxDepth`, and `maxLength`, and assuming no exceptions are raised, `Read` and `Print` are ``inverses''.

    TYPE Syntax <: REFANY;

A `Syntax` is a partial map from characters to read macros.

    PROCEDURE CopySyntax(s: Syntax := NIL): Syntax;

Allocate and return a new syntax table whose contents are the same as `s` or, if `s = NIL`, the same as the standard syntax table. The standard syntax table has no read macros.

    PROCEDURE SetReadMacro(s: Syntax; ch: CHAR; m: ReadMacro);

Set `s[ch] := m`. It is a checked runtime error if `s = NIL`, if `ch` is a whitespace character, or if `ch = ';'`. It is allowed for `m` to be `NIL`; this has the effect of removing the mapping, if any, from `ch` to a readmacro.

    TYPE ReadMacro = OBJECT METHODS
        read(rd: Rd.T; s: Syntax): RefList.T
          RAISES {ReadError, Thread.Alerted}
      END;

If you pass a `Syntax` `s` to `Read` or `ReadDelimitedList`, then the reading algorithm is modified as follows. After skipping whitespace and comments, and before reading a token, the next character in the input stream is consumed and examined. If `s` defines a read macro for this character, then this read macro is called with the same arguments that were passed to `Read` or `ReadDelimitedList`. The resulting list is spliced into the current list being built. In particular, if the macro returns `NIL`, then everything it read is ignored; if the macro returns a single-element list, then that single element is inserted into the list being built. `ReadError` is raised if the macro returns a non-list or if it returns a multi-element list in a context where no list is being built, such as at the top level of `Read`.

For example, the following program fragment constructs a syntax table that extends the standard syntax in two ways. First, additional comments are supported by ignoring all characters between `{` and `}`. Second, an expression of the form `[e1~...~en]` is turned into the list `(ARRAY e1~...~en)`:

     VAR syn := CopySyntax(); BEGIN
       SetReadMacro(syn, '{',
         NEW(ReadMacro, read := ReadComment));
       SetReadMacro(syn, '[',
         NEW(ReadMacro, read := ReadArray));
       ...

     PROCEDURE ReadComment(
         self: ReadMacro; rd: Rd.T; <* UNUSED *> s: Syntax)
       : RefList.T =
       BEGIN
         WHILE NOT Rd.EOF() AND Rd.GetChar(rd) # '}' DO
           (* SKIP *)
         END;
         RETURN NIL
       END ReadComment;

     VAR (*CONST*) arrayAtm := Atom.FromText("ARRAY");

     PROCEDURE ReadArray(self: ReadMacro; rd: Rd.T; s: Syntax)
       : RefList.T =
       VAR elements := ReadDelimitedList(rd, ']', s);
       BEGIN
         RETURN RefList.List1(RefList.Cons(arrayAtm, elements))
       END ReadArray;

The call to `RefList.List1` in `ReadArray` is important. If it were omitted, then the text

     (a b [c d])

would be read as

     (a b ARRAY c d)

instead of the intended

     (a b (ARRAY c d)).

    END Sx.