3.6 Analysis of Loops

In some of the examples of repetition, REPEAT .. UNTIL was more appropriate than the WHILE loop, because it saved a few statements, and because the loop was to execute at least once. Which of the two types of loop one uses depends on whether the test for repetition is to be at the top of the loop or at the bottom, that is, whether one needs to have the loop execute at least once in all cases. This is, however, a minor design consideration, for as the examples showed, it is possible to use the WHILE construction all the time by initializing the value of the controlling variable before entering the loop. Because this is so, some teachers discourage or even forbid their students from using the REPEAT..UNTIL construction at all. That is, there may be considerations other than program design that determine the details of the code. Local customs should be followed in such matters. This text will use the WHILE construction most of the time, but REPEAT will be employed when it is more natural. Many of the comments made in this section about the WHILE loops apply to the REPEAT loop as well.

In any case, the principle of top-down-design as applied to this situation means that one should choose the type of loop before starting to code. There must be a reason for putting each statement on paper, and the programmer must know what that reason is and be able to explain it.

3.6.1 Loops and Boolean Flags

In some programs, it may be necessary to execute the code in the loop indefinitely until some special condition is reached. This was done in several of the earlier examples in this chapter when the bulk of the program code was surrounded by a REPEAT..UNTIL NOT again construction.

If a loop can be exited only on some special value of the boolean expression controlling it, that value is called a sentinel value. If a variable is used to hold the value, it may be called a sentinel variable.

Example:

Write a module to simulate a simple four function calculator.

Discussion:

Such calculators maintain two values called the x-register and the accumulator. The x-register holds the most recently entered value, and the accumulator holds the most recently obtained result. Here, the code will expect an alternating sequence of one-character symbols (the operations) and numbers (the new x-register each time). It will perform the binary operations as (x-register operation accumulator) ==> accumulator.

Pseudocode:

  Print an informative heading
  Print the instructions
  Zero the x-register and the accumulator
  Repeat
    Print the accumulator
    Read a character (the operation)
    Set the flag opOk if it is a valid operation
    If the operation is not exit then
      Read the next x-register
      Set the numOk flag if the number was read correctly
    If the number was not ok then
      set the x-register to zero
    If the operation was ok, then
      If it was +, then
        add the x-register to the accumulator
        else if - then
          subtract the x-register from the accumulator
        else if * then
          multiply the accumulator by the x-register
        else if /, then
          if the x-register was not zero, then
            divide the accumulator by the x-register
            else print an error message
      else assume that the operation was = and
        set the accumulator to the value of the x-register
  until the operation is exit
MODULE FourBanger;

(* Written by R.J. Sutcliffe *)
(* to illustrate Boolean flags in loops *)
(* using P1 MPW Modula-2 for the Macintosh computer *)
(* last revision 1993 02 15 *)

FROM STextIO IMPORT
  WriteString, WriteLn, ReadChar, SkipLine;
FROM SIOResult IMPORT
  ReadResult, ReadResults;
FROM SRealIO IMPORT
  ReadReal, WriteFixed;

VAR
  xReg, accum: REAL;
  op : CHAR;
  opOK, numOK : BOOLEAN;

BEGIN 
  (* write information *)
  WriteString ("FourBanger was written by R.J. Sutcliffe");
  WriteLn;
  WriteString ("to illustrate Boolean flags in loops");
  WriteLn;
  WriteLn;
  WriteString ("This program simulates a four function calculator.");
  WriteLn;
  WriteString ("You enter an operation and then a number");
  WriteLn;
  WriteString ("The running result will be displayed.");
  WriteLn;
  WriteString ("If you enter 'E' for the operation the program exits.");
  WriteLn;
  WriteString ("The default operation is = which means 'display number.'");
  WriteLn;
  WriteLn;

  (* Initialize *)
  accum := 0.0;
  xReg := 0.0;
  
  REPEAT
    (* write out the accumulated value *)
    WriteFixed (accum, 6 ,25);
    WriteLn;
    ReadChar (op); (* no end of line, expect number now *)
    (* check for operation *)
    opOK := (ReadResult() = allRight) AND
           ((op = "+") OR (op = "-") OR (op = "*") OR (op = "/"));
    IF CAP (op) # "E"  (* not exit *)
      THEN
        (* obtain a number *)
        ReadReal (xReg);
        numOK := (ReadResult() = allRight);
        SkipLine;
        IF NOT numOK  (* num is bad for some reason *)
          THEN
            xReg := 0.0;
          END;
        IF opOK
          THEN
            (* go back and see what the operation was and do it *)
            IF op = "+"
              THEN
                accum := accum + xReg;
              ELSIF op = "-" THEN
                accum := accum - xReg;
              ELSIF op = "*" THEN
                accum := accum * xReg;
              ELSIF (op = "/") THEN
                IF (xReg # 0.0)
                  THEN
                    accum := accum / xReg;
                  ELSE
                    WriteString ("*** Divide by zero error ***");
                    WriteLn;
                  END;
              END;
          ELSE
            accum := xReg;
          END;
      END;
    UNTIL CAP (op) = "E";

END FourBanger.

NOTES: A new function procedure CAP has been introduced to replace such boolean expressions as (op = "e" ) OR (op = "E") by CAP (op) = "E". CAP is a standard identifier.

A run of FourBanger is recorded below, with some of the on-screen carriage returns deleted to save space.

FourBanger was written by R.J. Sutcliffe
to illustrate Boolean flags in loops

This program simulates a four function calculator.
You enter an operation and then a number
The running result will be displayed.
If you enter 'E' for the operation the program exits.
The default operation is = meaning 'display number'.

                 0.000000
+9.0             9.000000
-8.8             0.200000
*789.0         157.799850
/8.0            19.724981
/0.0
*** Divide by zero error ***
                19.724981
*777.0       15326.310547
e

Observe the necessity of capturing the value returned by ReadResult before calling SkipLine. If this were not done, and SkipLine were called first, the latter would reset the state obtained by ReadResult and the actual result of the operation ReadReal would be lost.

Instead of having ReadResult in a separate module (as in the ISO standard) to indicate success in reading from the standard I/O channel, many older versions of Modula-2 have a boolean flag in the library module that is imported and checked after every read operation. This is true of both the classical InOut and RealInOut modules. In such versions, one might write:

FROM InOut IMPORT
  ReadInt, Done;

and then in the program, one could use the value of Done, set by InOut, to determine whether a correct integer had been typed. Indeed, one could so arrange things that Done served as a flag to the program to proceed. If it were not TRUE, the user could be forced to try again until it were. Code could look like this:

REPEAT
  WriteString ("Enter the number here ==> ");
  ReadInt (theNumber);
  numOK := Done;  (* non-ISO version *)
  IF NOT Done
    THEN
      WriteString ("error in number typed ; please try again);
      WriteLn;
    END;
  Read (cr);
  (* these versions read carriage return as a character *)
UNTIL numOK;

The classical module RealInOut, where available separately from InOut, also has a variable Done that can be imported into a program module and used by it. The code

      IF CAP (op) # "E"
        THEN
          ReadReal (xReg);
          numOK := (ReadResult() = allRight);
          SkipLine;
        END;
      IF NOT numOK

in the example FourBanger would be written:

      IF CAP (op) # "E"
        THEN
          ReadReal (xReg);
          numOK := Done;
          Read (cr); (* get carriage return following *)
        END;
      IF NOT Done

See section 3.9 for what to do if it is necessary to import both the Done from InOut and the Done from RealInOut into the same program.


Contents