|
|
@@ -0,0 +1,563 @@
|
|
|
+# 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** `<file>.LST` is written with the
|
|
|
+ source text and any errors marked with a `^` and a message.
|
|
|
+- On success the compiler writes a bytecode image **`<Module>.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.
|