Beginner

Operators in Modula-2

Learn arithmetic, relational, logical, and set operators in Modula-2, including DIV, MOD, and operator precedence with practical Docker-ready examples

Operators are the building blocks of expressions. Modula-2 inherits Pascal’s clean operator design but refines it with stricter typing rules and a few additions that suit systems programming. Because the language is statically and strongly typed, operators are not silently overloaded across incompatible types — a REAL and an INTEGER cannot be added without an explicit conversion, and the compiler will tell you so.

Modula-2 distinguishes integer division (DIV) from real division (/), provides a true MOD operator with well-defined behavior on non-negative operands, and treats sets as first-class values with their own algebra. Logical operators read as English words (AND, OR, NOT) rather than punctuation, reinforcing the language’s emphasis on readability.

In this tutorial you’ll write a small program that exercises every major category of Modula-2 operator and observe exactly what each one produces.

Arithmetic Operators

Modula-2 provides the standard arithmetic operators, plus separate operators for integer and real division:

OperatorMeaningOperand types
+AdditionINTEGER, CARDINAL, REAL
-Subtraction / unary minusINTEGER, CARDINAL, REAL
*MultiplicationINTEGER, CARDINAL, REAL
/Real divisionREAL
DIVInteger (truncated) divisionINTEGER, CARDINAL
MODRemainder after DIVINTEGER, CARDINAL

Using / on integers is a compile-time error — the language forces you to choose between DIV and / deliberately.

Create a file named arithmetic.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
MODULE arithmetic;

FROM StrIO     IMPORT WriteString, WriteLn;
FROM NumberIO IMPORT WriteInt, WriteCard;
FROM RealIO    IMPORT WriteReal;

VAR
  a, b: INTEGER;
  x, y: REAL;

BEGIN
  a := 17;
  b := 5;

  WriteString("a + b   = "); WriteInt(a + b, 4);   WriteLn;
  WriteString("a - b   = "); WriteInt(a - b, 4);   WriteLn;
  WriteString("a * b   = "); WriteInt(a * b, 4);   WriteLn;
  WriteString("a DIV b = "); WriteInt(a DIV b, 4); WriteLn;
  WriteString("a MOD b = "); WriteInt(a MOD b, 4); WriteLn;

  x := 17.0;
  y := 5.0;
  WriteString("x / y   = "); WriteReal(x / y, 8); WriteLn
END arithmetic.

WriteInt(value, width) right-justifies the integer in a field of the given width; WriteReal(value, width) formats the real in scientific notation by default.

Relational and Logical Operators

Relational operators compare two values and yield a BOOLEAN. Modula-2 supports both # and <> for “not equal” — # is the original PIM spelling, <> was added by the ISO standard.

OperatorMeaning
=Equal
# or <>Not equal
<, <=Less, less-or-equal
>, >=Greater, greater-or-equal
AND (&)Logical conjunction
ORLogical disjunction
NOT (~)Logical negation

AND and OR short-circuit: the right operand is only evaluated when the result is not already determined by the left.

Create a file named logic.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
MODULE logic;

FROM StrIO IMPORT WriteString, WriteLn;

PROCEDURE WriteBool(b: BOOLEAN);
BEGIN
  IF b THEN WriteString("TRUE ") ELSE WriteString("FALSE") END
END WriteBool;

VAR
  a, b: INTEGER;
  p, q: BOOLEAN;

BEGIN
  a := 7;
  b := 12;

  WriteString("a < b      -> "); WriteBool(a < b);      WriteLn;
  WriteString("a = b      -> "); WriteBool(a = b);      WriteLn;
  WriteString("a # b      -> "); WriteBool(a # b);      WriteLn;
  WriteString("a >= 7     -> "); WriteBool(a >= 7);     WriteLn;

  p := (a < b);
  q := (a MOD 2 = 0);

  WriteString("p AND q    -> "); WriteBool(p AND q);    WriteLn;
  WriteString("p OR  q    -> "); WriteBool(p OR q);     WriteLn;
  WriteString("NOT p      -> "); WriteBool(NOT p);      WriteLn
END logic.

The standard library does not provide a built-in boolean printer, so a tiny WriteBool procedure keeps the output readable.

Set Operators and IN

Sets are a first-class type in Modula-2, and they carry their own algebra. The same +, -, and * symbols you saw with numbers also mean union, difference, and intersection when applied to sets — Modula-2 resolves the meaning from the operand types. The IN operator tests membership.

Create a file named sets.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
37
38
39
40
41
42
43
44
45
46
47
48
49
MODULE sets;

FROM StrIO IMPORT WriteString, WriteLn;

TYPE
  DigitSet = SET OF [0..9];

VAR
  evens, primes, both, either, diff: DigitSet;

PROCEDURE WriteSet(s: DigitSet);
VAR i: CARDINAL;
BEGIN
  WriteString("{ ");
  FOR i := 0 TO 9 DO
    IF i IN s THEN
      IF i = 0 THEN WriteString("0 ")
      ELSIF i = 1 THEN WriteString("1 ")
      ELSIF i = 2 THEN WriteString("2 ")
      ELSIF i = 3 THEN WriteString("3 ")
      ELSIF i = 4 THEN WriteString("4 ")
      ELSIF i = 5 THEN WriteString("5 ")
      ELSIF i = 6 THEN WriteString("6 ")
      ELSIF i = 7 THEN WriteString("7 ")
      ELSIF i = 8 THEN WriteString("8 ")
      ELSE             WriteString("9 ")
      END
    END
  END;
  WriteString("}")
END WriteSet;

BEGIN
  evens  := DigitSet{0, 2, 4, 6, 8};
  primes := DigitSet{2, 3, 5, 7};

  both   := evens * primes;   (* intersection *)
  either := evens + primes;   (* union        *)
  diff   := evens - primes;   (* difference   *)

  WriteString("evens     = "); WriteSet(evens);  WriteLn;
  WriteString("primes    = "); WriteSet(primes); WriteLn;
  WriteString("union     = "); WriteSet(either); WriteLn;
  WriteString("intersect = "); WriteSet(both);   WriteLn;
  WriteString("evens-prm = "); WriteSet(diff);   WriteLn;

  IF 4 IN evens THEN WriteString("4 IN evens"); WriteLn END;
  IF NOT (4 IN primes) THEN WriteString("4 NOT IN primes"); WriteLn END
END sets.

The same + - * symbols meaning “add/subtract/multiply” for numbers also mean “union/difference/intersection” for sets — context disambiguates them.

Precedence and Assignment

Modula-2 has a famously short precedence table — only four levels — which keeps expression evaluation predictable.

PriorityOperators
1 (highest)NOT, ~
2*, /, DIV, MOD, AND, &
3+, -, OR
4 (lowest)=, #, <>, <, <=, >, >=, IN

Note the trap: relational operators have the lowest priority, so a < b AND c < d parses as a < (b AND c) < d — a type error. Always parenthesize: (a < b) AND (c < d).

Assignment uses := (not =, which is the equality operator). Modula-2 has no compound assignment (+=, -=); you write the long form, or use the built-in INC and DEC procedures.

Create a file named precedence.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
MODULE precedence;

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

VAR
  result, counter: INTEGER;

BEGIN
  (* Mixing precedence levels:
     2 + 3 * 4   = 2 + 12 = 14
     (2 + 3) * 4 = 5  * 4 = 20 *)
  result := 2 + 3 * 4;
  WriteString("2 + 3 * 4    = "); WriteInt(result, 3); WriteLn;

  result := (2 + 3) * 4;
  WriteString("(2 + 3) * 4  = "); WriteInt(result, 3); WriteLn;

  (* Relational ops are LOWEST priority — parens are mandatory *)
  IF (result > 10) AND (result < 100) THEN
    WriteString("result is two digits"); WriteLn
  END;

  (* Modula-2 has no += operator; use INC / DEC instead *)
  counter := 10;
  INC(counter);        (* counter := counter + 1 *)
  INC(counter, 5);     (* counter := counter + 5 *)
  DEC(counter, 2);     (* counter := counter - 2 *)
  WriteString("counter      = "); WriteInt(counter, 3); WriteLn
END precedence.

Running with Docker

Pull the image and compile each program with gm2:

1
2
3
4
5
6
7
8
# Pull the image
docker pull codearchaeology/modula-2:latest

# Compile and run each example
docker run --rm -v $(pwd):/app -w /app codearchaeology/modula-2:latest sh -c 'gm2 -o arithmetic arithmetic.mod && ./arithmetic'
docker run --rm -v $(pwd):/app -w /app codearchaeology/modula-2:latest sh -c 'gm2 -o logic      logic.mod      && ./logic'
docker run --rm -v $(pwd):/app -w /app codearchaeology/modula-2:latest sh -c 'gm2 -o sets       sets.mod       && ./sets'
docker run --rm -v $(pwd):/app -w /app codearchaeology/modula-2:latest sh -c 'gm2 -o precedence precedence.mod && ./precedence'

Expected Output

arithmetic:

a + b   =   22
a - b   =   12
a * b   =   85
a DIV b =    3
a MOD b =    2
x / y   = 3.4E+00

logic:

a < b      -> TRUE 
a = b      -> FALSE
a # b      -> TRUE 
a >= 7     -> TRUE 
p AND q    -> FALSE
p OR  q    -> TRUE 
NOT p      -> FALSE

sets:

evens     = { 0 2 4 6 8 }
primes    = { 2 3 5 7 }
union     = { 0 2 3 4 5 6 7 8 }
intersect = { 2 }
evens-prm = { 0 4 6 8 }
4 IN evens
4 NOT IN primes

precedence:

2 + 3 * 4    =  14
(2 + 3) * 4  =  20
result is two digits
counter      =  14

Key Concepts

  • DIV and MOD are integer-only; / is real-only. There is no implicit conversion between INTEGER and REAL — you must use FLOAT or TRUNC to cross the boundary.
  • # and <> both mean “not equal”# is PIM, <> is ISO. Pick one and stay consistent within a module.
  • Logical operators are English words: AND, OR, NOT. & and ~ are accepted PIM synonyms for AND and NOT, but AND/NOT read more clearly.
  • AND and OR short-circuit, so guards like (p # NIL) AND (p^.value > 0) are safe.
  • Sets are first-class: + - * overload for union/difference/intersection, and IN tests membership. The base type is usually a small enumeration or subrange.
  • Relational operators have the lowest priority — always parenthesize boolean sub-expressions inside AND/OR.
  • Assignment is :=, equality is = — confusing them is a compile error, not a silent bug as in C.
  • No compound assignment operators; use INC(x), INC(x, n), DEC(x), DEC(x, n) for the common increment/decrement idioms.

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