Modula-2 compiler V2

Eric Streit b4fcf1ef4b docs: add Blaise process alignment reference note 8 giờ trước cách đây
MC4 d4287415e5 m2comp step 6 — WITH + CASE statements (compare-chain lowering, 61/61 tests green) 3 tuần trước cách đây
docs b4fcf1ef4b docs: add Blaise process alignment reference note 8 giờ trước cách đây
src 0e07513a3f m2comp step 7 — backend listing DumpImage with disassembly (65/65 tests green) 3 tuần trước cách đây
tests 0e07513a3f m2comp step 7 — backend listing DumpImage with disassembly (65/65 tests green) 3 tuần trước cách đây
M2c-git instructions.txt 123f3ff3a8 chore: add shared reference files (skills, grammar, instructions) 3 tuần trước cách đây
M2comp 0e07513a3f m2comp step 7 — backend listing DumpImage with disassembly (65/65 tests green) 3 tuần trước cách đây
README.md 09ea5f12f6 docs: step 7.1 — add project README and usage tutorial 8 giờ trước cách đây
build.sh a781af2287 m2comp step 4 — MC64 tree-walking backend (41/41 tests green) 3 tuần trước cách đây
gnu-m2-grammar.txt 123f3ff3a8 chore: add shared reference files (skills, grammar, instructions) 3 tuần trước cách đây
run_tests.sh 0e07513a3f m2comp step 7 — backend listing DumpImage with disassembly (65/65 tests green) 3 tuần trước cách đây
tutorial.md 09ea5f12f6 docs: step 7.1 — add project README and usage tutorial 8 giờ trước cách đây

README.md

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

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:

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

./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:
  ./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

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:

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

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 ARRAYs (multi-dimensional, whole-array copy), RECORDs (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 MODULEs (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.