Go to the first, previous, next, last section, table of contents.


I/O Subsystem

Most programs need to perform input (reading data), output (writing data), or both in order to be useful. The OOC library attempts to simplify these Input/Output (I/O) operations by providing several related abstractions that relate to I/O.

The two primary abstractions are channels and riders. The entire I/O Subsystem of the OOC library revolves around these two concepts.

Input/Output Overview

In order to provide uniform access to different sorts of devices (files, program arguments, sockets, and so forth) the I/O subsystem consists of several interrelated class hierarchies of related abstractions. The two primary abstractions are channels and riders.

The intention of these abstractions is to allow similar handling of devices; even, potentially, to the level of such exotic devices as a screen pixelmap, a windowing system, or a speech output device.

The benefit of this unified I/O handling approach allows a programmer to write procedures that operate on any kind of I/O channel. A program writing to stdout could be easily converted to allow writing into a file instead. Or, similarly, it could serve as a remote telnet connection.

All channels can share the same operations for text based I/O (ReadInt, WriteInt, and so forth). Riders (readers and writers) can then be attached to the channel, allowing standard I/O, regardless of the actual device used.

I/O Concepts

There are several conceptual layers to the I/O process that are modeled by various abstractions in the OOC library. Their relationships are shown here:

  data locations - where data resides (raw data).  
      |  (e.g., hard disk, memory block, keyboard port, RS232 links)
      |
      |
  channels - connections to data locations in the form of byte streams.  
      |  (e.g., files - on disk and in memory, pipes, 
      |   TCP/IP connections)
      |
  basic riders - basic operations on bytes.  
      | (e.g., SetPos, ReadByte, ReadBytes, WriteByte, WriteBytes)
      |
      |
  mappers - translations of high level data to and from a byte stream.  
        (e.g., binary reader/writer, text reader/writer)

A data location (or simply location) is a source of input data or destination of output data. It it the physical or logical place where data exists; say a hard disk, or keyboard buffer.

A channel is a connection to a data location. A channel is envisioned as a contiguous sequence, or stream, of bytes. Channels may be sequential as in the case of terminal I/O, a TCP stream, pipes, and so forth; or positionable like Files and ProgramArgs.

Riders are associated with a channel and provide read and write access of a location; they operate directly on a stream of bytes (i.e., a channel). Multiple readers and writers can exist for a single channel.

A mapper is a high-level rider; it operates on a particular format of data, like textual or binary representation of elementary data types. Mappers rely on the primitive operations of basic riders to build more complex operations.

The benefit of differentiating these layers is allowing a way to distinguish between the simple access layer, that doesn't know a thing about the byte stream being read or written, and the interpretation layer that transforms bytes into useful data.

Riders and Mappers

The term rider can be used to describe any operator that provides read or write operations on channels. However, there is a distinction between low-level (basic riders) and high-level operations (mappers).

Basic riders are associated directly with a particular channel type. Notice that the rider, not the channel, has a position property (the place where reading or writing occurs). Several riders can operate on the same channel at the same time. Riders may provide sequential or positionable (i.e., random) access depending on the type of channel.

In general, there are only two types of basic riders: readers and writers.

Mappers are similar to basic riders and, like riders, may be either readers or writers. They translate between a sequence of data items and an uninterpreted sequence of bytes. But mappers may also provide more sophisticated read/write operations; for instance, scanners are mappers that can distinguish between different types of data within a particular format, and then read in that data based on the type. See section Module TextRider, and See section Module BinaryRider, for descriptions of the simplest mappers.

Please note: a basic rider is dependent on the implementation of its channel, (e.g., a file rider must know how to position itself within a file). When a channel type is extended, usually the rider must be extended as well.

Mappers, on the other hand, are independent of a particular channel's implementation; mappers use riders in their implementation. This independence means that every mapper may be used on any compatible rider without the need to implement all combinations of mappers and riders individually.

Locators and Opening Channels

Before reading or writing to a location, a connection must be created by opening a channel on the location. The operations for opening channels are collectively called locators. The primary function of locators is to resolve a data location (as specified by a file name, URL, etc.), and then open a channel to that location.

Locators may be simply a set of functions; for instance:

  PROCEDURE New* (...): ChannelType;

  PROCEDURE Old* (...): ChannelType;

For channels that correspond to a location that can be both read and changed, New() will create a new channel for the given data location, deleting all data previously contained in it. Old() will open a channel to existing data.

For channels representing a unidirectional byte stream (like output to/ input from terminal, or a TCP stream), only a procedure New() is provided. It will create a connection with the designated location.

The formal parameters of these procedures will normally include some kind of reference to the data being opened (e.g., a file name) and, optionally, flags that modify the way the channel is opened (e.g., read-only, write-only, etc). Their use (and therefore, interface) depends on the type of channel to be opened.

In more complex circumstances, actual locator types may be required; in that case, the locator type might provide type-bound procedures Old and New to create a new channel.

When finished reading to or writing from the location, the connection can be terminated by closing the channel ((each channel provides a Close method for this purpose; locators do not supply any close operations). This will free all resources allocated by the system for the channel. Once a channel is closed, no further input or output operations can be performed on it.

Please note: A channel implementation may limit the number of channels that can be open simultaneously. It's common for an OS to only support a limited number of open files or open sockets at the same time. See individual channel types for these limitations (if such limitations exist for that type).

Channels

This section describes the channel types provided by the OOC library. Each module contains both the channel and its associated basic riders. Constant values that are relevant to a particular channel type are also declared within the defining module.

Module Channel

Module Channel provides three abstract classes: Channel, Reader, and Writer.

All types and procedures declared in this module are considered abstract; they are never instanciated or called. Module Channel is of interest, however, because like all abstract classes, its types define the interface elements that are required for any concrete classes, which are derived from them.

Abstract class Channel is the base for all channel types.

Abstract classes Reader and Writer are the required basic rider types that must be declared for each channel. Notice that these define only read and write operations for sequences of bytes (see section Riders and Mappers).

See the various concrete channel classes for more detail and examples of usage (like section Module Files, section Module StdChannels, or section Module ProgramArgs). In particular, the chapter about Files can be read without any prior knowledge about channels.

Abstract Class Channel

Data type: Result = Msg.Msg
This is the result type for indication of error status, and includes the facilities for extracting error messages (see section Messages).

Abstract Class: Channel = POINTER TO ChannelDesc
This is the abstract base channel type. Channel types are used to connect to data locations (see section Input/Output Overview). Channel contains the following fields:
Field: res-: Result
res is the result (i.e., error flag) signalling failure of a call to NewReader, NewWriter, Flush, Close, etc. res is initialized to done when the channel is created. Every operation sets this to done if successful, or otherwise, res.code is set to an error value indicating the cause of the error (use either res.GetLText() or res.GetText() to get a plain text error description). See section Summary of Channel Constants, for a list of applicable error codes.
Field: readable-: BOOLEAN
readable is set to TRUE if, and only if, readers can be attached to this channel with NewReader.
Field: writable-: BOOLEAN
writable is set to TRUE if, and only if, writers can be attached to this channel with NewWriter.
Field: open-: BOOLEAN
open indicates the channel's status; that is, it is set to TRUE on channel creation, and set to FALSE by a call to Close. Closing a channel prevents all further read or write operations on it.
Method: (ch: Channel) Length (): LONGINT
Length returns the number of bytes of data for the channel ch. If ch represents a file, then this value is the file's size. If ch has no fixed length (e.g., because it's interactive), it returns noLength.
Method: (ch: Channel) GetModTime (VAR mtime: Time.TimeStamp)
GetModTime retrieves the modification time of the data location accessed by channel ch. If no such information is available, ch.res.code is set to noModTime; otherwise ch.res is set to done.
Method: (ch: Channel) NewReader (): Reader
This method attaches a new reader to the channel ch. The reader's position is set to the beginning of the channel, and its res field is initialized to done. ch.res is set to done on success and the new reader is returned. Otherwise, it returns NIL and ch.res.code is set to indicate the error cause. Please note: if the channel does not support multiple reading positions, the same reader is always returned.
Method: (ch: Channel) NewWriter (): Writer
This method attaches a new writer to the channel ch. The writer's position is set to the beginning of the channel, and its res field is initialized to done. ch.res is set to done on success and the new writer is returned. Otherwise, it returns NIL and ch.res.code is set to indicate the error cause. Please note: if the channel does not support multiple writing positions, the same writer is always returned.
Method: (ch: Channel) Flush
Flushes all buffers related to this channel. Any pending write operations are passed to the underlying OS and all buffers are marked as invalid. The next read operation will get its data directly from the channel instead of the buffer. If a writing error occurs, the field ch.res.code will be changed to writeError, otherwise ch.res is set to done. Please note: you must check the channel's res flag after an explicit Flush; none of the attached writers will indicate a write error in this case.
Method: (ch: Channel) Close
Flushes all buffers associated with ch, closes the channel, and frees all system resources allocated to it. This invalidates all riders attached to ch; they can't be used further. On success, if all read and write operations (including Flush) have completed successfully, ch.res is set to done. An opened channel can only be closed once, successive calls of Close are undefined. Please note: unlike the Oberon System all opened channels have to be closed explicitly. Otherwise resources allocated to them will remain blocked.
Method: (ch: Channel) ClearError
Sets the result flag ch.res to done.

Abstract Class Reader

Abstract Class: Reader = POINTER TO ReaderDesc
This is the abstract base reader type. Reader types are used to perform read operations on channels (see section Input/Output Overview). Reader contains the following fields:
Field: base-: Channel
base refers to the channel the reader is connected to.
Field: res-: Result
res is a result (error) flag that signals failure of a call to ReadByte, ReadBytes, or SetPos. res is initialized to done when creating a reader or by calling ClearError. The first failed read operation (or SetPos) changes this to indicate the error, all further calls to ReadByte, ReadBytes, or SetPos will be ignored until ClearError resets this flag. This means that the successful completion of an arbitrary complex sequence of read operations can be ensured by asserting that res equals done beforehand and also after the last operation. If res is not equal to done, res.code is set to the applicable error code. Use either of the methods res.GetLText() or res.GetText() to get a plain text error description of this error code. See section Summary of Channel Constants, for a list of applicable error codes.
Field: bytesRead-: LONGINT
bytesRead is set by ReadByte and ReadBytes to indicate the number of bytes that were successfully read.
Field: positionable-: BOOLEAN
positionable is set to TRUE if, and only if, the reader can be moved to another position with SetPos; for channels that can only be read sequentially, like input from the keyboard, this is set to FALSE.
Method: (r: Reader) Pos (): LONGINT
Returns the current reading position associated with the reader r in channel r.base, i.e., the index of the first byte that is read by the next call to ReadByte or ReadBytes. This procedure returns noPosition if the reader has no concept of a reading position (e.g., if it corresponds to input from keyboard), otherwise the result is non-negative.
Method: (r: Reader) Available (): LONGINT
Returns the number of bytes available for the next reading operation. For a file this is the length of the channel r.base minus the current reading position, for an sequential channel (or a channel designed to handle slow transfer rates) this is the number of bytes that can be accessed without additional waiting. The result is -1 if Close() was called for the channel (or the channel has been otherwise disconnected), or no more bytes are available. Please note: the number returned may be an approximation of the number of bytes that could be read at once; it could be lower than the actual value. For some channels or systems, this value may be as low as 1 even if more bytes are waiting to be processed.
Method: (r: Reader) SetPos (newPos: LONGINT)
Sets the reading position to newPos. Using a negative value of newPos, or calling this procedure for a reader that doesn't allow positioning, will set r.res.code to outOfRange. A value larger than the channel's length is legal, but the next read operation will most likely fail with an readAfterEnd error (unless the channel has grown beyond this position in the meantime). Calls to this procedure while r.res # done will be ignored; in particular, a call with r.res.code = readAfterEnd error will not reset res to done.
Method: (r: Reader) ReadByte (VAR x: SYSTEM.BYTE)
Reads a single byte from the channel r.base at the reading position associated with r and places it in x. The reading position is moved forward by one byte on success, and r.res is set to done. Otherwise, r.res.code indicates the error cause. Calling this procedure with the reader r placed at the end (or beyond the end) of the channel will set r.res.code to readAfterEnd. r.bytesRead will be 1 on success and 0 on failure. Calls to this procedure while r.res # done will be ignored.
Method: (r: Reader) ReadBytes (VAR x: ARRAY OF SYSTEM.BYTE; start, n: LONGINT)
Reads n bytes from the channel r.base at the reading position associated with r and places them in x beginning at index start. The reading position is moved forward by n bytes on success, and r.res is set to done. Otherwise, r.res.code indicates the error cause. Calling this procedure with the reader r positioned less than n bytes before the end of the channel will will set r.res.code to readAfterEnd. r.bytesRead will hold the number of bytes that were actually read (being equal to n on success). Calls to this procedure while r.res # done will be ignored. Pre-condition: n and start are non-negative. Also, there is enough space in array x, starting at index start, to hold n bytes.
Method: (r: Reader) ClearError
Sets the result flag r.res to done, re-enabling further read operations on r.

Abstract Class Writer

Abstract Class: Writer = POINTER TO WriterDesc
This is the abstract base writer type. Writer types are used to perform write operations on channels (see section Input/Output Overview). Writer contains the following fields:
Field: base-: Channel
This field refers to the channel the writer is connected to.
Field: res-: Result
res is a result (error) flag that signals failure of a call to WriteByte, WriteBytes, or SetPos. It is initialized to done when creating a writer or by calling ClearError. The first failed writing (or SetPos) operation sets res.code to indicate the error; all further calls to WriteByte, WriteBytes, or SetPos will be ignored until ClearError resets this flag. This means that the successful completion of an arbitrary complex sequence of write operations can be ensured by asserting that res equals done beforehand and also after the last operation. If res is not equal to done, res.code is set to the applicable error code. Use either of the methods res.GetLText() or res.GetText() to get a plain text error description of this error code. See section Summary of Channel Constants, for a list of applicable error codes. Please note: due to buffering, a write error may occur when flushing or closing the underlying channel; you have to check the channel's res field after any Flush() or the final Close() because a writer's res field may not indicate a write error in that case.
Field: bytesWritten-: LONGINT
Set by WriteByte and WriteBytes to indicate the number of bytes that were successfully written.
Field: positionable-: BOOLEAN
TRUE if, and only if, the writer can be moved to another position with SetPos; for channels that can only be written sequentially, like output to a terminal, this is FALSE.
Method: (w: Writer) Pos (): LONGINT
Returns the current writing position associated with the writer w in channel w.base, i.e., the index of the first byte that is written by the next call to WriteByte or WriteBytes. This procedure returns noPosition if the writer has no concept of a writing position (e.g., if it corresponds to output to terminal), otherwise the result is non-negative.
Method: (w: Writer) SetPos (newPos: LONGINT)
Sets the writing position to newPos. A negative value of newPos, or calling this procedure for a writer that doesn't allow positioning, will set w.res.code to outOfRange. A value larger than the channel's length is legal, however, the next write operation zero fills the intervening space. That is, the gap from the previous end of the channel to newPos are filled with 0X bytes. Calls to this procedure while w.res # done are ignored.
Method: (w: Writer) WriteByte (x: SYSTEM.BYTE)
Writes a single byte x to the channel w.base at the writing position associated with w. The writing position is moved forward by one byte on success, and r.res is set to done. Otherwise, w.res.code is set to indicate the error cause. w.bytesWritten will be 1 on success and 0 on failure. Calls to this procedure while w.res # done are ignored.
Method: (w: Writer) WriteBytes (VAR x: ARRAY OF SYSTEM.BYTE; start, n: LONGINT)
Writes n bytes from x, beginning at index start, to the channel w.base at the writing position associated with w. The writing position is moved forward by n bytes on success, and r.res is set to done. Otherwise, w.res.code is set to indicate the error cause. w.bytesWritten will hold the number of bytes that were actually written (being equal to n on success). Calls to this procedure while w.res # done are ignored. Pre-condition: n and start are non-negative. Also, this method requires that accessing n bytes in array x, starting from index start, will not go past the end of the array.
Method: (w: Writer) ClearError
Sets the result flag w.res to done, re-enabling further write operations on w.

Summary of Channel Constants

Constant: noLength
A result value for Channel.Length.

Constant: noPosition
A possible return value for Reader.Pos() or Writer.Pos() meaning that the reader or writer has no concept of a position (e.g., if it corresponds to input from keyboard or output to a terminal).

A specific channel implementation (e.g., see section Module Files) defines its own list of codes, containing aliases for the codes below (where appropriate) plus error codes of its own. These values are compared against the res.code field of the corresponding object (of types Channel, Reader, or Writer).

The methods res.GetLText() or res.GetText() can be used to translate any error code into a human readable message.

The following constant applies to the res field, and may be compared to it. (i.e., ch.res = done or ch.res # done.)

Constant: done
This indicates successful completion of the last operation.

If res is not equal to done, the following values may appear in res.code. These values apply to Channel, Reader, or Writer. Please note: These codes only cover the most typical errors.

Constant: invalidChannel
The channel isn't valid. For example, because it wasn't opened in the first place or was somehow corrupted.

Constant: writeError
A write error occured; usually this error happens with a writer, but for buffered channels this may also occur during a Flush or a Close.

Constant: noRoom
A write operation failed because there isn't any space left on the device. For example, the disk is full or you exeeded your quota; usually this error happens with a writer, but for buffered channels this may also occur during a Flush or a Close.

The following constants apply only to Reader.res.code and Writer.res.code:

Constant: outOfRange
SetPos has been called with a negative argument or it has been called on a rider that doesn't support positioning.

Constant: readAfterEnd
A call to ReadByte or ReadBytes has tried to access a byte beyond the end of the channel. This means that there weren't enough bytes left or the read operation started at (or after) the end.

Constant: channelClosed
The rider's channel has been closed, preventing any further read or write operations. This means there was a call to Channel.Close() (in which case, you probably made a programming error), or the channel has been otherwise disconnected (e.g., the process at the other end of the channel, say a pipe or TCP stream, closed the connection).

Constant: readError
An unspecified read error.

Constant: invalidFormat
Set by a mapper (e.g., TextRiders.Reader) if the byte stream at the current reading position doesn't represent an object of the requested type.

The following constants apply only to Channel.res.code:

Constant: noReadAccess
NewReader was called to create a reader on a channel that doesn't allow read access.

Constant: noWriteAccess
NewWriter was called to create a writer on a channel that doesn't allow write access.

Constant: closeError
An attempt to close the channel failed.

Constant: noModTime
No modification time is available for the given channel.

Constant: noTmpName
Creation of a temporary file failed because the system was unable to assign an unique name to it (closing or registering an existing temporary file beforehand might help in this case).

Constant: freeErrorCode
Free error code number. This is provided so that a specific channel implemenatation can start defining new error codes from this value.

Module Files

Most computer systems provide some way of storing persistent data--- information that exists between one program activation and the next. The most common way of accessing persistent data is through a file system. A file is generally a collection of data that is held on some physical medium like a hard disk or magnetic tape. A file system provides a means to manage files; grouping them logically into entities called directories, and otherwise accessing them through file names. As these are typical, basic computer concepts, this document will assume some familiarity with file systems.

Module Files provides facilities for accessing files using channel and rider abstractions. Files provides three related classes: File, Reader, and Writer. These classes are concrete subclasses of their conterparts in module Channel (see section Module Channel).

Class File is derived from the base channel type and adds additional methods for file specific operations. Files are probably the most frequently used channel implementation and, at the same time, the first channel to be used by a novice user. Therefore the description below incorporates all the relevant parts from the chapter about the abstract base type Channel.

As with all basic riders, Reader and Writer operate on sequences of bytes. Consequently, most of the time, after a file is opened, a mapper would be attached to provide more useful read/write operations (see section Module BinaryRider and section Module TextRider).

Please note: Most Unix systems only allow a fixed number of files (and sockets) to be open simultaneously. If this limit is reached, no new file can be opened or socket be created until an old file/socket is closed. For any POSIX compliant system at least 16 open files are supported, most implementations provide a much larger number.

Class File

Class File allows access to files as contiguous sequences of bytes.

Example:

VAR  f: Files.File;

f := Files.Old ("example.dat", {Files.read, Files.write}, res);
IF (res # Files.done) THEN
    (* Error processing: failed to open "old" file.  *)
END; ...

f.Close; (* Be sure to close the file so that resources are freed. *)

Class: File = POINTER TO FileDesc
This is the concrete subclass of Channel that corresponds to actual files. File inherits the following fields:
Field: res-: INTEGER
res is the result (i.e., error flag) signalling failure of a call to NewReader, NewWriter, Flush, Close, etc. res is initialized to done when the file is created. Every operation sets this to done if successful, or otherwise, res.code is set to an error value to indicate the cause of the error (use either res.GetLText() or res.GetText() to get a plain text error description). See section Summary of File Constants, for a list of applicable error codes.
Field: readable-: BOOLEAN
readable is set to TRUE if, and only if, readers can be attached to this file with NewReader.
Field: writable-: BOOLEAN
writable is set to TRUE if, and only if, writers can be attached to this file with NewWriter.
Field: open-: BOOLEAN
open indicates the file's status; that is, it is set to TRUE on file creation, and set to FALSE by a call to Close. Closing a file prevents all further read or write operations on it.

File inherits the following methods from the abstract class Channel:

Method: (f: File) Length (): LONGINT
Length returns the number of bytes of data for the file f. If f represents a genuine file, this value is the file's size. If f has no fixed length (e.g., because it's a FIFO special file), it returns noLength. Example:
(* For file,
 -rw-rw-r--   1 nikitin      8641 Jun  6 08:14 misc.txt
*)

VAR len: LONGINT;

len := f.Length();
    => len = 8641
Method: (f: File) GetModTime (VAR mtime: Time.TimeStamp)
GetModTime retrieves the modification time of the data location accessed by file f. If no such information is available, f.res.code is set to noModTime; otherwise, f.res is set to done. For more on time stamps See section Module Time. Example:
(* For file,
 -rw-rw-r--   1 nikitin      8641 Jun  6 08:14 misc.txt
*)

VAR fTime: Time.TimeStamp;

f.GetModTime(fTime);
    => fTime.days = 50605
    => fTime.msecs = 44064000
Method: (f: File) NewReader (): Reader
This method attaches a new (basic) reader to the file f (you will most likely never need to call this directly; you'd normally connect a mapper instead). The reader's position is set to the beginning of the file, and its res field is initialized to done. f.res is set to done on success and the new reader is returned. Otherwise, it returns NIL and f.res.code is set to indicate the error cause. Please note: if the file does not support multiple reading positions (e.g., because it's a FIFO special file), the same reader is always returned. Example:
VAR r: Files.Reader;

r := f.NewReader();
IF (f. res # Files.done) THEN
   (* Error processing:  failed to attach a new reader.  *)
END; 
Method: (f: File) NewWriter (): Writer
This method attaches a new writer to the file f (you will most likely never need to call this directly; you'd normally connect a mapper instead). The writer's position is set to the very start of the file, and its res field is initialized to done. f.res is set to done on success and the new writer is returned. Otherwise, it returns NIL and f.res.code is set to indicate the error cause. Please note: if the file does not support multiple writing positions (e.g., because it's a FIFO special file), the same writer is always returned. Example:
VAR w: Files.Writer;

w := f.NewWriter();
IF (f. res # Files.done) THEN
   (* Error processing:  failed to attach a new writer.  *)
END; 
Method: (f: File) Flush
Flushes all buffers related to this file. Any pending write operations are passed to the underlying OS and all buffers are marked as invalid. The next read operation will get its data directly from the channel instead of the buffer. If a writing error occurs, the field f.res.code will be set to writeError, otherwise, f.res is set to done. Please note: you must check the file's res flag after an explicit Flush; none of the attached writers will indicate a write error in this case. Example:
f.Flush;
IF (f.res # Files.done) THEN
   (* Error processing:  write error when flushing buffers. *)
END; 
Method: (f: File) Close
Flushes all buffers associated with f, closes the file, and frees all system resources allocated to it. This invalidates all riders attached to f; they can't be used further. On success, if all read and write operations (including Flush) have completed successfully, f.res is set to done. An opened file can only be closed once, successive calls of Close are undefined. Please note: unlike the Oberon System all opened Files have to be closed explicitly. Otherwise resources allocated to them will remain blocked. Example:
f.Close;
IF (f. res # Files.done) THEN
   (* Error processing:  error occured as file was closed.  *)
END; 
Method: (f: File) ClearError
Sets the result flag f.res to done. Example:
f.ClearError;
   => f.res = done

Besides its inherited methods, File has the following additional method:

Method: (f: File) Register
Registers the file f in the directory structure if it has been created with the Tmp procedure (see section File Locators). Registration happens atomically, i.e., it is guaranteed that any previously existing file is replaced by the newly registered one without any "in between" state. If the operation is interrupted, then either the old file still exists on the file system, or it has been replaced completely by the new one. Calling Tmp and Register successively has the same effect as calling New. Calling this procedure has no effect if the file f has been created with New or has been registered previously. Registration fails with an anonymousFile error if it was created by calling Tmp with an empty file name, and with a channelClosed error if f is closed. Example:
(* open named temporary file *)
f := Files.Tmp ("temp.fil", {Files.write}, res);

f.Close;
f.Register;
   => f.res.code = channelClosed
res.GetText (str);
   => str = "File has been closed"

(* open anonymous temporary file *)
f := Files.Tmp ("", {Files.write}, res); 

f.Register;
   => f.res.code = anonymousFile
res.GetText (str);
   => str = "Can't register anonymous file"

Class Reader

Class Reader provides primitive read operations on Files; that is, reading of bytes from a file. Most programmers would not use this class directly; a mapper class like BinaryRider.Reader or TextRider.Reader would be used instead (see section Module BinaryRider and section Module TextRider)

Class: Reader = POINTER TO ReaderDesc
This is a concrete rider type for reading bytes from files. Reader inherits the following fields from the base reader type:
Field: base-: Channel.Channel
base refers to the file the reader is connected to.
Field: res-: INTEGER
res is a result (error) flag that signals failure of a call to ReadByte, ReadBytes, or SetPos. res is initialized to done when creating a reader or by calling ClearError. The first failed read operation (or SetPos) changes this to indicate the error, all further calls to ReadByte, ReadBytes, or SetPos will be ignored until ClearError resets this flag. This means that the successful completion of an arbitrary complex sequence of read operations can be ensured by asserting that res equals done beforehand and also after the last operation. If res is not equal to done, res.code is set to the applicable error code. Use either of the methods res.GetLText() or res.GetText() to get a plain text error description of this error code. See section Summary of File Constants, for a list of applicable error codes.
Field: bytesRead-: LONGINT
bytesRead is set by ReadByte and ReadBytes to indicate the number of bytes that were successfully read.
Field: positionable-: BOOLEAN
positionable is set to TRUE if, and only if, the reader can be moved to another position with SetPos; for files that can only be read sequentially, this is set to FALSE.

Reader inherits the following methods from the abstract reader class:

Method: (r: Reader) Pos (): LONGINT
Returns the current reading position associated with the reader r in file r.base, i.e., the index of the first byte that is read by the next call to ReadByte or ReadBytes. This procedure returns a non-negative result.
Method: (r: Reader) Available (): LONGINT
Returns the number of bytes available for the next reading operation. For a file this is the length of the file r.base minus the current reading position. The result is -1 if Close() was called for the file (or the file has been otherwise closed), or no more bytes are available.
Method: (r: Reader) SetPos (newPos: LONGINT)
Sets the reading position to newPos. Using a negative value of newPos, or calling this procedure for a reader that doesn't allow positioning, will set r.res.code to outOfRange. A value larger than the file's length is legal, but the following read operation will most likely fail with an readAfterEnd error (unless the file has grown beyond this position in the meantime). Calls to this procedure while r.res # done will be ignored, in particular a call with r.res.code = readAfterEnd error will not reset res to done. Example:
(* For file,
 -r--r--r--   1 nikitin     12265 Jun  9 11:16 test.dat
*)

VAR pos, avail: LONGINT;
    r: Files.Reader;
    f: Files.File;

f := Files.Old("test.dat", {Files.read}, res);
r := f. NewReader();

pos := r.Pos();
   => pos = 0

avail := r.Available();
   => avail = 12265

r.SetPos(6000);

pos := r.Pos();
   => pos = 6000

avail := r.Available();
   => avail = 6265
Method: (r: Reader) ReadByte (VAR x: SYSTEM.BYTE)
Reads a single byte from the file r.base at the reading position associated with r and places it in x. The reading position is moved forward by one byte on success, and r.res is set to done. Otherwise r.res.code indicates the error cause. Calling this procedure with the reader r placed at the end (or beyond the end) of the file will set r.res.code to readAfterEnd. r.bytesRead will be 1 on success and 0 on failure. Calls to this procedure while r.res # done will be ignored. Example:
(* OOC assumes that SIZE(SYSTEM.BYTE) = SIZE(SHORTINT) *)
VAR byte: SHORTINT;
    ch  : CHAR;

r.ReadByte(byte);
r.ReadByte(ch);
Method: (r: Reader) ReadBytes (VAR x: ARRAY OF SYSTEM.BYTE; start, n: LONGINT)
Reads n bytes from the file r.base at the reading position associated with r and places them in x, beginning at index start. The reading position is moved forward by n bytes on success, and r.res is set to done. Otherwise, r.res.code indicates the error cause. Calling this procedure with the reader r positioned less than n bytes before the end of the file will will set r.res.code to readAfterEnd. r.bytesRead will hold the number of bytes that were actually read (being equal to n on success). Calls to this procedure while r.res # done will be ignored. Pre-condition: n and start are non-negative. Also, there is enough space in array x, starting at index start, to hold n bytes. Example:
VAR byteArr: ARRAY 256 OF SHORTINT;

r.ReadBytes(byteArr, 0, 16);
   => reads the next 16 bytes from r.base into byteArr[0..15]

r.ReadBytes(byteArr, 16, 100);
   => reads the next 100 bytes from r.base into 
        byteArr[16..115]
Method: (r: Reader) ClearError
Sets the result flag r.res to done, re-enabling further read operations on r. Example:
r.ClearError
   => r.res = done

Class Writer

Class Writer provides primitive write operations on Files; that is, writing of bytes to a file. Most programmers would not use this class directly; a mapper class like BinaryRider.Writer or TextRider.Writer would be used instead (see section Module BinaryRider and see section Module TextRider)

Class: Writer = POINTER TO WriterDesc
This is a concrete rider type for writing bytes to files. Writer inherits the following fields from the base writer type:
Field: base-: Channel.Channel
This field refers to the file the Writer is connected to.
Field: res-: INTEGER
res is a result (error) flag that signals failure of a call to WriteByte, WriteBytes, or SetPos. It is initialized to done when creating a writer or by calling ClearError. The first failed writing (or SetPos) operation sets res.code to indicate the error; all further calls to WriteByte, WriteBytes, or SetPos will be ignored until ClearError resets this flag. This means that the successful completion of an arbitrary complex sequence of write operations can be ensured by asserting that res equals done beforehand and also after the last operation. If res is not equal to done, res.code is set to the applicable error code. Use either of the methods res.GetLText() or res.GetText() to get a plain text error description of this error code. See section Summary of File Constants, for a list of applicable error codes. Please note: due to buffering, a write error may occur when flushing or closing the underlying file; you have to check the file's res field after any Flush() or the final Close().
Field: bytesWritten-: LONGINT
Set by WriteByte and WriteBytes to indicate the number of bytes that were successfully written.
Field: positionable-: BOOLEAN
TRUE if, and only if, the writer can be moved to another position with SetPos; for files that can only be written sequentially, this is FALSE.

Writer inherits the following methods from the abstract writer class:

Method: (w: Writer) Pos (): LONGINT
Returns the current writing position associated with the writer w in file w.base, i.e., the index of the first byte that is written by the next call to WriteByte or WriteBytes. This procedure returns a non-negative result.
Method: (w: Writer) SetPos (newPos: LONGINT)
Sets the writing position to newPos. A negative value of newPos, or calling this procedure for a writer that doesn't allow positioning, will set w.res.code to outOfRange. A value larger than the file's length is legal, however, the next write operation zero fills the intervening space. That is, the gap from the previous end of the file to newPos are filled with 0X bytes. Calls to this procedure while w.res # done are ignored. Example:
(* For file,
 -r--r--r--   1 nikitin     12265 Jun  9 11:16 test.dat
*)

VAR pos, LONGINT;
    w: Channel.Writer;
    f: Files.File;

f := Files.Old("test.dat", {Files.write}, res);
w := f. NewWriter();

pos := w.Pos();
   => pos = 0

w.SetPos(6000);

pos := w.Pos();
   => pos = 6000
Method: (w: Writer) WriteByte (x: SYSTEM.BYTE)
Writes a single byte x to the file w.base at the writing position associated with w. The writing position is moved forward by one byte on success, and r.res is set to done. Otherwise, w.res.code is set to indicate the error cause. w.bytesWritten will be 1 on success and 0 on failure. Calls to this procedure while w.res # done are ignored. Example:
(* OOC assumes that SIZE(SYSTEM.BYTE) = SIZE(SHORTINT) *)
VAR byte: SHORTINT;

byte = ODH;
w.WriteByte(byte);
w.WriteByte("A");
Method: (w: Writer) WriteBytes (VAR x: ARRAY OF SYSTEM.BYTE; start, n: LONGINT)
Writes n bytes from x, starting at index start in x, to the file w.base at the writing position associated with w. The writing position is moved forward by n bytes on success, and r.res is set to done. Otherwise, w.res.code is set to indicate the error cause. w.bytesWritten will hold the number of bytes that were actually written (being equal to n on success). Calls to this procedure while w.res # done are ignored. Pre-condition: n and start are non-negative. Also, this method requires that accessing n bytes in array x, starting from index start, will not go past the end of the array. Example:
(* OOC assumes that SIZE(SYSTEM.BYTE) = SIZE(CHAR). *)
VAR charArr: ARRAY 256 OF CHAR;

charArr := "abcdefghijklmnopqrstuvwxyz";  
        (* Note charArr[26] = 0X *)

w.WriteBytes(charArr, 0, 16);
   => writes exactly 16 values 
      (i.e., 0X is not automatically written) 
   => abcdefghijklmnop

w.WriteBytes(charArr, 16, 11);
   => writes exactly 11 values 
      (i.e., 0X is written from charArr[26]) 
   => qrstuvwxyz0X
Method: (w: Writer) ClearError
Sets the result flag w.res to done, re-enabling further write operations on w. Example:
w.ClearError
   => w.res = done

Besides its inherited methods, Writer has the following additional methods:

Method: (VAR w: Writer) Truncate (VAR newLength: LONGINT)
Causes the file associated with w to have the specified length. If the file was previously larger than newLength, the extra data is lost. If it was previously shorter, bytes between the old and new lengths are read as null bytes (i.e., 0X bytes). The writer's position is not modified in either case. Please note: On systems that do not support shortening files directly it is implemented as a partial file copy.

File Locators

The following locator procedures are provided for opening files. Possible values for the flags parameter are read, write, tryRead, tryWrite (see section Summary of File Constants).

Function: New (VAR file: ARRAY OF CHAR; VAR flags: SET; VAR res: Result): File
Creates a new file under the name file. On success, the new file object is returned, and res is set to done. Otherwise, it returns NIL and res.code and will indicate the problem.

If res is not equal to done, use either of the methods res.GetLText() or res.GetText() to get a plain text error description of this error code. See section Summary of File Constants, for a list of applicable error codes.

Please note: In terms of the Oberon System, this procedure combines the procedures New and Register.

Function: Old (VAR file: ARRAY OF CHAR; VAR flags: SET; VAR res: Result): File
Opens an existing file. On success the new file object is returned and res is set to done. Otherwise, it returns NIL and res.code will indicate the problem.

If res is not equal to done, use either of the methods res.GetLText() or res.GetText() to get a plain text error description of this error code. See section Summary of File Constants, for a list of applicable error codes.

Function: Tmp (VAR file: ARRAY OF CHAR; VAR flags: SET; VAR res: Result): File
Creates a temporary file that can be registered later on. On success the new file object is returned and res is set to done. Otherwise, it returns NIL and res.code will indicate the problem.

If res is not equal to done, use either of the methods res.GetLText() or res.GetText() to get a plain text error description of this error code. See section Summary of File Constants, for a list of applicable error codes.

Temporary files are created with only the user's write bit set, and the permissions are extended upon registration. The files are deleted if they haven't been registered and are closed, or the program terminates.

An unique temporary file name is created if the given file name is the empty string. Such a file can't be registered later. Note that some systems may have a low limit for the number of temporary file names. The limit is never less than 25. To be safe, you should never have more than 25 anonymous temporary files open simultaneously, or check that the TMP_MAX macro in /usr/include/stdio.h is large enough for your purposes.

With oo2c if file isn't empty, the new name is derived from the old one by appending "^", "^1", "^2", etc. in turn, until a file name is found that doesn't exist already. If such call to Tmp returns nameTooLong, then this refers to the constructed temporary name, not the one in file.

This function corresponds to Oberon System's New.

Other File Operations

It isn't always desirable to have to open a file before performing certain operations on it. You may not be interested in a file's contents; but rather some property of the file itself (for instance, does the named file even exist). As such, module Files provides some free-standing procedures:

Procedure: SetModTime (VAR file: ARRAY OF CHAR; VAR mtime: Time.TimeStamp; VAR res: Result)
Sets the modification time of the given file to mtime. On success res will be set to done. Otherwise, res.code holds an error code that indicates the problem.

Please note: under Unix this procedure will also change the access time to the value of mtime.

Procedure: GetModTime (VAR file: ARRAY OF CHAR; VAR mtime: Time.TimeStamp; VAR res: Result)
Gets the modification time of the given file to mtime. On success res will be set to done. Otherwise, res.code holds an error code that indicates the problem.

Function: Exists (VAR file: ARRAY OF CHAR): BOOLEAN
Returns TRUE if file file exists, FALSE otherwise. This procedure may be changed in future revisions to give more useful information on failure.

Example:

(* Attempting to open a "read-only" file for writing *)

f := Files.Old ("example.dat", {Files.write}, res);
   => res.code = accessDenied
res.GetText (str);
   => str = "Failed to open file with requested access rights"

Summary of File Constants

For constant values that are common to all channel types (see section Summary of Channel Constants), local names have been provided:

Constant: noLength
A result value for File.Length.

Constant: noPosition
A possible return value for Reader.Pos() or Writer.Pos() meaning that the reader or writer has no concept of a position.

The following constant applies to the res field, and may be compared to it. (i.e., ch.res = done or ch.res # done.)

Constant: done
This indicates successful completion of the last operation.

The following values are compared against the res.code field of the corresponding object (of types Channel, Reader, or Writer).

The methods res.GetLText() or res.GetText() can be used to translate any error code into a human readable message.

Constant: invalidChannel
The channel (i.e., file) isn't valid. For example, because it wasn't opened in the first place or was somehow corrupted.

Constant: writeError
A write error occured; usually this error happens with a writer, but for buffered files this may also occur during a Flush or a Close.

Constant: noRoom
A write operation failed because there isn't any space left on the device. For example, the disk is full or you exeeded your quota; usually this error happens with a writer, but for buffered files this may also occur during a Flush or a Close.

The following constants only apply to Reader.res.code and Writer.res.code:

Constant: outOfRange
SetPos has been called with a negative argument or it has been called on a rider that doesn't support positioning.

Constant: readAfterEnd
A call to ReadByte or ReadBytes has tried to access a byte beyond the end of the file. This means that there weren't enough bytes left or the read operation started at (or after) the end.

Constant: channelClosed
The rider's channel (i.e., file) has been closed, preventing any further read or write operations. This means there was a call to File.Close() (in which case, you probably made a programming error), or the channel has been otherwise closed.

Constant: readError
An unspecified read error.

Constant: invalidFormat
Set by a mapper (e.g., TextRiders.Reader) if the byte stream at the current reading position doesn't represent an object of the requested type.

The following constants only apply to File.res.code:

Constant: noReadAccess
NewReader was called to create a reader on a file that doesn't allow read access.

Constant: noWriteAccess
NewWriter was called to create a writer on a file that doesn't allow write access.

Constant: closeError
An attempt to close the file failed.

Constant: noModTime
No modification time is available for the given file.

Constant: noTmpName
Creation of a temporary file failed because the system was unable to assign an unique name to it (closing or registering an existing temporary file beforehand might help in this case).

The following values report problems when opening or modifying a file:

Constant: accessDenied
Access to the file was denied, e.g., because a file's permissions don't permit the requested access method, or because the given URL isn't publically readable.

Constant: isDirectory
The flags argument specified write access, and the file is a directory.

Constant: tooManyFiles
The process or the entire system has too many files open.

Constant: noSuchFile
The named file in a call to Old() does not exist. Or the directory part of a file name passed to New() or Tmp() does not exist.

Constant: directoryFull
The directory or the file system that would contain the new file cannot be extended, either because there is no space left or the directory has a fixed upper limit.

Constant: readOnlyFileSystem
The file resides on a read-only file system and it is attempted to create a new file or to gain write access for an existing one.

Constant: invalidTime
The time passed to procedure SetModTime is not a valid time stamp; either the millisecond part isn't valid, or the time value is too large or too small to be mapped to the time value of the underlying OS.

Constant: notOwner
Only the owner of a file can change its modification time.

Constant: anonymousFile
A file can only be registered if a file name was passed to the initial call to Tmp().

Constant: dirWriteDenied
You need to have write permission for the directory you want to add a new file to.

Constant: fileError
Unspecified error when opening/creating a file; this usually means that this module doesn't know how to interpret the error code delivered by the OS.

Constant: nameTooLong
Either the total length of the file name or of an individual file name component is too large; the operating system can impose such limits (see PATH_MAX and NAME_MAX in /usr/include/limits.h), or the file system itself restricts the format of names on it.

Constant: notDirectory
A file that is referenced as a directory component of the file name exists, but is not a directory.

Constant: linkLoop
Too many symbolic links were resolved while trying to look up the file name; the operating system has an arbitrary limit on the number of symbolic links that may be resolved in looking up a single file name, as a primitive way to detect loops.

The following are possible elements for the flags parameter of New, Old, or Tmp.

Please note: at least one of the following flags has to be set; otherwise you will get an "access denied" error:

Constant: read
If the file cannot be opened for reading access, then it isn't opened at all; in this case the error code is set to noReadAccess.

Constant: write
If the file cannot be opened for writing access, then it isn't opened at all; in this case the error code is set to noWriteAccess.

Constant: tryRead
Try to open this file for reading access; if the file permissions don't permit reading, the file is opened nevertheless, but the file descriptor's attribute readable is set to FALSE.

Constant: tryWrite
Try to open this file for writing access; if the file permissions don't permit writing, the file is opened nevertheless, but the file descriptor's attribute writable is set to FALSE.

Module StdChannels

Module StdChannels defines the standard I/O channels, which are predefined channels for input (typically the keyboard) and output (typically the computer screen).

Standard channels do not have to be opened by a client program because they are already open and ready for use. Their attributes and operations are described by the class Channel.Channel.

The standard channels (stdin, stdout, and stderr) should never be closed. You can close the standard channels (e.g., to detach a program from its terminal), but StdChannels does not provide a way to reopen them. Notice that the modules In, Out, Err, OakIn, and OakOut are all affected by such operations on standard channels. If, for example, you call stdout.Close, then the procedures in module Out will no longer function (unless you use Out.SetWriter to set another channel).

A fourth standard channel, null, is also provided.

Mappers may be attached to any of these channels to provide read and write operations for them. Mappers from module TextRider are most often used.

Also, be aware that modules In, Out, and Err provide simple interfaces to the standard channels (see section Standard I/O). So that, in many cases, you may not have to use module StdChannels directly.

Read-only Variable: stdin
The standard input channel, which is a predefined source of input for the program. The referenced channel is read-only.

Example:

VAR stringVar: ARRAY 256 OF CHAR; 
    rdr:       TextRider.Reader;

rdr := TextRider.ConnectReader(StdChannels.stdin);
rdr.ReadLine(stringVar);

Read-only Variable: stdout
The standard output channel, which is a predefined destination for output from the program. The referenced channel is write-only.

Example:

VAR wrtr: TextRider.Writer;

wrtr := TextRider.ConnectWriter(StdChannels.stdout);
wrtr.WriteString("A string to write"); wrtr.WriteLn;

Read-only Variable: stderr
The standard error channel, which can be used for error messages and diagnostics issued by the program. The referenced channel is write-only.

Example:

VAR wrtr: TextRider.Writer;

wrtr := TextRider.ConnectWriter(StdChannels.stderr);
wrtr.WriteString("An error has occured"); wrtr.WriteLn;

Read-only Variable: null
The null channel, which can be used as a destination for output that is to be discarded. The referenced channel is write-only.

Module ProgramArgs

This module provides access to the command line arguments passed to the program's invocation. They are mapped onto a standard channel args, with each argument transformed into a single line of text. Interpreting the list of arguments is usually done by applying an instance of TextRider.Reader or TextRider.Scanner to the argument channel.

The number of arguments is determined by calling args.ArgNumber(). If the invocation were, for example, foo bar 42, where foo is the name of the program itself, then the channel's contents would look like this:

foo
bar
42

For the above example, args.ArgNumber() would return 2; that is, the program name is not counted by ArgNumber even though it is present in args.

Note that any end-of-line characters within command line arguments are mapped to space (20X) characters. This ensures, that a single argument is always mapped onto a single line of text, even if it has embedded end-of-line characters.

Also, be careful with settings for TextRider.Reader and especially TextRider.Scanner: end-of-line characters are treated as whitespace by many of the read operations, which means, for a program foo, the reader or scanner has no way of distinguishing between

foo 123 bar
for "123 bar"

You would normally consider the first invocation as having two arguments, and the second as having one; which is also how ProgramArgs would interpret them. For foo 123 bar, args would contain

foo
123
bar

whereas, for foo "123 bar", args would contain

foo
123 bar

But a text reader or scanner, if set to treat end-of-line as whitespace, would treat both of these invocations as equivalent.

Please note: In cases where separate arguments need to be considered as a whole, the reader method ReadLine should be used. Unlike other read operations, such asReadInt or ReadIdentifier, leading whitespace is not skipped and, after completion, the reading position is just behind the end-of-line character.

So ReadLine should be used to read, for example, file name arguments because operating systems like Unix typically allow arbitrary characters in file names, including blanks and control codes.

Module ProgramArgs provides local equivalents for the following constants from module Channels: done, outOfRange, readAfterEnd, channelClosed, noWriteAccess, and noModTime.

Class: Channel = POINTER TO ChannelDesc
This class is derived from the abstract base channel class. In addition to its inherited fields and methods (see section Abstract Class Channel), the class provides the following method:
Method: (VAR ch: Channel) ArgNumber (): LONGINT
Returns the number of command line arguments (excluding the program name itself) passed to the program.

Read-only Variable: args
The predefined program arguments channel. The referenced channel is read-only.

As a further example, suppose a program foo required exactly two (positional) command line arguments. The first is an integer value and the second is an identifier. Also, suppose that all of the following invocations are to be considered equivalent:

foo 123 bar
foo +123 bar
foo "  +123" " bar"

Note that, the following module would not consider `foo 123 " bar "' or `foo 123+ bar' to be equivalent to the above invocations.

Example:

VAR r: TextRider.Reader;
    str: ARRAY 256 OF CHAR;
    int: LONGINT;

  r := TextRider.ConnectReader(ProgramArgs.args);
  IF r = NIL THEN 
     (* Error processing: failed to connect to `args' *)
  END;

  IF ProgramArgs.args.ArgNumber() # 2 THEN
     (* Error processing: wrong number of arguments *)
  END;

  (* skip past the line containing the program name `foo' *)
  r.ReadLn;

  r.ReadLInt(int);
  IF r.res # TextRider.done THEN
     (* Error processing: can't read an integer *)
  ELSIF ~r.Eol() THEN
     (* Error processing: this argument has other stuff after
        the integer just read *)
  END;

  r.ReadLn; (* skip to the next line *)

  r.ReadIdentifier(str);
  IF r.res # TextRider.done THEN
     (* Error processing: can't read an identifier *)
  ELSIF ~r.Eol() THEN
     (* Error processing: extra stuff after the identifier *)
  END;

Messages

Module `Msg' provides a framework for messages, which are used as a level of indirection between simple error codes and human readable error messages. Unlike numeric error codes, an instance of Msg carries its own interpretation context. Using this context, plus the error code stored in the message, and possibly additional data, the message can be converted into a description. The additional data can be text fragments, numbers, or other messages, and it can be inserted anywhere into the message's text. There is no need to determine the message text at the place the message is created. A message can be converted to text anywhere in the program.

This module actually combines several concepts: messages, message attributes, message contexts, and message lists. Although this may seem a bit complicated, the actual mechanism is very simple.

A message is an object that can be converted to human readable text and presented to a program's user. Within the OOC Library, messages are used to store errors in the I/O modules. Another example is an XML parser, which uses messages to create an error list when parsing an XML document.

Contexts and attributes are primarily of interest for modules that generate messages. These determine the content of the message, and how it can be translated into readable text. A typical user will mostly be in the position of message consumer, and will be handed filled in message objects. For a user, the typical operation will be to convert a message into descriptive text (see methods Message.GetText() and Message.GetLText()).

Message lists are a convenience feature for modules like parsers, which normally do not abort after a single error message. Usually, they try to continue their work after an error, looking for more problems and possibly emitting more error messages. Using message lists, errors can be collected together (e.g., within a compiler) to be presented to the user in a single batch.

Messages

Class: Msg = POINTER TO MsgDesc
A message's type is uniquely identified by its context and its code. Using these two attributes, a message can be converted to text. The text may contain placeholders, which are filled by the textual representation of attribute values associated with the message.
Field: nextMsg-: Msg
Field: prevMsg-: Msg
These two fields are initialized to NIL, and are used by MsgList.
Field: code-: Code
Field: context-: Context
Field: attribList-: Attribute
This list of attributes is sorted by name. Follow Attribute.nextAttrib to traverse the list.

The following function is a constructor for a message object:

Function: New (context: Context; code: Code): Msg
This function creates and returns a new message object for the given context, using the specified message code. The message's attribute list is empty.

Users of messages will be most interested in the following methods, which are used to retrieve the textual representation of a message:

Method: (msg: Msg) GetLText (VAR text: LString)
This method converts a message into a text string. The basic format of the string is determined by calling msg.context.GetTemplate. Then the attributes are inserted into the template string; the placeholder string `${foo}' is replaced with the textual representation of each attribute (see Context.GetTemplate). Pre-condition: LEN(text)<2^15 Please note: Behaviour is undefined if replacement text of an attribute contains an attribute reference.
Method: (msg: Msg) GetText (VAR text: String)
This method operates just like GetLText, but the message text is truncated to ISO-Latin-1 characters. All characters that are not part of ISO-Latin-1 are mapped to question marks `?'.

Example:

VAR r: TextRider.Reader;
    f: Files.File;
    str: ARRAY 256 OF CHAR;
    res: Files.Result;     (* `Result' is an alias for `Msg.Msg'. *)
    
  f := Files.Old("Sample.txt", {Files.read}, res);
  IF (f = NIL) THEN
     res.GetText(str);
     Err.String(str); Err.Ln;
  ELSE
     r := TextRider.ConnectReader(f); 
     IF (r # NIL) THEN 
        r.ReadLine(str);	    (* Read the lines of a file. *)
        WHILE r.res=Files.done DO
           Out.String(str); Out.Ln; (* And output them to the screen. *)
           r.ReadLine(str);	
        END;
        (*  Check to see if it stopped reading because it reached
         *  end-of-file.  If not, then print the error string.
         *)
        IF (r.res.code#Files.readAfterEnd) THEN 
           r.res.GetText(str);
           Err.String(str); Err.Ln;
        END;    
     END;
  END;

A programmer who is creating a library module can use the following methods to manage the attributes of a message:

Method: (msg: Msg) GetAttribute (name: String): Attribute
This method returns the attribute name of the message object. If no such attribute exists, the value NIL is returned.
Method: (msg: Msg) SetAttribute (attr: Attribute)
This method appends an attribute to the message's attribute list. If an attribute of the same name exists already, it is replaced by the new one. Pre-condition: Length(attr.name^)<=sizeAttrName and attr has not been attached to any other message.
Method: (msg: Msg) SetIntAttrib (name: String; value: LONGINT)
Pre-condition: Length(name)<=sizeAttrName

Example:

VAR
  lineVal, colVal: LONGINT;
  attrib1, attrib2: Msg.Attribute;

msg.SetIntAttrib ("line", lineVal);
msg.SetIntAttrib ("column", colVal);

...

attrib1 := GetAttribute("line");
attrib2 := GetAttribute("column");
Method: (msg: Msg) SetStringAttrib (name: String; value: StringPtr)
Pre-condition: Length(name)<=sizeAttrName
Method: (msg: Msg) SetLStringAttrib (name: String; value: LStringPtr)
Pre-condition: Length(name)<=sizeAttrName
Method: (msg: Msg) SetMsgAttrib (name: String; value: Msg)
Pre-condition: Length(name)<=sizeAttrName

Contexts and Attributes

When writing a library module (or perhaps a set of related library modules), a Context is defined, which may specify message formats and handle generation of messages. Specific Attributes that directly relate to a Context, and its related messages, are defined to go along with that Context.

The basic steps are

The following is an example showing how a Context can be set up. (In this case, for a command line parser). Note that use of Attributes is not required (and not shown in this example), and that this example has only a single error message.

MODULE CmdLine;

IMPORT Msg;

(* Context and template infrastructure *)
CONST
  connectFailed = 1;
  
TYPE
  ErrorContext = POINTER TO ErrorContextDesc;
  ErrorContextDesc = RECORD
    (Msg.ContextDesc)
  END;

VAR
  cmdLineContext: ErrorContext;

PROCEDURE (context: ErrorContext) GetTemplate* (msg: Msg.Msg; 
                                                VAR templ: Msg.LString);
  VAR
    t: ARRAY 128 OF Msg.LChar;
  BEGIN
    CASE msg. code OF
    | connectFailed:
      t := "Failed to connect reader to program arguments"
    END;
    COPY (t, templ)
  END GetTemplate;

PROCEDURE Error (code: Msg.Code): Msg.Msg;
(* Create error message for context `cmdLineContext', using the error
   code `code'.  *)
  VAR
    err: Msg.Msg;
  BEGIN
    err := Msg.New (cmdLineContext, code);
    RETURN err
  END Error;

BEGIN
  (* initialize error context *)
  NEW (cmdLineContext);
  Msg.InitContext (cmdLineContext, "CmdLine")
END CmdLine.

Class: Context = POINTER TO ContextDesc
Instances of this class describe the context under which messages are converted into their textual representation. Together, a message's context and its code identify the message type.
Field: id-: StringPtr
As a debugging aid, this field is usually filled with an string that identifies the module that created this context instance (see procedure InitContext).

The following is an initialization procedure for Contexts:

Procedure: InitContext (context: Context; id: String)
This procedure intializes an instance of Context. The string argument id should describe the message context to the programmer. It should not appear in output generated for a program's user, or at least, it should not be necessary for a user to interpret this string to understand the message. Generally, it is a good idea to use the module name of the context variable for the identifier. If this is not sufficient to identify the variable, add the variable name to the string.
Method: (context: Context) GetTemplate (msg: Msg; VAR templ: LString)
This method returns a template string for the message msg. The template is used as the basis for the human readable string returned by GetText. Typically, the string is derived from the message code, and it contains attribute references. Instead of the reference `${foo}', the procedure GetText (see below) will insert the textual representation of the attribute with the name `foo'. The special reference `${MSG_CONTEXT}' is replaced by the value of context.id, and `${MSG_CODE}' with msg.code. The default implementation returns this string:
MSG_CONTEXT: ${MSG_CONTEXT}
MSG_CODE: ${MSG_CODE}
attribute_name: ${attribute_name}
The last item is repeated for every attribute name. The lines are separated by CharClass.eol. Pre-condition: msg # NIL

Example:

PROCEDURE (context: aContext) GetTemplate* (msg: Msg.Msg; 
                                            VAR templ: Msg.LString);
VAR
   t: ARRAY 128 OF Msg.LChar;
BEGIN
   CASE msg. code OF

   ...  (* set the value of `t' with appropriate message *)

   END;
   COPY (t, templ);
   (* then append the line and column numbers ---
    * note that attribute values are later substituted by
    * `Msg.GetLText' or `Msg.GetText'.
    *)
   LongStrings.Append (" line=${line}, column=${column}", templ);
END GetTemplate;

Attributes

Constant: sizeAttrName
Maximum length of the attribute name for InitAttribute, NewIntAttrib, NewStringAttrib, NewLStringAttrib, or NewMsgAttrib.

Class: Attribute = POINTER TO AttributeDesc
An attribute is a (name, value) tuple, which can be associated with a message. When a message is tranlated into its readable version through the GetText function, the value part of each attribute can be converted to some textual representation, and then inserted into the message's text. Within a message, an attribute is uniquely identified by its name.
Field: nextAttrib-: Attribute
Field: name-: StringPtr
The name of an Attribute is restricted to sizeAttrName characters.

The following is an initialization procedure for Attributes:

Procedure: InitAttribute (attr: Attribute; name: String)
This procedure initializes an attribute object and sets its name.
Method: (attr: Attribute) ReplacementText (VAR text: LString)
This method converts the attribute value into some textual representation. The length of the resulting string must not exceed sizeAttrReplacement characters. Note that GetLText() calls this procedure with a text buffer of `sizeAttrReplacement+1' bytes.

The following are default implementations for some commonly used message attributes and their corresponding constructors and ReplacementText methods:

Class: IntAttribute = POINTER TO IntAttributeDesc
Field: int-: LONGINT

Function: NewIntAttrib (name: String; value: LONGINT): IntAttribute
This function creates and returns a new attribute (IntAttribute) object.

Pre-condition: Length(name)<=sizeAttrName

Method: (attr: IntAttribute) ReplacementText (VAR text: LString)

Class: StringAttribute = POINTER TO StringAttributeDesc
Field: string-: StringPtr

Function: NewStringAttrib (name: String; value: StringPtr): StringAttribute
This function creates and returns a new attribute (StringAttribute) object.

Pre-condition: Length(name)<=sizeAttrName

Method: (attr: StringAttribute) ReplacementText (VAR text: LString)

Class: LStringAttribute = POINTER TO LStringAttributeDesc
Field: string-: LStringPtr

Function: NewLStringAttrib (name: String; value: LStringPtr): LStringAttribute
This function creates and returns a new attribute (LStringAttribute) object.

Pre-condition: Length(name)<=sizeAttrName

Method: (attr: LStringAttribute) ReplacementText (VAR text: LString)

Class: MsgAttribute = POINTER TO MsgAttributeDesc
Field: msg-: Msg

Function: NewMsgAttrib (name: String; value: Msg): MsgAttribute
This function creates and returns a new attribute (MsgAttribute) object.

Pre-condition: Length(name)<=sizeAttrName

Method: (attr: MsgAttribute) ReplacementText (VAR text: LString)

Message Lists

Class: MsgList = POINTER TO MsgListDesc
Field: msgCount-: LONGINT
The number of messages on the list.
Field: msgList-: Msg
The messages of the list can be traversed using the fields Msg.nextMsg and Msg.prevMsg.

The following are for construction and initialization of MsgLists:

Procedure: InitMsgList (l: MsgList)
This procedure initializes a message list object.

Function: NewMsgList (): MsgList
This function creates and returns a new message list object.

The following methods are used to add messages to a message list:

Method: (l: MsgList) Append (msg: Msg)
Appends the message msg to the list l. Pre-condition: msg is not part of another message list.
Method: (l: MsgList) AppendList (source: MsgList)
Appends the messages of list source to l. Afterwards, source is an empty list, and the elements of source can be found at the end of the list l.

Standard Mappers

Mappers are high-level riders, which are used to translate between a sequence of data items and an uninterpreted sequence of bytes (see section Riders and Mappers). Thus, the reader and writer types in BinaryRider and TextRider are considered mappers.

The standard mappers, defined in this section, use the basic riders associated with a particular channel type for reading and writing bytes. You'll notice that there are very few error code constants defined within either of these modules; error codes are dependant on the channel being read, and so you'll have to use the constant values for readers and writers that are declared within each particular channel module.

Because OOC has both CHAR and LONGCHAR character types, mappers for textual data have been set up as a class hierarchy, with base classes in module `Rider' from which all other text mappers derive.

Text Mappers

The text mapper modules (`Rider', `LongRider', `TextRider', and `UnicodeRider') provide facilities for reading and writing values in text format. Text format is delimited, or otherwise formatted, sequences of character values that can be interpreted as words, numbers, symbols, and so forth. This corresponds to the way human beings read text, or perhaps how an Oberon-2 source file is parsed by a compiler. Data in text format are generally refered to simply as text.

Text can usually be interpreted in a limited number of ways. For example, the number 2 can be read as an INTEGER value or as a REAL. It could be an element of a SET, or perhaps even be part of an identifier such as oo2c. The interpretation is based on context and the format of the characters rather than as a fixed number of bytes.

Because the corresponding classes from the text mapper modules provide related facilities, they form a class hierarchy as follows:

            Rider [ABSTRACT]
            /    \
          /        \
        /            \
   TextRider        LongRider [ABSTRACT]
                         |
                         |
                         |
                    UnicodeRider

Module Rider

Module `Rider' encapsulates the base classes for all other text mapper classes. These base classes (Reader, Writer, and Scanner) are abstract classes that define the interface elements required for concrete classes derived from them.

See the concrete text mapper classes for more detail and examples of usage (section Module TextRider and section Module UnicodeRider).

Class Reader (Rider)

Constant: maxLengthEol
The maximum number of characters allowed in Reader.eol.

Abstract Class: Reader = POINTER TO ReaderDesc
This class provides facilities for reading various kinds of text. Note that this type does not inherit properties from any basic reader type; rather it uses the basic reader type associated with the channel it is attached to.

Also note that, after any failed read operation, all further attempts to read will be ignored until the error is cleared using ClearError.

See section Class Reader (TextRider), for examples of usage.

Field: opt-: SET
The current read options setting for the reader.
Field: res-: Msg.Msg
This field indicates the status of the last read operation (e.g., ReadLine, ReadInt, SetPos, etc.). Error codes (for res.code) are highly dependent on the channel being read, and therefore on the basic riders provided by that channel, so you must look at the result codes for a particular channel's reader type (e.g., Files.Reader error codes). See the various channel types for details of these error codes (i.e., section Module Files, section Module StdChannels, or section Module ProgramArgs). If res#done, use either res.GetLText() or res.GetText() to get a plain text error description corresponding to the error code.
Field: base-: Channel.Channel
This field refers to the channel the reader is connected to.

The following fields determine how the reader interprets end-of-line markers. Note that the end-of-line marker may contain the character `0X', which means its length must be stored in a separate field. The eol marker cannot be empty, and all characters must be an ASCII code in the range 00X..1FX.

Field: eol-: ARRAY maxLengthEol OF CHAR
The character sequence that represents an end-of-line marker. Note that this is a character array, not a string (i.e., it may contain the character `0X').
Field: eolLen-: INTEGER
The number of characters in eol. The default value for this is `-1', which means that end-of-line is auto detected (see SetEol below). Otherwise, this value is in the range 1 <= eolLen <= maxLengthEol.

The following methods can be used to check the status of a reader or, in some cases, change its state. Some methods are fully described in the abstract reader section (see section Abstract Class Reader), so only brief descriptions of those are given here.

Method: (r: Reader) Available () : LONGINT
Returns the number of bytes available for the next read operation.
Method: (r: Reader) ClearError
Clears error conditions on the reader r, re-enabling further read operations.
Method: (r: Reader) Eol (): BOOLEAN
This method returns TRUE if the reader is currently positioned at an end-of-line marker (see SetEol below). This will also return TRUE if r.res # done. Otherwise, FALSE is returned.
Method: (r: Reader) Pos (): LONGINT
Returns the current reading position associated with the reader r in channel r.base.
Method: (r: Reader) SetEol (marker: ARRAY OF CHAR; markerLen: INTEGER)
This method sets the end-of-line marker; that is, what character(s) is used to mark the end of a line. If the passed string marker does not fit into the field eol, or if it contains a character >= ` ', then r.res.code is set to invalidFormat. A marker length markerLen=-1 enables auto detection of the end-of-line convention used by the channel. For auto detection to work, the channel is required to use one of the following eol markers:
`LF'
used by Unix
`CR'
used by MacOS
`CR/LF'
used by MS-DOS and Windows
Please note: ReadChar is unaffected by the current eol setting. That is, if the end-of-line marker consists of more than one character (like `CR/LF'), each character is read separately. All other read operations view an end-of-line marker at an atomic entity when the channel is read sequentially. If auto detection is enabled, and the eol convention of the file is `CR/LF', then the first end-of-line marker is not skipped completely when reached by the reader (r.Pos() is at the `LF'). This is transparent to all reading procedures except ReadChar and Pos; the `LF' will be skipped automatically on the next read. This positioning inconsistency only applies for the very first eol encountered. Pre-condition: All of the following apply:
  1. r.res = done, and
  2. (markerLen = -1) OR (1 <= markerLen < LEN (marker)), and
  3. markerLen <= maxLengthEol, and
  4. for all i: marker[i] < 20X
Method: (r: Reader) SetOpts (opts: SET)
This method is used to set the reader options r.opt.
Method: (r: Reader) SetPos (newPos: LONGINT)
Sets the reading position to newPos.

The following methods read a value of the given type from the current position of the reader. Most read operations skip leading whitespace before reading a token; there are only three methods that do not skip whitespace: ReadChar, ReadLn, and ReadLine.

When attempting to read, and if the value is not properly formatted for its type, r.res.code is set to invalidFormat. The reader remains positioned at the character which caused the invalidFormat error, but further reading can not take place until the error is cleared.

If a number, or potential set element, is properly formatted, but has a value that is out of range of the target type, then a valueOutOfRange error occurs. In this case, the reader is positioned after the last character that was read. Again, further reading can not take place until the error is cleared.

A valueOutOfRange error also occurs for methods reading into an ARRAY OF CHAR (i.e., ReadLine, ReadIdentifier, and ReadString) if the character array is not large enough to hold the entire input.

Otherwise, for any operation attempting to read when there are no characters left to be read, a read-after-end error occurs and Reader.res.code is set to readAfterEnd.

In any case, whenever an error occurs, it is safest to assume that no value has been read. That is, the variable being read into is left with an undefined value.

All further calls of these read methods will be ignored if r.res#done. That is, no new characters will be read if an error has occurred previously.

Method: (r: Reader) ReadBool (VAR bool: BOOLEAN)
Reads in an identifier (see ReadIdentifier below), and if it is either of the tokens TRUE or FALSE, it is converted to a BOOLEAN value. If this method encounters any other token, an invalidFormat error occurs and the value of bool is undefined.
Method: (r: Reader) ReadChar (VAR ch: CHAR)
Reads in a single character value and places it in ch.
Method: (r: Reader) ReadHex (VAR lint: LONGINT)
Reads in characters in the form of an unsigned hexadecimal number and converts them to a LONGINT value. The first character must be a decimal digit (i.e., `0..9') and subsequent characters must be valid hexadecimal digits (i.e., `0..9' or `A..F'). If the first non-whitespace character is not a digit, then an invalidFormat error occurs. If the input is properly formatted as an unsigned hex number, but the value is out of range for a LONGINT, then a valueOutOfRange error occurs. Upon encountering an error, the value of lint is undefined. Please note: Because LONGINT values are signed, hex numbers in the range `80000000H..FFFFFFFFH' are interpreted as negative LONGINT values.
Method: (r: Reader) ReadIdentifier (VAR s: ARRAY OF CHAR)
Reads an Oberon-2 style identifier into s. An identifier is a sequence of letters and digits, which must begin with a letter. Sequences not beginning with a letter produce an invalidFormat error. If s is not large enough to hold the entire input, a valueOutOfRange error occurs. Upon encountering an error, the value of s is undefined.
Method: (r: Reader) ReadInt (VAR int: INTEGER)
Reads in characters in the form of a signed whole number and converts them to an INTEGER value. If the first character is not a digit, a "+" sign, or a "-" sign, then an invalidFormat error occurs. If the input is properly formatted as a signed whole number, but the value is out of range for an INTEGER, then a valueOutOfRange error occurs. Upon encountering an error, the value of int is undefined.
Method: (r: Reader) ReadLInt (VAR lint: LONGINT)
This method provides the same facility as ReadInt, except that it deals with LONGINT values.
Method: (r: Reader) ReadSInt (VAR sint: SHORTINT)
This method provides the same facility as ReadInt, except that it deals with SHORTINT values.
Method: (r: Reader) ReadLine (VAR s: ARRAY OF CHAR)
Reads a sequence of characters into s; reading continues until an end-of-line character is encountered, the array s is full, or r reaches the end of the channel. The end-of-line character is discarded and s is always terminated with 0X. If r is already positioned at an end-of-line character, s returns as an empty string. If s is not large enough to hold the entire input, a valueOutOfRange error occurs; s returns with the sequence of characters that have been read so far (terminated by 0X). If r has already reached the end of the channel (i.e., there are no more characters left to read), a readAfterEnd error occurs and s returns as an empty string.
Method: (r: Reader) ReadLn
This method reads and discards all characters up to and including the next end-of-line character. If the end of the channel is reached before encountering an end-of-line character, a readAfterEnd error occurs.
Method: (r: Reader) ReadString (VAR s: ARRAY OF CHAR)
Reads in a sequence of characters enclosed in single (') or double (") quote marks. The opening quote must be the same as the closing quote and must not occur within the string. Characters will be read until the terminating quote mark is encountered, an invalid character is read (end-of-line is always considered invalid), there are no more characters available in the channel, or the string s is full. s is always terminated with 0X. Unquoted strings produce an invalidFormat error. Strings with no terminating quote mark also result in an invalidFormat error. If s is not large enough to hold the entire input, a valueOutOfRange error occurs. Upon encountering an error, the value of s is undefined.
Method: (r: Reader) ReadReal (VAR real: REAL)
Reads in characters in the form of a signed fixed or floating-point number and converts them to a REAL value. If the first character is not a digit, a "+" sign, or a "-" sign, then an invalidFormat error occurs. If the input is properly formatted as a signed fixed or floating-point number, but the value is out of range for a REAL, then a valueOutOfRange error occurs. Upon encountering an error, the value of real is undefined.
Method: (r: Reader) ReadLReal (VAR lreal: LONGREAL)
This method provides the same facility as ReadReal, except that it deals with LONGREAL values.
Method: (r: Reader) ReadSet (VAR s: SET)
Reads in characters in the form of a set constructor and converts them to a SET. If the sequence of characters does not form a valid set constructor, then an invalidFormat error occurs. If the input is properly formatted as a set constructor, but a set element has a value out of the range 0..MAX(SET), then a valueOutOfRange error occurs. Upon encountering an error, the value of s is undefined.

Class Writer (Rider)

Abstract Class: Writer = POINTER TO WriterDesc
This class provides facilities for writing various types of text. Note that this type does not inherit properties from any basic writer type; rather it uses the basic writer type associated with the channel it is attached to.

See section Class Writer (TextRider), for examples of usage.

Field: opt-: SET
The current write options setting for the writer. See section Summary of TextRider Constants for possible option values.
Field: res-: Msg.Msg
This field indicates the status of the last write operation (e.g., WriteBytes, WriteInt, SetPos, etc.). Error codes are highly dependent on the channel being written to (and therefore on the basic riders provided for that channel), so you must look at the result codes for the basic writer that is associated with that particular channel (e.g., Files.Writer error codes). See the various channel types for details of these error codes (i.e., section Module Files, section Module StdChannels, or section Module ProgramArgs). If res#done, use either res.GetLText() or res.GetText() to get a plain text error description corresponding to the error code.
Field: base-: Channel.Channel
This field refers to the channel the writer is connected to.

The following methods can be used to check the status of a writer or, in some cases, change its state. Some methods are fully described in the abstract writer section (see section Abstract Class Writer), so only brief descriptions of those are given here.

Method: (w: Writer) ClearError
Clears error conditions on the writer w, re-enabling further write operations.
Method: (w: Writer) Pos () : LONGINT
Returns the current writing position associated with the writer w in channel w.base.
Method: (w: Writer) SetEol (marker: ARRAY OF CHAR; markerLen: INTEGER)
This method sets the end-of-line marker; that is, what character(s) is used to mark the end of a line. If the passed string marker does not fit into the field eol, then w.res.code is set to invalidFormat. The empty marker is permitted. The default value for a newly created writer is CharClass.systemEol. Pre-condition: All of the following apply:
  1. w.res = done, and
  2. 0 <= markerLen < LEN (marker), and
  3. markerLen <= maxLengthEol.
Method: (w: Writer) SetOpts (opts: SET)
This method is used to set the writer options w.opt.
Method: (w: Writer) SetPos (newPos: LONGINT)
Sets the writing position to newPos.

The following writer methods are used to write values in text format to the underlying channel. In some situations, it is possible for only part of the value to be actually written.

Method: (w: Writer) WriteBool (bool: BOOLEAN)
Writes the value of bool as text. That is, either TRUE or FALSE.
Method: (w: Writer) WriteChar (ch: CHAR)
Writes a single character value ch.
Method: (w: Writer) WriteHex (lint: LONGINT; d: LONGINT)
Writes the value of lint as an unsigned hexadecimal number with a minimum field width of d. Leading zeros are written if the value of lint requires less than d places. If d is less than or equal to zero, field width is 8.
Method: (w: Writer) WriteInt (int: INTEGER; n: LONGINT)
Writes the value of int as a decimal number with a minimum field width of n. Leading spaces are written if the value of int requires less than n places. A sign is written only for negative values.
Method: (w: Writer) WriteLInt (lint: LONGINT; n: LONGINT)
This method provides the same facility as WriteInt, except that it deals with LONGINT values.
Method: (w: Writer) WriteSInt (sint: SHORTINT; n: LONGINT)
This method provides the same facility as WriteInt, except that it deals with SHORTINT values.
Method: (w: Writer) WriteReal (real: REAL; n, k: LONGINT)
Writes the value of real as a floating-point number with a minimum field width of n. If the value of k is greater than 0, that number of significant digits is included. Otherwise, an implementation-defined number of significant digits is included. The decimal point is not included if there are no significant digits in the fractional part. The number is scaled with one digit in the whole number part. A sign is included only for negative values.
Method: (w: Writer) WriteLReal (lreal: LONGREAL; n, k: LONGINT)
This method provides the same facility as WriteReal, except that it deals with LONGREAL values.
Method: (w: Writer) WriteRealEng (real: REAL; n, k: LONGINT)
Writes the value of real as a floating-point number with a minimum field width of n. If the value of k is greater than 0, that number of significant digits is included. Otherwise, an implementation-defined number of significant digits is included. The decimal point is not included if there are no significant digits in the fractional part. The number is scaled with one to three digits in the whole number part and with an exponent that is a multiple of three. A sign is included only for negative values.
Method: (w: Writer) WriteLRealEng (lreal: LONGREAL; n, k: LONGINT)
This method provides the same facility as WriteRealEng, except that it deals with LONGREAL values.
Method: (w: Writer) WriteRealFix (real: REAL; n, k: LONGINT)
Writes the value of real as a fixed-point number with a minimum field width of n. The value is rounded to the given value of k relative to the decimal point. The decimal point is suppressed if k is less than 0. The number will have at least one digit in the whole number part. A sign is included only for negative values.
Method: (w: Writer) WriteLRealFix (lreal: LONGREAL; n, k: LONGINT)
This method provides the same facility as WriteRealFix, except that it deals with LONGREAL values.
Method: (w: Writer) WriteSet (s: SET)
Writes the value of s as an Oberon-2 set constructor, including curly braces, commas, and range indicators ("..") where appropriate.
Method: (w: Writer) WriteString (s: ARRAY OF CHAR)
Writes a string value up to, but not including, the terminating 0X character. The behaviour of this method is undefined if s is an unterminated character array. Please note: ReadString and WriteString are not symmetric. That is, WriteString does not enclose the written string in quote marks; only the actual character values contained in s are written.
Method: (w: Writer) WriteLn
Writes an end-of-line marker (i.e., a "newline"). The default value for a newly created writer is CharClass.systemEol (see SetEol above).

Class Scanner (Rider)

A text scanner is a special type of reader, which is used to parse text for different kinds of tokens. Integers, reals, strings, identifiers, set constructors, the boolean tokens TRUE and FALSE, and other special symbols are all tokens recognized by this kind of scanner.

These tokens are scanned sequentially, converted to an appropriate type, and then returned in one of the scanner's fields. The scanner's type field is then used to determine the type of token which has been scanned.

Along with some typical reader methods, such as SetPos, the primary method of a scanner is Scan, which simply scans the next token based on the scanner's current options setting.

See section Class Scanner (TextRider), for examples of usage.

Data type: String
A string type of pre-defined length for use within a scanner. Note that because this type is of finite length, a scanner is limited in the length of string it can scan.

Please note: LEN() can be used on a variable of type String to determine the maximum size that can be held by a scanner string.

Abstract Class: Scanner = POINTER TO ScannerDesc
This class provides facilities for scanning sequences of characters from a channel and parsing those characters into various tokens. The tokens a scanner can recognize are defined by the constants provided for its type field (section Summary of TextRider Constants).

Note that a scanner will not continue to read (via calls to Scan) if it has scanned an invalid token or an error occurs; ClearError must be called explicitly before scanning can continue. The difference is that invalid means that the token could not be interpreted; a sequence of characters was read, but could not be interpreted as a valid token.