This tutorial walks you through using the V1 compiler: building it,
compiling small Modula-2 programs, running them on the MC64 interpreter
(mcint), and understanding the output. Every example below was actually
compiled and run to produce the shown result.
You don't need any tooling besides the project itself, GNU Modula-2 (gm2),
the Coco/R generator (CR), and the mcint interpreter.
The language has no text I/O library, so programs signal their result by setting a global variable
VAR ExitCode : INTEGER;. If present, the generated program prints its value as a decimal number (plus a newline) and then exits. The built-in statementsWriteString("...")andWriteInt(42)write text directly.
Two prerequisites live as sibling projects of this one:
| what | where | environment override |
|---|---|---|
| Coco/R generator | ../CocoGm2/CR |
CRBIN |
| MC64 interpreter | ../m-code-64/mcint |
MCINT |
Everything here assumes you are in the project root:
cd .../m2compiler-V1
./build.sh # regenerate Coco/R sources, compile with gm2 -fiso, link ./M2c
./build.sh runs Coco/R on src/M2c.atg to regenerate the scanner, parser
and driver (src/M2cS.mod, src/M2cP.mod, src/M2c.mod), compiles the
hand-written modules (FileIO, SymTab, MGen), and links the compiler
binary ./M2c.
From here on we use mcint with the default sibling path:
MCINT=../m-code-64/mcint
Save a program in a .mod file and run the compiler on it:
./M2c hello.mod
The compiler prints to standard output:
Parsing hello.mod
--- Symbol table ---
INTEGER : PREDEF #0
...
Hello : MODULE #-1
ExitCode : VAR #0
Parsed correctly
Parsing ... then the symbol table dump (a teaching feature of
this compiler), then the verdict: Parsed correctly or
Incorrect source.<file>.LST is written with the
source text and any errors marked with a ^ and a message.<Module>.MC4 — named
from the MODULE name, not the file name — into the current directory.Run the image on the MC64 interpreter:
$MCINT Hello.MC4 # right after a successful ./M2c hello.mod
MODULE Hello;
VAR ExitCode : INTEGER;
BEGIN
WriteString("Hello, MC64!");
ExitCode := 42
END Hello.
./M2c hello.mod # -> "Parsed correctly", writes Hello.MC4
$MCINT Hello.MC4
Output:
Hello, MC64!42
WriteString prints the text without a newline, and the embedded helper
then prints ExitCode = 42 followed by a line break.
All structured statements: FOR, WHILE, REPEAT, LOOP/EXIT,
CASE, IF/ELSIF/ELSE.
MODULE Control;
CONST Limit = 20;
VAR i, sum, n : INTEGER;
VAR ExitCode : INTEGER;
BEGIN
sum := 0;
FOR i := 1 TO 10 DO sum := sum + i END; (* sum = 55 *)
n := sum MOD Limit; (* n = 15 *)
WHILE n < 0 DO n := n + 1 END;
REPEAT n := n + 1 UNTIL n >= 17; (* n = 17 *)
LOOP
IF n > 100 THEN EXIT END;
n := n + 1;
IF n >= 19 THEN EXIT END
END; (* n = 19 *)
CASE n OF
19 : ExitCode := sum + n
ELSE ExitCode := 0
END
END Control.
./M2c control.mod
$MCINT Control.MC4
Output:
74
Nested procedures, value and VAR parameters, functions with RETURN
(including recursion), and open-array formals with HIGH.
MODULE Procs;
VAR a : ARRAY [1 .. 5] OF INTEGER;
VAR acc : INTEGER;
VAR ExitCode : INTEGER;
PROCEDURE Fact(n : INTEGER) : INTEGER; (* recursive function *)
BEGIN
IF n <= 1 THEN RETURN 1 ELSE RETURN n * Fact(n - 1) END
END Fact;
PROCEDURE Accumulate(VAR total : INTEGER; x : INTEGER); (* VAR param *)
BEGIN
total := total + x
END Accumulate;
PROCEDURE SumOpen(x : ARRAY OF INTEGER) : INTEGER; (* open array *)
VAR i, s : INTEGER;
BEGIN
s := 0;
FOR i := 0 TO HIGH(x) DO s := s + x[i] END;
RETURN s
END SumOpen;
BEGIN
a[1] := 1; a[2] := 2; a[3] := 3; a[4] := 4; a[5] := 5;
acc := 0;
Accumulate(acc, Fact(5)); (* acc := 120 *)
Accumulate(acc, SumOpen(a)); (* acc := 120 + 15 *)
ExitCode := acc
END Procs.
./M2c procs.mod
$MCINT Procs.MC4
Output:
135
MODULE Records;
TYPE
Point = RECORD x, y : INTEGER END;
Person = RECORD name : ARRAY [1 .. 10] OF CHAR; age : INTEGER END;
VAR p : Point;
VAR me : Person;
VAR ExitCode : INTEGER;
BEGIN
p.x := 3;
p.y := 4;
me.name := "Ada";
me.age := 36;
WITH me DO (* fields of me in scope *)
age := age + 1 (* same as me.age := me.age + 1 *)
END;
ExitCode := p.x + p.y + me.age (* 3 + 4 + 37 *)
END Records.
./M2c records.mod
$MCINT Records.MC4
Output:
44
Records nest, fields can be composite (rec.b.u), and whole records copy
with q := p.
POINTER TO, NEW, ^ dereference and NIL. Indicative DISPOSE exists
too (it maps to the VM DEALLOCATE). Note that NIL dereference currently
reads as 0 and arrays are not range-checked — see the limits section.
MODULE List;
TYPE Node = RECORD val : INTEGER; next : POINTER TO Node END;
VAR head, q : POINTER TO Node;
VAR total : INTEGER;
VAR ExitCode : INTEGER;
PROCEDURE Push(v : INTEGER);
VAR n : POINTER TO Node;
BEGIN
NEW(n);
n^.val := v;
n^.next := head;
head := n
END Push;
BEGIN
head := NIL;
Push(1); Push(2); Push(3); Push(4); (* list is 4 -> 3 -> 2 -> 1 *)
total := 0;
q := head;
WHILE q # NIL DO
total := total + q^.val; (* 1 + 2 + 3 + 4 *)
q := q^.next
END;
ExitCode := total
END List.
./M2c list.mod
$MCINT List.MC4
Output:
10
Strings are ARRAY [1 .. n] OF CHAR; you assign a literal, compare with
=/#/</> and index elements.
MODULE Strings;
VAR s, t : ARRAY [1 .. 10] OF CHAR;
VAR ExitCode : INTEGER;
BEGIN
s := "hello";
t := "hello";
IF s = t THEN ExitCode := 1 ELSE ExitCode := 0 END; (* 1 *)
t := "world";
IF s # t THEN ExitCode := ExitCode + 2 END; (* +2 *)
IF s < t THEN ExitCode := ExitCode + 4 END; (* +4 *)
WriteString(s)
END Strings.
./M2c strings.mod
$MCINT Strings.MC4
Output:
hello7
(The 7 is the printed ExitCode: 1 + 2 + 4.)
Gotcha: a single-character literal such as " " is treated as a
CHAR, not a string — a WriteString(" ") call does not print that
character. Use multi-character literals for string data.
SET OF with literal elements, .. ranges, IN, = and the +/-/*
operators; enumerations with a CASE over the literals.
MODULE Sets;
TYPE Color = (Red, Green, Blue);
VAR s, t : SET OF [0 .. 7];
VAR c : Color;
VAR ExitCode : INTEGER;
BEGIN
s := {1, 2, 3};
t := {5 .. 7};
c := Green;
ExitCode := 0;
IF (2 IN s) AND (s = {1, 2, 3}) THEN ExitCode := 14 ELSE ExitCode := 0 END;
IF 6 IN t THEN ExitCode := ExitCode + 1 END;
CASE c OF
Red : ExitCode := ExitCode + 0 |
Green : ExitCode := ExitCode + 1 |
Blue : ExitCode := ExitCode + 2
END
END Sets.
./M2c sets.mod
$MCINT Sets.MC4
Output:
16
A local MODULE M with an EXPORT list hides locals and exposes M.x /
M.Proc to the enclosing program. Optional BEGIN ... END init bodies in
modules run at startup, before the program's own statements, in declaration
order.
MODULE MMod;
MODULE M EXPORT cnt, Inc; (* local module, exports 2 names *)
VAR cnt : INTEGER;
PROCEDURE Inc;
BEGIN
cnt := cnt + 1
END Inc;
END M;
VAR ExitCode : INTEGER;
BEGIN
M.cnt := 10;
M.Inc(); (* exported call *)
M.Inc();
ExitCode := M.cnt (* 12 *)
END MMod.
./M2c mmod.mod
$MCINT MMod.MC4
Output:
12
The V1 compiler compiles a library split across a definition and an
implementation, plus a client — all in one session — into a single
image. This is the step-11 feature; the library's BEGIN init runs before
the program body.
mathlib.def:
DEFINITION MODULE MathLib;
CONST PiSq = 9;
VAR calls : INTEGER;
PROCEDURE Square(x : INTEGER) : INTEGER;
PROCEDURE Bump(VAR x : INTEGER);
END MathLib.
mathlibimpl.mod:
IMPLEMENTATION MODULE MathLib;
PROCEDURE Square(x : INTEGER) : INTEGER;
BEGIN
RETURN x * x
END Square;
PROCEDURE Bump(VAR x : INTEGER);
BEGIN
x := x + 1
END Bump;
BEGIN
calls := 0 (* module init: runs first *)
END MathLib.
app.mod:
MODULE App;
FROM MathLib IMPORT Square; (* unqualified import *)
VAR total : INTEGER;
VAR ExitCode : INTEGER;
BEGIN
total := Square(3) + MathLib.PiSq; (* 9 + 9 = 18 *)
MathLib.Bump(total); (* qualified call, VAR param *)
ExitCode := total + MathLib.calls (* 19 + 0 *)
END App.
Compile all three files on one command line — definitions first, then the implementation, then the program module:
./M2c mathlib.def mathlibimpl.mod app.mod
$MCINT App.MC4
Output:
19
From
mathlib.def, the imported name isSquare(procedure), the qualified access isMathLib.PiSq(constant) andMathLib.Bump(procedure);.LSTfiles are written for every unit. A mismatch between an implementation heading and its definition is error 231.
Say you assign an INTEGER to a BOOLEAN:
MODULE BadTest;
VAR i : INTEGER;
VAR b : BOOLEAN;
VAR ExitCode : INTEGER;
BEGIN
i := 1;
b := i; (* wrong: BOOLEAN := INTEGER *)
ExitCode := i
END BadTest.
./M2c badtest.mod
stdout ends with:
Incorrect source
For scripting:
M2ccurrently returns exit status 0 even when the source is rejected — judge success by theParsed correctly/Incorrect sourceverdict on stdout, or by the absence of a generated.MC4file.
and badtest.LST shows the offending line with a caret and message:
Listing:
1 MODULE BadTest;
2 VAR i : INTEGER;
3 VAR b : BOOLEAN;
4 VAR ExitCode : INTEGER;
5 BEGIN
6 i := 1;
7 b := i; (* wrong: BOOLEAN := INTEGER *)
***** ^ incompatible assignment
8 ExitCode := i
9 END BadTest.
1 error
Static semantic errors use numeric codes 200–233 (201 undeclared
identifier, 210 incompatible assignment, 233 invalid procedure call,
…); constructs the compiler does not lower yet are rejected with 230
("not supported in this phase"). See the table in README.md.
pi_sq does
not lex; use PiSq.WriteString("...") and WriteInt(n); the
standard result channel is the ExitCode convention.CHAR, not a string.INTEGER DIV/MOD truncate toward zero; AND/OR are eager (no
short-circuit); mixed INTEGER/REAL arithmetic is rejected (use
assignments to widen).NIL dereference reads 0 —
no runtime trap yet.WITH, one image per
program (no multi-.MC4 linkage, no circular imports, no opaque types).src/Showcase*.mod — four "tour" programs exercising the whole language
(157, 83, 168 and the step-11 225).docs/summary_step*.md — one development-step summary each (what was
added, test counts, bugs found).README.md — full feature list, error-code table, layout and build
details.tests/ — the 158 programs of the regression suite used by
run_tests.sh; a great source of runnable examples for every construct.