8.10 Lower Level I/O in ISO Modula-2

The modules StreamFile and SeqFile, when used in conjunction with the procedures in TextIO, WholeIO, RealIO, and LongIO impose a model for I/O for channels opened by them based on the notion of the restricted stream or the rewindable stream, respectively.

The modules StreamFile, SeqFile, and others of a similar kind are examples of device drivers, that is, of modules that enforce a high level model of I/O appropriate for a particular abstract (logical) device.

The device referred to in the above definition is abstract because the physical device (say, disk media) may be capable of handling channels connected to any one of several different logical devices. Thus, from a logical point of view, the ISO standard sees sequential devices as distinct from stream devices or other devices, regardless of the actual hardware being employed. Operations (such as Close or Rewind) performed on channels in this way depend on the logical model; that is, a check is made to ensure that the channel was in fact opened by the correct device driver.

In some cases, it may be necessary to perform operations at a lower level than that available directly from the device driver facilities. This may be because it is necessary to perform just a few operations that fall outside the strict model, or because the application program needs a rather different model than those provided by the vendor. In the extreme case, the user may wish to write her own device driver to implement an appropriate abstract model.

In order to meet such needs, ISO Modula-2 provides a module at a lower level that allows I/O to be performed without reference to any device driver.

Input and output performed directly on channels without the benefit of a high level device driver is called device independent.

In non-standard Modula-2, all the I/O facilities were likely to be contained in the two modules TextIO and Files/FileSystem/Filer. It is up to the programmer to impose the logical model through the appropriate use of such routines. In ISO Modula-2, there is a separate module called IOChan that enables device independent operations on a channel by channel basis. Operations in IOChan include:

PROCEDURE Look (cid: ChanId; VAR ch: CHAR; VAR res: IOConsts.ReadResults);
  (* If there is a character as the next item in the input stream cid, assigns its value to ch without removing it from the stream; otherwise the value of ch is not defined.  res (and the stored read result) are set to the value allRight, endOfLine, or endOfInput. *)

PROCEDURE Skip (cid: ChanId);
  (* If the input stream cid has ended, the exception skipAtEnd is raised; otherwise the next character or line mark in cid is removed, and the stored read result is set to the value allRight. *)

PROCEDURE SkipLook (cid: ChanId; VAR ch: CHAR; VAR res: IOConsts.ReadResults);
  (* If the input stream cid has ended, the exception skipAtEnd is raised; otherwise the next character or line mark in cid is removed.  If there is a character as the next item in cid stream, assigns its value to ch without removing it from the stream. Otherwise, the value of ch is not defined.  res (and the stored read result) are set to the value allRight, endOfLine, or endOfInput. *)

PROCEDURE WriteLn (cid: ChanId);
  (* Writes a line mark over the channel cid. *)
 
PROCEDURE TextRead (cid: ChanId; to: SYSTEM.ADDRESS; maxChars: CARDINAL; VAR charsRead: CARDINAL);
  (* Reads at most maxChars characters from the current line in cid, and assigns corresponding values to successive components of an ARRAY OF CHAR variable for which the address of the first component is to. The number of characters read is assigned to charsRead. The stored read result is set to allRight, endOfLine, or endOfInput. *)

PROCEDURE TextWrite (cid: ChanId; from: SYSTEM.ADDRESS; charsToWrite: CARDINAL);
  (* Writes a number of characters given by the value of charsToWrite, from successive components of an ARRAY OF CHAR variable for which the address of the first component is from, to the channel cid. *)
 
PROCEDURE RawRead (cid: ChanId; to: SYSTEM.ADDRESS; maxLocs: CARDINAL; VAR locsRead: CARDINAL);
  (* Reads at most maxLocs items from cid, and assigns corresponding values to successive components of an ARRAY OF LOC variable for which the address of the first component is to. The number of characters read is assigned to locsRead. The stored read result is set to the value allRight, or endOfInput. *)
 
PROCEDURE RawWrite (cid: ChanId; from: SYSTEM.ADDRESS; locsToWrite: CARDINAL);
  (* Writes a number of items given by the value of locsToWrite, from successive components of an ARRAY OF LOC variable for which the address of the first component is from, to the channel cid. *)

Several other operations are also available; these are detailed in a full listing of this module in Appendix 5. When a channel is opened by a device driver, procedures to do all the above operations on that channel must be provided by the device driver, even though the specific I/O model does not necessarily use them all. When I/O is done at the level of IOChan, however, any of the channel's low level procedures may be employed through calls to the above. Here is yet another version of the file copying program to illustrate some use of IOChan.

MODULE FileCopyLow;

(* Written by R.J. Sutcliffe *)
(* to illustrate the use of ISO module IOChan *)
(* last revision 1994 02 23 *)

(* Copies a text file character by character using the low level procedures IOChan.Look and IOChan.Skip. *)

FROM STextIO IMPORT
  WriteString, WriteChar, WriteLn, ReadString, SkipLine;
FROM SeqFile IMPORT
  text, old, ChanId, OpenResults, OpenRead, OpenWrite, Close;
FROM IOConsts IMPORT
  ReadResults;
FROM IOChan IMPORT
  Look, Skip;
IMPORT TextIO;

VAR
  inFile, outFile: ChanId;
  inFileName, outFileName: ARRAY [0 .. 79] OF CHAR;
  ch: CHAR;
  resOpen: OpenResults;
  resRead: ReadResults;

BEGIN
  WriteString ('Name of input file ? ==>');
  ReadString (inFileName);
  SkipLine;
  OpenRead (inFile, inFileName, text, resOpen);

  IF resOpen = opened (* for read *)
    THEN
      WriteString ('Name of output file ? ==>');
      ReadString (outFileName);
      SkipLine;
      OpenWrite (outFile, outFileName, text, resOpen);
 
      IF resOpen = opened (* for write *)
        THEN
          REPEAT
            Look (inFile, ch, resRead);
             (* see if character available -- uses low level IOChan *)
            IF resRead = allRight
              THEN (* yes, so *)
                TextIO. WriteChar (outFile, ch); (* output it *)
                Skip (inFile); (* skip to next one in input *)
              ELSIF resRead = endOfLine THEN
                TextIO. WriteLn (outFile);
                 (* place end of line in output *)
                Skip (inFile);
                 (* remove input end of line mark/state *)
              END (* if resRead *);
          UNTIL resRead = endOfInput; (* only possibility left *)
          WriteString ("Copy complete. ");
          WriteString (inFileName);
          WriteString (" copied to ");
          WriteString (outFileName);
          WriteLn;
          Close (outFile);
        END; (* if resOpen = opened (* for write *) *)
      Close (inFile);
    END; (* if resOpen = opened (* for read *) *)
END FileCopyLow.

When this program was run, one session looked like:

Name of input file ? ==>FileCopy.MOD
Name of output file ? ==>FileCopy.MOD.BAK
Copy complete. FileCopy.MOD copied to outFileName

An examination of the output file showed that its contents were indeed correct.

The procedures of IOChan also provide some idea how some of those in TextIO (which is not device dependent) might be implemented by calling device independent routines. Here are a few from the author's own implementation:

PROCEDURE ReadChar (cid: IOChan.ChanId; VAR ch: CHAR);
  (* If possible, removes a character from the input stream cid and assigns the corresponding value to ch.  The read result is set to the value allRight, endOfLine, or endOfInput.  *)

VAR
  howMany : CARDINAL;

BEGIN
  IOChan.TextRead (cid, SYSTEM.ADR(ch),1, howMany)
END ReadChar;

PROCEDURE ReadRestLine (cid: IOChan.ChanId; VAR s: ARRAY OF CHAR);
  (* Removes any remaining characters from the input stream cid before the next line mark, copying to s as many as can be accommodated as a string value. The read result is set to the value allRight, outOfRange, endOfLine, or endOfInput. *)
VAR
  res : IOConsts.ReadResults;
  ch : CHAR;

BEGIN
  ReadString (cid,s);
  IF IOChan.ReadResult (cid) = IOConsts.allRight (* more there *)
    THEN
      res := IOConsts.outOfRange;
      REPEAT
        ReadChar (cid, ch);
      UNTIL IOChan.ReadResult (cid) # IOConsts.allRight;
      IOChan.SetReadResult (cid, res);
    END;

END ReadRestLine;

PROCEDURE ReadString (cid: IOChan.ChanId; VAR s: ARRAY OF CHAR);
  (* Removes only those characters from the input stream cid before the next line mark that can be accommodated in s as a string value, and copies them to s.  The read result is set to the value allRight, endOfLine, or endOfInput. *)
VAR
  numToRead, numRead : CARDINAL;

BEGIN
  numToRead := HIGH(s) + 1;
  IOChan.TextRead (cid, SYSTEM.ADR (s), numToRead, numRead);
  IF numRead < numToRead
    THEN
      s[numRead] := 0C
    END;
END ReadString;

  (* The following procedure reads past the next line mark *)

PROCEDURE SkipLine (cid: IOChan.ChanId);
  (* Removes successive items from the input stream cid up to and including the next line  mark, or until the end of input is reached.  The read result is set to the value allRight, or endOfInput.  *)
VAR
  res : IOConsts.ReadResults;
  ch : CHAR;

BEGIN
  IOChan.Look (cid, ch, res);
  WHILE res = IOConsts.allRight
    DO
      IOChan.SkipLook (cid, ch, res);
    END;
  IF res = IOConsts.endOfLine
    THEN
      IOChan.Skip (cid);
    END;

END SkipLine;

Although these procedures are all provided in a standard conforming implementation, the reader may well require the use of the ideas they contain in writing applications programs that need to deal with channel I/O on a device independent basis.


Contents