Beginner

Control Flow in Modula-2

Master control flow in Modula-2 - IF/ELSIF conditionals, CASE statements, FOR/WHILE/REPEAT loops, and LOOP/EXIT with runnable Docker examples using gm2

Control flow determines the order in which statements execute. As an imperative, procedural language, Modula-2 offers a complete and remarkably clean set of structured control statements - a direct inheritance from Niklaus Wirth’s design philosophy of “make it simple, make it consistent.”

What makes Modula-2 distinctive is that every control structure is fully bracketed with an explicit END. There are no dangling-else ambiguities, no reliance on indentation, and no need for begin/end blocks around multiple statements like Pascal requires. An IF ends with END, a WHILE ends with END, and a CASE ends with END. This consistency makes Modula-2 control flow predictable and readable - one of the reasons it was favored for teaching systems programming.

In this tutorial you’ll learn Modula-2’s conditional statements (IF/ELSIF/ELSE and CASE), its four loop constructs (FOR, WHILE, REPEAT, and LOOP), and the EXIT statement for breaking out of a LOOP. Notably, Modula-2 has no ternary operator and no break/continue keywords in the C sense - it uses the structured LOOP/EXIT pairing instead.

Conditionals: IF, ELSIF, ELSE

The IF statement chooses between alternatives. Multiple conditions chain together with ELSIF (a single keyword, not ELSE IF), and the whole statement closes with END. Boolean conditions use the AND, OR, and NOT keywords.

Create a file named conditionals.mod:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
MODULE Conditionals;

FROM StrIO IMPORT WriteString, WriteLn;
FROM NumberIO IMPORT WriteInt;

VAR
  score: INTEGER;

BEGIN
  score := 78;

  WriteString("Score: ");
  WriteInt(score, 1);
  WriteLn;

  (* IF / ELSIF / ELSE chain - no braces, closed with END *)
  IF score >= 90 THEN
    WriteString("Grade: A")
  ELSIF score >= 80 THEN
    WriteString("Grade: B")
  ELSIF score >= 70 THEN
    WriteString("Grade: C")
  ELSE
    WriteString("Grade: F")
  END;
  WriteLn;

  (* Boolean operators: AND, OR, NOT *)
  IF (score > 0) AND (score < 100) THEN
    WriteString("Valid percentage")
  END;
  WriteLn
END Conditionals.

A score of 78 fails the >= 90 and >= 80 tests but passes >= 70, so it prints “Grade: C”. Note that sub-expressions like score > 0 are wrapped in parentheses - Modula-2’s relational operators have lower precedence than AND/OR, so the parentheses are required.

CASE Statements

When you need to branch on a single value with many discrete outcomes, CASE is cleaner than a long IF/ELSIF chain. Case labels are separated by |, you can list multiple values per label or use a low..high range, and an optional ELSE handles everything not matched.

Create a file named case_demo.mod:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
MODULE CaseDemo;

FROM StrIO IMPORT WriteString, WriteLn;

VAR
  day: INTEGER;

BEGIN
  day := 3;

  WriteString("Day type: ");
  CASE day OF
    1..5: WriteString("Weekday") |
    6, 7: WriteString("Weekend")
  ELSE
    WriteString("Invalid day")
  END;
  WriteLn
END CaseDemo.

The label 1..5 is a range covering Monday through Friday, while 6, 7 is an explicit list. With day set to 3, the first label matches and prints “Weekday”. The ELSE branch catches any value outside the defined labels - without it, an unmatched value would raise a runtime error in standard Modula-2.

FOR and WHILE Loops

Modula-2 provides two pre-test counting/conditional loops. The FOR loop iterates a control variable across a range and even supports a BY clause for custom step sizes (including negative steps for counting down). The WHILE loop runs as long as its condition holds, testing before each iteration.

Create a file named loops.mod:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
MODULE Loops;

FROM StrIO IMPORT WriteString, WriteLn;
FROM NumberIO IMPORT WriteInt;

VAR
  i, sum: INTEGER;

BEGIN
  (* FOR loop counting up *)
  WriteString("Count up:");
  FOR i := 1 TO 5 DO
    WriteString(" ");
    WriteInt(i, 1)
  END;
  WriteLn;

  (* FOR loop with BY -1 to count down *)
  WriteString("Count down:");
  FOR i := 5 TO 1 BY -1 DO
    WriteString(" ");
    WriteInt(i, 1)
  END;
  WriteLn;

  (* WHILE loop accumulating a sum *)
  sum := 0;
  i := 1;
  WHILE i <= 5 DO
    sum := sum + i;
    INC(i)
  END;
  WriteString("Sum 1..5 = ");
  WriteInt(sum, 1);
  WriteLn
END Loops.

The WriteInt(i, 1) call prints the integer in a field at least 1 character wide. The INC(i) built-in increments the variable - it’s the idiomatic Modula-2 equivalent of i := i + 1 (with a matching DEC for decrement). The FOR loop manages its own control variable automatically, so you never write INC inside it.

LOOP, EXIT, and REPEAT

Modula-2 deliberately omits C-style break and continue. Instead, for loops whose exit point isn’t naturally at the top or bottom, it offers the LOOP statement - an unconditional loop that you escape with EXIT. The EXIT statement only ever exits the innermost enclosing LOOP (not a FOR, WHILE, or REPEAT).

The REPEAT/UNTIL loop is the post-test counterpart to WHILE: it always executes its body at least once, then tests the condition at the bottom.

Create a file named loop_control.mod:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
MODULE LoopControl;

FROM StrIO IMPORT WriteString, WriteLn;
FROM NumberIO IMPORT WriteInt;

VAR
  i, n: INTEGER;

BEGIN
  (* LOOP with EXIT - find the first multiple of 7 at or above 21 *)
  i := 21;
  LOOP
    IF i MOD 7 = 0 THEN
      EXIT
    END;
    INC(i)
  END;
  WriteString("First multiple of 7 >= 21: ");
  WriteInt(i, 1);
  WriteLn;

  (* REPEAT ... UNTIL - body always runs at least once *)
  n := 1;
  WriteString("Powers of 2:");
  REPEAT
    WriteString(" ");
    WriteInt(n, 1);
    n := n * 2
  UNTIL n > 16;
  WriteLn
END LoopControl.

The LOOP begins at 21, immediately finds that 21 MOD 7 = 0, and EXITs on the very first iteration. The REPEAT loop doubles n starting from 1, stopping once n exceeds 16 - because the test is at the bottom, the value 16 is printed before the loop ends.

Running with Docker

Each example is a standalone program module. Pull the image once, then compile and run each file with gm2:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
# Pull the GNU Modula-2 image
docker pull codearchaeology/modula-2:latest

# Run the conditionals example
docker run --rm -v $(pwd):/app -w /app codearchaeology/modula-2:latest sh -c 'gm2 -o conditionals conditionals.mod && ./conditionals'

# Run the CASE example
docker run --rm -v $(pwd):/app -w /app codearchaeology/modula-2:latest sh -c 'gm2 -o case_demo case_demo.mod && ./case_demo'

# Run the loops example
docker run --rm -v $(pwd):/app -w /app codearchaeology/modula-2:latest sh -c 'gm2 -o loops loops.mod && ./loops'

# Run the loop control example
docker run --rm -v $(pwd):/app -w /app codearchaeology/modula-2:latest sh -c 'gm2 -o loop_control loop_control.mod && ./loop_control'

Expected Output

Running conditionals:

Score: 78
Grade: C
Valid percentage

Running case_demo:

Day type: Weekday

Running loops:

Count up: 1 2 3 4 5
Count down: 5 4 3 2 1
Sum 1..5 = 15

Running loop_control:

First multiple of 7 >= 21: 21
Powers of 2: 1 2 4 8 16

Key Concepts

  • Every control structure is explicitly closed with END - there are no braces and no statement-grouping begin/end blocks needed around multiple statements.
  • ELSIF is a single keyword for chaining conditions, and ELSE provides the catch-all branch in both IF and CASE.
  • CASE labels use | separators and support both value lists (6, 7) and ranges (1..5); always provide an ELSE to avoid runtime errors on unmatched values.
  • Four loop constructs serve distinct needs: FOR for counted iteration (with optional BY step), WHILE for pre-tested conditions, REPEAT/UNTIL for post-tested loops that always run once, and LOOP for loops that exit from the middle.
  • EXIT replaces break and only terminates the innermost LOOP - there is no continue equivalent, encouraging clearer loop structure.
  • INC and DEC are built-in procedures for incrementing and decrementing variables, idiomatic in WHILE and LOOP bodies.
  • Relational operators need parentheses inside boolean expressions because AND/OR bind more tightly than comparisons (e.g., (score > 0) AND (score < 100)).
  • Modula-2 has no ternary operator - all conditional selection is done with the structured IF or CASE statements.

Running Today

All examples can be run using Docker:

docker pull codearchaeology/modula-2:latest
Last updated:

Comments

Loading comments...

Leave a Comment

2000 characters remaining