- `readiness: ready | refine` is a SECOND axis, orthogonal to status.
`ready` = the brainstorm is complete and the decisions are LOCKED (a spec
approved, or the forks explicitly confirmed). `refine` = open forks remain
and it cannot be planned yet
- `status: refine` RETIRED because it carried both meanings at once, so a held
iteration with an approved spec (language 18, 26) was indistinguishable from
one nobody had thought about. status is now purely where the WORK is:
done | in-progress | pending | hold — `pending` was already the board's own
rendering word, so nothing new was invented
- all 47 iterations classified from EVIDENCE in their own text, not by guess:
"the four forks are SETTLED" / "spec + plan approved" / "Approved spec:" for
ready; "Forks the spec must settle" / "no spec exists yet" for refine. Every
shipped iteration is ready by definition. 19 done, 5 in-progress, 15
pending, 8 hold; 27 ready, 20 refine
- two iterations moved refine -> in-progress rather than -> pending: language
31 and 34 are absorbed into 24 and work on them is literally happening, which
the board already showed as 🔄 while their frontmatter said otherwise. That
disagreement is now gone
- board legend, board-views' frontmatter contract, and two new Dataview
queries updated — the useful one being `readiness: ready AND status:
pending`, the startable set
WHAT THE NEW AXIS IMMEDIATELY SURFACED: of 15 pending iterations, exactly ONE
is startable — databasev2 4, io_uring group-commit, whose forks were confirmed
settled 2026-08-20. Everything else pending needs a brainstorm first. That was
invisible while one key carried both meanings, and it is now on the board.
Also caught by the sweep, unrelated to readiness but found by cross-checking
frontmatter against the board: SIX duplicate rows. Every iteration moved into
databasev2 was still listed in the LANGUAGE pending table under its retired id
(23, 32, 33, 20, 21, 27) as well as its new one. Stale copies removed. And two
databasev2 rows made claims the sweep contradicts — iteration 1 was billed
"startable today" while its forks are open, and 6 still called itself the
ceiling-raiser after 2 took that role.
Docs only. linkcheck 0 broken / 0 anchors.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
9.2 KiB
| iteration | status | readiness |
|---|---|---|
| 36 | in-progress | ready |
Iteration 36 — operator parity: not, bitwise, hex literals, compound assigns
Format:
product/story-iteration-template. Part of Story — one language, one runtime, one database, one binary.Inserted 2026-08-22, forks settled the same day (decisions below, story-19 discipline). Driver: a gap survey of the operator surface against the Go reference checkout (
.dev/reference/go—src/go/token/token.go, spec §Arithmetic operators) found four holes that every mainstream language covers. The survey also found a latent defect:+=/-=are lexed (lexer.ml:541/548, tokens attoken.ml:149-150) but no parser rule consumes them — dead tokens since the haxe-parity control-surface task, zero uses in the corpus, sox += 1today dies as a generic WO-E101 instead of working or being honestly absent.The driving consumer is iteration 34 (crypto builtins): HMAC's ipad/opad step is a byte-wise XOR, and the doctrine question recorded there — C-side builtin vs pure-
.wo— is unanswerable while the language cannot spell XOR at all. Secondary consumer:http/auth.wo's pure-.wobase64 does with division/modulo what shifts and masks say directly.Plan:
2026-08-22-operator-parity.md(5 tasks: lexer/tokens; AST/parser incl. compound-assign desugar; type checker incl. literal shift-count rejection; emit + VM opcodes.wobv6; corpus/gates/docs closeout).CODE LANDED 2026-08-22 (branch
operator-parity), full stack in one day: tokens/lexer (incl. hex0x/ binary0b/_separators), Go-rung parsing, WO-E201 Int-only/Bool-only checking, WO-E223 literal shift rejection, opcodesWOP_BAND..WOP_SHR42-46 +WO_T_SHIFTas.wobv6. The dead-token defect is closed:x += 1parses (all five compound assigns, parse-time sugar — the desugar-equivalence contract incl. double index-eval is incompiler/src/CODE-LOGIC.md). Gates:woc-test543/0,wovm-testASan both flavors,oop-acceptALL MET,deps-accept8/0,web-app26/0 — all unchanged. Deviation by developer directive: NO test fixtures were written — acceptance is MANUAL via the new sampledocs/examples/operators/(woc ., run, read the ok/FAIL lines;trapmode proves WO_T_SHIFT). The corpus pins this plan called for (grouping, arithmetic>>, ipad/opad, trap) live in that sample instead. Status stays in-progress until the developer's manual pass; the AC below reads as written pre-deviation.
Why this iteration exists
The expression grammar (parser.ml ~594, token.ml:134-150) stops at:
arithmetic + - * / %, concat .., comparison == != < <= > >=,
logical and/or, unary minus. Four common gaps, in priority order:
- No boolean negation. No
not, no!, no unary NOT node in the AST. The only spelling isx == false— every caller pays, every day. Highest value for smallest surface. - No bitwise operators. No
&,|,^,<<,>>in any form. Blocks iteration 34's HMAC honestly; base64/multipart boundary code emulates masks with arithmetic. - No hex/binary integer literals. The lexer scans decimal digit
runs only (
lexer.ml:386on). Bitwise code without0xFFmasks is write-only. - Compound assigns half-promised.
+=/-=lex but never parse (the latent defect above);*=/=%=do not exist at all.
Deliberately NOT gaps (doctrine, not omission): &&/||/! symbol
forms (words won — KwAnd/KwOr precedent), ternary ?: (switch is
already an expression, parser.ml:1181), ++/--, function values,
inheritance-family keywords (WO-E105 rejects them by name).
Goals
notkeyword, unary boolean, sitting with unary minus in the grammar (settled below: Lua placement, binds tighter than comparison). Bool-only operand, diagnosed like every other type error.- Five bitwise operators on Int only:
&AND,|OR,^XOR,<<left shift,>>right shift. Precedence copies Go's C-trap fix (token.go:266-280):&/<</>>at the multiplicative level,|/^at the additive level — both ABOVE comparison, sox & mask == 0groups the AND first. Complement is spelled-1 ^ x(single signed 64-bit Int makes Go's mask rule collapse to exactly this); no~, no unary^, no&^. - Hex and binary literals:
0xand0bprefixes on Int, plus_digit separators in all integer forms. Decimal lexing stays byte-identical for every existing fixture (the iteration-19 discipline). - Compound assigns wired:
+=-=parse into the existing assignment statement (resurrecting the dead tokens),*=/=%=join them. Statement-level sugar over the existing place logic — no new AST evaluation semantics. - Runtime: new i64 register opcodes beside
WOP_ADD..WOP_NEG(runtime/src/wob.h:194) for AND/OR/XOR/SHL/SHR (+NOT if the emitter wants it);.wobversion bump, loader validation, disasm coverage — the v5 (Float/Bytes) change is the template.
Settled decisions (2026-08-22)
>>is arithmetic (sign-extending). Go's own choice for signed integers, and writeonce has exactly one Int, signed 64-bit. For the driving consumers the fork is moot anyway — crypto/base64 shift byte-range values whose sign bit is never set, where arithmetic and logical are bit-identical. No logical-shift operator ships; a caller who wants zero-fill masks first.- Out-of-range shift count traps, like DIV0. Valid counts are
0..63; a negative or ≥64 count at run time is a named trap on the
same machinery as
WOP_DIV's DIV0 (wob.h:197) — the established Int honesty precedent (story 19 kept the trap deliberately). NOT Go's saturate-to-0/-1 (spec surface serving generic-width code writeonce doesn't have) and NOT hardware masking (x86's count%64 makesx << 64 == x, the classic silent wat). Real code shifts by literals: when the count is a literal, the compiler rejects it at emit time and the trap never runs. notsits at the unary level, beside minus (Lua placement).not a == bparses(not a) == b. The grammar's ordering is already anchored to Lua by name (parser.ml~601 "This ordering matches Lua's"), and joiningparse_unaryadds zero new precedence rungs. Python's looser placement reads closer to English, but it buys a new rung to prevent a misparse that Bool-only typing already converts into a compile error in every mixed-type case; the one silent case (Bool compared to Bool throughnot) gets a pinned fixture so the choice stays visible.if not doneandwhile not empty()— the actual daily uses — read identically under both.
Acceptance Criteria
- Given the corpus, when fixtures land for
not(plain, chained with and/or, non-Bool operand diagnosed), each bitwise operator, precedence pins (x & mask == 0and one shift-vs-additive case), hex/binary/underscore literals (value-identical to decimal twins), out-of-range shift (literal count rejected at compile time, variable count trapping at run time), thenot a == bgrouping pin, and all five compound assigns on let/field/index places, then corpus passes with zero regressions on every pre-existing fixture. - Given iteration 34's ipad/opad step written in pure
.wowith^overbyte_atvalues, when it runs against an RFC 2104 test vector's intermediate, then the bytes match — the consumer that motivated the iteration is demonstrably unblocked. - Given the existing gates (
just corpus-equivalent,just web-app,just deps-accept,test_wal), when the iteration lands, then all pass unchanged — the.wobbump breaks no replay/loader path. - Given
x += 1in a fixture today-vs-after, when compiled, then the before is the recorded WO-E101 and the after executes — the dead-token defect is provably closed, not papered over.
Out Of Scope
Unary complement operator (spell -1 ^ x), Go's &^ AND-NOT, bitwise
compound assigns (&= family), ++/--, ternary, symbol forms
&&/||/!, bitwise on Float/Bytes/Bool, overflow-checked arithmetic
changes, octal literals.
Proposed Solution
Lexer: extend the operator switch (the +/+= two-char pattern at
lexer.ml:541-548 is the template) for the five operators and the
three new compound assigns; extend the digit scanner with 0x/0b
prefix branches and _ skipping, leaving the bare-decimal path
untouched. Parser: two new precedence rungs threaded into the existing
recursive-descent chain exactly where Go's table says; not joins
parse_unary; compound assigns join the statement that already
matches Token.Eq (parser.ml:510). Types: all five operators and
not are Int -> Int -> Int / Bool -> Bool in the same checker
table that owns +. Emit/runtime: new opcodes in wob.h's i64 block,
WOB_VERSION bump, interpreter cases beside the existing wrapping
arithmetic, disasm names. The forks are settled above, so no separate
spec is owed (the story-19 deviation precedent: format decisions
recorded normatively where they land, reasoning in CODE-LOGIC.md); the
plan follows once this story is approved on the board.