# V3 step 3.3 — sets: multi-word masks, full expressions, all ops (done 2026-09-20) Suite 50/50 (21 run — incl. 3 trap-aborts — + 29 reject). LL(1)-clean (only the known-benign TypeBlock warning). ## Model (locked: multi-word, full expressions, all ops + IN) Sets are memory addresses (globals or `alloc8` temps), never scalars — uniform with arrays. No header (size is static); W words of bitmask, bit `v-lo`. Unrolled straight-line word code everywhere (counts static, deterministic). ## Grammar (`M2.atg`) - `SetType`: `SET OF` bool/char/bounded-subrange (span ≤ 256); enums wait for ordinals, `INTEGER` is unbounded → 230. - `SetLit`/`SetElem` in `Fact`: literals are `SET OF [0..255]` (8 words); elements int/char/bool, folded values checked 0..255 statically (222), computed values trap via `SetBit`; ranges always lower via `SetRange` (literals validated, inverted runtime ranges are naturally empty, like `FOR 5 TO 1`). - `+ - * /` branch to union/difference/intersection/symdiff when both sides are sets (result spans max, typed exactly so downstream word counts match allocations); `IN` added to `Rel` (mismatch → 222); set `=`/`#` via word-wise `CmpSet`; `CASE` labels reject sets (arrays/strings already 213 themselves). - `Assign` copies sets (`CopySet` with dst/span word counts). ## Backend (`QbeGen`, all probed first) `DeclSet`, `NewSetTemp` (`alloc8`, 8-rounded), `SetZero`, `SetBit`/`SetRange` (span traps), `SetBinOp` (overlap via op, larger-side extras copied or zeroed), `CmpSet` (min words + zero-check extras), `CopySet` (min-word `memcpy`, zero-fill, **trap when src extras beyond dst are nonzero** — narrowing `{100}` into `SET OF [0..7]` aborts instead of silently dropping bits), `InSet` (bit test + span trap). Probe facts reused: `alloc8`, `shr/shl`, strict width checking (a mixed-width `mul` in the probe failed loudly — hence `WidenIndex` everywhere). ## SymTab (lenient-by-design, documented) `SubBounds/SetBase/SetWords/SetBaseLo/SetCount`; `SetBasesOk` accepts any two suitable bases (masks compare over min words); `InCheck` accepts small int/char/bool against suitable sets. Static 222s stay for what is *known* bad (out-of-span literals, non-sets in `IN`); the runtime trap catches the rest. ## Tests `t_set` → 131 (all four ops, `IN`, `=`/`#`, empty, var index), `t_setchar` → 77 (word-3 bits), `t_setrange` → 55 (ranges, var element, empty compare); `t_bad_setelem`/`t_bad_in` → 222, `t_bad_setbase` → 230; `t_trap_setspan` → abort (rc=134). Deferred: enum bases (ordinals), string `=`/`+`, multi-word performance (unrolled is fine for now), `CHAR`-bound subtleties.