writeonce/docs/stories/language-runtime-database/36-operator-parity.md
shoney.arickathil 1fe808b7a4 docs(stories): add readiness, retire status: refine, sweep all 47 iterations
- `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>
2026-08-27 16:54:45 +02:00

172 lines
9.2 KiB
Markdown

---
iteration: "36"
status: in-progress
readiness: 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](00-story.md).
>
> **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 at
> `token.ml:149-150`) but no parser rule consumes them — dead tokens
> since the haxe-parity control-surface task, zero uses in the corpus,
> so `x += 1` today 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-`.wo` base64 does with division/modulo what shifts and masks say
> directly.
>
> **Plan:** [`2026-08-22-operator-parity.md`](../../superpowers/plans/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
> `.wob` v6; corpus/gates/docs closeout).
>
> **CODE LANDED 2026-08-22** (branch `operator-parity`), full stack in
> one day: tokens/lexer (incl. hex `0x` / binary `0b` / `_` separators),
> Go-rung parsing, WO-E201 Int-only/Bool-only checking, WO-E223 literal
> shift rejection, opcodes `WOP_BAND..WOP_SHR` 42-46 + `WO_T_SHIFT` as
> `.wob` v6. The dead-token defect is closed: `x += 1` parses (all five
> compound assigns, parse-time sugar — the desugar-equivalence contract
> incl. double index-eval is in `compiler/src/CODE-LOGIC.md`). Gates:
> `woc-test` 543/0, `wovm-test` ASan both flavors, `oop-accept` ALL MET,
> `deps-accept` 8/0, `web-app` 26/0 — all unchanged. **Deviation by
> developer directive: NO test fixtures were written** — acceptance is
> MANUAL via the new sample `docs/examples/operators/` (`woc .`, run,
> read the ok/FAIL lines; `trap` mode 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:
1. **No boolean negation.** No `not`, no `!`, no unary NOT node in the
AST. The only spelling is `x == false` — every caller pays, every
day. Highest value for smallest surface.
2. **No bitwise operators.** No `&`, `|`, `^`, `<<`, `>>` in any form.
Blocks iteration 34's HMAC honestly; base64/multipart boundary code
emulates masks with arithmetic.
3. **No hex/binary integer literals.** The lexer scans decimal digit
runs only (`lexer.ml:386` on). Bitwise code without `0xFF` masks is
write-only.
4. **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
- **`not` keyword**, 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, so
`x & mask == 0` groups 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**: `0x` and `0b` prefixes 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); `.wob` version bump, loader validation, disasm coverage —
the v5 (Float/Bytes) change is the template.
## Settled decisions (2026-08-22)
1. **`>>` 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.
2. **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
makes `x << 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.
3. **`not` sits at the unary level, beside minus (Lua placement).**
`not a == b` parses `(not a) == b`. The grammar's ordering is
already anchored to Lua by name (`parser.ml` ~601 "This ordering
matches Lua's"), and joining `parse_unary` adds 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 through `not`) gets a pinned
fixture so the choice stays visible. `if not done` and `while 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 == 0` and 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), the `not a == b` grouping 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 `.wo` with
`^` over `byte_at` values, **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 `.wob` bump breaks no
replay/loader path.
- **Given** `x += 1` in 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.