lib/regex: fold-2b tranche B4 — run_thread (thread-machine core)

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.
This commit is contained in:
2026-06-04 13:16:22 +09:00
parent d642017643
commit e481cb86bd
2 changed files with 233 additions and 20 deletions

View File

@@ -1,8 +1,8 @@
// regex_test — exercises the lib/regex fold-1 data model (the type
// model + finish()), the fold-2a compile() literal core, and the
// fold-2b tranche-A/B thread machine (thread/newmatch + result_free
// + strerror; delete_thread/is_consuming_inst/add_thread; run_thread
// and the exec surface are deferred — see regex.ww). Run with
// + strerror; delete_thread/is_consuming_inst/add_thread/run_thread;
// search and the exec surface are deferred — see regex.ww). Run with
// `out/bin/ww run lib/regex/regex_test.ww`.
//
// Private symbols (thread, newmatch) are reached unqualified: this
@@ -30,6 +30,7 @@
package regex;
import regex;
import io;
import os;
import strings;
@@ -544,6 +545,125 @@ fn ic_one(v: regex.inst, want: bool) void = {
if (ts3[2].start_idx != (5: size)) { fail(); };
};
// run_thread (regex.ha:589-742) driven directly over compile("ab")'s
// real program [skip, lit 'a', lit 'b', match(false)] — the arms
// fold-2a can emit. Phases: parked-skip spawn (len 1→2, parent pc
// unmoved — the unanchored-restart engine), lit advance, lit
// mismatch (failed=true AND pc still steps — ha:741 runs regardless
// of the arm's verdict), EOF on a consuming pc (failed, pc frozen),
// match arm (root_capture spans start_bytesize..str_bytesize +
// matched + `is newmatch`), and the matched-thread early return
// (ha:599-601).
@test fn run_thread_literal_program() void = {
// typed-let + match receive, the compile_literal_program shape
let c: (regex.regex | regex.error | nomem) = regex.compile("ab");
match (c) {
case let re: regex.regex => {
// skip spawn: thread 0 parks on the skip, child enters at pc 1
let ra: (rune | io.eof) = 'a';
let ts: []thread = [];
append(ts, thread { pc = 0, ... });
let r1: (void | newmatch | nomem) = run_thread(0, &re, "ab", &ts, ra, 0, 0);
if (!(r1 is void)) { fail(); };
if (len(ts) != 2) { fail(); };
if (ts[0].pc != (0: size)) { fail(); };
if (ts[1].pc != (1: size)) { fail(); };
if (ts[1].failed) { fail(); };
// lit match advances pc past 'a'
let r2: (void | newmatch | nomem) = run_thread(1, &re, "ab", &ts, ra, 0, 0);
if (!(r2 is void)) { fail(); };
if (ts[1].pc != (2: size)) { fail(); };
if (ts[1].failed) { fail(); };
// lit mismatch fails the thread; pc steps anyway (ha:741)
let rx: (rune | io.eof) = 'x';
let r3: (void | newmatch | nomem) = run_thread(1, &re, "ab", &ts, rx, 1, 1);
if (!(r3 is void)) { fail(); };
if (!ts[1].failed) { fail(); };
if (ts[1].pc != (3: size)) { fail(); };
// EOF on a consuming pc fails the thread before pc steps
let ev: io.eof;
let reof: (rune | io.eof) = ev;
let ts2: []thread = [];
append(ts2, thread { pc = 1, ... });
let r4: (void | newmatch | nomem) = run_thread(0, &re, "ab", &ts2, reof, 2, 2);
if (!(r4 is void)) { fail(); };
if (!ts2[0].failed) { fail(); };
if (ts2[0].pc != (1: size)) { fail(); };
// match arm: root_capture spans start_bytesize..str_bytesize,
// matched set, newmatch returned
let ts3: []thread = [];
append(ts3, thread { pc = 3, ... });
let r5: (void | newmatch | nomem) = run_thread(0, &re, "ab", &ts3, reof, 2, 2);
if (!(r5 is newmatch)) { fail(); };
if (!ts3[0].matched) { fail(); };
if (ts3[0].failed) { fail(); };
if (ts3[0].root_capture.start != (0: size)) { fail(); };
if (ts3[0].root_capture.start_bytesize != (0: size)) { fail(); };
if (ts3[0].root_capture.end != (2: size)) { fail(); };
if (ts3[0].root_capture.end_bytesize != (2: size)) { fail(); };
if (strings.compare(ts3[0].root_capture.content, "ab") != 0) { fail(); };
// an already-matched thread is inert (ha:599-601): void
// return, state untouched
let r6: (void | newmatch | nomem) = run_thread(0, &re, "ab", &ts3, ra, 3, 3);
if (!(r6 is void)) { fail(); };
if (ts3[0].root_capture.end != (2: size)) { fail(); };
// idx/bytesize split: every all-ASCII row has idx ==
// bytesize, so a port swapping start/start_bytesize (or
// end/end_bytesize) in root_capture passes them. One 2-byte
// rune ('ß') consumed before the match start makes all four
// values distinct: start=1 start_bytesize=2 end=3
// end_bytesize=4; content = bytes[2:4] = "ab".
let ts4: []thread = [];
append(ts4, thread { pc = 3, start_idx = 1, start_bytesize = 2, ... });
let r7: (void | newmatch | nomem) = run_thread(0, &re, "ßab", &ts4, reof, 3, 4);
if (!(r7 is newmatch)) { fail(); };
if (ts4[0].root_capture.start != (1: size)) { fail(); };
if (ts4[0].root_capture.start_bytesize != (2: size)) { fail(); };
if (ts4[0].root_capture.end != (3: size)) { fail(); };
if (ts4[0].root_capture.end_bytesize != (4: size)) { fail(); };
if (strings.compare(ts4[0].root_capture.content, "ab") != 0) { fail(); };
regex.finish(&re);
};
case => fail();
};
};
// The anchored route (ha:621-624) needs a (true: inst_match) program
// — compile() can't emit `$` yet, so it is HAND-BUILT — pinned from
// both sides: anchored + string-not-exhausted fails the thread;
// anchored + EOF falls through to the match (empty content).
@test fn run_thread_anchored_route() void = {
let insts: []regex.inst = [];
append(insts, (true: regex.inst_match));
let re: regex.regex;
re.insts = insts;
re.n_reps = 0;
let ra: (rune | io.eof) = 'a';
let ts: []thread = [];
append(ts, thread { pc = 0, ... });
let r1: (void | newmatch | nomem) = run_thread(0, &re, "ab", &ts, ra, 0, 0);
if (!(r1 is void)) { fail(); };
if (!ts[0].failed) { fail(); };
if (ts[0].matched) { fail(); };
let ev: io.eof;
let reof: (rune | io.eof) = ev;
let ts2: []thread = [];
append(ts2, thread { pc = 0, ... });
let r2: (void | newmatch | nomem) = run_thread(0, &re, "", &ts2, reof, 0, 0);
if (!(r2 is newmatch)) { fail(); };
if (!ts2[0].matched) { fail(); };
if (ts2[0].root_capture.content.len != 0) { fail(); };
if (ts2[0].root_capture.end != (0: size)) { fail(); };
};
export fn main() i32 = {
signalled = 1; lit_and_match();
signalled = 2; size_aliases_distinct();
@@ -562,5 +682,7 @@ export fn main() i32 = {
signalled = 15; is_consuming_kinds();
signalled = 16; delete_thread_middle();
signalled = 17; add_thread_dedup_inherit();
signalled = 18; run_thread_literal_program();
signalled = 19; run_thread_anchored_route();
return 0;
};