8.6 Text I/O in ISO Standard Modula-2

This section assumes that the files being used are text files--that is, that they are sequential recordings of text streams. There are (at least) two ways of viewing a text stream.

A Restricted Stream: Reading is only from the beginning and writing is only to the current position. If an existing file is opened for writing, it is written over, not appended to, unless it is first read all the way to the end.

A Rewindable Sequential Stream: As above, except that there is the capability of rewinding the read or write position back to the start of the file. This permits it, for instance, to be written to and then read over, or vice versa, without having to close it and then re-open it first.

In the two subsections that follow, the ISO modules that elaborate each of these models will be discussed.

8.6.1 The Restricted Stream Model

Sequential files are connected to using the facilities of the module StreamFile that contains procedures for defining, opening, and closing such files. Writing to and reading from such files is done using TextIO, WholeIO, RealIO, and LongIO. These are the analogues of the corresponding S-modules, except that each procedure has one more parameter--the identifier of the file channel being employed. For instance, to write a cardinal to the standard output, one uses

SWholeIO.WriteCard (theCard, n);

whereas, to write to some previously opened file channel called outChan, one employs

WholeIO.WriteCard (outChan, theCard, n);

To see how the various components of StreamFile and the xxIO modules work together, consider the following module, which gathers a sequence of numbers from the keyboard and records them in a sequential file.

MODULE GetNStash;

(* Written by R.J. Sutcliffe *)
(* to illustrate the use of StreamFile *)
(* using ISO standard Modula-2 *)
(* last revision 1994 02 23 *)

(* This module reads a series of Integers from the keyboard and places them into a disk file called "numbers". *)

FROM StreamFile IMPORT
  ChanId, Open, write, Close, OpenResults;
FROM SWholeIO IMPORT
  ReadInt;
FROM STextIO IMPORT
  WriteLn, WriteString, WriteChar, SkipLine;
FROM SIOResult IMPORT
  ReadResult, ReadResults;
IMPORT WholeIO; (* done qualified to distinguish *)
IMPORT TextIO;  (* from the S-modules above *)

VAR
  outfile : ChanId; (* identifier of the channel to use *)
  number  : INTEGER;
  res : OpenResults; (* to store the result of an open attempt *)

BEGIN
  (* Establish output channel and attach to disk file *)
  Open (outfile, "numbers", write, res); 
  IF res = opened
    THEN (* Collect the numbers from the keyboard *)
      WriteString ( "Type in the integers.  Separate them");
      WriteLn;
      WriteString (" by carriage returns, and end  ");
      WriteLn;
      WriteString ("by typing a non-integer"); 
      WriteLn;
      REPEAT
        WriteChar (">"); (* prompt *)
        ReadInt (number);
        IF ReadResult() = allRight
          THEN
            SkipLine;
            WholeIO.WriteInt (outfile, number, 0);
            TextIO.WriteLn (outfile); (* separate the numbers *)
          END;  (* if *)
      UNTIL ReadResult() # allRight;
      Close (outfile); (* Now close physical file. *)
    END (* if res = opened *)
 
END GetNStash.

When this program was run the file Numbers was created and as numbers were typed, its contents became:

12
15
54
-100
0

It is important to understand that the procedures in such modules as TextIO, WholeIO, RealIO, and LongIO are identical to those of STextIO, SWholeIO, SRealIO, and SLongIO except for requiring the additional parameter specifying the channel. Notice the import of procedures from both STextIO and TextIO, as some writing was done to the standard output and some was done to the file. If it is considered desirable, all output could be done with TextIO provided that output to the standard channel is identified as such. This requires:

FROM StdChans IMPORT
  OutChan;

and then in the code, one may have both of:

  
  WriteInt (outfile, number, 0);
  WriteString (OutChan (), "by typing a non-integer");

by importing only from TextIO and WholeIO, and not bothering with STextIO. The same considerations apply to input, so that SWholeIO could be dispensed with altogether if one wrote:

FROM StdChans IMPORT
  StdInChan;

and then in the code:

  
  ReadInt (StdInChan(), number);

The function procedures StdChan.OutChan and StdChan.InChan return the currently selected output (input) channels for standard output (input), even if it has been redirected by RedirStdIO.OpenOutput or RedirStdIO.OpenInput. If it is important to guarantee that the output goes to the default standard output stream (and is not subject to redirection) then import and use StdChan.StdOutChan or StdChan.StdInChan in the same way.

WARNING: The enclosing compiler or outer operating system cannot necessarily be assumed to know anything about the files created by user written program modules. Consequently, the program is responsible for closing all its own files. If this is not done, there is no guarantee that data will not be lost from the file. In fact, one can almost guarantee that data will be lost. Some implementations provide protection by having any program termination automatically close all open files, but this behaviour cannot be counted upon.

In the example that follows, some of the variations mentioned above have been adopted for another module that reads all those numbers back from the disk file, sums them and prints out the sum.

MODULE ReadNAdd;

(* Written by R.J. Sutcliffe *)
(* to illustrate the use of StreamFile *)
(* using ISO standard Modula-2 *)
(* last revision 1994 02 23 *)

(* This module reads a series of Integers from the disk file called "numbers". It sums them and prints out the sum. *)

FROM StreamFile IMPORT
  ChanId, Open, read, Close, OpenResults;
FROM TextIO IMPORT
  WriteLn, WriteString, SkipLine, ReadChar;
FROM IOResult IMPORT
  ReadResult, ReadResults;
FROM WholeIO IMPORT 
  ReadInt, WriteInt;
FROM StdChans IMPORT
  StdInChan, StdOutChan;

VAR
  infile, stdOut, stdIn : ChanId;
  number, sum : INTEGER;
  res : OpenResults;
  ch : CHAR;

BEGIN
  stdOut := StdOutChan(); (* to force screen output *)
  stdIn := StdInChan(); (* to force keyboard input *)
  
  Open (infile, "numbers", read, res);
  IF res = opened
    THEN
      sum := 0; (* initialize sum *)
      REPEAT  (* Collect the numbers from the file *)
        ReadInt (infile, number); 
        IF ReadResult (infile) = allRight
          THEN
            SkipLine (infile);
            INC (sum, number);    (* OK, so add to sum *)
          END;  (* if *)
      UNTIL ReadResult (infile) # allRight;
 
     Close (infile);
     WriteString (stdOut, "The sum of the numbers is ");
     WriteInt (stdOut, sum, 6);
     WriteLn (stdOut);
    ELSE
     WriteString (stdOut,"Sorry, couldn't open the file");
     WriteLn (stdOut);
    END; (* if *)
  WriteString (stdOut, "type a character to continue==> ");
  SkipLine (stdIn);
  
END ReadNAdd.

When this program was run and allowed to work on the file generated above, the output was:

The sum of the numbers is    -19
type a character to continue==> 

Notice that the mode of the stream is set with the third parameter of Open. If a restricted stream is to be written to as well as read from, the procedure is called in the form:

  Open (thefile, "numbers", read+write, res);

Of course, since in this model streams cannot be rewound, writing can only be done at the point last read. If it is required that the program be permitted to write over (erasing) the contents of an existing file, then the command is given in the form:

  Open (thefile, "numbers", write+old, res);

8.6.2 The Rewindable Sequential Stream Model

The rewindable sequential file model is supported by the ISO library suite using the facilities of the module SeqFile that contains procedures for defining, opening, and closing such files. While a file is open, and has been read in part or all, the reading position can be rewound to the beginning. This is done using

PROCEDURE Reread (cid: ChanId);

If the purpose is to erase the current contents of the file and write over the top of them from the beginning, use

PROCEDURE Rewrite (cid: ChanId);

In order to open a file initially for reading, one uses OpenRead. If it is desired to open the file for the purpose of writing to the end rather than to the beginning as was done in the StreamFile example above, it should be opened with OpenAppend, otherwise with OpenWrite. However, all three open procedures take the read, write, and/or old parameters so as to allow additional combinations. Whatever parameter is passed in the third position:

OpenRead implies read mode and unless write is passed, it implies old
OpenWrite implies write
OpenAppend implies both write and old

Each of the three Open procedures initializes a mode; a sequential file is either available for being read or written to at any given time, but not both. The third parameter in these routines is actually a set of permission flags relating to these modes. For the present purposes, these flags can be any combination of read, write, and old. If more than one is given, use the + operator between them. (The syntax of working with such sets will be covered in more detail later in the text.) Thus, if a file is to be permitted to be open for either reading and writing, one can use either OpenRead or OpenWrite depending on what is to be done first, but the flag parameter should have write in the first case, and read in the second. This makes it possible to change the mode of the file at a later time.

If the flag old is used with OpenWrite then an existing file can be written over; otherwise, it is an error to attempt to open an existing file for writing.

One might begin with OpenWrite or OpenAppend and also supply the read parameter if planning to issue a Reread later on. Indeed, if such was the plan, the read would have to be supplied, or Reread would be unable to select a mode and after it tried to execute neither reading nor writing could then take place. Likewise, if the plan were to begin with OpenRead and later to write over or append to the file (the latter after reading to the end) by issuing a Rewrite then the write parameter would have to be supplied initially.

However, at any given time, the file is in either input mode or output mode. The mode is chosen initially according to the routine that opens it. The mode can then be changed by Rewrite or Reread, provided that the appropriate write or read flags were provided in the first place. Attempts to write when not in output mode or to read when not in input mode both produce run time errors.

Notice that the same physical file may be opened with either StreamFile or with SeqFile depending on the logical needs at the time. Of course, few operating systems would allow the file to be open in two ways simultaneously. One might make a devious attempt:

IMPORT  SeqFile;
IMPORT StreamFile;
VAR
  seq : SeqFile.ChanId;
  stream :  StreamFile.ChanId;
  
BEGIN (* pathological code *)
  StreamFile.Open (stream, "Myfile", StreamFile.read+StreamFile.write);
  seq := stream; (* they are assignment compatible *)
  SeqFile.Rewrite (seq); (* exception occurs *)

This would fail, because the procedures of SeqFile check first to ensure that the channel operated on by them has in fact been opened by SeqFile and not by some other module (in this case StreamFile).

What follows is a module to illustrate some of the points made thus far. The same file employed above is opened once again, some more numbers are typed for it; the Reread is issued, and then the contents typed to the screen. Note the use of both user-connected and standard channels, and the need, for instance, to use a channel parameter when issuing SkipLine commands.

MODULE StashMoreAndType;

(* Written by R.J. Sutcliffe *)
(* to illustrate the use of SeqFile *)
(* using ISO standard Modula-2 *)
(* last revision 1994 02 23 *)

(* This module reads a series of Integers from the keyboard and appends them to a disk file called "numbers". *)

FROM SeqFile IMPORT
  ChanId, OpenAppend, write, read, Close, Reread, OpenResults;
FROM TextIO IMPORT
  WriteLn, WriteString, SkipLine, ReadChar, WriteChar;
FROM IOResult IMPORT
  ReadResult, ReadResults;
FROM WholeIO IMPORT 
  ReadInt, WriteInt;
FROM StdChans IMPORT
  StdInChan, StdOutChan;

VAR
  thefile, stdOut, stdIn : ChanId;
  number : INTEGER;
  res : OpenResults;
  ch : CHAR;

BEGIN
  stdOut := StdOutChan(); (* to force screen output *)
  stdIn := StdInChan(); (* to force keyboard input *)

    (* Establish channel and attach to disk file *)
  OpenAppend (thefile, "numbers", write+read, res); 
  IF res = opened
    THEN (* Collect the numbers from the keyboard *)
      WriteString (stdOut, "Type in the integers.  Separate ");
      WriteLn (stdOut);
      WriteString (stdOut, " by carriage returns, and end  ");
      WriteLn (stdOut);
      WriteString (stdOut, "by typing a non-integer"); 
      WriteLn (stdOut);
      REPEAT
        WriteChar (stdOut, ">"); (* prompt *)
        ReadInt (stdIn ,number);
        IF ReadResult(stdIn) = allRight
          THEN
            SkipLine (stdIn);
            WriteInt (thefile, number, 0);
            WriteLn (thefile); (* separate the numbers *)
          END;  (* if *)
      UNTIL ReadResult(stdIn) # allRight; 
      Reread (thefile);  (* go back to the start *)
      REPEAT  (* Collect the numbers from the file *)
        ReadInt (thefile, number); 
        IF ReadResult (thefile) = allRight
          THEN (* ok, so skip to next and print on screen *)
            SkipLine (thefile);
            WriteInt (stdOut, number, 10); 
          END;  (* if *)
      UNTIL ReadResult (thefile) # allRight;

      Close (thefile); (* Now close physical file. *)
      WriteLn (stdOut);
      WriteString (stdOut, "Contents typed to standard output.");
      WriteLn (stdOut);
      WriteString (stdOut, "type a character to continue ==> ");
      ReadChar (stdIn, ch);

  END (* if res = opened *)
 
END StashMoreAndType.

When this program was executed and a few more numbers typed, the screen looked like this:

Type in the integers.  Separate 
 by carriage returns, and end  
by typing a non-integer
>12
>15
>-3
>done
        12        15        54      -100         0      
  12        15        -3
Contents typed to standard output.
type a character to continue ==> 

8.6.3 File Text I/O in non-Standard Modula-2

With minor changes to the names of modules, some of the procedure names, and some of the syntax, the material of this section can be adapted to the majority of non-ISO library systems as well.

Such systems are usually based on the same two sets of routines described above

The non-standard comprehensive TextIO referred to here usually includes routines for all the Readxx and Writexx tasks that in the ISO libraries are split among WholeIO, RealIO, LongIO, and TextIO. The file housekeeping module (whatever its name) defines the data type File (or FILE) and handles all the opening, creating, renaming, and other housekeeping tasks, and usually has some rudimentary text I/O such as ReadChar and WriteChar upon which the routines in TextIO are usually built.

DEFINITION MODULE TextIO; (* non-standard *)

FROM Files IMPORT
  File;

CONST
  eol = 15C; (* implementation dependent character *)

VAR
  consoleOK, txDone : BOOLEAN;
(* The first of these is for examining the success of the last operation on the console, and the second is for all other streams. *)
  termCH : CHAR;
  console : FILE;

PROCEDURE WriteChar (f : FILE; ch : CHAR);
PROCEDURE WriteString (f : FILE; s : ARRAY OF CHAR);
PROCEDURE WriteLn (f : FILE);
PROCEDURE WriteInt (f : FILE; i : INTEGER; flen : CARDINAL); 
PROCEDURE WriteCard (f : FILE; c : CARDINAL; flen : CARDINAL); 
PROCEDURE WriteReal (f : FILE; r : REAL; flen : CARDINAL; 
                        digits: INTEGER);
PROCEDURE ReadChar (f : FILE; VAR ch : CHAR);
PROCEDURE ReadString (f : FILE; VAR s : ARRAY OF CHAR);
PROCEDURE ReadInt (f : FILE; VAR i : INTEGER);
PROCEDURE ReadCard (f : FILE; VAR c : CARDINAL);
PROCEDURE ReadReal (f : FILE; VAR r : REAL);
END TextIO.

There is much more variation in modules for file handling. Not only are the names different, so is the syntax. Only a careful perusal of the manuals can enlighten the user. Indeed, since such a module is somewhat low level, even an ISO standard library suite probably imposes the two sequential file models on top of some implementation specific file handling module that is called to do all the actual work. An example of such a module can be found in the appendix. In any event, it is not too difficult to adapt the programs of the first two sections to work with these non-standard versions with a little re-writing.

To illustrate the use of such a module, here follows a program that will copy the file of integers above to another file with a different name. This copy will not employ TextIO, but instead will import only from a module called Filer.

The student should make careful note of the differences in syntax and spelling that may be necessary.

MODULE FileCopy;

(* Written by R.J. Sutcliffe *)
(* to illustrate the use of nonstandard/low-level file handlers *)
(* last revision 1994 02 23 *)

(* This module reads a file called "numbers" and copies it into a file called "numbers.bak" with type "TEXT" and creator "NISI". *)

FROM Filer IMPORT
  File, (* the type of the ADT *)
  Open, Close, Create, (* typical housekeeping stuff *)
  FileErr,  (* the type of the result variables *)
  FileDone,  (* a global of the above type, set by all operations *)
  ReadChar, WriteChar; (* simple I/O within the filing module *)

FROM InOut IMPORT (* let's go entirely classical on this one *)
  WriteLn, WriteString;

VAR
  infile, outfile : File;
  nameIn, nameOut : ARRAY [0 .. 30] OF CHAR;
  ch : CHAR;

BEGIN
  nameIn := "numbers";
  Open (infile, nameIn);
  nameOut:= "numbers.bak";
  Create (nameOut, "NISI", "TEXT"); (* Macintosh specific *)
  Open (outfile, nameOut);
  REPEAT
    ReadChar (infile, ch);
    IF FileDone = FileOK
      THEN
        WriteChar (outfile, ch);
      END;
  UNTIL FileDone # FileOK; 

  Close (infile);
  Close (outfile);
  WriteString ("Copy complete.  ");
  WriteString (nameIn);
  WriteString ("==>  ");
  WriteString (nameOut);
END FileCopy.

Contents