# The Rd interface

An `Rd.T` (or ``reader'') is a character input stream. The basic operation on a reader is `GetChar`, which returns the source character at the ``current position'' and advances the current position by one. Some readers are ``seekable'', which means that they also allow setting the current position anywhere in the source. For example, readers from random access files are seekable; readers from terminals and sequential files are not. character input stream

input stream

stream input

reader

Some readers are ``intermittent'', which means that the source of the reader trickles in rather than being available to the implementation all at once. For example, the input stream from an interactive terminal is intermittent. An intermittent reader is never seekable.

Abstractly, a reader `rd` consists of

     len(rd)           the number of source characters
     src(rd)           a sequence of length "len(rd)+1"
     cur(rd)           an integer in the range "[0..len(rd)]"
     avail(rd)         an integer in the range "[cur(rd)..len(rd)+1]"
     closed(rd)        a boolean
     seekable(rd)      a boolean
     intermittent(rd)  a boolean

These values are not necessarily directly represented in the data fields of a reader object. In particular, for an intermittent reader, `len(rd)` may be unknown to the implementation. But in principle the values determine the state of the reader.

The sequence `src(rd)` is zero-based: `src(rd)[i]` is valid for `i` from 0 to `len(rd`). The first `len(rd)` elements of `src` are the characters that are the source of the reader. The final element is a special value `eof` used to represent end-of-file. The value `eof` is not a character.

The value of `cur(rd)` is the index in `src(rd)` of the next character to be returned by `GetChar`, unless `cur(rd) = len(rd)`, in which case a call to `GetChar` will raise the exception `EndOfFile`.

The value of `avail(rd)` is important for intermittent readers: the elements whose indexes in `src(rd)` are in the range `[cur(rd)..avail(rd)-1]` are available to the implementation and can be read by clients without blocking. If the client tries to read further, the implementation will block waiting for the other characters. If `rd` is not intermittent, then `avail(rd)` is equal to `len(rd)+1`. If `rd` is intermittent, then `avail(rd)` can increase asynchronously, although the procedures in this interface are atomic with respect to such increases.

The definitions above encompass readers with infinite sources. If `rd` is such a reader, then `len(rd)` and `len(rd)+1` are both infinity, and there is no final `eof` value.

Every reader is a monitor; that is, it contains an internal lock that is acquired and held for each operation in this interface, so that concurrent operations will appear atomic. For faster, unmonitored access, see the `UnsafeRd` interface.

If you are implementing a long-lived reader class, such as a pipe or TCP stream, the index of the reader may eventually overflow, causing the program to crash with a bounds fault. We recommend that you provide an operation to reset the reader index, which the client can call periodically.

    INTERFACE Rd;

    IMPORT AtomList;
    FROM Thread IMPORT Alerted;

    TYPE T <: ROOT;

    EXCEPTION EndOfFile; Failure(AtomList.T);

Since there are many classes of readers, there are many ways that a reader can break---for example, the connection to a terminal can be broken, the disk can signal a read error, etc. All problems of this sort are reported by raising the exception `Failure`. The documentation of a reader class should specify what failures the class can raise and how they are encoded in the argument to `Failure`.

Illegal operations cause a checked runtime error.

    PROCEDURE GetChar(rd: T): CHAR
      RAISES {EndOfFile, Failure, Alerted};

Return the next character from `rd`. More precisely, this is equivalent to the following, in which `res` is a local variable of type `CHAR`:

     IF closed(rd) THEN Cause checked runtime error END;
     Block until "avail(rd) > cur(rd)";
     IF cur(rd) = len(rd) THEN
       RAISE EndOfFile
     ELSE
       res := src(rd)[cur(rd)]; INC(cur(rd)); RETURN res
     END

Many operations on a reader can wait indefinitely. For example, `GetChar` can wait if the user is not typing. In general these waits are alertable, so each procedure that might wait includes `Thread.Alerted` in its `RAISES` clause.

    PROCEDURE EOF(rd: T): BOOLEAN RAISES {Failure, Alerted};

Return `TRUE` iff `rd` is at end-of-file. More precisely, this is equivalent to:

     IF closed(rd) THEN Cause checked runtime error END;
     Block until "avail(rd) > cur(rd)";
     RETURN cur(rd) = len(rd)

Notice that on an intermittent reader, `EOF` can block. For example, if there are no characters buffered in a terminal reader, `EOF` must wait until the user types one before it can determine whether he typed the special key signalling end-of-file. If you are using `EOF` in an interactive input loop, the right sequence of operations is:

1. prompt the user;
2. call `EOF`, which probably waits on user input;
3. presuming that `EOF` returned `FALSE`, read the user's input.

    PROCEDURE UnGetChar(rd: T) RAISES {};

``Push back'' the last character read from `rd`, so that the next call to `GetChar` will read it again. More precisely, this is equivalent to the following:

     IF closed(rd) THEN Cause checked runtime error END;
     IF cur(rd) > 0 THEN DEC(cur(rd)) END

except there is a special rule: `UnGetChar(rd)` is guaranteed to work only if `GetChar(rd)` was the last operation on `rd`. Thus `UnGetChar` cannot be called twice in a row, or after `Seek` or `EOF`. If this rule is violated, the implementation is allowed (but not required) to cause a checked runtime error.

    PROCEDURE CharsReady(rd: T): CARDINAL RAISES {Failure};

Return some number of characters that can be read without indefinite waiting. The ``end of file marker'' counts as one character for this purpose, so `CharsReady` will return 1, not 0, if `EOF(rd)` is true. More precisely, this is equivalent to the following:

     IF closed(rd) THEN Cause checked runtime error END;
     IF avail(rd) = cur(rd) THEN
       RETURN 0
     ELSE
       RETURN some number in the range "[1~..~avail(rd) - cur(rd)]"
     END;

Warning: `CharsReady` can return a result less than `avail(rd) - cur(rd)`; also, more characters might trickle in just as `CharsReady` returns. So the code to flush buffered input without blocking requires a loop:

     LOOP
       n := Rd.CharsReady(rd);
       IF n = 0 THEN EXIT END;
       FOR i := 1 TO n DO EVAL Rd.GetChar(rd) END
     END;

    PROCEDURE GetSub(rd: T; VAR (*OUT*) str: ARRAY OF CHAR)
      : CARDINAL RAISES {Failure, Alerted};

Read from `rd` into `str` until `rd` is exhausted or `str` is filled. More precisely, this is equivalent to the following, in which `i` is a local variable:

     i := 0;
     WHILE i # NUMBER(str) AND NOT EOF(rd) DO
       str[i] := GetChar(rd); INC(i)
     END;
     RETURN i

    PROCEDURE GetSubLine(rd: T; VAR (*OUT*) str: ARRAY OF CHAR)
      : CARDINAL RAISES {Failure, Alerted};

Read from `rd` into `str` until a newline is read, `rd` is exhausted, or `str` is filled. More precisely, this is equivalent to the following, in which `i` is a local variable:

     i := 0;
     WHILE
       i # NUMBER(str) AND
       (i = 0 OR str[i-1] # '\n') AND
       NOT EOF(rd)
     DO
       str[i] := GetChar(rd); INC(i)
     END;
     RETURN i

Note that `GetLine` strips the terminating line break, while `GetSubLine` does not.

    PROCEDURE GetText(rd: T; len: CARDINAL): TEXT
      RAISES {Failure, Alerted};

Read from `rd` until it is exhausted or `len` characters have been read, and return the result as a `TEXT`. More precisely, this is equivalent to the following, in which `i` and `res` are local variables:

     res := ""; i := 0;
     WHILE i # len AND NOT EOF(rd) DO
       res := res & Text.FromChar(GetChar(rd));
       INC(i)
     END;
     RETURN res

    PROCEDURE GetLine(rd: T): TEXT
      RAISES {EndOfFile, Failure, Alerted};

If `EOF(rd)` then raise `EndOfFile`. Otherwise, read characters until a line break is read or `rd` is exhausted, and return the result as a `TEXT`---but discard the line break if it is present. A line break is either ``\n`` or ``\r\n`` More precisely, this is equivalent to the following, in which `ch` and `res` are local variables:

     IF EOF(rd) THEN RAISE EndOfFile END;
     res := ""; ch := '\000'; (* any char but newline *)
     WHILE ch # '\n' AND NOT EOF(rd) DO
       ch := GetChar(rd);
       IF ch = '\n' THEN
         IF NOT Text.Empty(res) AND
             Text.GetChar(res, Text.Length(res)-1) = '\r' THEN
           res := Text.Sub(res, 0, Text.Length(res)-1)
         END
       ELSE
         res := res & Text.FromChar(ch)
       END
     RETURN res

    PROCEDURE Seek(rd: T; n: CARDINAL) RAISES {Failure, Alerted};

This is equivalent to:

     IF closed(rd) OR NOT seekable(rd) THEN
       Cause checked runtime error
     END;
     cur(rd) := MIN(n, len(rd))

    PROCEDURE Close(rd: T) RAISES {Failure, Alerted};

Release any resources associated with `rd` and set `closed(rd) := TRUE`. The documentation of a procedure that creates a reader should specify what resources are released when the reader is closed. This leaves `rd` closed even if it raises an exception, and is a no-op if `rd` is closed.

    PROCEDURE Index(rd: T): CARDINAL RAISES {};

This is equivalent to:

     IF closed(rd) THEN Cause checked runtime error END;
     RETURN cur(rd)

    PROCEDURE Length(rd: T): INTEGER RAISES {Failure, Alerted};

This is equivalent to:

     IF closed(rd) THEN
       Cause checked runtime error
     END;
     RETURN len(rd)

If `len(rd)` is unknown to the implementation of an intermittent reader, `Length(rd)` returns -1.

    PROCEDURE Intermittent(rd: T): BOOLEAN RAISES {};
    PROCEDURE Seekable(rd: T): BOOLEAN RAISES {};
    PROCEDURE Closed(rd: T): BOOLEAN RAISES {};

Return `intermittent(rd)`, `seekable(rd)`, and `closed(rd)`, respectively. These can be applied to closed readers.

    END Rd.