# M2c tutorial — compiling Modula-2 to the MC64 virtual machine 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 statements `WriteString("...")` and > `WriteInt(42)` write text directly. --- ## 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-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: ```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 ./M2c hello.mod ``` The compiler prints to standard output: ```text 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`**. - Alongside each source file, a **listing** `.LST` is written with the source text and any errors marked with a `^` and a message. - On success the compiler writes a bytecode image **`.MC4`** — named from the `MODULE` *name*, not the file name — into the current directory. Run the image on the MC64 interpreter: ```sh $MCINT Hello.MC4 # right after a successful ./M2c hello.mod ``` --- ## 3. Example 1 — Hello, MC64! ```modula2 MODULE Hello; VAR ExitCode : INTEGER; BEGIN WriteString("Hello, MC64!"); ExitCode := 42 END Hello. ``` ```sh ./M2c hello.mod # -> "Parsed correctly", writes Hello.MC4 $MCINT Hello.MC4 ``` Output: ```text Hello, MC64!42 ``` `WriteString` prints the text without a newline, and the embedded helper then prints `ExitCode` = 42 followed by a line break. --- ## 4. Example 2 — Control flow All structured statements: `FOR`, `WHILE`, `REPEAT`, `LOOP`/`EXIT`, `CASE`, `IF`/`ELSIF`/`ELSE`. ```modula2 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. ``` ```sh ./M2c control.mod $MCINT Control.MC4 ``` Output: ```text 74 ``` --- ## 5. Example 3 — Procedures, functions and open arrays Nested procedures, value and `VAR` parameters, functions with `RETURN` (including recursion), and open-array formals with `HIGH`. ```modula2 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. ``` ```sh ./M2c procs.mod $MCINT Procs.MC4 ``` Output: ```text 135 ``` --- ## 6. Example 4 — Records and WITH ```modula2 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. ``` ```sh ./M2c records.mod $MCINT Records.MC4 ``` Output: ```text 44 ``` Records nest, fields can be composite (`rec.b.u`), and whole records copy with `q := p`. --- ## 7. Example 5 — Pointers: a linked list `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. ```modula2 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. ``` ```sh ./M2c list.mod $MCINT List.MC4 ``` Output: ```text 10 ``` --- ## 8. Example 6 — Strings Strings are `ARRAY [1 .. n] OF CHAR`; you assign a literal, compare with `=`/`#`/`<`/`>` and index elements. ```modula2 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. ``` ```sh ./M2c strings.mod $MCINT Strings.MC4 ``` Output: ```text 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. --- ## 9. Example 7 — Sets and enumerations `SET OF` with literal elements, `..` ranges, `IN`, `=` and the `+`/`-`/`*` operators; enumerations with a `CASE` over the literals. ```modula2 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. ``` ```sh ./M2c sets.mod $MCINT Sets.MC4 ``` Output: ```text 16 ``` --- ## 10. Example 8 — Local modules 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. ```modula2 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. ``` ```sh ./M2c mmod.mod $MCINT MMod.MC4 ``` Output: ```text 12 ``` --- ## 11. Example 9 — Separate compilation units 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`: ```modula2 DEFINITION MODULE MathLib; CONST PiSq = 9; VAR calls : INTEGER; PROCEDURE Square(x : INTEGER) : INTEGER; PROCEDURE Bump(VAR x : INTEGER); END MathLib. ``` `mathlibimpl.mod`: ```modula2 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`: ```modula2 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: ```sh ./M2c mathlib.def mathlibimpl.mod app.mod $MCINT App.MC4 ``` Output: ```text 19 ``` > From `mathlib.def`, the imported name is `Square` (procedure), the > qualified access is `MathLib.PiSq` (constant) and `MathLib.Bump` > (procedure); `.LST` files are written for every unit. A mismatch between > an implementation heading and its definition is error **231**. --- ## 12. Example 10 — When things go wrong Say you assign an `INTEGER` to a `BOOLEAN`: ```modula2 MODULE BadTest; VAR i : INTEGER; VAR b : BOOLEAN; VAR ExitCode : INTEGER; BEGIN i := 1; b := i; (* wrong: BOOLEAN := INTEGER *) ExitCode := i END BadTest. ``` ```sh ./M2c badtest.mod ``` stdout ends with: ```text Incorrect source ``` > For scripting: `M2c` currently 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. and `badtest.LST` shows the offending line with a caret and message: ```text 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`. --- ## 13. Language gotchas and limits - **Identifiers are only letters and digits** — no underscores. `pi_sq` does not lex; use `PiSq`. - The only text output is `WriteString("...")` and `WriteInt(n)`; the standard result channel is the `ExitCode` convention. - A single-character literal is a `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). - Array indexing is **not range-checked**, and `NIL` dereference reads 0 — no runtime trap yet. - Current caps: 64 procedures, 64 actuals per call, 16 names per parameter section, 8-deep nested calls, 8-deep `WITH`, one image per program (no multi-`.MC4` linkage, no circular imports, no opaque types). - Everything that is parsed but not lowered yet reports error **230** — the compiler deliberately keeps a clean single-pass semantic model. --- ## 14. Where to go next - `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.