Part B--Program Control and Error Handling Issues
10.8 Transfer of Control

10.8.1 GOTO and Other Noxious Weeds

Unlike most other programming languages, Modula-2 does not allow statements to be given numbers or labels nor does it permit a GOTO line number or a GOTO label to be used.

Statements like the latter are often used by programmers in some languages to transfer control indiscriminately all over a program. Their use therefore results in programs that are hard to read, harder still to modify, and nearly impossible to debug. Programmers who think in an organized fashion and design in well-defined modules with pre-planned interfaces, and carefully packaged procedures do not need unconditional transfers of control except under very special circumstances. Modula-2 does provide for those special circumstances where a regulated transfer of control is permitted.

10.8.2 RETURN in a Regular Procedure

In Chapter 4, RETURN <value> was used as part of a function procedure to send a value back to the expression in which the function procedure was being invoked. RETURN can also be used in a regular procedure all by itself. It will immediately transfer control to a point just after the last statement in the body of the regular procedure, and may therefore be employed to achieve a controlled premature exit from a procedure, say, when an error is encountered. It also allows for multiple exit points from a procedure, depending on certain conditions that might prevail on the data being manipulated.

A RETURN used alone in a regular procedure is called a simple return. There is an implicit simple return to the external environment of the code at the end of a regular procedure.
PROCEDURE DemoReturn (VAR a : REAL);

BEGIN
  (* statement sequence 1 *) 
  IF error
    THEN
      RETURN (* just gives up *)
    END;
  (* statement sequence 2 *)   (* may never even get here *)
  IF error
    THEN
      RETURN (* or gives up here*)
    END;
  (* statement sequence 3 *)   (* much less here *)
(* execution of either return puts control here, at end. *)
END DemoReturn;

Some regard the use of this statement as a drastic step and as not being in particularly good taste, claiming that it should be employed only if there is no alternative. Certainly, it should not be abused, as its frequent use leads to hard-to-read code.

Of course, it is not permitted to use a bare RETURN in a function procedure, just as it is not permitted to use a RETURN <value> statement in a regular procedure.

Note also that since program module execution is essentially similar to that of procedure execution, the use of RETURN in the body of a module will cause a transfer of control to a point just after the last statement in the body associated with that scope--in this case, terminating the module.

MODULE ReturnDemo;
(* program by R. Sutcliffe
   to demonstrate return from a module
   revised 1994 04 15 *)
FROM STextIO IMPORT
  WriteString, WriteLn, ReadChar, SkipLine;
FROM SIOResult IMPORT
  ReadResults, ReadResult;
  
PROCEDURE FinishUp;
BEGIN
  WriteLn;
  WriteString ("Press return to end == >");
  ReadChar (ch);
  SkipLine;
END FinishUp;

VAR
  ch : CHAR;

BEGIN
  WriteString ("This program written to demonstrate");
  WriteString (" returning from a module body");
  WriteLn;
  REPEAT
    WriteString ("Do you wish to:");
    WriteLn;
    WriteString ("1. continue without returning?");
    WriteLn;
    WriteString ("2. return from the module");
    WriteLn;
    WriteString ("3. terminate normally");
    WriteLn;
    ReadChar (ch);
    IF (ReadResult () # allRight) OR (ch = "2")
      THEN
        SkipLine;
        WriteString ("returning from module");
        FinishUp;
        RETURN
      ELSE
        SkipLine;
      END;
  UNTIL (ReadResult () # allRight) OR (ORD (ch) > ORD ("3"));
  WriteString ("Regular termination");
  FinishUp;
END ReturnDemo.

When this program was run, the action of executing the RETURN from within the body of the module was indistinguishable from the action of merely concluding the program at the END in the normal way. This is as it should be, because RETURN transfers control to a point just beyond the call to FinishUp and before the END of the enclosing procedure scope, which is in this case the module.

What happens after a program terminates depends on the operating system being used; the user should be returned to the environment from which the program was run in the first place.

Either of
(i) allowing a program to run to its end, or
(ii) transferring control to the end by a simple RETURN statement
is called normal program termination.

10.8.3 Repetition Revisited--The Generalized LOOP Statement

Besides the WHILE, REPEAT, and FOR loops, Modula-2 allows a generalized type of loop from which there may be several possible exits. Here is an outline example that indicates one possible form in which this could exist:

LOOP
  Statement Sequence 1;
  IF Condition 1
    THEN
      EXIT
    END;   (* if *)
END;    (* loop *)

The syntax diagrams are simple:

An EXIT statement within a LOOP serves the same purpose as a RETURN in a regular procedure, in that it transfers control to the END and thence out of the LOOP. It differs, however, in that it is the only way to exit a LOOP, for this repetition will execute indefinitely if some EXIT statement is not encountered.

To put it another way, the EXIT provides a mid-loop test in the same way that the WHILE loop has a top-of-loop test and the REPEAT loop has a bottom-of-loop test. The following example reads in a Y or N, printing out a message only if some other character is pressed:

LOOP
  WriteString ('Enter <Y> or <N> please ... ');
  Read (ch); SkipLine;
  ch := CAP (ch);
  IF (ch = 'Y') OR (ch = 'N')
    THEN
      EXIT
    END; (* If *)
  WriteString ('Error. You must '); (* and go around again *)
END; (* Loop *)

Other more familiar repetition structures can be simulated with the LOOP.

REPEAT
  Statement Sequence
UNTIL Condition;

could be simulated by

LOOP
  Statement Sequence;

  IF Condition
    THEN
      EXIT
    END;
END;

Moreover, there may be multiple test points within a LOOP for a possible exit from the structure, provided that the run-time logic eventually allows one of the exit points to be reached.

LOOP
  Statement Sequence1;
  IF Condition1
    THEN
      EXIT
    END;
  Statement Sequence2;
  IF Condition2
    THEN
      EXIT
    END;
  Statement Sequence3;
END;

Note, however, that in most cases the LOOP structure is not needed. It should be used when

The latter case, where one uses LOOP..EXIT as a means to leave a very deeply nested loop within a loop without having to transfer control out one level at a time, can be illustrated in the outline:

LOOP
  REPEAT
    (* some code *)
    REPEAT
      (* some more code *)
      CASE selector OF
        one:
          (* more code *) |
        two:
          (* alternate *)
        ELSE
          EXIT
        END;
    UNTIL condition1
  UNTIL condition2;
  (* yet another statement sequence *)
  EXIT; (* if we get here, processing done *)
END;

In cases like these, a normal repetition structure from which one wants to have an alternate exit points surrounded with a LOOP that has an EXIT at the very end. If processing reaches that end point, normal termination of the repetition is assured by the terminating EXIT. Alternately, it is possible to exit without concluding the repetition if necessary.

LOOP structures are often used when processing data from or to sequential files, though they are usually not strictly necessary, and just save a little writing. Typical code from Chapter 8 such as

  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;

could be written:

  LOOP
    WriteChar (">"); (* prompt *)
    ReadInt (number);
    IF ReadResult() = allRight
      THEN
        SkipLine;
        WholeIO.WriteInt (outfile, number, 0);
        TextIO.WriteLn (outfile); (* separate the numbers *)
      ELSE
        EXIT
      END;  (* if *)
  END; (* loop *)

Notice that the only saving here was not having to write the condition on ReadResult twice, and indeed, if the conditions for processing were slightly different from those for exiting, there would be no gain at all. In general, it is better to try to employ one of the more conventional loop structures, and this can be done in the vast majority of situations.

EXIT and RETURN are the only statements in Modula-2 that cause an immediate transfer of the normal flow of control, and these will transfer only to a point after the last statement in the statement sequence associated with the loop or the body of the current PROCEDURE. There are abnormal transfers resulting from errors; these will be considered in the section on exceptions later in this chapter.


Contents