6.3 The Standard Library (1)--I/O

6.3.1 ISO Standard I/O Modules

The prefix S in the module names STextIO, SRealIO, and SWholeIO stands for simple. All I/O controlled by these modules is directed to/from a standard I/O channel (normally the keyboard/screen.) A call to SWholeIO.WriteCard probably results in a call to WholeStr.CardToStr to convert the cardinal to a string, followed by a call to STextIO.WriteString. However, vendors are free to implement the ISO standard without this relationship among the various modules, as long as they provide the standard items themselves. The module SIOResults, for determining the outcome of a Readxx operation from one of these modules must also be present. Here are the full listings of some of these modules (see Appendix 5 for syntax):

DEFINITION MODULE STextIO;
 
PROCEDURE ReadChar (VAR ch: CHAR);
PROCEDURE ReadRestLine (VAR s: ARRAY OF CHAR);
PROCEDURE ReadString (VAR s: ARRAY OF CHAR);
PROCEDURE ReadToken (VAR s: ARRAY OF CHAR);
PROCEDURE SkipLine;
PROCEDURE WriteChar (ch: CHAR);
PROCEDURE WriteLn;
PROCEDURE WriteString (s: ARRAY OF CHAR);

END STextIO.

NOTE: The exact meaning of the reserved word DEFINITION will be made clear later in the chapter. It is enough to know that these are just lists of the entities that are importable from these modules.

The Readxx procedures produce the ReadResult allRight if everything goes well. Other possible results are endOfLine or endOfInput. In the case of those procedures taking an ARRAY OF CHAR parameter, another possible outcome is outOfRange if the supplied parameter is not big enough to hold the input.

ReadRestLine removes the characters from the input until the next endOfLine state is reached, copying as many as possible to the string parameter. ReadString, on the other hand, copies as many characters as possible to the string parameter until it either runs out of room or gets to the end of the line. Thus, it might return with the value allRight, but with some characters still remaining as available input before the next endOfLine state. ReadToken will skip spaces before reading other characters and then read as far as a space or an endOfLine state, whichever comes first.

NOTE: What is here called ReadToken is in non-standard versions usually called ReadString, and the other two are not usually provided in such versions.

SkipLine removes characters from the input to the next endOfLine state and discards them. It also clears the end of line state.

DEFINITION MODULE SWholeIO;
PROCEDURE ReadInt (VAR int: INTEGER);
PROCEDURE WriteInt (int: INTEGER; width: CARDINAL);
PROCEDURE ReadCard (VAR card: CARDINAL);
PROCEDURE WriteCard (card: CARDINAL; width: CARDINAL);

END SWholeIO.

DEFINITION MODULE SRealIO;

PROCEDURE ReadReal (VAR real: REAL);
PROCEDURE WriteFloat (real: REAL; sigFigs: CARDINAL; width: CARDINAL);
PROCEDURE WriteEng (real: REAL; sigFigs: CARDINAL; width: CARDINAL);
PROCEDURE WriteFixed (real: REAL; place: INTEGER; width: CARDINAL);
PROCEDURE WriteReal (real: REAL; width: CARDINAL);
END SRealIO.

DEFINITION MODULE SLongIO;

PROCEDURE ReadReal (VAR real: LONGREAL);
PROCEDURE WriteFloat (real: LONGREAL; sigFigs: CARDINAL; width: CARDINAL);
PROCEDURE WriteEng (real: LONGREAL; sigFigs: CARDINAL; width: CARDINAL);
PROCEDURE WriteFixed (real: LONGREAL; place: INTEGER; width: CARDINAL);
PROCEDURE WriteReal (real: LONGREAL; width: CARDINAL);
 
END SLongIO.

If the procedures ReadInt, ReadCard, SRealIO.ReadReal or SLongIO.ReadReal are given input that cannot be integer, cardinal, real, or longreal respectively, the ReadResult returned is wrongFormat. This might happen, for instance, if a ReadCard were executed and then a negative whole number or a real were found on the actual input.

Except for the SLongIO procedures, (which all compare to those in SRealIO) all the rest have been well used thus far in the text. Notice that in the ISO standard, there is no lower level module called Terminal or Screen. Rather, it is assumed that the normal origin and destination of I/O from these S-modules is the standard terminal for the system.

6.3.2 Classical I/O--The InOut Family

The reader who has the ISO standard I/O modules only may skip most of this section unless interested in the historical development of Modula-2. The high level modules InOut and RealInOut (or RealIO) have been referred to a few times in this text, but without a complete list of all the things available for import from them. Here they are--with parameters where applicable:

DEFINITION MODULE InOut;
CONST
  EOL =  CHR (13) (* may be system dependent *)

VAR
  Done : BOOLEAN;
  termCh : CHAR;

PROCEDURE OpenInput (defext : ARRAY OF CHAR);
PROCEDURE OpenOutput (defext : ARRAY OF CHAR);
PROCEDURE CloseInput;
PROCEDURE CloseOutput;
PROCEDURE Read (VAR ch : CHAR);
PROCEDURE ReadString (VAR s : ARRAY OF CHAR);
PROCEDURE ReadInt (VAR x : INTEGER);
PROCEDURE ReadCard (VAR x : CARDINAL);
PROCEDURE Write (ch : CHAR);
PROCEDURE WriteLn;
PROCEDURE WriteString (s : ARRAY OF CHAR);
PROCEDURE WriteInt (x : INTEGER; n : CARDINAL);
PROCEDURE WriteCard (x, n : CARDINAL);
PROCEDURE WriteOct (x, n : CARDINAL);
PROCEDURE WriteHex (x, n : CARDINAL);

END InOut.

NOTE: 1. These contents are only typical, and may vary considerably from vendor to vendor.

2. Some implementations also include the less typical procedure ClearScreen in this module. It may even be necessary to call this procedure before attempting any other screen output.

3. Others include the procedure HoldScreen to pause and wait for a key This avoids the necessity of writing two lines of code for this purpose.

4. OpenOutput and OpenInput are procedures designed to allow the output to go to other than the screen and the input to come from other than the keyboard. An example will be given in the next chapter.

5. defext is short for "default extension" and is usually "TEXT," if it is anything at all. If the user of a program containing one of these redirecting procedures answers the prompt asking for a file name by typing, say, by "Mydisk:Superfile." that is, ending the name with a period, then the default extension "TEXT" is added, and the file that is actually looked for will be "Mydisk:Superfile.TEXT".

6. In (less typical) versions that take file names directly as parameters for "OpenOutput", no prompts are given, but it is then the responsibility of the programmer to ensure the correct file name syntax. As this may differ from one system to another, such programs are probably not portable.

DEFINITION MODULE RealInOut;

VAR
  Done : BOOLEAN;

PROCEDURE ReadReal (VAR x : REAL);
PROCEDURE WriteReal (x : REAL; n : CARDINAL);
PROCEDURE WriteRealOct (x : REAL);
END RealInOut.

NOTES: 1. In some implementations the contents of RealInOut are contained inside the module InOut. In such cases, there is only one variable Done.

2. As previously observed, the meaning of WriteReal varies widely.

Which of these have not already been used in this text? There are WriteOct and WriteHex that output the CARDINAL type in a different format than as conventional decimal numerals, and WriteRealOct that similarly outputs a REAL. These abbreviations refer to the octal and hexadecimal formats for numbers that many programmers find useful when working with the computer at a very low level. (That's base eight and base sixteen notation for those who have already taken the Mathematics.) For now, experiment with these. They will be also discussed in a later Chapter.

NOTE: Not all systems bother with octal notation, as it is seldom used. The relevant procedures might be omitted in some implementations.

ReadString has not been seen before, though its purpose may seem obvious, especially given its parameter. It skips leading spaces and then reads characters from the input and constructs a string in the parameter, stopping when it comes to either a space or a line marker. The variable termCH is used to hold the character that caused ReadString to stop reading.

NOTES: 1. This behaviour is typical, but some versions supply a ReadString that does not stop until the string parameter is full or the end of line marker is reached, whatever comes first. Such versions (and this includes the ISO standard) supply a separate ReadToken with the meaning of the ReadString described here.

2. Different implementations vary as to whether a leading end of line marker is consumed by ReadString. Some do, and some do not. The latter require a specific Read(cr) or a call to a procedure ReadLn to consume the end of line marker before the next string can be read.

As mentioned earlier, there is a hierarchy of modules involved in I/O. There may also be a hierarchy of the procedures within a module. Thus it is typical, for example, for an invocation of ReadReal to involve several other procedures in the manner of the following pseudocode:

ReadReal:
  Perform an InOut.ReadString (ReadToken) to get the input
    Use InOut.Read to obtain the first character and put it in the string
    while character read is neither a space or end-of-line
      read another character and put it in the string
  invoke a conversion procedure to convert the characters to a real 

The conversion procedure employed to change the string representation of the real that was read into the appropriate internal representation for the system being used will normally be in yet another module. It is variously termed Reals.Convert, Conversions.StringToReal, or some similar name. The programmer must check the implementation in order to use these conversion routines directly; they are regarded as lower level, and vary considerably in non-standard implementations.

Another aspect of this hierarchy is that input and output can either be connected to the terminal (screen and keyboard) or redirected somewhere else. When either is attached to the terminal, the procedures in InOut may act by calling those of a module by that name. Here is a typical definition:

DEFINITION MODULE Terminal;

PROCEDURE Read (VAR ch : CHAR);
PROCEDURE ReadLn (VAR s : ARRAY OF CHAR);
PROCEDURE BusyRead (VAR ch : CHAR);
PROCEDURE ReadAgain;
PROCEDURE Write (ch : CHAR);
PROCEDURE WriteString (s : ARRAY OF CHAR);
PROCEDURE WriteLn;

END Terminal.

NOTE: Some implementations also include the (even less standard) procedure ClearScreen in this module.

When it is provided, the programmer can if desired use this module directly instead of InOut if that output is to go to the terminal and not elsewhere. Since some of these procedures have the same names as the ones in InOut, one must avoid importing any duplicates. As previously indicated, one imports either Terminal or InOut as a whole by using a statement like:

  IMPORT Terminal;

and then using qualified identifiers such as:

  Terminal.Read (ch);
  InOut.Write (ch);

and so on. In this manner, one may redirect the output of InOut (and therefore of RealInOut which uses it) by employing OpenOutput and sending it to a file, while continuing to write prompts to the screen using Terminal.WriteString. See 6.3.4 for an example.

There are a few new ones in this collection, though. BusyRead (where supplied) returns character zero (also called NULL) if something has not already been typed at the keyboard when it is invoked. If some character is waiting in the input from the keyboard, it returns that character.

NOTE: In most implementations providing this module, BusyRead (and sometimes Read also) will not echo what they fetch to the screen as do the read procedures in InOut. This could be useful if one wanted a program to ask the user to type in a password before carrying on with business, and didn't want some one reading that secret access code from the screen as it was typed.

An even simpler use for BusyRead is to check on whether any key has been pressed. In many programs, after writing out a message to the user, and just before filling the screen with new information, the programmer may wish to print the message "Press any key to continue => " and then wait for the user to read and digest the information on the screen before carrying on. Examples so far have used:

  WriteString ("Press any key to continue => ");
  Read (ch);
  Read (cr);

which has the advantage of simplicity, but the disadvantage that if the user presses the <RETURN> key, the program is thrown off by one character in its interpretation of the input stream. A better way is:

  PROCEDURE HoldScreen ();

  VAR
    Ch : CHAR;

  BEGIN
    WriteString ("Press any key to continue => ");
    REPEAT
      BusyRead (Ch);
    UNTIL (Ch # CHR (0));

  END HoldScreen;

where the character read will not be examined, and can be anything that can be typed.

A few implementations have a procedure already present in InOut to achieve this same goal. It may be called HoldScreen, WaitForKeypress, or some similar name.

ReadAgain (where supplied) takes the last character read and places it back into the part of the computer's memory from which input characters are read (called a buffer). The next invocation of Read will obtain the same character that was read the last time. This may seem a little obscure, and simple programs would not use it, but one application for this could be if the item being read necessitated a transfer of control to another module that did not have access to the variable used for the read. It could find out what character was read by looking for itself.

ReadLn (often written ReadLine to distinguish it from a ReadLn discussed above) reads a line of characters into a string variable. It (usually) echoes characters to the screen as they are read, and stops when it gets to a carriage return--which is not a part of the line read. In most cases, editing of the input is allowed before the <RETURN> key is pressed to end the line. Typically, a backspace typed by the user deletes the last character typed, a <DEL> may delete them all and <ESC> may be treated as <ESC RET> terminating the ReadLn, and leaving the variable holding the value of <ESC>.

NOTE: These rules are typical, but may vary on some terminals.

As a final note (for now) on I/O modules, some versions includes the module Screen whose definition is given below, and whose position in the hierarchy is below that of Terminal.

DEFINITION MODULE Screen;
PROCEDURE HomeCursor;   (* move cursor to upper left *)
PROCEDURE ClearScreen;  (* erase screen, home cursor *)
PROCEDURE EraseLine;    (* erase from cursor to end of line *)
PROCEDURE GotoXY (X, Y : CARDINAL);
  (* move cursor to row X column Y on screen *)
END Screen.

NOTE: In certain implementations, some or all of these procedures may be located in the module Terminal or even in InOut rather than in a separate one as described here. When that is the case, if output is connected to a non-screen device such as a printer or a file, calls to these routines are simply ignored.

These procedures can be very useful in producing nicely formatted output to the screen from program control and most implementations of Modula-2 should have something similar, though the names and syntax may vary.

6.3.3 Alternate Origins and Destinations

Up to this point, all the library tools imported for the examples have been Read and Write statements from STextIO, SWholeIO, and SRealIO, or from InOut and RealInOut. Furthermore, these programs have all obtained their data input from the standard input device (usually a keyboard) and sent their output to the standard output device (usually a screen.)

The combination of screen and keyboard are referred to together as the standard console.

The purpose of this section is to use some other library tools to obtain input from, and to send output to, other places than the console. This section can also serve as a preview of some concepts important to the development of chapter 7. Certain typical terminology is used:

The origin of input is called a source.
The destination of output is called a sink.
A data collection in transit from a source or to a sink is a stream.
The (physical or abstract) means by which data gets from a source to the processor, or from the processor to the sink is called a channel.

In figure 6.1, the solid lines represent channels that are presently in use, and the dashed lines channels that are potentially available. There may be many of these channels. Each may represent access to a device, such as a printer, screen, keyboard, or mouse. Or, a channel may be an access to a file stored on a device such as a tape or disk drive.

A channel has one of three conditions. It may be:
closed: only potentially available to a program,
open: attached to a program and immediately available for its use,
in use: open and with a data stream actually flowing in it.
A channel has one of three modes. It may be:
read: a channel from a source,
write: a channel to a sink,
read/write: a channel connection with something that is both a source and a sink.

Thus, the connection between the program and the source or sink can be thought of as a channel in which runs a stream of characters. Individual characters are inserted into (output) or plucked from (input) these streams by the I/O procedures. Modules like STextIO, SWholeIO, and SRealIO employ Writexx procedures to send data streams to a sink, and Readxx procedures to obtain data from a source. Typically, a procedure like WriteCard calls on a conversions module to convert the Cardinal data to a string, and then employs WriteString to do the output. The input/output modules will always mark a single special input channel and a single special output channel as open. These are called the standard I/O channels.

When these modules first start up, the default console channels are automatically opened for standard I/O. Data can be redirected elsewhere by attaching the open mode to some other channel(s), but these simple modules can only have one input and one output channel open at a time. More sophisticated modules that will be studied later can have several of each open simultaneously. Furthermore, since a single processor can only do one thing at a time, only one of the open channels can actually be in use at a time.

Without going into further detail at this time, there are more modules that can also control the flow of such information at a lower level, adding the power to open, close, and manipulate such streams from a program in more detail than can the S-modules (or InOut). The typical module hierarchy these considerations produced may be represented in part as in figure 6.2. The module names are positioned beside the diagram in their approximate place in the hierarchy. Additional details, including how to use the middle and lower levels of these modules, will be discussed in Chapter 7.

6.3.4 Redirection (optional)

WARNING: All information in this section is non-standard implementation specific and may not be available in the form given on the reader's own system.

In order to accomplish the re-direction of standard output to some other device (such as a printer or a file) one needs the following entities:

OpenOutput redirects the output away from the screen.

CloseOutput redirects output back to the console.

Here is a brief sample to illustrate the use of these:

  OpenOutput;   (* system will ask user where to send output *)
  WriteString ("The answer is ");   (* output will go there *)
  WriteReal (ans, 15);
  WriteLn;
  CloseOutput;   (* now output to console *)

If using classical Modula-2, OpenOutput and CloseOutput are both imported from InOut. In this event, the syntax for OpenOutput is usually something like:

  OpenOutput ("TEXT")

or, just

  OpenOutput ("")

When a program containing an InOut.OpenOutput statement is executed, the system is supposed to place a message like

	Output file?

or, perhaps

	out>

on the screen when this statement is encountered. At that point the user just types the name of the place where the output is to go in response.

NOTES: 1. Some systems may allow InOut.OpenOutput to take a device name as its program object. Here, to redirect to the printer, use OpenOutput ("PRN:").

2. If the user response is "ThisProg." (i.e. it ends with a period), the output will be sent to "ThisProg.TEXT" in the current disk directory (or folder.) The suffix "TEXT" is called the default extension and is obtained from the InOut.OpenOutput statement as it was used in the program. This could as easily have been "TXT" or "CODE," or whatever is appropriate. It is added only if the file name ends in a period.

3. Other systems will search for the given file name first, then append the default extension and try again.

4. A few versions of InOut.OpenOutput use a radically different approach. In these, OpenOutput("MyFile") is an instruction to find a particular file, and no prompts are given to the user at all.

5. Pressing just the <ENTER> or <RETURN> key (or, in some cases <ESC><RETURN>) in response to such a prompt from OpenOutput may cause the program to carry on without redirecting the Output. If this is the case, InOut.Done is FALSE at this point, however, and this can be checked and appropriate action taken. On the other hand, such a response may simply not be allowed in some systems.

6. If the file named exists on the disk already, its contents are overwritten. Some versions of OpenOutput give a prompt to confirm that this is the intention of the user.

7. The use of OpenOutput in InOut also redirects the output from RealInOut, because the latter uses the former.

If it is input that is to be redirected so as to come from a file, use:

OpenInput redirects input to come from other than the keyboard

CloseInput redirects input back from the console.

There are complex and varying rules for redirection (The author has counted at least nine different models in various implementations.) providing yet another motivation for having a standard. In ISO standard Modula-2, there is also a means to redirect the standard I/O channels, but the actual procedures to do so are unfortunately not included among the standard library modules. They are easy to write, however and details will be given later in this book. Perhaps they are already available on the student's system. Here, it will be assumed that the user of an otherwise ISO-standard system also has access to:

DEFINITION MODULE RedirStdIO;

(* =========================================
  Definition and Implementation © 1993-1997
                by R. Sutcliffe
        Trinity Western University
7600 Glover Rd., Langley, BC Canada V3A 6H4
         e-mail: rsutc@twu.ca
    Last modification date 1997 07 02
=========================================== *)

IMPORT ChanConsts;

TYPE
  OpenResults = ChanConsts.OpenResults;
	
PROCEDURE OpenResult () : OpenResults;
(* returns the result of the last attempt to open a file for redirection *)

PROCEDURE OpenOutput;
(* engages the user in a dialog to obtain a file for redirection of standard Output and attempts to open the file so obtained *)

PROCEDURE OpenOutputFile (VAR fileName: ARRAY OF CHAR);
(* opens the file specified by FileName for redirection of output. If the name supplied is the empty string, control passes to OpenOutput and the filename eventually used is returned in the parameter. *)

PROCEDURE CloseOutput;
(* returns the standard output channel to the default value *)

PROCEDURE OpenInput;
(* engages the user in a dialog to obtain a file for redirection of standard Input and attempts to open the file so obtained *)

PROCEDURE OpenInputFile (VAR fileName: ARRAY OF CHAR);
(* opens the file specified by fileName for redirection of input. If the name supplied is the empty string or is not found, control passes to OpenInput and the filename eventually used is returned in the parameter. *)

PROCEDURE CloseInput;
(* returns the standard input channel to the default value *)

END RedirStdIO.

Whatever version of redirection is available (see the manuals for the individual system), the following apply:

NOTE: Each system has its own rules in this regard, and all the possible details for correct syntax, device names, path names, and so on, cannot be covered here. System operating manuals must be consulted.

WARNING: Failure to close a channel to a file using CloseOutput and/or CloseInput, as appropriate, before the program terminates could result in some of the data being lost or the file damaged. The operating system is not responsible for (and indeed may not even "know" about) files that are opened by a program.

To illustrate some of the points made in this section, here is a program in classical-style Modula-2 that reads a sequence of real numbers from the keyboard and stores them into a data file on the disk for later use by another program. It determines that the sequence is finished when it fails to read a valid real or the number typed equals zero.

MODULE NumsToDisk;

(* Written by R.J. Sutcliffe *)
(* in non-ISO classical Modula-2 *)
(* to illustrate the use of redirection *)
(* using Metropolis Modula-2 for the Macintosh computer *)
(* last revision 1991 02 22 *)

FROM InOut IMPORT
  OpenOutput, CloseOutput, WriteLn, WriteString;
FROM RealInOut IMPORT
  ReadReal, WriteReal, Done;

VAR
  num : REAL;

BEGIN
  WriteString ("This program creates a disk file and enters ");
  WriteLn;
  WriteString ("the real numbers you type into the file. ");
  WriteLn;
  WriteLn;
  WriteString ("Type the numbers, separated by returns ");
  WriteLn;
  WriteString ("When finished, type a letter, or the number 0");
  WriteLn;
  WriteLn;
  OpenOutput ("TEXT");

  REPEAT
    ReadReal (num);
    IF Done
      THEN
        WriteReal (num, 0);
        WriteLn;
      END;
  UNTIL NOT Done OR (num = 0.0);

  CloseOutput;

END NumsToDisk.

This program was run and some numbers entered. A picture of the screen is reproduced in figure 6.3 below:

Following this, the file test contained:

12.20000
15.67800
11.90000
0.3450000
17.10000
2.500000

The use of OpenInput and CloseInput closely parallels that of OpenOutput and CloseOutput. The following module was used to open the file created above, examine the numbers in it, find the maximum, sum, and average and then display the results. It, however, is written in ISO Standard Modula-2, and employs the module RedirStdIO shown above.

Pseudocode:

Print an informative message
Set the maximum, number read, and sum to zero
Invoke OpenInput
Repeat
  read the next number
  If read was successful then
    add to the sum
    increase the count of those read successfully
    If the number is greater than the maximum then
      set maximum to the number
Until the read is unsuccessful
Print the results
MODULE StatsFromDisk;

(* Written by R.J. Sutcliffe *)
(* to illustrate the use of redirection on input *)
(* using ISO Modula-2 for the Macintosh computer *)
(* and RedirStdIO as implemented by R. Sutcliffe *)
(* last revision 1993 03 02 *)

FROM STextIO IMPORT
   WriteLn, WriteString, ReadChar, SkipLine;
FROM SWholeIO IMPORT
  WriteCard;
FROM RedirStdIO IMPORT
  OpenInput, CloseInput;
FROM SRealIO IMPORT
  ReadReal, WriteFixed;
FROM SIOResult IMPORT
  ReadResult, ReadResults;

VAR
  numRead, max, sum, mean : REAL;
  readOK : BOOLEAN;
  howMany : CARDINAL;
  key : CHAR;

BEGIN
  howMany := 0; (* initialize variables *)
  max := 0.0;
  sum := 0.0;
  WriteString ("This program opens a disk file of real numbers");
  WriteLn;
  WriteString ("and computes their sum, average, and maximum. ");
  WriteLn;
  WriteLn;
  OpenInput; (* no default extension used in this version. *)

  REPEAT
    ReadReal (numRead); (* get a number from the file *)
    readOK := (ReadResult () = allRight);
    SkipLine;
    IF readOK
      THEN
        sum := sum + numRead; (* if ok, add to sum *)
        howMany := howMany + 1; (* and increment how many read *)
        IF numRead > max
          THEN
            max := numRead; (* reset maximum if needed *)
          END;
      END;
  UNTIL NOT readOK;

  CloseInput;
  mean := sum / FLOAT (howMany);
  
  (* report results *)
  WriteString ("There were ");
  WriteCard (howMany, 0);
  WriteString (" numbers read, of which the largest was ");
  WriteFixed (max, 5, 0);
  WriteLn;
  WriteString ("The sum of these numbers was ");
  WriteFixed (sum, 5, 0);
  WriteString (" and the mean was ");
  WriteFixed (mean, 5, 0);
  WriteLn;
  WriteString ("Press a key to continue ==> ");
  ReadChar (key);

END StatsFromDisk.

There follow two pictures of the screen taken when this program was run. In the first, the Macintosh operating system has brought up a standard dialog box used to find existing files. The user of the program has found the correct folder, has highlighted the correct file, and is about to press the return key to allow the file to be opened.

The following is picture of the screen taken when this program was run.

A common use of OpenInput and OpenOutput is to give the user the choice of where to obtain or send data. Subsequent program statements are then executed in the light of the user choice. The following module is a modification of the DateCalc example of chapter 3 so as to employ the last of the refinements discussed there and also to allow for a series of input data to come from a file. Notice that the flag usingFile is reset according to the results of the attempt to do the redirection.

MODULE DateCalcB;

FROM STextIO IMPORT
  WriteString, WriteLn, ReadChar, SkipLine;
FROM SWholeIO IMPORT
  ReadCard, WriteCard;
FROM RedirStdIO IMPORT
  OpenInput, CloseInput, OpenResults, OpenResult;
FROM SIOResult IMPORT
  ReadResult, ReadResults;

VAR
  day, month, year, result, adjMonth, daysSinceMarch : CARDINAL;
  usingFile, again, leap, readOK: BOOLEAN;
  response : CHAR;

BEGIN
  (* header information left out*)

  REPEAT (* main repeat loop *) 
    (* check to see if user wants to use a file *)
    WriteString ("Do you wish the information to come");
    WriteString (" from a file? (Y/N) ====> ");
    ReadChar (response);
    SkipLine;
    WriteLn;
    WriteLn;
    usingFile := CAP (response) = "Y";
    (* all other responses mean we are not using file input *)

    IF usingFile
      THEN
        OpenInput;
      END;
    usingFile := usingFile AND (OpenResult () = opened);

    (* now get the data, either from file or keyboard *)
    REPEAT (* process until bad data *)
      IF NOT usingFile  (* prompts printed only if no file used *)
        THEN
          WriteString ("Enter the year number here ====>");
        END;
      ReadCard (year);
      readOK := (ReadResult() = allRight);
      SkipLine;
      IF readOK
        THEN
          IF NOT usingFile
            THEN
              WriteLn;
              WriteString ("Enter the month (1 - 12) here ====>");
            END;
        END;
      ReadCard (month);
      readOK := (ReadResult() = allRight);
      SkipLine;
      IF readOK
        THEN
          IF NOT usingFile
            THEN
              WriteLn;
              WriteString ("Enter the day number here ====>");
            END;
          END;
      ReadCard (day);
      readOK := (ReadResult() = allRight);
      SkipLine;
      IF readOK
        THEN
            (* next section computes number of days *)
          leap:= (year MOD 400 = 0)
                  OR ((year MOD 4 = 0) AND (year MOD 100 # 0));
          adjMonth := month + 12 * (( 12 - month) DIV 10);
          daysSinceMarch := TRUNC(30.6 * FLOAT (adjMonth - 3) + 0.5);
          result := (59 + daysSinceMarch + day);
          IF result > 365 
            THEN
              DEC (result, 365)
            END;
          IF leap AND (month > 2)
            THEN
              INC (result);
            END;
          
          (* Output the result in the required form *)
          WriteCard (year, 4);
          WriteCard (month, 3); (* ensure one space between *)
          WriteCard (day, 3);
          WriteString (" is day number ");
          WriteCard (result, 4);
          WriteString (" in that year.");
          WriteLn;
          WriteLn;
        END;  (* if *)
    UNTIL NOT (readOK);

     (* close the file if it was open *)
    IF  usingFile
      THEN
        CloseInput;
      ELSE
        WriteLn;
      END;
    WriteString ( "Do you wish to do another group? Y or N ==> ");
    ReadChar (response);
    again := CAP (response) = "Y";
    SkipLine;
    WriteLn;
  UNTIL NOT again;

END DateCalcB.

When this program was run, the data below was placed in a file that was chosen at the point when the user asked for file input:

1992
03
01
1992
12
31
2000
12
31
1500

Observe that the fourth data group contains only a year and is therefore incomplete. Here is a picture of the screen taken during a run of this program:

Notice the two places at which input errors are trapped and the series terminated.

6.3.5 Writing Special Characters To a Terminal

It is sometimes important to be aware that certain control characters have rather standard actions when sent to a terminal device. These considerations apply regardless of whether the I/O library is of the classical style or is ISO standard, and regardless of whether Modula-2 is used, or some other programming notation. Rather, the actions of these characters when they arrive at an output device like a screen or other terminal, is hard wired into that device and not under the control of the program or the user. There may be others as well, but the following are likely (but not guaranteed) to have the effect indicated:

1. CHR (30), also called EOL (End-Of-Line)--Sets the writing position to the beginning of the next line. (Use WriteLn instead.)
2. CHR (13), also called CR (Carriage Return)--Sets the writing position to the beginning of the current line.
3. CHR (10), also called LF (LineFeed)--Moves the writing position down to the same position on the next line.
4. CHR (8), also called BS (Backspace)--Moves the writing position back by one position leaving the character backspaced over intact.
5. CHR (127), also called DEL (Delete)--Backspaces and deletes the character.
6. CHR (12), also called FF (Form Feed)--Clears the screen, setting the cursor in the top left corner.

NOTE: On some terminals and on many printers an EOL, CR, CR/LF or LF/CR all do the same thing as a carriage return followed by a line feed. It is because of this variation from one system to another that the ISO standard regards the reaching of the end-of-line as a state or condition rather than as the reading of a particular character. (It may be more than one character in character based systems.)

Other control codes may be implemented on some terminals, but the above are the most common. All of these can be defined as constants, or if a module such as ASCII is provided (See Appendix 4.7), the names (EOL, CR, etc.) may be imported instead. Some such modules may spell these with lower case letters.


Contents