# V3 step 4.2 — nested procedures: static-link display (done 2026-09-22) Suite 81/81 (39 run — incl. 4 trap-aborts — + 42 reject). LL(1)-clean (only the known-benign TypeBlock advisory). Nested/recursive/module-level all lower; methods stay 230. ## What lowers now - **Nested procedures** (any depth) with full up-level access: reads, writes, addresses, calls, recursion. Lexical scoping is honest: inner scopes are entered/exited per procedure, and name lookup walks the scope chain outward. - **Sibling procedures** sharing a parent's locals (both `B` and `D` mutate `A`'s `s`), three-level nesting (`A > B > C`), and nested recursion (`Down` calls itself through the display). ## How the display works (Chappel / classic Wirth) - Every procedure takes a hidden leading static-link parameter `l`. Its entry allocates an 8-byte-per-slot link record (`alloc8 520`, 64 slots + parent link) and stores the incoming link at `[0]`. - `LocAdd` (locals + params) stores each slot address into the record: `[1..]`. `AddrOf` of a same-scope var returns the slot; of an up-level var walks the chain. - **Up-level access** (`LocFindUp` returns levels + flat index): walk `loadl` `levels` times from the current link, then load the link cell. For *composites* the cell holds the address; for *scalars* the cell holds the slot address (so indexing/ deref compose unchanged). - **Call sites** resolve the link at `CallBegin` time (the caller context can't change mid-call): module callers pass `0`; others walk `funcDepth - calleeDepth` links. Depth comes from `SymTab.ProcDepthOf` (lexical, recorded at heading time). ## Backend problem and fix (the section's hard-won lesson) QBE rejects a function definition nested inside a function body, but the parser reaches `Inner`'s `BEGIN` while still inside `Outer`'s body. First fix: buffer nested-function text and emit it after `main`. **That failed for 3 levels**: a single shared buffer preserved *parse* order, so `C` (parsed inside `B`) landed textually inside `B`'s emitted body → "label expected". Cure: **one buffer per nesting depth** (`nestBufs[0..15]`), so each function's own text stays contiguous regardless of how the parse interleaves; `EndModule` flushes them in depth order. `outSel = funcDepth` is derived, not counted (AbortFunc and EndFunc both restore it), which removed the stale-flag class of bug entirely. ## Grammar notes - `ProcDecl` no longer sets `NoEmit` for nesting (methods still do); the `EndFuncHeader` flush is at the `Block` boundary so FORWARD/DefUnit paths discard cleanly via `AbortFunc`. - **Bare `P;` calls** (parameterless proper procedures) are now legal (`AssOrCall` third alternative); they were a syntax error before. `Inner;` in Modula-2 style finally parses. `F();` (function-as-statement) and `P(args);` with wrong arity stay 233. - `LocFull` guard → 233 past 64 locals per scope. ## SymTab (additive) `fdep` per procedure (lexical depth at heading), `ProcDepthOf`. Nothing else changed — the scope tree from step 1.1 already modelled nesting, so name lookup needed no work. ## Tests `t_nestedproc` → 20 (inner reads+writes parent locals, called twice), `t_nestdeep` → 211 (three levels + siblings sharing the parent, proven by the output), `t_nestrec` → 15 (nested recursion); `t_nestedproc` promoted from 230-reject to a real run test. Total 81. Deferred: methods still 230 (class lowering), procedure-type variables, separate compilation + opaque completion + `HIGH`/ `LEN` (4.3), >64 locals, `F()^` result suffixes.