Dead until the { arm lands (tranche-A precedent); 13-row direct
private-fn table, error texts byte-exact. Hare's verbatim
((void|size),(void|size),size) tuple return can't cross a union
boundary yet — cstage's (tuple|error) return store is cgen-unwired and
wwstage's variant-match rejects the tuple case arm (filed #47) — so it
respells as the private repparts struct per the scope-fold5 §3
pre-signed fallback; graduates back to the tuple when #47 closes.
Riders: call-result .N tuple read loud-rejects (filed #48, bind-first
local); strings.index's standing i32 convention (#8) stays internal,
widened at each size boundary; ha:494's same-name re-bind is rejected,
second local feb.
compile: '(' (ha:317-323) appends inst_groupstart(capture_idx) and
grows jump_idxs per level; ')' (ha:324-334) appends the void
inst_groupend, fixes up the level's pending alternation jumps (#70
range + #58 assert), range-deletes them (#8) and closes the level;
the loop-exit done arm gains the Unmatched-'(' check (ha:277-282).
The anchors' group_level arms, the postfix inst_groupend/groupstart
arms and find_last_groupstart's success path go live unchanged.
run_thread: inst_groupstart (ha:636-652) fill-grows captures to
idx+1 (count-loop spelling of Hare's 3-arg fill-append) and opens
the group with the SIZE_MAX end sentinel via the #20-fixed indexed
struct store; inst_groupend (ha:653-668) closes the innermost
unclosed capture (ha:655's 2-clause for respelled — ww has no
cond;post form) and slices content from the bytesize span through
the addr-of-element pointer.
search: the ha:820 loud bound flips to the real capture spread
(#35/#25); the pad fill self-activates for unset trailing groups.
Tests: ( ) graduate from the metachar-loud table into real-text
error rows (+ ww-added anchor-in-group / Unused-on-groupstart
re-verify rows); hand-built groupstart/groupend arm cases; the 5a
find table (+test.ha:257-275 group/alternation, :499-503/:607 jump
bugs, :610-621 submatch family, :635/:640 alternation-group,
:649-665 nested minus the 5b {m,n} twins, + ww-added multibyte
row) with len(res) pinned per row; submatch content rows
(+test.ha:704-708 + ww-added multibyte) — the 5a acceptance gate.
The loud bound (capture dup not yet portable, #35/#34/#7) flips to the
real dup now that #35's spread place-chain sources landed: fresh slice
header + spread-append, the D3 spelling of Hare's alloc-dup. The
ok/defer-if frees drop (free() is the documented no-op, #27).
Dup-independence rows drive add_thread directly: values carried,
backing independent both directions, empty parent → empty dup.
wwstage's parsef64 (naive i64-accumulator + pow-10 fold) diverged from
cstage's strtod: >19-digit mantissas overflowed the accumulator (sign-bit
garbage), DBL_MIN was +1 ULP, DBL_MAX -2 ULP — the #59.10 ratchet pin.
C-strtod oracle confirms cstage correctly rounded on every vector, so
wwstage aligns to it by dogfooding strconv.stof64 (correctly-rounded
decimal engine, already imported by lex.ww). Overflow literals now
reject in both stages (stof64 overflow -> errat, mirroring ERANGE).
Fix + #59.10 M_DIVERGE->M_ID graduation + pins land together per the
ratchet's designed flow (the gate trips loud demanding graduation):
oracle-pinned vectors in toktest.ww floatfold_cases (lexer-unit) and
989_floatlit_run (compiler fold: runtime bits + byte-id + overflow
reject parity). Retained subnormal accept-set asymmetry filed as task
#21, documented at the lexnum site.
The flat checker scope makes ANY decl named assert/abort anywhere in
the combined unit disable the builtin unit-wide (the #45 shadow shape:
scope_lookup_prefer's cross-module fallback finds it). lib carried
three colliding @symbol("rt_abort") shims (os, time, strconv/stof)
plus the os.assert wrapper, so a bare assert(cond) in ANY program
importing os mis-bound os.assert and failed arity — a hard blocker for
regex fold-5 (regex.ha:660/670 bring builtin-assert mass). Ruled
respell-now per the recurrence test (#45 -> #58).
Delete the shims and the os.assert wrapper; every bare abort(msg)
caller (regex, strings, utf8, hash, getopt, encoding/*, time, stof)
now lands on the builtin, and the ~40 os.assert(c, m) sites respell to
the builtin assert(c, m) — restoring the exact Hare spelling the lib
ports diverged from (e.g. ref/hare/bytes/tokenize.ha:23). os.assert
had no Hare counterpart (Hare's assert is a language builtin); rule-9
wrapper removed. temp/dirs/bufio already use the non-colliding rtabort
spelling and keep it.
Now-dead 'import os;' lines kept (pre-existing precedent:
lib/strconv/strconv.ww carries one); a tree-wide dead-import sweep is
a separate concern. regex.ww's if+abort workarounds citing #58 stay
for the fold-5 owner to fold back into assert.
combined.ww regenerated for all five selfhost tools + the smoke
fixture via make.
Port of ref/hare/regex/regex.ha:135-225 (handle_bracket, whole),
265-275 (in_bracket dispatch), 313-314 (the `[` flip), 249-252 (the
bracket state quad) and 704-737 (the consuming charset arm). `[`
graduates from the fold-2a loud set; `(` `)` `{` are the last
three loud metachars. The POSIX-class arm keeps its DETECTION
verbatim but loud-aborts its BODY (charclass_map stays #25-blocked;
falling through to the literal arm would silently compile
[[:alpha:]] as a 9-literal charset). is_consuming_inst already
covered inst_charset.
Spelling divergences, all site-documented: the dispatch propagates
via the explicit D13 match, not `?` (compile's 64B sret return is
the #38b loud-stop; the fold-3 find_last_groupstart precedent);
charset's declaration moves BELOW its member types (cstage sizes a
tagged alias with forward-declared members at a degenerate 8B —
ww-core #69, wwstage is correct); run_thread binds the charset
structurally, not via the alias (alias-typed slice locals mis-scale
their index reads in wwstage — ww-core #68).
Tests: Hare's own bracket block (+test.ha:278-345, the group and
POSIX rows excluded with their loud arms) as the 72-row find/test
table incl. multibyte literal+range brackets and an unanchored
[ab]+ composition row; charsets-table content pins (lit/range
discrimination, first-char ]/[ literals, literal dashes, multibyte
codepoints); program-shape pins ([abc] / ^[abc]$ / [^ab] /
[ab][cd] / [abc]*); exact-text error rows (Unmatched '[' ×3 incl
the escape interaction, descending [z-a]); findall composition.
The [[:alpha:]] abort text is unpinnable in-process (it kills the
runner) — source-audited until the POSIX fold.
Ports compile()'s fold-3 arms (ref/hare/regex/regex.ha): \ escape
(286-293), ^ (294-300), $ (301-312, goes live with run_thread's
anchored ha:621-624 pin), | (335-367) over the restored jump_idxs
prologue (241-256 subset: jump_idxs + was_prev_rune_pipe +
group_level verbatim-but-0) + the whole-expression fixup (470-473,
the SIZE_MAX-sentinel overwrite 2a dropped), ? (403-420), * (421-443),
+ (444-459); find_last_groupstart (104-119, whole — error arm is the
live one until the group fold) and shift (123-133). run_thread's
inst_split/inst_jump aborts FLIP live (606-611, all 2b-proven
shapes). Still loud: [ ( ) { in compile; groupstart/groupend/repeat/
charset arms; add_thread's capture-dup bound.
Spelling divergences, each cited at site:
- multi-type case (inst_lit | inst_charset | inst_any) (ha:407) is
loud-rejected both stages (PE5; Hare-parity #13) -> three void arms.
- find_last_groupstart(...)? in the dead groupend arms hits the #38b
>32B-tagged-return propagate loud-stop -> explicit D13 match
(harec's own ? desugaring).
- by-value range over an indexed element (for (let x .. jump_idxs[g]))
SEGFAULTS both stages (NEW, filed ww-core #57) -> D9 index loops at
ha:351-356 + 470-473.
- assert(cond, msg) is wwstage-broken (CALL assert(SB), undefined at
link; NEW, filed ww-core #58) -> if+abort at the ha:355 site.
- Hare's match/if EXPRESSIONS (ha:337-346) -> statement spellings
(the #51 search/scanrune precedent).
Tests: the 11-pattern loud row shrinks to the 4 remaining metachars;
strerror reroutes through a[. New: fold3_programs (exact inst
sequences incl. the a|b sentinel fixup), fold3_compile_errors (12
exact texts incl. Hare's own ab\|^cd ERROR fixture), direct
find_last_groupstart/shift rows, fold3_find_cases (42 rows from
Hare's +test.ha anchors/postfix/alternation blocks, group-free
subset, end=-1 resolved to rune-length; rob's dedup/leftmost/longest
riders now observable: a* over aaaa -> single (0,4), b+ over abab ->
(1,2), b* / ^b* over aaaabbbb -> (4,8)/(0,0); multibyte b+ row keeps
idx != bytesize) + the a*-over-baa findall rider (longest-pick beats
the zero-length candidate; trailing zero-length match takes the
ha:942-945 break). Both drivers green; regex_test.combined.ww cs==ww
byte-identical.
findall (regex.ha:923-960) over the memio seeker: one fixed stream
for the whole string, per-call suffix substring, absolute io.seek(SET)
past the scanner readahead after each match. The append-then-mutate
m[0] fix-up is verbatim Hare (the appended header shares m's backing);
the zero-length-match rune advancement guard (ha:946-952) carries the
infinite-loop protection. search's |success|=2 unwrap is the D13
explicit 3-arm match (ww-core #14); nomem propagates.
result_freeall (ha:1119-1124) verbatim, frees no-op (#27).
Tests port Hare's own findall table (+test.ha:719-731, the three
fold-2a-reachable rows) through run_findall_case's checks, plus field
rows pinning adjacency, the one-result overlap pick, multibyte
zero-length advancement (utf8sz step != 1), idx != bytesize, the
tail-match break, and the empty no-match slice. 989's run gets the
conventional timeout-180 wrap: a regression of the zero-length guard
would otherwise hang the gate (no-op frees, so no quick OOM exit).
io.error grows errors.invalid (ref/hare/io/types.ha:11 spreads
...errors::error, which includes it; Hare's memio seek returns it on
out-of-bounds, stream.ha:134-136) — appended last so existing member
tags stay put; no exhaustive io.error matches exist in lib.
seekfn mirrors ref/hare/memio/stream.ha:122-140 over the flat header,
shared by fixed/dynamic/dynamicfrom (Hare wires the same seek into
both vtables). The io.off-vs-i64 arithmetic runs on an i64 copy:
cstage binop typing is nominal on aliases, wwstage accepts (filed,
ww-core #54).
791's stream_seek_unsupported row re-pins st_seek's void-arm on a
hand-built seekerless vtable: its old premise (memio wires no seeker)
is retired by this commit; memio seek success is pinned by memiotest.
w6c/wwdump main.combined.ww regen'd: they embed lib/io + lib/memio
(the freshness gates are blind to this — embedded-source discipline);
w6a/w6l/ww don't embed io, verified untouched.
Port of test (ref/hare/regex/regex.ha:901-904) and find (ha:907-918)
— the exported exec surface over tranche C's search. fold 2b is
COMPLETE: compile → test/find runs end to end on the 2a literal
programs.
Both are |success|=2 `?` sites in Hare (`search(...)?` over
(void | []capture | nomem)); ww's `?` is gated to single-success
unions (the C6 interim), so each spells the propagation as the
explicit 3-arm match harec lowers `?` into (ref/harec/src/check.c:
2780) — the ratified D13 spelling, on task #14's acceptance list for
reversion when subset-union `?` lands. find's no-match `return [];`
(ha:916) binds a zero header first (#25/#31 ruling) — a valid empty
result the caller still result_frees. io::handle args are the landed
memio→io cast (&strm.vt); import memio added.
PD1 probed first (scratch/pd1.ww): []capture values returning
through (result | nomem) — the nominal-alias member — plus the
(bool | nomem) round-trip; build/run/byte-id green at base, so no
new compiler surface was crossed.
regex_test: +2 @test fns (signalled 23-24). test_matches = 8-row
tcase table (the six search-table inputs → true, thread-drain and
EOF-mid-pattern → false). find_cases = 8-row fcase table: the six
match rows reuse the search table's root-capture expectations (all
four indices + content), the two no-match rows pin the empty result;
result_free on every row, matched or not. Every fcase row also
cross-pins test/find agreement (test(re, s) == find(re, s) matched),
so an arm-swap in either D13 match is caught by the other surface.
Both drivers run the fixture exit 0; w6c vs w6c_ww on the
regenerated combined byte-identical (FC0).
Port of search (ref/hare/regex/regex.ha:746-898) per the drew §9c
map: the thread-machine driver over bufio.scanrune — per-rune
dispatch, all_matched best-pick (leftmost-longest), need_captures
early-exit, first-match leftmost trim, same-pc dedup, failed sweep.
compile()'s literal programs now match end to end; test/find (the
exec surface) ride tranche D behind the C6 multi-success `?` gate.
Spelling divergences, each documented at site with its ha cite:
io::handle param → io.stream; alloc([thread{...}])? → decl + append;
defer-block cleanup omitted (single-expr defer, no-op frees, #27);
rep_counters prefill → ratified loud n_reps>0 abort (no 2b program
can set it); newscanner default maxread → types.I32_MAX; scanrune
nomem arm dropped (no such member) and multi-type arms split (#13);
`return [];` → bind-first zero header (#25/#31 ruling); `result`
internals spelled []capture (#20/#38 alias family — reverts with
#47); ha:820's indexed capture spread loud-bounded provably-empty
(#35); `&..` by-ref ranges → index loops; the ha:821 sized
fill-append → count loop (self-activates with the group fold).
Two checker findings surfaced mid-port, probe-isolated, dodged at
site and FILED: #51 (cs≠ww — the cstage checker types a
match-EXPRESSION by its first arm's yield and rejects the io.eof
arm against rune; w6c_ww accepts the expression form and emits
runtime-correct code, review-verified on scratch/r51.ww), so the
scanrune receive is a statement match assigning into a pre-declared
(rune | io.eof); #52 (cs≠ww, wwstage only) — a same-name let in a
CLOSED sibling scope poisons a later for-init rhs (`let j: i64 =
i + 1` resolves i against the dead `let i: size`), so the ha:872
dedup counters are di/dj at site (scratch probes p51/p51b/c/d
isolate the trigger and prove the rename byte-identical).
add_thread's dedup bound reverts to len(*threads) — the FB1/#41
dodge, fix landed at 796d41b. Imports grow bufio + types.
regex_test: +3 @test fns (signalled 20-22) driving private search
directly over memio.fixed streams. search_matches = 6 struct-row
table rows: full match mid-string, mismatch-restart bcd/abcd,
leftmost-longest aa/aaa, zero-length ""/"" (the all_matched path
with matchlen 0 must NOT take the need_captures=false early-exit),
multibyte b.d over "aßbxd" (root 2/3..5/6 — every idx differs from
its bytesize), dedup-heavy aa/aaaa (stable across >=3 same-pc
passes). search_early_exit pins ha:845-847 (empty result, len 0);
search_no_match pins thread-drain void + EOF-mid-pattern void.
Every match row checks all four root indices plus content and
result_frees its result (including the early-exit empty one).
Coverage limit, mutation-verified and documented at the dedup row:
in 2a's fixed-length program space every match ties on match_len,
so the leftmost trim (ha:860-866) and the dedup sweep (ha:872-889)
are result-invisible — disabling either still passes the table;
disabling the failed sweep hangs (caught). Both turn result- and
termination-visible with the split/star fold; result stability is
the only external pin available today (threads is search-local).
Both drivers run the fixture exit 0; w6c vs w6c_ww on the
regenerated combined are byte-identical (FC0). PC1-PC4 + P12 probed
at base; PC2's blocker fix is the separate #49 commit (c34a48a).
First end-to-end engine execution in tree: compile("ab")'s program
runs through skip-spawn / lit advance / match capture under the @test
drivers (search and the exec surface stay tranche C/D). Ported
Hare-verbatim from ref/hare/regex/regex.ha:589-742 — the #40 arg
wiring carries the ha:602 loop condition with no let-bind; arm bodies
stay verbatim so the group/repeat fold pastes straight into this
match. Arms compile() cannot emit are one loud not-yet-ported abort
each (the fold boundary); Hare's bare unreachable abort()s carry a
message because os.ww's private abort(msg) shadows the builtin
cross-module (filed, ww-core #45). The (anchored: bool)/(lit: rune)
casts are checker-required (ww aliases are nominal where Hare relies
on transparency), WHY-cited at site.
Port is_consuming_inst (regex.ha:553-555), delete_thread (ha:547-551)
and add_thread (ha:557-587) per the tranche-B scope (drew §9b). D12
chained-|| spells the multi-type is (parity task #13); D9 index loops
replace the &.. ranges (#11); the dedup-scan bound reads .len, not
the len() builtin (FB1, #41 — len(*p) loads the data pointer as the
length). add_thread's capture/rep_counter dup is loud-bounded per the
rob-ratified ruling: every ww route into the dup is blocked at HEAD
(#35 spread source, #34 element source, #7 element let-copy), and
fold-2a compile() cannot emit inst_groupstart/inst_repeat, so both
parent slices are provably empty in every reachable program; the
verbatim dup lands with the group/repeat fold (#3). run_thread (B4)
stays out: its loop condition passes the 56B inst by value from a
slice-element source, gated on the #38b extension (#40, in flight).
Fixture cases 15-17 drive the three fns directly (package regex):
all-10-kind consuming table, delete at middle/last/0-to-empty,
dedup suppress/strict-</matched-guard + inheritance + zeroed headers.
Byte-cmp on the regenerated combined holds the FC0-only baseline;
both drivers run the fixture green.
thread (regex.ha:55-64) and newmatch (ha:66) land verbatim ahead of
their engine consumers; result_free (ha:1113-1116) and strerror
(ha:1126-1127) complete the exported error/result surface.
delete_thread/add_thread deferred behind #15 (append-through-ptr)
and #17 (deref-spine element reads); is_consuming_inst deferred
behind #19 (>48B tagged by-value call boundary unwired + divergent
callee receive) — noted at the Hare-order site.
Tests are row-table driven: thread_shape reads back both appended
threads against a [2]texp want table (root_capture rows deferred —
every read route is compiler-blocked, #6/#7, probed at HEAD);
newmatch_discriminates drives one row per (void|newmatch|nomem)
member, incl. a nomem-vs-newmatch row; result_free also covers the
zero-header empty result (find()'s ha:915-916 no-match shape).
Byte-id re-verified on the regenerated combined: the pre-existing
FC0 regex.finish hunk is the only divergence.
Port Hare's auto-grow newscanner (scanner.ha:72) and scan_rune
(scanner.ha:259). scanner gains a maxread field (== cap for
newscannerbuf, scanner.ha:101); readahead grows by BUFSZ up to
maxread via alloc+copy (Hare appends; ww flat ptr/cap scanner,
old block left to process-exit reclaim). scanbytes' overflow
test gains the avail >= maxread leg (Hare's pending >= readahead
predicate) so a growable scanner refills instead of overflowing.
finish ports the free(scan.buffer) verbatim per the regex #27
precedent. Tests: rune scan over 1/2/3/4-byte UTF-8 + EOF,
invalid initial/truncated/surrogate sequences, newscanner grow
round-trip + scanrune-over-newscanner, maxread overflow.
ref/hare/regex/regex.ha:96-102 body restored word-for-word now that
the free() builtin is a documented no-op: each free evaluates its
operand and reclaims nothing (ww is a no-free runtime, rt/alloc.s:30).
Drops the fold-1 empty-body stub and its held-back note.
The loud-arm row asserted only a non-empty error on "a*" — a
half-ported arm returning any other error text, or another metachar
falling to the literal default, would have passed. Table over one
pattern per deferred arm ('^' leading, so the r_idx==0 skip gate
composes with the loud arm) compared against the exact boundary
text via strings.compare.
The lit/match appends went through a let-temp; the direct Hare
spelling append(insts, (r: inst_lit)) compiles and runs correctly
(probed at the real 48B-payload inst shape), so the temps were an
undocumented reshape. Void variants (skip/any) keep the typed let —
a bare type name is a symbol ref in ww — now documented at-site.
The bare-slice-decl zeroing cite pointed at shlex.ww:215, which
zeroes its header EXPLICITLY and so proves nothing; the real
mechanism is cgen.c:9836's no-rhs multi-word composite zero-fill.
Ports ref/hare/regex/regex.ha:227-263 literal arms: leading unanchored
inst_skip, inst_lit / inst_any, epilogue inst_match(false). Every
deferred metacharacter arm returns a loud not-yet-ported error (the
fold boundary); state serving only deferred arms drops with them.
Hare free()/defer-if cleanup omitted (no-free runtime, #27); bare
append per #36. 4 new @test rows pin the emitted programs incl. the
empty-input and loud-boundary cases; compile()'s >24B tagged return
doubles as a #38 sret consumer. Rides #34/#38/#44/#45/#48 — all five
fold-2a blockers now closed on master.
shlex.appendstr, getopt.appendoption, bytes.appendslice and
strings.appendstr existed only because the append builtin stored the
first 8 bytes of the element; each carried its own @symbol("rt_ensure")
bind and a grow-then-store-through-*T body, with comments promising to
"collapse in one go when the append builtin is fixed". The previous
commit fixed the builtin; this removes all four helpers and their
rt_ensure binds and spells every call site as plain append().
Bonus correctness: getopt's appendoption passed a hardcoded membsz of
24, stale since the str 24B redesign made option {rune, str} 32B — the
manual growth under-allocated past 6 options while &opts.ptr[i] strode
32 (latent OOB). The builtin derives membsz from the type table
(probe: MOVQ $32, SI), closing that drift by construction.
Port of ref/hare/regex/regex.ha fold 1 (the data model). Lands the
full type model — error, the inst_* variants + 10-variant inst union
(the nominally-distinct same-underlying size/void aliases included),
result/capture, charset + items, the regex struct — plus finish().
Test 989_regex_run pins variant discrimination, payload extraction,
struct shapes, and finish() on cstage; w6c == w6c_ww byte-identical.
Two fold-1 constructs are held back behind filed compiler/fidelity
gaps, documented at their sites (regex tasks A–D):
- charclass_map (regex.ha:74-87): const [](str, *fn(rune) bool)
table — blocked on the array-literal->slice element-coercion
checker gap (type.c:402-404 #258 borrow uses exact type_eq,
no element decay). It needs `import ascii;`, so both land with
the consuming fold (compile) once the gap is fixed.
- finish() free()s; ww is a no-free runtime (rt/alloc.s:30), so the
faithful body drops the frees, as the port drops every Hare
free(). Kept as a no-op for API parity.
DEFERRED to later folds: compile()/exec/find/replace.
Replace kwlookup's 30-arm streqn if-ladder with two parallel module-level
tables — kwnames: [30]str + kwkinds: [30]tkind — scanned linearly via
strings.compare, and delete the hand-rolled streqn. Rides #18 (module-
level [N]str static-init + relocations) for the kwnames data and #8
([N]enum element sizing) for the kwkinds[i] read, which is itself the
construct that surfaced the #8 elemsizeofc/array-init-store miscompile.
Two parallel arrays rather than a [N]kwent array-of-struct: a str inside
an aggregate element is the filed #18 follow-up. Mirrors the C twin
cmd/wcc/tok.c kwlookup (N=30, linear, no hash). Source re-applied on top
of the #8 cgen fix and regenerated fresh: the wwstage-compiled toktest
now runs correctly (exit 0, no segfault) where pre-#8 it smashed the
frame on the local [N]tkind init store.
The local doexit reimplemented os.exit via a raw rt_syscall(60) decl
with no documented divergence, while the file already imports + uses
os. os.exit (lib/os/os.ww:68) is byte-identical (syscall1(nr.EXIT=60));
ostest/stattest siblings already use os.exit.
strings.bytesub two endpoint guards, wcc cgdot/cgassign 4-deep
allptr/N_IDENT/localfindnode pyramids, and w6l isarchive's 8 sequential
magic-byte rejects. The isarchive len<8 read-guard stays a separate
statement before the || chain so the byte reads remain bounded. Not
byte-id-neutral (short-circuit emits tighter branches / renumbered
labels) but functionally identical; cs==ww stage-parity holds.
Regenerated all embedding combined.ww.
Fold the 69-arm `if (k == nkind.N_X) return "..."` ladder in nkname to a
single `switch (k)` with the terminal `return "?"` as the fall-past
default. The other ast.ww ladders stay: pr()'s kind dispatch is
side-effecting (emits output, recurses) and uses ||-grouped multi-kind
predicates, not a pure value->value mapping a switch can express.
Not byte-id-neutral (if-chain -> switch dispatch changes the asm), so
the ladder->switch equivalence is pinned by a new table-driven test:
lib/ww/asttest.ww drives nkname over every nkind plus the out-of-band
"?" fallback, wired as 905_nkname_run (same `ww run` @test shape as
904_tok_run). The 990_selfhost wwdump diff only covers kinds that
appear in its corpus.
Regenerates the w6c + wwdump combined.ww amalgamations (nkname region
only).
Fold the ~88-arm `if (k == tkind.TK_X) return "..."` ladder in tokname
to a single `switch (k)` with the terminal `return "<?>"` as the
fall-past default. kwlookup stays an if-ladder: it dispatches on
streqn() string compares over distinct literals, which a value-switch
can't express.
Not byte-id-neutral (if-chain -> switch dispatch changes the asm), so
the ladder->switch equivalence is pinned by a new table-driven test:
lib/ww/lex/toktest.ww drives tokname over every tkind plus the
out-of-band "<?>" fallback, and kwlookup over every keyword plus
non-keywords, wired as 904_tok_run (same `ww run` @test shape as
904_ascii_run). The 990_selfhost wwdump diff only covers kinds that
appear in its corpus.
Regenerates the w6c + wwdump combined.ww amalgamations (tokname region
only).
Drop 112 redundant `let x: T = rhs` annotations where the rhs already
infers T (newnode→*node, p.curfile/curtext→str, p.curline/curcol→i32,
accepttok/== →bool, parse*→*node). stmt.ww (75) + parse.ww (37).
Regenerate the two embedders' combined.ww (w6c, wwdump). Byte-id-neutral:
cstage-w6c asm of each combined.ww is identical pre/post.
61 over-annotations dropped where the rhs unambiguously infers the
declared type: 22 overflow:bool comparison binds in checked.ww, and
the mem/s/sl memio.stream/io.stream/log.stdlogger triplet across 13
@test sites in logtest.ww. Sub-word res:/fullres: binds with casts or
truncation are kept. Byte-identical asm in both stages, both files
non-embedded.
Pure byte-id-neutral substitution of ASCII magic decimals with char
literals across lex.ww (131) + tok.ww (19); regen w6c + wwdump
combined.ww (lex is embedded in those two only).
escape() out-values for \a (7) and \b (8) are now '\a'/'\b' — both
the C bootstrap lexer (cmd/wcc/lex.c:160-161) and ww's own escape()
map them, so the literals are value-equivalent and stage-symmetric.
Digit-value arithmetic (parseint/parsef64/hexchar: c - '0' over u8)
is left decimal: a rune/i32 char literal would promote the u8 operand
and is not byte-id-neutral. Multi-byte type-suffix comments (i8../f64)
are kept — they label more than a single char.
Replace magic ASCII decimals with char literals in ascii/fnmatch/shlex
predicates (e.g. `c < 48` → `c < '0'`). Byte-id-neutral: ascii params are
rune, so rune<rune emission is unchanged; fnmatch/shlex compare u8 against
value-preserving (<=126) rune constants. Range bounds (0/31/127), the ±32
case offset, the 128 high-bit mask, and fnmatch 0u8 sentinels stay decimal.
Regenerate the three combined.ww that embed ascii (w6c, wwdump, smoke).
Add functional rows pinning predicates reachable only via fnmatch ctype
classes / shlex split: [[:space:]]/[[:print:]]/[[:graph:]] + the '\t' arm
of [[:blank:]] (fnmatchtest), '\t'/'\n' split separators + issafe's
special-char set (shlextest) — so a wrong substitution would be caught.
The literal array dimensions ([64]u8/[8]u32/[64]u32) where Hare uses the
BLOCKSZ def / [_]u32 are forced by ww rejecting a def in array-dimension
position. rule-7 requires a retained divergence carry a filed-task
pointer; add the #269 cite to the header divergence list and the state.x
at-site note (previously described the limitation but cited no task).
Port of ref/hare/crypto/sha256/sha256.ha — block-processed [64]u8
chunks, u32 modular arithmetic, hash::hash + io.writer surface. The
state embeds hash.hash (inline vtable at offset 0); the vtable + sum/
reset slots are wired post-construction (base64/memio convention).
u32 WRAPPING + vtable dispatch CONFIRMED CLEAN: all NIST vectors verify
byte-identical — empty, "abc", the 56-byte block-boundary case, and the
one-million-'a' multi-block stream (1000-byte chunks across many blocks,
stressing write()'s partial-block carry). cgen truncates u32 add/shift/
rotate to 32 bits correctly; no masking workaround needed.
Semantics-preserving spelling divergences (slice-copy as byte loops,
close()/digest loops) are noted at-site per CLAUDE.md rule 5/13.
ONE BEHAVIORAL DIVERGENCE, blocked on a cgen bug (flagged for ken/drew):
Hare's sum() snapshots the state (`let copy = *h`) so it is re-entrant.
That deref-copy of an array-containing struct miscompiles in ww cgen
(copied array fields come back zeroed). So sum() runs on the live state
and is SINGLE-SHOT until the cgen fix lands; every current caller does
one terminal sum(), so the digests are unaffected. Minimal repro:
type t = struct { h: [4]u32 };
let c: t = *(&s); // c.h reads back wrong
A sibling bug (array return-by-value zeroes the result) was also found
and is avoided in the test's buffer-based helper. Both filed for ken.
The hash/crypto modules are dead-imported (no selfhost combined.ww
regen). 9xx test numbers are full, so the run-test shares the 989
prefix with siphash (distinct `short` name; 949_* multi-file precedent).
Subset port of ref/hare/crypto/math/bits.ha: the 32-bit rotations
sha256's message schedule and compression need. The wider bits.ha
surface (rotl64/rotr64, the constant-time compare family, xor) lands as
callers arrive. rotr32 is exercised end-to-end by the sha256 NIST
digest vectors, so no standalone @test ships here.
Port of ref/hare/hash/hash.ha — the general-purpose hashing-function
interface that crypto/sha256 (and, later, the lib/hash/* checksums)
embed as their first field. Rides the proven io.stream + inline-vtable
shape: a hash is an io-write-only stream plus sum()/reset()/sz()/bsz().
One divergence (documented at-site): Hare's `stream: io::stream` field
becomes an inline `vt: io.vtable` at offset 0. ww's io collapse (#94)
makes the dispatchers take the vtable pointer directly, so the vtable
must be embedded inline for a *state to recover from the dispatch arg —
the base64/memio/io convention.
Rewrite the buffer-based base64 placeholder as a faithful port of
ref/hare/encoding/base64/base64.ha over the just-landed io-streaming
surface (mirrors lib/encoding/hex).
Ships: std_encoding/url_encoding (module-level `def` consts; decmap
trailing 0xff run spelled out, no '...', to stay on #251 and avoid the
#250 repeat-fill sugar); the streaming encoder newencoder/encode/
encodeslice/encodestr with a padding closer wired into the inline
vtable; encodedsize/decodedsize; and decodestr as a direct in-memory
decode via decmap (the same divergence hex took for its direct path —
its return union carries errors.invalid, unconstrained by io.error).
Deferred (at-site notes): the streaming decoder newdecoder/decode_reader
(#247-sibling, blocked on #199b — io.error lacks errors.invalid).
clear() wipes the work buffers with explicit full-length slices
(`[0:len(...)]`) rather than Hare's bare-array decay (pending #258
[N]T->[]T coercion) to preserve the whole-array hygiene wipe.
base64 graduates off 900_stdlib (cross-module refs resolve only via
driver concatenation, as hex did); coverage at 984_base64_run over the
RFC 4648 §10 vectors for std and url.
The old buffer surface (encodedsize/decodedsize + encode(dst,src) i32 +
decode(dst,src) (i32|invalid)) does not exist in Hare — it predates the
#94 io vtable and mis-cited hex.ha:175 while implementing a different
signature. Replace it with Hare's real surface
(ref/hare/encoding/hex/hex.ha):
- newencoder(out: io.handle) (:28) — write-only encoder stream.
- encode(out: io.handle, in) (size | io.error) (:91).
- encodestr(in) str (:68).
- decodestr(s) ([]u8 | errors.invalid) (:175).
Divergences (documented at-site):
- The streaming DECODER (newdecoder/decode_reader, :120,:129) is
DEFERRED to #247, blocked on #199b: Hare's decode_reader returns
errors::invalid, which fits Hare's io::error (spreads
...errors::error). ww's io.error (lib/io/types.ww:55-62) does not
carry errors.invalid, and io.read's (size|eof|error) can't propagate
it, so a hex decoder *stream* can't faithfully report invalid hex
through io.read yet. decodestr ships as a direct transform meanwhile.
- nomem dropped from encodestr/decodestr returns (ww memio.dynamic has
no failure path — same memio.string rule-9 carve-out, memio.ww:208).
- The local hex.invalid type is deleted in favor of errors.invalid
(that was the original divergence).
- encode uses a single io.write rather than Hare's io::writeall (ww has
none — fmt.fprint:498-501: callers drive write-all over raw io.write;
encode_writer is whole-slice so a single write is equivalent).
- dump (:212) deferred: ww has no default-arg support and fmt's
formattable lacks u64 (#209), so the address column can't be ported
faithfully yet.
hex is now import-bearing, so it moves off the 900_stdlib standalone-
compile list (like fmt/os/strings/bufio/bytes/errors before it); coverage
stays at 979_hex_run.c. The stale "mirrors lib/encoding/hex.encode"
comments in lib/encoding/utf8/utf8.ww are updated, which regenerates the
6 selfhost combined.ww (5 cmd + test/smoke) (comment-only, byte-id-neutral).
tinfocachelookup walked a flat prepend-only association list on every
cache miss -> O(N) scan x O(N) calls = O(N2) (91% of all wwstage
instructions on a 5k-line input; w6c_ww ~265x slower than its C twin).
Replace the single list head with a node-ptr hash index, mirroring
sym.ww scope.buckets (rule-12): NBUCKETS_TINFO=8192 power-of-two
buckets, ptr hashed via (key>>4)&(N-1) (>>4 drops the always-zero
aligned low bits so buckets don't cluster), cnext now chains within a
bucket. First-match-in-bucket preserves the old most-recent-bind-wins
order -> identical *tinfo per node -> byte-identical asm.
cstage (cmd/wcc C) has no such cache, so this is wwstage-internal:
no emitted-asm change, no cstage-symmetry obligation. Verified
byte-identical output (baseline vs new binary, same 32k-line input)
and 52.6s -> 0.54s (~97x). combined.ww regenerated for w6c + wwdump
(only tools embedding typ.ww). test-unit (235) + smoke green.
Graduates the integer FORMAT side to verbatim Hare ports, completing the
round-trip whose parse half landed in fold-1, and adds the machine-word
entry points.
- u64tos: ref/hare/strconv/utos.ha:10-42. Replaces the pre-graduation
basedigit() helper with Hare's rune LUT (lut_upper/lut_lower), single
static buffer + bytes.reverse, and strings.frombytes for the
`*(&s: *str)` reinterpret (rule-9 carve-out; ww's lib/types has no
`string` struct). basedigit deleted (now dead).
- i64tos: ref/hare/strconv/itos.ha:10-32. Now `if (i >= 0) u64tos(i)`
else negate-and-prefix via `u64tos((-i): u64)`. This fixes the
i64tos-on-I64_MIN bug (cgen.ww #144): the old `n = -n; for (n > 0)`
left n at the I64_MIN bit pattern (still negative), emitting just
"-". The `(-i): u64` two's-complement reinterpret yields the true
magnitude 9223372036854775808.
- itos/utos/ztos/uptrtos: int/uint/size/uintptr 8B machine-word
wrappers (itos.ha:52, utos.ha:62/67/72), parallel to fold-1's
stoi/stou/stoz. The existing iN/uN width wrappers are unchanged.
Divergences documented at-site: no static assert; LUT-select + base
normalize via the existing basenum() (ww has no if-expression); explicit
copy loop for Hare's slice-assign.
Probes (drew PROBE-BEFORE-COMMIT, all green on BOTH stages):
- i64tos(I64_MIN) == "-9223372036854775808": cstage `ww run` exit 0 +
wwstage-compiled binary exit 0; cs==ww .s byte-identical on the real
combined (30190 lines).
- static `[0...]` fill + rune LUT static-init emit byte-identically
cross-stage (isolated smoke probe + the combined byte-id).
- frombytes (not a types::string mirror) per rule 9.
Tests: extend inttest.ww with test_u64tos[_bases] / test_i64tos[_bases]
(verbatim utos.ha:74-103 / itos.ha:54-87, flat assert sequences;
feedback_test_match_hare_source) + test_word_wrappers. I64_MIN inputs
spelled -I64_MAX-1 (proj #245: wwstage mis-lexes the 2^63 literal).
combined.ww regen: strconv is compiler-imported via fmt, so w6c +
wwdump main.combined.ww + smoke.combined.ww are regenerated.
Add the int/uint/size entry points (ref/hare/strconv/stoi.ha:53,
stou.ha:107,113). Hare clamps to types::INT_MIN/MAX, UINT_MAX, SIZE_MAX
via stoiminmax/stoumax; ww's int/uint/size are 8B machine words
(INT/UINT/SIZE limits == I64/U64 per lib/types/types.ww:30-37), so the
clamp is a no-op — the full i64/u64 range parses with no spurious
overflow. Documented at-site (the bound consts are package-private, so
inlining them would just re-encode I64/U64_MAX).
Tests: extend inttest.ww with test_stoi_stou_stoz — value path, sign,
overflow pass-through, and the no-clamp fidelity (I64_MAX/U64_MAX parse
without overflow) plus hex/bin bases through the shared parseint core.
combined.ww regen: w6c + wwdump main.combined.ww.
Port ref/hare/strconv/stou.ha:8-65 (rune_to_integer + parseint) and the
stoi64/stou64 fidelity rewrite (stoi.ha:9-17, stou.ha:70-76) over the old
digval loop. parseint is the shared sign + per-digit + multiply-overflow
core returning ((bool, u64) | invalid | overflow); stoi64/stou64 destructure
its `(sign, u)` tuple-in-union result — the shape unblocked by #242/#241.
Wins over the prior ad-hoc parse: leading '+' accepted, '-' on stou64 is
overflow (not silently dropped), wraparound overflow detection (n < old),
and the invalid payload carries the offending byte index per Hare.
Tests: lib/strconv/test/inttest.ww (run via test/wcc/922_strconv_int_run.c),
inline per-case checks mirroring Hare's assert sequences stoi.ha:56-86 /
stou.ha:116-138 (Hare's strconv int tests are flat sequences, not row
tables; feedback_test_match_hare_source). Covers valid dec/hex/oct/bin,
+/- sign, invalid+index, overflow, and U64_MAX / I64_MAX / I64_MIN
boundaries. The I64_MIN expectation is spelled -I64_MAX-1 (Hare's own
two's-complement identity) to isolate the test from #245 (wwstage mis-lexes
the literal 9223372036854775808 -> 0); the parse INPUT is unaffected and
yields the correct value on both stages.
combined.ww regen: strconv is compiler-imported (via fmt), so w6c +
wwdump main.combined.ww are regenerated.
Restore Hare's two-tier delegation: strlower/strupper alloc a buffer
then delegate to strlower_buf/strupper_buf, which fold ASCII case
into a caller-provided buffer. Too-small buffer returns nomem via the
`let nm: nomem` value form. ref/hare/ascii/string.ha:21,43.
Regen w6c/wwdump/smoke combined.ww — all three embed lib/ascii.
Port bytes::cut / bytes::rcut from ref/hare/bytes/tokenize.ha:392,413.
Both return borrowed (before, after) views split on the first / last
delimiter instance; void-case yields (whole input, empty). Needle order
is ww's (u8 | []u8), matching index/rindex (bytes.ww:57/91) rather than
Hare's ([]u8 | u8).
Unblocked by #10 (wide tuple-return / sret): ([]u8, []u8) is 48B,
over-cap, returned via sret and received by the call-site destructure
the tests exercise. combined.ww amalgamations regenerated (bytes is
compiler-imported via strings).