Browse Source

step11.2: add project README and usage tutorial

- README.md: overview, build/test instructions, language status,
  error-code table, layout, related projects.
- tutorial.md: verified walk-through with ten compile-and-run
  examples (incl. separate compilation units) and gotchas.
Eric Streit 16 hours ago
parent
commit
ab2a0f1d14
2 changed files with 785 additions and 0 deletions
  1. 222 0
      README.md
  2. 563 0
      tutorial.md

+ 222 - 0
README.md

@@ -0,0 +1,222 @@
+# M2c — 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; the back end emits a runnable **MC64** bytecode image (`.MC4`) that
+runs on the `mcint` interpreter.
+
+This is the "V1" of a family of experimental compilers (V1 → V2 → V3). It was
+developed in numbered, test-guarded steps, each recorded in `docs/`. As of
+**step 11** the suite is **158/158 green** (99 accept/run, 65 rejection).
+
+## How it works
+
+```text
+src/M2c.atg  --Coco/R (CR)-->  src/M2cS.mod + M2cP.mod + M2c.mod
+                                     |            |
+                              (scanner)      (parser + semantic actions)
+                                     \           /
+                                      \  gm2 -fiso
+                                       v
+                              ./M2c (native compiler)
+                                       |
+                          parse + type-check + codegen (MGen)
+                                       v
+                                 <Module>.MC4   -->  mcint
+```
+
+- **`src/M2c.atg`** — the whole language: tokens, productions, and the semantic
+  actions that drive the symbol table and the code generator.
+- **`src/M2cS.mod` / `src/M2cP.mod` / `src/M2c.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, and all static type checking.
+- **`src/MGen`** — the MC64 emitter (buffers an image in memory and writes it).
+- **`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; `SymTab.Init` and
+`MGen.OpenModule` run once, and the program unit names the image.
+
+## 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`).
+
+## Build
+
+From the project root:
+
+```sh
+./build.sh
+```
+
+This regenerates the scanner/parser/driver from `src/M2c.atg`, recompiles the
+hand-written modules, and links `./M2c`. Object files (`.o`) stay in the
+project root; the generated sources live in `src/`.
+
+## Compiling a program
+
+```sh
+./M2c tests/t_arith.mod        # parse/type-check + codegen
+mcint TArith.MC4               # run the image
+```
+
+- A `.LST` listing (with error messages) is written next to each source, and
+  the driver prints `Parsed correctly` or `Incorrect source` on stdout.
+- Multi-file (separate compilation) sessions pass the units in dependency
+  order; the *last* file must be the program module:
+
+  ```sh
+  ./M2c tests/d_lib.def tests/d_libimpl.mod tests/d_basic.mod
+  mcint DBasic.MC4
+  ```
+
+- The image name comes from the program module, so the output is
+  `<Module>.MC4` in the current directory.
+
+### Test convention (no I/O in the language)
+
+There is no text I/O in the language subset, 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 `./M2c`, runs the resulting `.MC4` under
+`mcint`, and compares the printed `ExitCode`; rejection tests assert the exact
+message text in the `.LST`. Run tests:
+
+- **accept/parse** — `expect_ok`
+- **compile + run** — `expect_run` / `expect_run_none`
+- **multi-file run** — `expect_run_files`
+- **rejections** — `expect_fail` / `expect_fail_files`
+
+**158/158 green** at step 11.
+
+## Layout
+
+```text
+src/            M2c.atg (grammar), *.frm (frames), hand modules, generated
+tests/          *.mod test cases + expected .LST listings
+MC4/            compiled .MC4 images (test outputs)
+docs/           per-step summaries (summary_stepN.md) and goal notes
+build.sh        regenerate + compile + link -> ./M2c
+run_tests.sh    compile -> mcint -> compare
+```
+
+Root-level `*.o` and `./M2c` are build artifacts. Shared reference material
+lives at the root as well: `skills.md` (a Modula-2 language reference),
+`gnu-m2-grammar.txt`, and the `Showcase*.mod` tours in `src/`.
+
+## Language status
+
+**Lowered today** (steps 1–11):
+
+- Program modules; `CONST`/`TYPE`/`VAR` including `INTEGER`, `CARDINAL`,
+  subranges, `REAL`, `CHAR`, `BOOLEAN`, enumerations.
+- Composites: fixed and **open** `ARRAY`s, `RECORD`s, `SET`s (union `+`,
+  difference `-`, intersection `*`, inclusive `..` ranges, `IN`),
+  `POINTER`s (`NEW`/`DISPOSE`, `^`, `NIL`), and **strings**
+  (`ARRAY OF CHAR` literal assignment and comparison).
+- Full scalar and composite expressions; whole array/record copy.
+- Statements: assignment, procedure call, `IF`, `CASE`, `WHILE`, `REPEAT`,
+  `LOOP`/`EXIT`, `FOR`, `WITH` (nested), `RETURN`, `NEW`, `DISPOSE`.
+- Procedures: nested, value and `VAR` parameters, functions, recursion,
+  `FORWARD` headings, `HIGH` on open arrays.
+- **Local `MODULE`s** (`EXPORT`ed constants, types, variables and procedures;
+  optional `BEGIN` init bodies run at startup in declaration order).
+- **Separate compilation units**: `DEFINITION MODULE`, `IMPLEMENTATION MODULE`,
+  `IMPORT`/`FROM ... IMPORT`, qualified `M.x`, compiled into a single image.
+
+**Parsed and type-checked, but rejected with error 230** (deferred): open
+arrays beyond the supported shapes, whole array/record copy except string
+literals, value composite params/returns, non-literal `CONST` expressions and
+`BY` steps, `EXIT` outside `LOOP`, imported names used as values, procedure
+types/variables, `LONGINT`/`LONGCARD`, `LONGREAL`, and opaque types.
+
+### Known semantic edges
+
+- `INTEGER` `DIV`/`MOD` truncate toward zero; `CARDINAL` past `MAXINT`
+  compares as signed; `AND`/`OR` are eager.
+- `REAL` is binary64; `INTEGER` widens to `REAL`.
+- Array indexing is unchecked (no `DA`/`DB`); `NIL` dereference reads 0
+  (no static check, no VM trap yet).
+- `CHAR`/`BOOLEAN` arrays are byte-packed (1 byte/element, slots
+  over-allocated 8×).
+- Phased caps: 64 procedures, 64 actuals per call, 16 names per FP-section,
+  8-deep nested calls, 8-deep `WITH`, 1024 slots max per type; 8 definitions,
+  32 interface names each, 64 `FROM`-aliases; one image (`depCount` 0).
+
+## 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_step1.md` …
+`summary_step11.md`, plus `summary_step11.1.md` for the real-comparison
+codegen fix) and the forward-looking `goal_step4.md` / `goal_step5.md`. Read
+`src/M2c.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 `M2c`.
+- **`CocoGm2`** — the GNU-Modula-2 port of Coco/R (`CR`) and its frames,
+  used to generate this compiler's scanner/parser/driver.
+- **`m2compiler-V2`**, **`m2compiler-V3`** — later, more ambitious
+  generations of the same experiment.
+
+## Working conventions
+
+- Grammar grows section by section in `src/M2c.atg`, kept LL(1)-clean.
+- Every step keeps the full suite green; each step is recorded in
+  `docs/summary_stepN.md` and tagged (`step1` … `step11`).
+- `*.mod` generated by Coco/R are never edited by hand: fix the `.frm`/`.atg`
+  sources and rebuild.

+ 563 - 0
tutorial.md

@@ -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.