- Modula-2 95.9%
- Shell 4.1%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
Maps the m2compiler-V2 phases and working discipline to the Blaise compiler project's phased process (graemeg/blaise), with an honest deviations section (no self-hosting, Wirth-school single-pass heritage, different roadmap ahead). |
||
| docs | ||
| MC4 | ||
| src | ||
| tests | ||
| build.sh | ||
| gnu-m2-grammar.txt | ||
| M2c-git instructions.txt | ||
| M2comp | ||
| README.md | ||
| run_tests.sh | ||
| tutorial.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 fromCRBIN; defaults to the siblingCocoGm2/CR. mcint, the MC64 interpreter, for running the produced images. Taken fromMCINT; 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>.MC4in 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/VARwith the predefinedINTEGERfamily (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 CHARliteral assignment, indexing,CHARliterals). - Full expressions:
+ - * / DIV MOD AND OR NOT, all six relations plus<>=aliases (#=<>),IN;INTEGERwidens toREAL;REALis binary64 with E-notation literals. - Statements: assignment, procedure call,
IF/ELSIF/ELSE,CASE(label lists, ranges,ELSE,CHARselectors),WHILE,REPEAT,LOOP/EXIT,FOR(runtime bounds,BY, downward),WITH(nested),RETURN(proper check of value/variable procedures). - Procedures: nested, value and
VARparameters, 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, qualifiedM.x, compiled into a single image; an implementation'sBEGINinit 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
INTEGERDIV/MODtruncate toward zero;AND/ORare eager.REALis binary64;INTEGERwidens toREAL.- Array indexing is unchecked, and
NILdereference reads 0 (no static check, no VM trap yet). - Composites are stored slot-per-element (no byte packing) — a documented
deviation from m2c;
CHAR/BOOLEANelements still take one slot. - A single-character literal (
"h",'b') is aCHAR, not a string. - In
DEFINITIONunits, 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, plusm2comp-day1,m2comp-docs,m2comp-goal8). *.modgenerated by Coco/R are never edited by hand: fix the.frm/.atgsources and rebuild.