Decision: no restart. The AST/two-phase change is a middle-of-the- compiler refactor, not a rewrite. We reuse everything and convert the frontend's pass structure on a dedicated branch.
This document is the plan of record; docs/session-handoff-2026-10-01.md
has the short version.
Everything painful in the last two sessions traced to one cause: lowering happens during parsing, so nothing can be looked at twice. An AST-first compiler fixes that — but a from-scratch restart would throw away the parts that are expensive and already correct:
| Component | Reuse on restart? | Reuse on refactor? |
|---|---|---|
Coco/R frame, scanner/parser driver (M2S/M2P/compiler.frm) |
mostly | yes |
SymTab — descriptors, scoping, checks, classes, vtables |
large, hard-won | yes |
QbeGen — emission, layouts, calls, sets, records, classes |
the whole backend | yes |
runtime shim, stdlib/, FileIO, fixpoint + corpus harnesses, tests |
yes | yes |
docs/ (language report, gaps, grammar) |
yes | yes |
AST.def/.mod (Stage A, tag v3-ast-stageA) |
n/a | already started |
M2.atg production grammar |
yes (recognition is fine) | yes |
M2.atg production actions |
rewritten | rewritten (same grammar) |
The one thing a restart legitimately replaces is the dialect/grammar — and the grammar is not the bottleneck. The pass structure is.
So: change what the grammar actions do (build nodes instead of emit), not what the grammar recognizes.
per unit:
1. Parse -> AST (declarations + bodies), SymTab entries for
names/kinds as declared (order-independent later)
2. Resolve/Check -> bind identifiers, complete types, 2xx diagnostics
3. Lower -> walk AST, call the existing QbeGen API
4. Emit -> unchanged (buffered image, EndModule)
Key property: step 3 runs after the whole unit's declarations are in
SymTab, so forward references resolve naturally. The text-patching
machinery and the Design placeholder path are deleted.
M2.atg flag TWO_PHASE (a grammar-level BOOLEAN or a
QbeGen.SetLower switch).Lower(n); run it in a test-only mode
(compile a unit, lower from the AST, compare to the old .ssa).ast-stage-c)Suggested slices, each ending in build.sh + run_tests.sh +
fixpoint.sh (revert the slice if either fails):
TWO_PHASE flag; parser builds AST nodes for
one trivial production (e.g. Ident/NkIdent) alongside emission.
Nothing consumes them. Green.Expr/SimExpr/Term/Fact build
NkBinExpr/NkUnary/NkIdent/literals, in addition to emitting.
Add Lower for expressions, exercised in a test-only compare.Block/Stat build NkAssign/NkIf/NkWhile/
…; Lower them.ConstBlock/TypeBlock/VarBlock/
ProcDecl; the unit's decls complete SymTab before any body is
lowered.FwdPatchAll /
FixLoadClass / FixStoreClass, the Design forward-variable
placeholder, and pendVar* if superseded.docs/features.md, language-report.md,
analysis_gaps.md; tag../bootstrap/fixpoint.sh must print
FIXPOINT OK (byte-identical stage2/stage3). The compiler compiles
itself, so it exercises the new path on real, large input.run_tests.sh (178/178 today) after every slice.tools/v3-corpus/corpus.sh) — expect the undeclared
identifier / forward-reference buckets to shrink once the AST lands;
track the compile-OK counts..ssa vs old .ssa for
the test suite; they should match until a feature deliberately
changes.QbeGen.FwdPatchAll, FwdDesignator, FwdMangle,
FwdAddrOper, FwdPatch, FwdElemPatch, FixLoadClass,
FixStoreClass, BufReplaceAll; the grammar's FwdVarNote/
FwdVarFlush/nFvarRefs; the Design forward branch and
SymTab.FwdVar*; EXPR's IsFwdVar leniency.CONST (unblocks
ChanConsts/StreamFile, parked in docs/wip/); statement-context
result suffixes (F()[i] := x); GOTO/labels; typed CONST.M2.atg/QbeGen.mod must still compile under the
current compiler; keep declaration-before-use until the flip.Branch off v3-session-2026-10-02 (03f250e). Baseline: suite 178/178,
fixpoint OK (2,900,782 bytes). Start with §4 slice 1.