Просмотр исходного кода

docs: step 7.1 — add project README and usage tutorial

- README.md: overview, front/back (AST) pipeline, build/test instructions,
  language status, error-code table, layout, related projects.
- tutorial.md: verified walk-through with twelve compile-and-run examples
  (incl. separate compilation units) and gotchas.
Eric Streit 7 часов назад
Родитель
Сommit
09ea5f12f6
2 измененных файлов с 920 добавлено и 0 удалено
  1. 267 0
      README.md
  2. 653 0
      tutorial.md

+ 267 - 0
README.md

@@ -0,0 +1,267 @@
+# M2comp — a Modula-2 compiler for the MC64 virtual machine
+
+A small, self-contained Modula-2 compiler written **in Modula-2** (built with
+GNU Modula-2, `gm2 -fiso`). The front end is generated by
+[Coco/R](https://ssw.jku.at/Research/Projects/Coco/) from an attributed
+grammar; a tree-walking back end emits a runnable **MC64** bytecode image
+(`.MC4`) that runs on the `mcint` interpreter.
+
+This is the "V2" of a family of experimental compilers (V1 → V2 → V3). Unlike
+V1, which emits code in a single pass while parsing, **V2 follows the Blaise
+front/back split**: the front end builds a complete checked **abstract syntax
+tree** (per Mössenböck's Coco/R recipe), and the back end (`MGen`) walks that
+tree to produce the image. V2 was developed in numbered, test-guarded steps;
+as of **step 7** the suite is **65/65 green**.
+
+## How it works
+
+```text
+src/M2comp.atg  --Coco/R (CR)-->  src/M2compS.mod + M2compP.mod + M2comp.mod
+                                      |               |
+                                 (scanner)   (parser + semantic actions,
+                                              SymTab checks, AST build)
+                                                        |
+                                                    AST
+                                                        |
+                                                  MGen.EmitModule
+                                                   (tree walk)
+                                                        v
+                                       <Module>.MC4   -->  mcint
+```
+
+- **`src/M2comp.atg`** — the whole language: tokens, productions, and the
+  semantic actions that drive the symbol table and build the AST.
+- **`src/M2compS.mod` / `src/M2compP.mod` / `src/M2comp.mod`** — generated by
+  Coco/R on every build. **Do not edit them**: edit their sources
+  (`src/compiler.frm`, `src/scanner.frm`, `src/parser.frm`) and the grammar.
+- **`src/SymTab`** — scopes, type descriptors, static type checking
+  (errors 200–224, 230–233), procedure signatures, exports, loop depth.
+- **`src/AST`** — heap-allocated tree of declarations/statements/expressions
+  per the Mössenböck "How to Build Abstract Syntax Trees with Coco/R" recipe;
+  error-tolerant so dumps exist for rejected sources too.
+- **`src/MGen`** — the MC64 backend: walks the checked AST, writes
+  `<Module>.MC4`, and prints the step-7 disassembly listing (`DumpImage`).
+- **`src/FileIO`** — the FileIO/Storage I/O library reused from Coco/R.
+
+The driver runs **one session over several files**: definition files, then
+implementation files, then exactly one program module (program last). The
+symbol table and the emit buffer live for the whole session; each
+implementation is merged into its definition unit, and the *program* module
+names the image. `SymTab.Init`/`MGen` run once; up to 15 library units per
+session.
+
+## Prerequisites
+
+- **GNU Modula-2** (`gm2 -fiso`), developed with GCC 16.0.1 experimental.
+- **Coco/R `CR`** (the generator). Path is taken from `CRBIN`; defaults to the
+  sibling `CocoGm2/CR`.
+- **`mcint`**, the MC64 interpreter, for running the produced images. Taken
+  from `MCINT`; defaults to the sibling `../m-code-64/mcint`.
+- Standard shell tools (`sh`, `grep`, `sed`, `tr`, `timeout`).
+
+## Build
+
+From the project root:
+
+```sh
+./build.sh
+```
+
+This runs Coco/R on `src/M2comp.atg` (which reports **"Compilation completed.
+No errors detected."**), recompiles `FileIO`, `SymTab`, `AST`, `MGen` and the
+generated modules with `gm2 -fiso`, and links `./M2comp`. Object files (`.o`)
+and the generated sources stay in `src/`.
+
+## Compiling a program
+
+```sh
+./M2comp tests/r_flow.mod       # parse + type-check + AST + codegen
+../m-code-64/mcint RFlow.MC4    # run the image
+```
+
+For every unit the driver prints `Parsing <file>`, the symbol table and the
+`--- AST ---` tree on stdout. After a successful session it prints the
+step-7 backend listing `--- Code <Module> ---` (global layout per scope, the
+procedure table, then the disassembled image) followed by the verdict
+`Parsed correctly` — or `Incorrect source` on any error. A `.LST` listing
+(with `***** ^ message` markers) is written next to each source file.
+
+- Multi-file (separate compilation) sessions pass the units in dependency
+  order — definitions, then implementations, then the program:
+
+  ```sh
+  ./M2comp tests/d_lib.def tests/d_lib.mod tests/d_basic.mod
+  ../m-code-64/mcint DBasic.MC4
+  ```
+
+- The image name comes from the program module, so the output is always
+  `<Module>.MC4` in the current directory. A session without a program unit
+  is rejected (`Incorrect source`).
+
+### Test convention (no I/O in the language)
+
+The V2 language subset has no text I/O statements (no `WriteString` /
+`WriteInt` as in V1), so programs signal their result through a global
+variable. If the source declares
+
+```modula2
+VAR ExitCode : INTEGER;
+```
+
+the epilogue prints its value as decimal + CRLF through an embedded print
+helper; without it the program simply ends.
+
+## Testing
+
+After building, and with `mcint` available:
+
+```sh
+./run_tests.sh
+```
+
+The runner compiles each test with `./M2comp`, runs the resulting `.MC4` under
+`mcint`, and compares the printed `ExitCode`; it also asserts AST-dump node
+names and backend-listing opcodes for representative sources. Helpers:
+
+- **accept** — `expect_ok` / `expect_run` / `expect_run_none` /
+  `expect_run_files` (multi-file sessions)
+- **AST dump** — `expect_dump`
+- **rejections** — `expect_fail` / `expect_fail_files` /
+  `expect_fail_multi` (program-last gate)
+- **backend listing** — `expect_code` (e.g. `realCmp`, `jpfalse`,
+  `storeIndir0`, the proc-table entry)
+
+Emitted images are collected into `MC4/`. **65/65 green** at step 7
+(61 inherited + 4 listing checks).
+
+## Layout
+
+```text
+src/            M2comp.atg (grammar), *.frm (frames), FileIO/SymTab/AST/MGen,
+                generated M2compS/M2compP/M2comp and *.o
+tests/          *.mod test cases + expected .LST listings
+MC4/            collected .MC4 images (test outputs)
+docs/           per-step summaries (summary_m2comp_stepN.md), goal_step8.md
+                roadmap, session notes, AST.pdf, oberon-master/, skills.*
+build.sh        regenerate + compile + link -> ./M2comp
+run_tests.sh    compile -> mcint -> compare
+gnu-m2-grammar.txt   GNU Modula-2 EBNF reference
+```
+
+`src/*.o`, the generated `.mod` sources and `./M2comp` are build artifacts
+(regenerated by `./build.sh`).
+
+## Language status
+
+**Lowered today** (steps 1–7):
+
+- Program modules; `CONST`/`TYPE`/`VAR` with the predefined `INTEGER`
+  family (`CARDINAL`, `SHORTINT`, `LONGINT`), subranges (`[lo .. hi]`,
+  `INTEGER[lo .. N]`), `REAL`/`LONGREAL`, `CHAR`, `BOOLEAN`.
+- Composites: fixed `ARRAY`s (multi-dimensional, whole-array copy),
+  `RECORD`s (whole-record copy, `WITH`), `SET OF <ordinal>` (assignment,
+  `IN`, `=`, `#`), `POINTER TO` (assignment, `NIL`, `^`), and **strings**
+  (`ARRAY OF CHAR` literal assignment, indexing, `CHAR` literals).
+- Full expressions: `+ - * / DIV MOD AND OR NOT`, all six relations plus
+  `<>=` aliases (`#` = `<>`), `IN`; `INTEGER` widens to `REAL`; `REAL` is
+  binary64 with E-notation literals.
+- Statements: assignment, procedure call, `IF`/`ELSIF`/`ELSE`, `CASE`
+  (label lists, ranges, `ELSE`, `CHAR` selectors), `WHILE`, `REPEAT`,
+  `LOOP`/`EXIT`, `FOR` (runtime bounds, `BY`, downward), `WITH` (nested),
+  `RETURN` (proper check of value/variable procedures).
+- Procedures: nested, value and `VAR` parameters, functions, recursion.
+- **Local `MODULE`s** (Wirth form, optional priority `[n]`, `EXPORT`
+  [`QUALIFIED`], optional module init bodies that run at startup).
+- **Separate compilation units**: `DEFINITION MODULE`, `IMPLEMENTATION
+  MODULE`, `IMPORT` / `FROM ... IMPORT`, qualified `M.x`, compiled into a
+  single image; an implementation's `BEGIN` init body runs first.
+
+**Parsed and type-checked, but rejected with error 230** (deferred): value
+composite parameters/returns, open arrays outside `VAR` formals, non-literal
+`CONST` expressions and subrange bounds, real/string `CONST` in definitions,
+`EXIT` outside `LOOP`, non-foldable `CASE` labels, imported names used as
+values, plus — not in the grammar yet — enumeration types, set literals
+`{..}`, `NEW`/`DISPOSE`, procedure types/variables, and standard procedures
+(`HIGH`, `INC`, `DEC`, …). Opaque types are the next milestone
+(`docs/goal_step8.md`).
+
+### Known semantic edges
+
+- `INTEGER` `DIV`/`MOD` truncate toward zero; `AND`/`OR` are eager.
+- `REAL` is binary64; `INTEGER` widens to `REAL`.
+- Array indexing is unchecked, and `NIL` dereference reads 0 (no static
+  check, no VM trap yet).
+- Composites are stored slot-per-element (no byte packing) — a documented
+  deviation from m2c; `CHAR`/`BOOLEAN` elements still take one slot.
+- A single-character literal (`"h"`, `'b'`) is a `CHAR`, not a string.
+- In `DEFINITION` units, formal parameter names live in the definition
+  scope: reusing a parameter name in a *second* heading is a duplicate
+  identifier (error 200).
+- `<>` is accepted as an alias of `#`.
+- Phased caps: 256 symbols, 8-deep call stack, 7 array dimensions, 15
+  library units per session, one image per program.
+
+## Error codes
+
+Static semantic errors use the following codes (the listing prints the
+message, not the number):
+
+| code | message |
+| ---- | ------- |
+| 200 | duplicate identifier |
+| 201 | undeclared identifier |
+| 202 | module/procedure name mismatch |
+| 210 | incompatible assignment |
+| 211 | arithmetic operand must be numeric |
+| 212 | boolean operand required |
+| 213 | incompatible comparison |
+| 214 | `BOOLEAN` condition required |
+| 215 | not a `RECORD` type |
+| 216 | unknown field |
+| 217 | not an `ARRAY` type |
+| 218 | array index must be integer |
+| 219 | not a `POINTER` type |
+| 220 | `FOR` needs integer variable and bounds |
+| 221 | not a type name |
+| 222 | set operand mismatch |
+| 223 | cyclical type definition |
+| 224 | ordinal type required |
+| 230 | not supported in this phase |
+| 231 | procedure forward mismatch or missing body |
+| 232 | bad `RETURN` |
+| 233 | invalid procedure call |
+
+## Documentation
+
+`docs/` contains a summary per development step (`summary_m2comp_step1.md` …
+`summary_m2comp_step7.md`), the forward-looking `goal_step8.md` roadmap
+(opaque types, procedure types, quad-driven backend), `session_steps1-4.md`
+(a day-wrap note with hard-won facts), and reference material (`AST.pdf`,
+`oberon-master/`, `skills.*`, `BenjaminKowarsh.txt`, `blaise-process.md`).
+Read `src/M2comp.atg`'s header comment for the most current
+feature/limits list.
+
+## Related projects
+
+Siblings in `MyWork/`:
+
+- **`m-code-64`** — the MC64 virtual machine (`mcint`, loader, assembler) and
+  its specification (`docs/mc64-spec.md`); the execution target of M2comp.
+- **`CocoGm2`** — the GNU-Modula-2 port of Coco/R (`CR`) and its frames,
+  used to generate this compiler's scanner/parser/driver.
+- **`m2compiler-V1`**, **`m2compiler-V3`** — the earlier and later
+  generations of the same experiment (V1 emits in one pass; V3 is the
+  larger successor).
+
+## Working conventions
+
+- Grammar grows section by section in `src/M2comp.atg`, kept LL(1)-clean and
+  confirmable by Coco/R (**"No errors detected."**).
+- Front/back split: the front end builds the AST, the back end walks it;
+  op-code groups use disjoint ranges so the tree-walk dispatches on the code
+  alone.
+- Every step keeps the full suite green and is recorded in
+  `docs/summary_m2comp_stepN.md`, tagged (`m2comp-step1` … `m2comp-step7`,
+  plus `m2comp-day1`, `m2comp-docs`, `m2comp-goal8`).
+- `*.mod` generated by Coco/R are never edited by hand: fix the `.frm`/`.atg`
+  sources and rebuild.

+ 653 - 0
tutorial.md

@@ -0,0 +1,653 @@
+# 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** `<file>.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 <Module> ---` (global layout, procedure table, disassembled
+  image) and then the verdict: **`Parsed correctly`** or **`Incorrect
+  source`**.
+- The bytecode image **`<Module>.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 <ordinal type>`: 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.