summary_step10.md 3.2 KB

Step 10 — Module BEGIN bodies + self-reference (uncommitted)

143/143 tests green (137 + 3 run + 3 rejection), mc64 boot + example green, both Showcases unchanged (157, 83).

Goal

Complete single-file local modules: optional BEGIN..END init bodies run at startup in declaration order, including calls to the module's own procedures (plain and M.-qualified).

What was built

  • MGen: module inits compile as parameterless proper procedures (ModInitBegin allocates a proc-table number + ENTER, ModInitEnd emits LEAVE); BeginBody calls them in order after const inits, before program statements. Max 8 inits (module limit), sharing the 64 proc numbers (print helper still takes maxNum+1 at EndModule).
  • M2c.atg ModuleDecl: optional BEGIN StatSeq, jumped over inline (Jmp/DefLabel like procedures), skipped entirely for nested/duplicate modules. RETURN in a body is already 232 (not in a proc); header updated (no bodies dropped).
  • SymTab: modLevs[] records each module's body scope level; SelfKind(mod, exp) resolves M.x against the live body scope (the export table only fills at END), guarded to body level so nested scopes can't hijack; SelfQual builds M.x for codegen.
  • M2c.atg call sites use it: DesignTail (vars/consts/procs), CallTail (by-number via ProcNum), Fact (return types), QualIdent (M.T inside M).

Tests — 143/143 (137 + 3 + 3)

3 run: b_init 42, b_order 111 (two inits, order + cross-module read), b_call 15 (plain Inc() and M.Inc() from init). 3 rejections, each exactly one error: b_bad_ret 232, b_bad_nest 230, b_bad_dup 200.

Bugs found and fixed

  1. M.Inc() inside M gave 201: the export table is empty until END. Self-scope fallback fixes vars, consts, procs, and types uniformly.
  2. gm2 16.0.1 ICE (deterministic, no source location) on x = -1 & (y = -1) in generated M2cP.mod — minimized to IF a = -1 & (b = -1) THEN END in 20 lines. Workaround: nested IFs with < 0 (single F() = -1 and (x < 0) & ... shapes compile). New rule: never compare against a negative literal inside an & chain.

Known limits (deferred)

DEFINITION/IMPLEMENTATION split (the remaining big item), WITH-field VAR actuals, CHAR/BOOLEAN tails as VAR actuals, open-to-open.

Post-step fix — init/proc table-slot collision (new commit on top)

Showcase3 (A.x*100 + B.y + C.cnt + D.val = 168) exposed a real bug: with an early plain init plus a later procedure-owning module, the run printed 72 (A.x = 0, C.cnt = 15). Root cause: ModInitBegin took maxNum+1 from emission order while SymTab numbers procedures from a separate declaration-order pool — so module A's init stole C.Inc's number 1, and ProcEntry(1) for Inc overwrote the init's table cell (startup then called Inc instead of A's init: 5 + 10 = 15 in cnt, x never assigned). Single-module tests passed only because each procedure body was emitted before its module's init. Fix: init numbers now come from SymTab.AllocInitNum(), sharing the nProcs pool (procs[] entry initialized like a parameterless proper procedure). Regression b_mix (110) locks the shape; suite 144/144, Showcases unchanged (157, 83), Showcase3 prints 168.