# 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 .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 `.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 `, the symbol table and the `--- AST ---` tree on stdout. After a successful session it prints the step-7 backend listing `--- Code ---` (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 `.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 ` (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.