# Step 6 — Local modules hardened (uncommitted) 112/112 tests green (99 regressions + 5 run + 8 rejection), mc64 boot + example green. ## Goal Harden the step-6 WIP (single-file local `MODULE`s, no nesting, no bodies): exported `VAR`s, `CONST`s and parameterless proper procedures, used as `M.x` / `M.P()`. Fix the bugs found by probing, lock behavior in with rejection tests, keep every old suite green. ## What was built - `SymTab`: `ExpQual` now qualifies `KindConst` as well as `KindVar` (`"M.x"`); `ExitModule` became a `BOOLEAN` function — `TRUE` iff every `EXPORT` name was declared (ghost exports report `FALSE`). - `M2c.atg`: `ModuleDecl` gained an `enterOk` flag (failed `EnterModule` skips export recording and `ExitModule`, killing the duplicate-module cascade); `END` reports `SemError(201)` on ghost exports; header comment documents local-module support and limits. - `run_tests.sh`: new `Step-6` sections (5 run + 8 rejection). ## Tests — 112/112 (99 + 5 + 8) 5 run: `m_mod` 12 (exported var + `Inc()` twice), `m_multi` 7 (two modules), `m_arr` 42 (exported array + `M.arr[2]`), `m_outer` 11 (module proc touches outer global), `m_const` 7 (exported `CONST`). 8 rejections, each exactly one error: `m_bad_priv` 201 (private `M.secret`), `m_bad_nest` 230 (module in procedure), `m_bad_dup` 200 (duplicate module), `m_bad_exp` 200 (duplicate export), `m_bad_ghost` 201 (`EXPORT ghost`), `m_bad_param` 233 (`M.Add(5)`), `m_bad_func` 233 (`M.Get()` in expression), `m_bad_end` 202 (`END N` mismatch). ## Bugs found and fixed 1. Exported `CONST` read back 0: `ExpQual` returned an empty name for non-`Var` kinds, so `GlobalAddr("")` hit a temp slot. Qualifying `KindConst` fixes the load (`M.C` → 7) with no ATG change (address + `LoadIndir` path is shared with vars). 2. Ghost `EXPORT` silently accepted: `ExitModule` dropped undeclared names. Now returns `FALSE` and the ATG reports 201 at `END`. 3. Duplicate module gave 2 errors (200 at name + 200 at its `EXPORT`, since `ModuleAddExp` ran with no module context). `enterOk` guard → 1 error. ## Known limits (each with a rejection test or documented) Module procedures take no parameters (`M.Add(5)` → 233), module functions are statement-only (`M.Get()` in an expression → 233), no type exports (`M.T` → 221 + 230 via `QualIdent`), no `DEFINITION`/`IMPLEMENTATION` split, no module `BEGIN` bodies, `NoteSfx`/`NoteIdx` VAR-tail helpers still unwired (`a[i]`/`p^`/fields as VAR actuals → 233), max 8 modules / 32 exports per module / 128 exports total. ## Text output (I/O builtins, verified this session) No new machinery needed for simple output — step-5 builtins cover it: `WriteString("...")` / `WriteString(charArrayVar)` emits raw bytes via SYSTEM service 1 (no newline); `WriteInt(n)` emits unsigned decimal + CRLF via the embedded `EmitPrint` helper; a declared `ExitCode` global is printed the same way at program end. Demo verified under `mcint`: `WriteString("Hello"); WriteString(" world"); WriteInt(42);` → `Hello world42\r\n`. Limits: no `WriteLn`, single-`CHAR` literals rejected (210 — `"A"` lexes as `CHAR`, not a string), `WriteInt` wraps negatives, one value per call. A `WriteLn`/`WriteChar` would be a small isolated addition (new ATG builtin + `MGen` emitter + tests).