lib/regex: fold-2b tranche B1-B3 — thread-machine leaf fns (run_thread held on #40)
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.
This commit is contained in:
@@ -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 thread-machine scaffolding (thread/newmatch +
|
||||
// result_free + strerror; the engine fns are deferred behind
|
||||
// compiler fixes #15/#17/#19 — see regex.ww). Run with
|
||||
// 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
|
||||
// `out/bin/ww run lib/regex/regex_test.ww`.
|
||||
//
|
||||
// Private symbols (thread, newmatch) are reached unqualified: this
|
||||
@@ -428,6 +428,122 @@ type nmexp = struct {
|
||||
};
|
||||
};
|
||||
|
||||
// is_consuming_inst must discriminate the three consuming kinds from
|
||||
// the seven non-consuming ones across all 10 inst variants
|
||||
// (regex.ha:553-555) — the tranche-A-deferred row, graduated by the
|
||||
// #19 >48B by-value arg wiring. Sequential typed-let + helper calls,
|
||||
// not a [10](inst, bool) table: tagged-element array literals
|
||||
// under-copy (#12), and a cast/literal rvalue arg source is
|
||||
// #38b-unwired, so each value goes through a typed let (the
|
||||
// #19-landed ident source).
|
||||
fn ic_one(v: regex.inst, want: bool) void = {
|
||||
if (is_consuming_inst(v) != want) { fail(); };
|
||||
};
|
||||
|
||||
@test fn is_consuming_kinds() void = {
|
||||
let lit: regex.inst = ('a': regex.inst_lit);
|
||||
ic_one(lit, true);
|
||||
let av: regex.inst_any;
|
||||
let any: regex.inst = av;
|
||||
ic_one(any, true);
|
||||
let cs: regex.inst = (inst_charset { idx = 0, is_positive = true });
|
||||
ic_one(cs, true);
|
||||
let kv: regex.inst_skip;
|
||||
let sk: regex.inst = kv;
|
||||
ic_one(sk, false);
|
||||
let sp: regex.inst = ((5: size): regex.inst_split);
|
||||
ic_one(sp, false);
|
||||
let jm: regex.inst = ((6: size): regex.inst_jump);
|
||||
ic_one(jm, false);
|
||||
let mt: regex.inst = (false: regex.inst_match);
|
||||
ic_one(mt, false);
|
||||
let gs: regex.inst = ((2: size): regex.inst_groupstart);
|
||||
ic_one(gs, false);
|
||||
let gv: regex.inst_groupend;
|
||||
let ge: regex.inst = gv;
|
||||
ic_one(ge, false);
|
||||
let rp: regex.inst = (inst_repeat {
|
||||
id = 1, origin = 4, min = (2: size), max = void,
|
||||
});
|
||||
ic_one(rp, false);
|
||||
};
|
||||
|
||||
// delete_thread (regex.ha:547-551) removes exactly the indexed
|
||||
// element and preserves order; its frees are no-ops (no-free
|
||||
// runtime), so the survivors' capture headers stay readable.
|
||||
@test fn delete_thread_middle() void = {
|
||||
let caps: []regex.capture = [];
|
||||
append(caps, capture {
|
||||
content = "x", start = 0, start_bytesize = 0,
|
||||
end = 1, end_bytesize = 1,
|
||||
});
|
||||
let ts: []thread = [];
|
||||
append(ts, thread { pc = 1, start_idx = 11, captures = caps, ... });
|
||||
append(ts, thread { pc = 2, start_idx = 22, ... });
|
||||
append(ts, thread { pc = 3, start_idx = 33, ... });
|
||||
delete_thread(1, &ts);
|
||||
if (len(ts) != 2) { fail(); };
|
||||
if (ts[0].pc != (1: size)) { fail(); };
|
||||
if (ts[0].start_idx != (11: size)) { fail(); };
|
||||
if (ts[0].captures.len != 1) { fail(); };
|
||||
if (ts[1].pc != (3: size)) { fail(); };
|
||||
if (ts[1].start_idx != (33: size)) { fail(); };
|
||||
if (ts[1].captures.len != 0) { fail(); };
|
||||
// boundary rows: delete at the last index, then at index 0 down
|
||||
// to empty — the failed-sweep loop (regex.ha:891-896) deletes at
|
||||
// every position including both ends.
|
||||
delete_thread(1, &ts);
|
||||
if (len(ts) != 1) { fail(); };
|
||||
if (ts[0].pc != (1: size)) { fail(); };
|
||||
delete_thread(0, &ts);
|
||||
if (len(ts) != 0) { fail(); };
|
||||
};
|
||||
|
||||
// add_thread (regex.ha:557-587): same-pc dedup suppression fires only
|
||||
// when the existing thread is unmatched AND started strictly earlier
|
||||
// than the parent (ha:561-565); otherwise the child appends,
|
||||
// inheriting the parent's start/matched/failed with fresh empty
|
||||
// capture/rep_counter headers and a zeroed root_capture. The
|
||||
// capture-dup loud bound must NOT fire on these empty-caps parents.
|
||||
@test fn add_thread_dedup_inherit() void = {
|
||||
let ts: []thread = [];
|
||||
append(ts, thread { pc = 0, start_idx = 5, start_bytesize = 4,
|
||||
matched = false, failed = true, ... });
|
||||
// inherit: fresh pc, parent fields copied, rest zeroed
|
||||
let r: (void | nomem) = add_thread(&ts, 0, 7);
|
||||
if (!(r is void)) { fail(); };
|
||||
if (len(ts) != 2) { fail(); };
|
||||
if (ts[1].pc != (7: size)) { fail(); };
|
||||
if (ts[1].start_idx != (5: size)) { fail(); };
|
||||
if (ts[1].start_bytesize != (4: size)) { fail(); };
|
||||
if (ts[1].matched) { fail(); };
|
||||
if (!ts[1].failed) { fail(); };
|
||||
if (ts[1].captures.len != 0) { fail(); };
|
||||
if (ts[1].rep_counters.len != 0) { fail(); };
|
||||
if (ts[1].root_capture.content.len != 0) { fail(); };
|
||||
if (ts[1].root_capture.end != (0: size)) { fail(); };
|
||||
// same-pc same-start does NOT suppress (strict <, ha:563-565)
|
||||
let r2: (void | nomem) = add_thread(&ts, 0, 7);
|
||||
if (!(r2 is void)) { fail(); };
|
||||
if (len(ts) != 3) { fail(); };
|
||||
// an earlier-started unmatched existing thread DOES suppress
|
||||
let ts2: []thread = [];
|
||||
append(ts2, thread { pc = 0, start_idx = 5, ... });
|
||||
append(ts2, thread { pc = 7, start_idx = 2, ... });
|
||||
let r3: (void | nomem) = add_thread(&ts2, 0, 7);
|
||||
if (!(r3 is void)) { fail(); };
|
||||
if (len(ts2) != 2) { fail(); };
|
||||
// a MATCHED existing thread never suppresses
|
||||
let ts3: []thread = [];
|
||||
append(ts3, thread { pc = 0, start_idx = 5, ... });
|
||||
append(ts3, thread { pc = 7, start_idx = 2, matched = true, ... });
|
||||
let r4: (void | nomem) = add_thread(&ts3, 0, 7);
|
||||
if (!(r4 is void)) { fail(); };
|
||||
if (len(ts3) != 3) { fail(); };
|
||||
if (ts3[2].pc != (7: size)) { fail(); };
|
||||
if (ts3[2].start_idx != (5: size)) { fail(); };
|
||||
};
|
||||
|
||||
export fn main() i32 = {
|
||||
signalled = 1; lit_and_match();
|
||||
signalled = 2; size_aliases_distinct();
|
||||
@@ -443,5 +559,8 @@ export fn main() i32 = {
|
||||
signalled = 12; newmatch_discriminates();
|
||||
signalled = 13; result_free_noop();
|
||||
signalled = 14; strerror_identity();
|
||||
signalled = 15; is_consuming_kinds();
|
||||
signalled = 16; delete_thread_middle();
|
||||
signalled = 17; add_thread_dedup_inherit();
|
||||
return 0;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user