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.
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.
Write a module to simulate a simple four function calculator.
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.
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.