# M2comp tutorial — compiling Modula-2 to the MC64 virtual machine This tutorial walks you through using the **V2** 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 V2 language has **no text I/O statements** (that is V1's `WriteString` / > `WriteInt`), so a program signals its 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. --- ## 1. Setup 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: ```sh cd .../m2compiler-V2 ./build.sh # regenerate Coco/R sources, compile with gm2 -fiso, link ./M2comp ``` `./build.sh` runs Coco/R on `src/M2comp.atg` (the grammar reports **"Compilation completed. No errors detected."**), recompiles the hand-written modules (`FileIO`, `SymTab`, `AST`, `MGen`), and links the compiler binary `./M2comp`. From here on we use `mcint` with the default sibling path: ```sh MCINT=../m-code-64/mcint ``` ## 2. The compile → run cycle Save a program in a `.mod` file and run the compiler on it: ```sh ./M2comp hello.mod ``` The compiler prints to standard output, for **every** unit of the session: ```text Parsing hello.mod --- Symbol table --- INTEGER : PREDEF #0 ... Hello : MODULE #-1 ExitCode : VAR #0 --- AST --- Module Hello Var ExitCode :0 Block Assign Name ExitCode :0 Int 42 :0 ... ``` - **`Parsing ...`**, the **symbol table dump** and the **`--- AST ---` tree** (a teaching feature of this compiler: V2 builds a full syntax tree before emitting code). - Alongside each source file, a **listing** `.LST` is written with the source text and any errors marked with a `^` and a message. - After a successful session the compiler prints the step-7 backend listing `--- Code ---` (global layout, procedure table, disassembled image) and then the verdict: **`Parsed correctly`** or **`Incorrect source`**. - The bytecode image **`.MC4`** — named from the `MODULE` *name*, not the file name — is written into the current directory. Run the image on the MC64 interpreter: ```sh $MCINT Hello.MC4 # right after a successful ./M2comp hello.mod ``` --- ## 3. Example 1 — Hello, MC64! ```modula2 MODULE Hello; VAR ExitCode : INTEGER; BEGIN ExitCode := 42 END Hello. ``` ```sh ./M2comp hello.mod # -> "Parsed correctly", writes Hello.MC4 $MCINT Hello.MC4 ``` Output: ```text 42 ``` The embedded helper prints `ExitCode` = 42 followed by a line break. --- ## 4. Example 2 — Control flow All structured statements: `IF`/`ELSIF`/`ELSE`, `WHILE`, `REPEAT`, `LOOP`/`EXIT`, and `FOR` (including a `BY` step and a downward loop). ```modula2 MODULE Control; (* Structured statements: IF/ELSIF/ELSE, WHILE, REPEAT, LOOP with EXIT, and FOR (with BY, and downward). *) VAR ExitCode : INTEGER; s, i : INTEGER; BEGIN s := 0; IF 1 > 2 THEN s := 1 ELSIF 2 > 3 THEN s := 2 ELSE s := 3 END; i := 0; WHILE i < 10 DO i := i + 1; s := s + i END; REPEAT s := s - 1 UNTIL s < 60; LOOP s := s + 1; IF s >= 60 THEN EXIT END END; FOR i := 1 TO 5 BY 2 DO s := s + i END; FOR i := 10 TO 1 DO s := s + 0 END; ExitCode := s END Control. ``` ```sh ./M2comp control.mod $MCINT Control.MC4 ``` Output: ```text 69 ``` --- ## 5. Example 3 — Procedures and functions Nested procedures, value and `VAR` parameters, recursive functions with `RETURN`. ```modula2 MODULE Procs; (* Recursive functions, VALUE and VAR parameters, nested procedures. *) VAR ExitCode : INTEGER; PROCEDURE Fact(n : INTEGER) : INTEGER; BEGIN IF n <= 1 THEN RETURN 1 END; RETURN n * Fact(n - 1) END Fact; PROCEDURE Bump(VAR x : INTEGER); (* VAR parameter: changes caller *) BEGIN x := x + 1 END Bump; PROCEDURE Sum2(a, b : INTEGER) : INTEGER; VAR loc : INTEGER; PROCEDURE Double(t : INTEGER) : INTEGER; (* nested procedure *) BEGIN RETURN t * 2 END Double; BEGIN loc := a + b; RETURN loc + Double(loc) END Sum2; BEGIN ExitCode := Fact(5); (* 120 *) Bump(ExitCode); (* 121 *) ExitCode := ExitCode + Sum2(10, 5) (* 121 + 45 *) END Procs. ``` ```sh ./M2comp procs.mod $MCINT Procs.MC4 ``` Output: ```text 166 ``` --- ## 6. Example 4 — Records and WITH ```modula2 MODULE Records; (* RECORD fields, WITH abbreviation, whole-record copy. *) VAR ExitCode : INTEGER; r, q : RECORD x, y : INTEGER END; BEGIN r.x := 10; r.y := 20; WITH r DO (* fields of r in scope *) x := x + 1; (* r.x := 11 *) y := y + x (* r.y := 31 *) END; q := r; (* whole-record copy *) ExitCode := q.x + q.y END Records. ``` ```sh ./M2comp records.mod $MCINT Records.MC4 ``` Output: ```text 42 ``` `WITH` nests (including on array elements, e.g. `WITH a[1] DO ... END`), and whole records copy with `q := r`. --- ## 7. Example 5 — CASE `CASE` works on ordinal and `CHAR` selectors, with comma-separated label lists, `..` ranges, and `ELSE`. ```modula2 MODULE CaseDemo; (* CASE over integer and CHAR selectors: label lists, ranges, ELSE. *) VAR ExitCode : INTEGER; i, k : INTEGER; c : CHAR; BEGIN ExitCode := 0; i := 2; CASE i OF 1: ExitCode := ExitCode + 1 | 2, 3: ExitCode := ExitCode + 10 | 4..6: ExitCode := ExitCode + 100 ELSE ExitCode := ExitCode + 1000 END; k := 7; CASE k OF 1: k := 0 ELSE k := k + 1 END; ExitCode := ExitCode + k; c := 'b'; CASE c OF 'a': ExitCode := ExitCode + 100 | 'b': ExitCode := ExitCode + 20 ELSE ExitCode := ExitCode + 200 END; i := 99; CASE i OF 1: i := 0 ELSE i := i + 1 END; ExitCode := ExitCode + i END CaseDemo. ``` ```sh ./M2comp casedemo.mod $MCINT CaseDemo.MC4 ``` Output: ```text 138 ``` --- ## 8. Example 6 — Strings Strings are `ARRAY [lo .. hi] OF CHAR`; you assign a literal, copy the whole array, and index elements (each index is a `CHAR`). ```modula2 MODULE Strings; (* Strings are ARRAY ... OF CHAR; literals index as CHAR. *) VAR ExitCode : INTEGER; s, t : ARRAY [0 .. 7] OF CHAR; BEGIN ExitCode := 0; s := "hi"; t := s; (* whole-array copy *) IF s[0] = "h" THEN ExitCode := ExitCode + 1 END; IF s[1] = "i" THEN ExitCode := ExitCode + 10 END; IF t[0] = "h" THEN ExitCode := ExitCode + 100 END; s[0] := 'H'; (* single char assignment *) s[1] := '!'; IF s[1] = "!" THEN ExitCode := ExitCode + 1000 END END Strings. ``` ```sh ./M2comp strings.mod $MCINT Strings.MC4 ``` Output: ```text 1111 ``` *Gotcha:* a **single-character** literal (`"h"`, `'b'`) is a `CHAR`, not a string — exactly right for `s[0] = "h"`. --- ## 9. Example 7 — Arrays Fixed arrays, multi-dimensional arrays, nested loops, and whole-array copy. ```modula2 MODULE Arrays; (* One- and two-dimensional arrays, whole-array copy. *) VAR ExitCode : INTEGER; v, w : ARRAY [0 .. 9] OF INTEGER; m : ARRAY [0 .. 2], [0 .. 3] OF INTEGER; i, j : INTEGER; BEGIN FOR i := 0 TO 9 DO v[i] := i * 2 END; w := v; (* whole-array copy *) FOR i := 0 TO 2 DO FOR j := 0 TO 3 DO m[i, j] := i * 10 + j END END; ExitCode := v[5] + w[3] + m[2, 3] END Arrays. ``` ```sh ./M2comp arrays.mod $MCINT Arrays.MC4 ``` Output: ```text 39 ``` --- ## 10. Example 8 — REAL arithmetic `REAL` (binary64) arithmetic with decimal and E-notation literals, and comparisons. Note the unary minus on a real expression. ```modula2 MODULE Reals; (* REAL arithmetic: + - * / , E-notation, comparisons. *) VAR ExitCode : INTEGER; r : REAL; BEGIN ExitCode := 0; r := 1.5 + 2.5; IF r = 4.0 THEN ExitCode := ExitCode + 1 END; r := 10.0 / 4.0; IF (r > 2.4) AND (r < 2.6) THEN ExitCode := ExitCode + 10 END; r := 3.5 - 1.5 * 2.0; IF r = 0.5 THEN ExitCode := ExitCode + 100 END; IF -r < -0.25 THEN ExitCode := ExitCode + 1000 END END Reals. ``` ```sh ./M2comp reals.mod $MCINT Reals.MC4 ``` Output: ```text 1111 ``` `INTEGER` values widen to `REAL` automatically in mixed expressions. --- ## 11. Example 9 — Local modules A local `MODULE M` (Wirth form) with an `EXPORT` list hides its locals and exposes `M.x` / `M.Proc()` to the enclosing program. Optional module `BEGIN` init bodies run at startup, before the program's own statements, in declaration order. ```modula2 MODULE LocalMod; (* Local module (Wirth form) with EXPORT list and an init body. *) VAR ExitCode : INTEGER; MODULE M; EXPORT q, Get; VAR q : INTEGER; PROCEDURE Get() : INTEGER; BEGIN RETURN q + 1 END Get; BEGIN q := 41 (* module init runs before program body *) END M; BEGIN ExitCode := M.q + M.Get() (* 41 + 42 *) END LocalMod. ``` ```sh ./M2comp localmod.mod $MCINT LocalMod.MC4 ``` Output: ```text 83 ``` `MODULE M [n];` local forms with a priority number are accepted as well. --- ## 12. Example 10 — Sets `SET OF `: assignment, whole-set copy, `IN`, `=` and `#`. (V2 has no set literals `{...}` yet — sets start empty and you test them.) ```modula2 MODULE Sets; (* SET OF an ordinal base: assignment, IN, equality/inequality. *) TYPE Sub = [0 .. 9]; VAR ExitCode : INTEGER; s, t : SET OF Sub; b : BOOLEAN; BEGIN ExitCode := 0; t := s; (* set copy (both empty) *) b := 3 IN s; (* FALSE: s is empty *) IF b THEN ExitCode := 1 ELSE ExitCode := 2 END; IF s = t THEN ExitCode := ExitCode + 10 END; IF s # t THEN ExitCode := ExitCode + 100 END END Sets. ``` ```sh ./M2comp sets.mod $MCINT Sets.MC4 ``` Output: ```text 12 ``` --- ## 13. Example 11 — Separate compilation units V2 compiles a library split across a definition and an implementation, plus a client — all in **one session** — into a single image. Session order is definitions, then implementations, then exactly one program module; the program must come last. The library's `BEGIN` init body runs before the program body. `mathlib.def`: ```modula2 DEFINITION MODULE MathLib; CONST PiSq = 9; VAR calls : INTEGER; PROCEDURE Square(a : INTEGER) : INTEGER; PROCEDURE Bump(VAR x : INTEGER); END MathLib. ``` `mathlib.mod` (the implementation): ```modula2 IMPLEMENTATION MODULE MathLib; VAR total : INTEGER; PROCEDURE Square(a : INTEGER) : INTEGER; BEGIN RETURN a * a END Square; PROCEDURE Bump(VAR x : INTEGER); BEGIN x := x + 1 END Bump; BEGIN total := 0; (* module init: runs before the program body *) calls := 0 END MathLib. ``` `app.mod`: ```modula2 MODULE App; FROM MathLib IMPORT Square; IMPORT MathLib; VAR ExitCode : INTEGER; n : INTEGER; BEGIN n := Square(3) + MathLib.PiSq; (* 9 + 9 = 18 *) MathLib.Bump(n); (* 19 *) ExitCode := n + MathLib.calls (* 19 + 0 *) END App. ``` Compile the three files on one command line: ```sh ./M2comp mathlib.def mathlib.mod app.mod $MCINT App.MC4 ``` Output: ```text 19 ``` Imports resolve against completed definitions: `FROM MathLib IMPORT Square` (unqualified call), `MathLib.PiSq` (constant), `MathLib.Bump` (qualified procedure). An implementation heading that doesn't match its definition is error **231**; importing an unknown or unimplemented module is **201**. --- ## 14. Example 12 — When things go wrong Say you assign an `INTEGER` to a `BOOLEAN`: ```modula2 MODULE BadTest; VAR i : INTEGER; b : BOOLEAN; ExitCode : INTEGER; BEGIN i := 1; b := i; (* wrong: BOOLEAN := INTEGER *) ExitCode := i END BadTest. ``` ```sh ./M2comp badtest.mod ``` stdout ends with: ```text Incorrect source ``` and `badtest.LST` shows the offending line with a caret and message: ```text Listing: 1 MODULE BadTest; 2 VAR i : INTEGER; 3 b : BOOLEAN; 4 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, `231` forward mismatch, `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`. > For scripting: `M2comp` returns **exit status 0 even when the source is > rejected** — judge success by the `Parsed correctly` / `Incorrect source` > verdict on stdout, or by the absence of a generated `.MC4` file. --- ## 15. Language gotchas and limits - **Identifiers are only letters and digits** — no underscores. `pi_sq` does not lex; use `PiSq`. - **No text I/O statements**: the only output channel is the `ExitCode` convention described above. - A single-character literal is a `CHAR`, not a string. - `INTEGER` `DIV`/`MOD` truncate toward zero; `AND`/`OR` are eager (no short-circuit); `#` and `<>` are the same operator. - Definitions scope their formal parameter names: don't reuse `x` in a second heading of the same `DEFINITION` (duplicate identifier, error 200). - Not in this generation yet: enum types (`(A, B, C)`), set literals `{...}`, `NEW`/`DISPOSE`, and standard procedures (`HIGH`, `INC`, `DEC`); open arrays exist only as `VAR` formals. Opaque types are the planned next step. - Array indexing is **not range-checked**, and `NIL` dereference reads 0 — no runtime trap yet. - Current caps: 256 symbols, 8-deep call stack, 7 array dimensions, 15 library units per session. --- ## 16. Where to go next - `tests/showcase.mod` and `tests/showcase5.mod` — two "tours" exercising the whole language; `docs/summary_m2comp_step*.md` — one development-step summary each (what was added, test counts, bugs found). - `docs/goal_step8.md` — the roadmap (opaque types, procedure types, quad backend). - `README.md` — full feature list, error-code table, layout and build details. - `tests/` — the 65 programs of the regression suite used by `run_tests.sh`; a great source of runnable examples for every construct.