# V3 step 1.9 — statements + boolean/relational core (done 2026-09-19) Interactive grammar §4 (last big section before composites). Suite 28/28 (13 run + 15 reject), LL(1)-clean throughout. ## Grammar (`compiler/src/M2.atg`) - `Expr = SimExpr [Rel SimExpr]`: `= # < <= > >=` (no `<>`, no `IN` yet), `RelCheck` → 213, result BOOLEAN via new `QbeGen.Cmp` (ceqw/cnew/csltw/cslew/csgtw/csgew, eager 0/1). - `OR`/`AND` (BoolCheck → 212) eager via or/and; `NOT` via xor-1. - `BOOLEAN`/`CHAR` variables load like integers now (still `w`); named CHAR consts load too; other named-const use stays 230. `VAR x: BOOLEAN/CHAR` accepted (enums/subranges still 230). - Statements: IF/ELSIF/ELSE, WHILE, REPEAT/UNTIL, LOOP/EXIT (230 outside LOOP via loop-label stack), FOR/TO/static-sign-BY, full CASE (labels, `a..b` ranges, ELSE), RETURN with 232 checks (outside proc / value-vs-proper mismatch / missing value). - FOR: integer var + integer bounds (220), literal non-zero BY (ConstInt; non-literal → 230, zero → 220), sign picks cslew/csgew. - WITH waits for records (step 3). ## Backend (`QbeGen`: labels, jumps, dead positions) New: deterministic `@LN` labels, EmitLabel/Jmp/Jnz, Cmp, NotQ, LOOP-exit stack. Plus the section's hard lesson, handled once in the backend instead of every action: - QBE forbids instructions after a terminator with no intervening label. Two rules fix all shapes: (1) value-emitters auto-open an unreachable block past a terminator (keeps every temporary defined — a *skipped* definition would dangle later uses); (2) Jmp/Jnz past a terminator are skipped (provably unreachable). - CASE needed a real layout fix on top: each alternative now ends its tests with `jmp lAfter`, so no-match fallthrough skips the body (`cmp; jnz(lBody,lF); lF: ...; jmp lAfter; lBody: S; ... lAfter:`). The first attempt (fallthrough into the body) ran the wrong branch and is recorded here so it isn't reinvented. ## SymTab (small) Proc-stack: EnterProc/ReenterProc push, new CloseProc pops both scope and stack (all three heading sites converted); InProc/CurRes drive the RETURN 232s. Frame gains 232 "bad RETURN". ## Bugs the tests caught 1. Missing LOOP back-edge — the exact bug Test2's own summary records ("body ran once"); t_loop proved it again. 2. CASE no-match fallthrough executed the first body (100 vs 103). 3. Double-terminator QBE rejections (EXIT inside IF bodies). ## Tests Run: t_if/while/repeat/loop/for (incl. `BY -2` and empty-range skip)/case (labels+ranges+ELSE)/bool (vars, chars, `#`, NOT) + `showcase3` tour → 183. Reject: 214 non-boolean WHILE, stray EXIT, module-level `RETURN 1`, `BY 0`. Total 28 (13 run + 15 reject). Open (step 2/3/4): REAL lowering, `WITH`, `IN`, CHAR arithmetic policy (currently 211), unmatched-FORWARD 231, call emission.