This answers "where are we now?".
compiler/run_tests.sh).tools/v3-corpus/corpus.sh over the gm2 testsuite; the harness now
resolves V3's own shipped libraries); the rest are genuine language
gaps, gm2 leniencies V3 deliberately rejects, or unresolved
imports of gm2-only modules.master; tree clean.per unit:
Parse -> AST + declarations; each procedure body is skipped and
re-parsed at unit end, so forward references bind normally
Lower -> walk the AST, emit QBE (the only emitter)
Emit -> buffered image, EndModule
-notwophase restores the legacy inline parse for
debugging.Check module was deleted once it was
proven vestigial (gutting it left the emitted .ssa byte-identical):
binding is late by construction.Lower is the only emitter; the AST is the interface.Program modules; CONST/VAR/TYPE (scalars, subranges, enums,
arrays, records incl. variants, sets, pointers); WITH/IN; whole
array/record copy; NEW/DISPOSE; full expressions and control flow;
GOTO/labels; procedures (value/VAR params, functions, recursion,
FORWARD, nested procedures, procedure values); Clarion-form CLASS
(methods, single inheritance, virtual dispatch); PIM coroutines;
Unicode (UCHAR/UString, UTF-8 edges); ISO type conversions T(x);
separate compilation (FROM/IMPORT, qualified L.x, opaque types);
generics (ISO 10514-2 refinement): type/value/procedure
parameters, constraints, nested generics, generic classes.
Multi-dimensional arrays use the PIM/ISO repeated-bracket form
(ARRAY [lo..hi], [lo..hi] OF T) as well as V3's earlier one-bracket
comma list. Subranges keep their ordinal base: ['A'..'Z'] is a
character type and [red..blue] belongs to its enumeration, so
base-typed values assign and compare; FOR accepts any ordinal
control variable (FOR c := red TO blue). The ISO empty string
literal (""/'') is compatible with CHAR (value 0) in assignment,
comparison, CASE labels, FOR bounds, actuals and RETURN, while
remaining a string for ARRAY OF CHAR (whole-array assignment and
content comparison).
Lower (L0–L7) — Lower emits the whole session from the AST.Check deleted.T(x) (incl. CONST folding), the builtins
ODD/TRUNC/FLOAT, and BOOLEAN/ENUM array bounds + indices:
corpus 365 → 381.CharSet{"0".."9"},
a union of set constants) is folded to a mask at parse and re-emitted
as a pooled data $setN descriptor. Builtin CONST folding
(CAP/ABS/ODD over immediates), BOOLEAN CONSTs,
representation-changing CONST conversions (VAL(REAL, int),
VAL(LONGINT, int), …), procedure values (ADR(proc),
VAL↔PROC conversions, p := foo into a PROC), SYSTEM sized
reals (REAL32/REAL64), FROM SYSTEM IMPORT SHIFT/ROTATE/CAST/…
(a builtin import is accepted), and ISO foreign modules
(DEFINITION/IMPLEMENTATION MODULE FOR "C", EXPORT UNQUALIFIED),
and ISO CONVERT(T, x) (a VAL sibling), set↔word VAL/CAST
(BITSET), and enumeration literals with their type (export/import/
qualified; imp*) and ORDL de-reserved (a gm2/FileIO helper, not
a built-in; fileio/baseimport): corpus 382 → 405. Fixed a
SHIFT-by-a-negative fault and the qualified-const value
(LoadDesignator)..defs); this more accurate harness is the method from
2026-10-18.M2 exit codes (0 ok / 1 errors / 2 usage-IO).PopPos wiping repass diagnostics; global record data
size (w 0 → l 0 for pointer/LONGINT/proc fields); template
arithmetic over a type formal; proc-typed formals in DEF/IMPL;
imported procedures used as values.showcase23 (generic doubly linked list).V3 follows Modula-2 (PIM/ISO), not gm2's degradations. In
particular the language requires declaration before use (the only
forward reference is a POINTER's base type), so the use-before-
declaration programs that gm2 accepts are rejected by design. The
corpus is gcc's gm2 testsuite, so its number mixes conforming programs
with gm2 leniencies; each known case is catalogued (with a reproducer)
in docs/gaps/.
Known non-conforming behaviour still retained (pre-existing, not yet removed): forward procedure calls from a body (the two-phase parse defers procedure bodies, and the generated parser's mutually recursive productions rely on it). Everything else is strict:
VAR references,
forward types inside a TYPE block, and forward SET OF <type> are
rejected (Track A, 2026-10-18);SymTab declaration sequence +
Lookup). FORWARD remains the way to call a later procedure.
Corpus 427 → 421 (the drop is 6 gm2-leniency files now correctly
rejected, e.g. block, blockindirect, nestedfor).Module-level forward procedure references and FORWARD
declarations still work (t_fwdproc, t_fwdok).
ARRAY [..], [..] OF, typed CHAR/enum
subranges, ordinal FOR, ISO empty string ""/'' as CHAR).
The remaining LANG entries are mostly:
48 files importing libc that need real prototypes (V3's
FROM libc IMPORT declares a permissive no-result varargs external,
so using printf/malloc in an expression fails); declaration
order cases V3 rejects by policy (docs/gaps/); ISO
exceptions/PACKEDSET/SYSTEM sized types (WORD32, BITSET32,
qualified SYSTEM.CARDINAL8); and gm2-only modules
(M2RTS/Builtins/…). See docs/analysis_gaps.md.docs/summary_l3_selfhost_fix.md). It was never
size-sensitive: the fixpoint gate matched the literal string
"Parsed correctly" that the ASTDUMP debug dump printed
unconditionally, so the gate passed while the compiler session was
actually reporting Incorrect source (Lower's forward procedure
references → invalid call). Removing ASTDUMP merely removed the
masking line, which looked like a size effect. Fixed by a3ef897
(remove ASTDUMP; gate on Parsed correctly; declare Lower's walkers
in Lower.def). The gate now fails on errors (verified with
t_fwdvar), and self-hosting stays byte-identical.t_manyproc), the label/GOTO tables, the 32-actual AST buffer and
SymTab's MaxTypes all report 230 (implementation limit) instead
of truncating silently. Still weak: error recovery and message polish.ChanConsts and
StreamFile (ISO 10514-1) on top of IOChan, and the PIM/gm2 I/O
family gained NumberIO, RealInOut, FpuIO (StrIO/InOut
already existed). Also fixed the corpus harness fidelity: it now
searches V3's own shipped libraries (stdlib, runtime/syslib) via a
default $V3_LIBS, so a file importing e.g. Strings resolves
against the real .def instead of being mislabelled LIB.
Building StreamFile also fixed a real bug: a set CONST exported
from a definition module is now emitted under its global name
(<unit>_<name>), so a client's qualified reference binds. Still
missing: gm2 runtime modules (M2RTS, Builtins, Processes, …).docs/INDEX.md is the entry point; the
unreferenced historical records are under docs/archive/ (2026-10-19).
resolve-check was merged to master.cd compiler
./build.sh # M2.atg -> scanner/parser/driver -> ./M2
./run_tests.sh # 249/249
cd ..
./bootstrap/fixpoint.sh # FIXPOINT OK (byte-identical stage2/stage3)