Files
ww/lib/regex/regex_test.ww
Hojun-Cho da48b29a7c lib/regex: fold-3 — anchors, escape, postfix ?/*/+, alternation
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.
2026-06-04 17:25:05 +09:00

1388 lines
49 KiB
Plaintext

// regex_test — exercises the lib/regex fold-1 data model (the type
// model + finish()), the fold-2a compile() literal core, the
// fold-2b tranche-A/B thread machine (thread/newmatch + result_free
// + strerror; delete_thread/is_consuming_inst/add_thread/run_thread),
// the tranche-C search end-to-end matches, the tranche-D exec
// surface (test/find), and the fold-2c findall/result_freeall. Run
// with `out/bin/ww run lib/regex/regex_test.ww`.
//
// Private symbols (thread, newmatch) are reached unqualified: this
// file declares `package regex`, so the import unifies it with the
// lib sources (the decimaltest precedent,
// lib/strconv/test/decimaltest.ww).
//
// Fold 2a ports compile()'s lit/any/match arms only; exec lives in
// later folds, so the compile_* cases pin the emitted inst PROGRAM
// (shape + payloads via indexed match-extraction), not matching.
// charclass_map's fn-ptr table is deferred behind the array→slice
// element-coercion checker gap (see regex.ww), so this test does not
// exercise the POSIX-class predicate dispatch yet — it pins variant
// discrimination (including the nominally-distinct same-underlying
// inst_split/inst_jump/inst_groupstart `size` aliases and the
// inst_any/inst_skip/inst_groupend `void` aliases), payload extraction,
// the regex/capture struct shapes, and finish(). Same
// signalled-then-fail()-with-+10 pattern as the rest of the stdlib
// run-tests; the non-zero exit pinpoints the failing case.
//
// Struct literals below name the type UNQUALIFIED (`inst_charset { … }`,
// not `regex.inst_charset { … }`): the parser rejects a module-qualified
// name in struct-literal position (#29), and the imported type
// is in scope unqualified.
package regex;
import regex;
import io;
import memio;
import os;
import strings;
let signalled: i32 = 0;
fn fail() void = { os.exit(signalled + 10); };
// inst_lit / inst_match carry distinguishable payloads (rune / bool).
@test fn lit_and_match() void = {
let a: regex.inst = ('a': regex.inst_lit);
match (a) {
case let l: regex.inst_lit => { if ((l: rune) != 'a') { fail(); }; };
case => fail();
};
let m: regex.inst = (true: regex.inst_match);
match (m) {
case let b: regex.inst_match => { if (!(b: bool)) { fail(); }; };
case => fail();
};
};
// The three `size`-aliased variants are nominally distinct: a value
// built as inst_split must match inst_split, never inst_jump /
// inst_groupstart, despite identical underlying storage.
@test fn size_aliases_distinct() void = {
let sp: regex.inst = ((5: size): regex.inst_split);
match (sp) {
case let s: regex.inst_split => { if ((s: size) != (5: size)) { fail(); }; };
case let j: regex.inst_jump => fail();
case let g: regex.inst_groupstart => fail();
case => fail();
};
let jp: regex.inst = ((9: size): regex.inst_jump);
match (jp) {
case let j: regex.inst_jump => { if ((j: size) != (9: size)) { fail(); }; };
case let s: regex.inst_split => fail();
case => fail();
};
let gs: regex.inst = ((2: size): regex.inst_groupstart);
match (gs) {
case let g: regex.inst_groupstart => { if ((g: size) != (2: size)) { fail(); }; };
case let s: regex.inst_split => fail();
case => fail();
};
};
// The `void`-aliased variants are likewise nominally distinct.
@test fn void_aliases_distinct() void = {
let av: regex.inst_any;
let an: regex.inst = av;
match (an) {
case let a: regex.inst_any => void;
case let k: regex.inst_skip => fail();
case let e: regex.inst_groupend => fail();
case => fail();
};
let sv: regex.inst_skip;
let sk: regex.inst = sv;
match (sk) {
case let k: regex.inst_skip => void;
case let a: regex.inst_any => fail();
case => fail();
};
let gv: regex.inst_groupend;
let ge: regex.inst = gv;
match (ge) {
case let e: regex.inst_groupend => void;
case let a: regex.inst_any => fail();
case let k: regex.inst_skip => fail();
case => fail();
};
};
// inst_charset carries a struct payload; its fields survive the union
// round-trip.
@test fn charset_payload() void = {
let c: regex.inst = (inst_charset { idx = 3, is_positive = true });
match (c) {
case let cs: regex.inst_charset => {
if (cs.idx != (3: size)) { fail(); };
if (!cs.is_positive) { fail(); };
};
case => fail();
};
};
// inst_repeat round-trips through the inst union with its plain `size`
// fields intact. Matching the nested (void | size) min/max bounds back
// out is DEFERRED: `match` on a tagged-union-typed struct field
// diverges cs≠ww (#26 — the wwstage frames it wider), so
// asserting the bounds here would seed a rule-10-divergent fixture.
@test fn repeat_payload() void = {
let r: regex.inst = (inst_repeat {
id = 1, origin = 4, min = (2: size), max = void,
});
match (r) {
case let rp: regex.inst_repeat => {
if (rp.id != (1: size)) { fail(); };
if (rp.origin != (4: size)) { fail(); };
};
case => fail();
};
};
// The regex/capture structs hold their fields; finish() is a no-op
// (no-free runtime) and must accept a built regex.
@test fn struct_shapes_and_finish() void = {
let cap: regex.capture = capture {
content = "abc",
start = 0,
start_bytesize = 0,
end = 3,
end_bytesize = 3,
};
if (cap.content.len != 3) { fail(); };
if (cap.end != (3: size)) { fail(); };
// regex's insts/charsets ([]inst / []charset) are left empty here:
// fold 1 ports no compile() to populate them, an empty `[]` literal
// is unspellable as a typed slice (#25 — array→slice
// element-coercion gap), and a struct-literal slice-field store
// drops len/cap (#24). Declaring the regex zeroes both
// slice headers to {0,0,0}; only n_reps is set explicitly.
let re: regex.regex;
re.n_reps = 0;
if (re.n_reps != (0: size)) { fail(); };
if (re.insts.len != 0) { fail(); };
regex.finish(&re);
};
// compile("abc") emits the 5-inst literal program: the leading
// unanchored inst_skip (regex.ha:261-263), one inst_lit per rune, the
// epilogue inst_match(false) (ha:475-477). compile()'s
// (regex | error | nomem) return is the first >24B tagged payload in
// the tree — the receive shapes here double as #38 sret consumers
// (typed-let + match here; scrutinee-direct in compile_empty_program).
@test fn compile_literal_program() void = {
let c: (regex.regex | regex.error | nomem) = regex.compile("abc");
match (c) {
case let re: regex.regex => {
if (re.insts.len != 5) { fail(); };
match (re.insts[0]) {
case let k: regex.inst_skip => void;
case => fail();
};
match (re.insts[1]) {
case let l: regex.inst_lit => { if ((l: rune) != 'a') { fail(); }; };
case => fail();
};
match (re.insts[2]) {
case let l: regex.inst_lit => { if ((l: rune) != 'b') { fail(); }; };
case => fail();
};
match (re.insts[3]) {
case let l: regex.inst_lit => { if ((l: rune) != 'c') { fail(); }; };
case => fail();
};
match (re.insts[4]) {
case let m: regex.inst_match => { if ((m: bool)) { fail(); }; };
case => fail();
};
if (re.charsets.len != 0) { fail(); };
if (re.n_reps != (0: size)) { fail(); };
regex.finish(&re);
};
case => fail();
};
};
// '.' compiles to inst_any between the literals (regex.ha:460-461):
// [skip, lit 'a', any, lit 'c', match(false)].
@test fn compile_any_program() void = {
let c: (regex.regex | regex.error | nomem) = regex.compile("a.c");
match (c) {
case let re: regex.regex => {
if (re.insts.len != 5) { fail(); };
match (re.insts[0]) {
case let k: regex.inst_skip => void;
case => fail();
};
match (re.insts[1]) {
case let l: regex.inst_lit => { if ((l: rune) != 'a') { fail(); }; };
case => fail();
};
match (re.insts[2]) {
case let a: regex.inst_any => void;
case => fail();
};
match (re.insts[3]) {
case let l: regex.inst_lit => { if ((l: rune) != 'c') { fail(); }; };
case => fail();
};
match (re.insts[4]) {
case let m: regex.inst_match => { if ((m: bool)) { fail(); }; };
case => fail();
};
regex.finish(&re);
};
case => fail();
};
};
// compile("") is exactly [inst_match(false)]: the leading skip must
// not fire on immediate done (regex.ha:261 gates on `next is rune`),
// and the epilogue guard must fire on the empty program.
@test fn compile_empty_program() void = {
match (regex.compile("")) {
case let re: regex.regex => {
if (re.insts.len != 1) { fail(); };
match (re.insts[0]) {
case let m: regex.inst_match => { if ((m: bool)) { fail(); }; };
case => fail();
};
regex.finish(&re);
};
case => fail();
};
};
// Every deferred metacharacter is a LOUD error carrying the exact
// fold-boundary text — falling through to the literal default would
// silently compile a wrong program, and any OTHER error text would
// mean an arm was half-ported. One pattern per deferred arm so the
// fold that ports an arm consciously deletes its row. Fold 3 flipped
// \ ^ $ | ? * + positive (fold3_* below); the bracket/group/
// repetition four remain.
@test fn compile_metachar_loud() void = {
let pats: [4]str = [
"a[", "a(", "a)", "a{",
];
let i: i32 = 0;
for (i < len(pats)) {
match (regex.compile(pats[i])) {
case let e: regex.error => {
if (strings.compare((e: str),
"regex: metacharacter not yet ported") != 0) {
fail();
};
};
case => fail();
};
i += 1;
};
};
// The thread struct (regex.ha:55-64) is in tree ahead of its engine
// consumers so the pending #15/#17 fix probes exercise the real type.
// Pin the field layout via the P6-proven wide-literal append + a
// depth-1 read-back row per appended thread; the Hare `...` partial
// fill (P5) must zero everything the second row's literal omits.
// root_capture has NO row: every read route into it is
// compiler-blocked today — the depth-2 chain behind the index links
// the field as a global (#6 F4), the element let-copy is #7 F5, and
// a probed `&threads[i].root_capture` deref segfaults byte-id on
// both stages — so its row lands with those fixes.
type texp = struct {
pc: size,
start_idx: size,
start_bytesize: size,
matched: bool,
failed: bool,
// .len reads as i32 (check.c:1239), so the count columns match it
ncaps: i32,
nreps: i32,
};
@test fn thread_shape() void = {
let rc: capture = capture {
content = "ab", start = 1, start_bytesize = 1,
end = 2, end_bytesize = 2,
};
let pcaps: []capture = [];
append(pcaps, rc);
let prep: []size = [];
append(prep, (7: size));
let threads: []thread = [];
append(threads, thread {
pc = 5,
start_idx = 6,
start_bytesize = 7,
root_capture = rc,
captures = pcaps,
rep_counters = prep,
matched = false,
failed = true,
});
append(threads, thread { pc = 9, ... });
let want: [2]texp = [
texp { pc = 5, start_idx = 6, start_bytesize = 7,
matched = false, failed = true,
ncaps = 1, nreps = 1 },
texp { pc = 9, start_idx = 0, start_bytesize = 0,
matched = false, failed = false,
ncaps = 0, nreps = 0 },
];
if (len(threads) != len(want)) { fail(); };
let i: i32 = 0;
for (i < len(want)) {
if (threads[i].pc != want[i].pc) { fail(); };
if (threads[i].start_idx != want[i].start_idx) { fail(); };
if (threads[i].start_bytesize != want[i].start_bytesize) {
fail();
};
if (threads[i].matched != want[i].matched) { fail(); };
if (threads[i].failed != want[i].failed) { fail(); };
if (threads[i].captures.len != want[i].ncaps) { fail(); };
if (threads[i].rep_counters.len != want[i].nreps) { fail(); };
i += 1;
};
};
// newmatch (regex.ha:66) must discriminate nominally against plain
// void — and against nomem, the third payload-free member — across
// run_thread's (void | newmatch | nomem) return boundary: the P8
// shape on the real lib type, one row per returned member.
fn nm_probe(x: i32) (void | newmatch | nomem) = {
if (x == 1) {
let nm: newmatch;
return nm;
};
if (x == 2) {
let n: nomem;
return n;
};
return;
};
type nmexp = struct {
arg: i32,
want_nm: bool,
want_void: bool,
want_nomem: bool,
};
@test fn newmatch_discriminates() void = {
let rows: [3]nmexp = [
nmexp { arg = 1, want_nm = true, want_void = false,
want_nomem = false },
nmexp { arg = 0, want_nm = false, want_void = true,
want_nomem = false },
nmexp { arg = 2, want_nm = false, want_void = false,
want_nomem = true },
];
let i: i32 = 0;
for (i < len(rows)) {
let r: (void | newmatch | nomem) = nm_probe(rows[i].arg);
if ((r is newmatch) != rows[i].want_nm) { fail(); };
if ((r is void) != rows[i].want_void) { fail(); };
if ((r is nomem) != rows[i].want_nomem) { fail(); };
i += 1;
};
};
// result_free (regex.ha:1114-1116) accepts a built result; free() is
// the documented no-op (no-free runtime), so the header must stay
// readable after — a future real free changes this row consciously.
// The local is spelled []regex.capture, not the regex.result alias:
// wwstage falsely loud-bails appending a struct literal onto an
// alias-typed dst (#20); the alias + signature stay exercised by the
// result_free call itself. Reverts to `regex.result` when #20 lands.
@test fn result_free_noop() void = {
let res: []regex.capture;
append(res, capture {
content = "x", start = 0, start_bytesize = 0,
end = 1, end_bytesize = 1,
});
regex.result_free(res);
if (len(res) != 1) { fail(); };
if (res[0].end != (1: size)) { fail(); };
// The zero-header edge: find()'s no-match path returns an empty
// result (regex.ha:915-916) the caller still result_free()s. The
// bare decl is alias-typed — the #20 dodge above is append-only,
// so the alias stays exercised in value position here.
let empty: regex.result;
regex.result_free(empty);
if (len(empty) != 0) { fail(); };
};
// strerror (regex.ha:1127) is identity on the boundary text — routed
// through a REAL compile() error, completing the exported error
// surface end to end.
@test fn strerror_identity() void = {
match (regex.compile("a[")) {
case let e: regex.error => {
if (strings.compare(regex.strerror(e),
"regex: metacharacter not yet ported") != 0) {
fail();
};
};
case => fail();
};
};
// 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(); };
};
// 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(); };
};
// search (regex.ha:746-898) driven DIRECTLY (private fn, package-regex
// test) over memio-backed streams — the exec surface (test/find) is
// tranche D. Each match row pins the root capture's four indices plus
// content; the multibyte row keeps idx != bytesize honest. Rows share
// (expr, input, need_captures, want) shape — the P12 struct-row table.
type scase = struct {
expr: str,
input: str,
nc: bool,
start: size,
sb: size,
end: size,
eb: size,
content: str,
};
@test fn search_matches() void = {
let rows: [6]scase = [
// full match mid-string: skip-respawn + dispatch +
// all_matched exit
scase { expr = "ab", input = "xab", nc = true,
start = 1, sb = 1, end = 3, eb = 3, content = "ab" },
// mismatch-restart: the idx-0 child fails and is swept; the
// restarted thread wins (failed-sweep interplay)
scase { expr = "bcd", input = "abcd", nc = true,
start = 1, sb = 1, end = 4, eb = 4, content = "bcd" },
// leftmost-longest best-pick + first_match_idx trim
scase { expr = "aa", input = "aaa", nc = true,
start = 0, sb = 0, end = 2, eb = 2, content = "aa" },
// zero-length: the all_matched path with matchlen 0 must
// NOT take the need_captures=false early-exit (ha:845
// requires matchlen > 0) — hence nc=false expecting the
// FULL one-capture result, not the empty early-exit slice
scase { expr = "", input = "", nc = false,
start = 0, sb = 0, end = 0, eb = 0, content = "" },
// multibyte: the 2-byte ß before the match start splits
// every idx from its bytesize; inst_any consumes 'x'
scase { expr = "b.d", input = "aßbxd", nc = true,
start = 2, sb = 3, end = 5, eb = 6, content = "bxd" },
// dedup-heavy: same-pc threads spawn on every step across
// >=3 passes (ha:872-889); the pick must stay stable.
// Result stability is the only external pin available this
// fold: 2a programs are all fixed-length, every match ties
// on match_len, and best-pick's insertion-order tiebreak
// alone yields leftmost — so the dedup sweep and the
// leftmost trim are result-invisible (mutation-verified:
// disabling either still passes this table; disabling the
// failed sweep hangs). Both turn result- and
// termination-visible with the split/star fold.
scase { expr = "aa", input = "aaaa", nc = true,
start = 0, sb = 0, end = 2, eb = 2, content = "aa" },
];
let i: i32 = 0;
for (i < len(rows)) {
let ex: str = rows[i].expr;
let inp: str = rows[i].input;
let c: (regex.regex | regex.error | nomem) = regex.compile(ex);
match (c) {
case let re: regex.regex => {
let strm: memio.stream =
memio.fixed(strings.toutf8(inp));
let r: (void | []capture | nomem) =
search(&re, inp, &strm.vt, rows[i].nc);
if (!(r is []capture)) { fail(); };
let caps: []capture = r as []capture;
if (len(caps) != 1) { fail(); };
if (caps[0].start != rows[i].start) { fail(); };
if (caps[0].start_bytesize != rows[i].sb) { fail(); };
if (caps[0].end != rows[i].end) { fail(); };
if (caps[0].end_bytesize != rows[i].eb) { fail(); };
let wc: str = rows[i].content;
if (strings.compare(caps[0].content, wc) != 0) {
fail();
};
regex.result_free(caps);
regex.finish(&re);
};
case => fail();
};
i += 1;
};
};
// ha:845-847: a non-zero-length newmatch with need_captures=false
// returns the empty result immediately, skipping the best-pick pass.
@test fn search_early_exit() void = {
let c: (regex.regex | regex.error | nomem) = regex.compile("ab");
match (c) {
case let re: regex.regex => {
let strm: memio.stream = memio.fixed(strings.toutf8("xab"));
let r: (void | []capture | nomem) =
search(&re, "xab", &strm.vt, false);
if (!(r is []capture)) { fail(); };
let caps: []capture = r as []capture;
if (len(caps) != 0) { fail(); };
regex.result_free(caps);
regex.finish(&re);
};
case => fail();
};
};
// void rows: no match anywhere ("ab" over "xyz" — every thread fails,
// the list drains, ha:777-779) and EOF mid-pattern ("ab" over "a" —
// the consuming-inst EOF fail).
@test fn search_no_match() void = {
let c: (regex.regex | regex.error | nomem) = regex.compile("ab");
match (c) {
case let re: regex.regex => {
let strm: memio.stream = memio.fixed(strings.toutf8("xyz"));
let r: (void | []capture | nomem) =
search(&re, "xyz", &strm.vt, true);
if (!(r is void)) { fail(); };
let strm2: memio.stream = memio.fixed(strings.toutf8("a"));
let r2: (void | []capture | nomem) =
search(&re, "a", &strm2.vt, true);
if (!(r2 is void)) { fail(); };
regex.finish(&re);
};
case => fail();
};
};
// test() (regex.ha:901-904) — the exported boolean surface over the
// same inputs the search table pins, plus the two void rows.
type tcase = struct {
expr: str,
input: str,
want: bool,
};
@test fn test_matches() void = {
let rows: [8]tcase = [
tcase { expr = "ab", input = "xab", want = true },
tcase { expr = "bcd", input = "abcd", want = true },
tcase { expr = "aa", input = "aaa", want = true },
tcase { expr = "", input = "", want = true },
tcase { expr = "b.d", input = "aßbxd", want = true },
tcase { expr = "aa", input = "aaaa", want = true },
tcase { expr = "ab", input = "xyz", want = false },
tcase { expr = "ab", input = "a", want = false },
];
let i: i32 = 0;
for (i < len(rows)) {
let ex: str = rows[i].expr;
let inp: str = rows[i].input;
let c: (regex.regex | regex.error | nomem) = regex.compile(ex);
match (c) {
case let re: regex.regex => {
let tr: (bool | nomem) = regex.test(&re, inp);
if (!(tr is bool)) { fail(); };
if ((tr as bool) != rows[i].want) { fail(); };
regex.finish(&re);
};
case => fail();
};
i += 1;
};
};
// find() (regex.ha:910-918) — the exported result surface: match rows
// reuse the search table's expectations; no-match rows return the
// empty result (ha:916) the caller still result_frees. Every row also
// cross-pins test() == (find() matched).
type fcase = struct {
expr: str,
input: str,
matches: bool,
start: size,
sb: size,
end: size,
eb: size,
content: str,
};
@test fn find_cases() void = {
let rows: [8]fcase = [
fcase { expr = "ab", input = "xab", matches = true,
start = 1, sb = 1, end = 3, eb = 3, content = "ab" },
fcase { expr = "bcd", input = "abcd", matches = true,
start = 1, sb = 1, end = 4, eb = 4, content = "bcd" },
fcase { expr = "aa", input = "aaa", matches = true,
start = 0, sb = 0, end = 2, eb = 2, content = "aa" },
fcase { expr = "", input = "", matches = true,
start = 0, sb = 0, end = 0, eb = 0, content = "" },
fcase { expr = "b.d", input = "aßbxd", matches = true,
start = 2, sb = 3, end = 5, eb = 6, content = "bxd" },
fcase { expr = "aa", input = "aaaa", matches = true,
start = 0, sb = 0, end = 2, eb = 2, content = "aa" },
fcase { expr = "ab", input = "xyz", matches = false, ... },
fcase { expr = "ab", input = "a", matches = false, ... },
];
let i: i32 = 0;
for (i < len(rows)) {
let ex: str = rows[i].expr;
let inp: str = rows[i].input;
let c: (regex.regex | regex.error | nomem) = regex.compile(ex);
match (c) {
case let re: regex.regex => {
let fr: (regex.result | nomem) = regex.find(&re, inp);
if (!(fr is regex.result)) { fail(); };
let res: regex.result = fr as regex.result;
if (rows[i].matches) {
if (len(res) != 1) { fail(); };
if (res[0].start != rows[i].start) { fail(); };
if (res[0].start_bytesize != rows[i].sb) {
fail();
};
if (res[0].end != rows[i].end) { fail(); };
if (res[0].end_bytesize != rows[i].eb) {
fail();
};
let wc: str = rows[i].content;
if (strings.compare(res[0].content, wc) != 0) {
fail();
};
} else {
if (len(res) != 0) { fail(); };
};
// the two surfaces share search; pin their
// agreement so an arm-swap in either D13 match
// can't hide behind a one-sided table
let tr: (bool | nomem) = regex.test(&re, inp);
if (!(tr is bool)) { fail(); };
if ((tr as bool) != (len(res) != 0)) { fail(); };
regex.result_free(res);
regex.finish(&re);
};
case => fail();
};
i += 1;
};
};
// findall() (regex.ha:923-960) content/count rows ported from Hare's
// OWN findall table (+test.ha:719-731) via run_findall_case's checks
// (+test.ha:102-130: result count + results[i][0].content), restricted
// to the rows fold-2a can compile (the fo{2,} / a* rows ride the
// repeat/star folds). Variable-length expectations live in a flat
// targets pool indexed by per-row (toff, tcnt).
type facase = struct {
expr: str,
input: str,
toff: i32,
tcnt: i32,
};
@test fn findall_content() void = {
let targets: [9]str = [
"abc", "abあ", "abq",
"a", "a",
"", "", "", "",
];
let rows: [3]facase = [
// multi-match + inst_any over the 3-byte あ
facase { expr = "ab.",
input = "hello abc and abあ test abq thanks",
toff = 0, tcnt = 3 },
// adjacent single-rune matches
facase { expr = "a", input = "aa", toff = 3, tcnt = 2 },
// zero-length: one empty match per position INCLUDING
// end-of-string (the ha:942-945 break appends first)
facase { expr = "", input = "abc", toff = 5, tcnt = 4 },
];
let i: i32 = 0;
for (i < len(rows)) {
let ex: str = rows[i].expr;
let inp: str = rows[i].input;
let c: (regex.regex | regex.error | nomem) = regex.compile(ex);
match (c) {
case let re: regex.regex => {
let fr: ([]regex.result | nomem) =
regex.findall(&re, inp);
if (!(fr is []regex.result)) { fail(); };
let results: []regex.result = fr as []regex.result;
if (len(results) != rows[i].tcnt) { fail(); };
let k: i32 = 0;
for (k < rows[i].tcnt) {
let want: str = targets[rows[i].toff + k];
if (strings.compare(results[k][0].content,
want) != 0) {
fail();
};
k += 1;
};
regex.result_freeall(results);
regex.finish(&re);
};
case => fail();
};
i += 1;
};
};
// findall() field rows: every capture index plus content per result,
// against a flat expectation pool. Pins adjacency (non-overlap), the
// one-result overlap pick, the multibyte zero-length advancement
// (utf8sz step != 1 splits idx from bytesize), the tail-match break,
// and the empty no-match slice. result_freeall on every row.
type fdcase = struct {
expr: str,
input: str,
eoff: i32,
ecnt: i32,
};
type fdexp = struct {
start: size,
sb: size,
end: size,
eb: size,
content: str,
};
@test fn findall_fields() void = {
let exp: [12]fdexp = [
// ("ab", "abxab")
fdexp { start = 0, sb = 0, end = 2, eb = 2, content = "ab" },
fdexp { start = 3, sb = 3, end = 5, eb = 5, content = "ab" },
// ("ab", "abab") — adjacent, non-overlapping
fdexp { start = 0, sb = 0, end = 2, eb = 2, content = "ab" },
fdexp { start = 2, sb = 2, end = 4, eb = 4, content = "ab" },
// ("aa", "aaa") — ONE result: leftmost-longest then
// advance-past; findall must not re-enter mid-match
fdexp { start = 0, sb = 0, end = 2, eb = 2, content = "aa" },
// ("", "ßx") — zero-length advancement over a 2-byte rune:
// bytesize steps 0→2→3 while idx steps 0→1→2
fdexp { start = 0, sb = 0, end = 0, eb = 0, content = "" },
fdexp { start = 1, sb = 2, end = 1, eb = 2, content = "" },
fdexp { start = 2, sb = 3, end = 2, eb = 3, content = "" },
// ("b.d", "aßbxd") — multibyte before the match start
// splits every idx from its bytesize
fdexp { start = 2, sb = 3, end = 5, eb = 6, content = "bxd" },
// ("ab", "xab") — tail match: the post-match seek lands at
// end-of-string and the next search returns void
fdexp { start = 1, sb = 1, end = 3, eb = 3, content = "ab" },
// ("a*", "baa") — fold-3 rider: search's longest-pick beats
// the zero-length candidate at 0 (the b*-over-"aaaabbbb"
// semantics), so the greedy (1,3) "aa" leads; the trailing
// end-of-string zero-length match takes the ha:942-945
// break, pinning a real splitting pattern through the 2c
// zero-length machinery
fdexp { start = 1, sb = 1, end = 3, eb = 3, content = "aa" },
fdexp { start = 3, sb = 3, end = 3, eb = 3, content = "" },
];
let rows: [8]fdcase = [
fdcase { expr = "ab", input = "abxab", eoff = 0, ecnt = 2 },
fdcase { expr = "ab", input = "abab", eoff = 2, ecnt = 2 },
fdcase { expr = "aa", input = "aaa", eoff = 4, ecnt = 1 },
fdcase { expr = "", input = "ßx", eoff = 5, ecnt = 3 },
fdcase { expr = "b.d", input = "aßbxd", eoff = 8, ecnt = 1 },
fdcase { expr = "ab", input = "xab", eoff = 9, ecnt = 1 },
// no match → empty slice the caller still result_freealls
fdcase { expr = "ab", input = "xyz", eoff = 10, ecnt = 0 },
fdcase { expr = "a*", input = "baa", eoff = 10, ecnt = 2 },
];
let i: i32 = 0;
for (i < len(rows)) {
let ex: str = rows[i].expr;
let inp: str = rows[i].input;
let c: (regex.regex | regex.error | nomem) = regex.compile(ex);
match (c) {
case let re: regex.regex => {
let fr: ([]regex.result | nomem) =
regex.findall(&re, inp);
if (!(fr is []regex.result)) { fail(); };
let results: []regex.result = fr as []regex.result;
if (len(results) != rows[i].ecnt) { fail(); };
let k: i32 = 0;
for (k < rows[i].ecnt) {
let w: fdexp = exp[rows[i].eoff + k];
if (results[k][0].start != w.start) { fail(); };
if (results[k][0].start_bytesize != w.sb) {
fail();
};
if (results[k][0].end != w.end) { fail(); };
if (results[k][0].end_bytesize != w.eb) {
fail();
};
if (strings.compare(results[k][0].content,
w.content) != 0) {
fail();
};
k += 1;
};
regex.result_freeall(results);
regex.finish(&re);
};
case => fail();
};
i += 1;
};
};
// ---- fold 3: anchors / escape / postfix / alternation ----------------
// instsig — flatten an inst for the table-driven program pins below:
// kind base + payload. Takes the 56B inst by value (the #19-landed
// is_consuming_inst shape).
fn instsig(v: regex.inst) i64 = {
match (v) {
case let l: regex.inst_lit => return 1000 + ((l: rune): i64);
case regex.inst_skip => return 2000;
case regex.inst_any => return 3000;
case let s: regex.inst_split => return 4000 + ((s: size): i64);
case let j: regex.inst_jump => return 5000 + ((j: size): i64);
case let m: regex.inst_match => {
if ((m: bool)) { return 6001; };
return 6000;
};
case let g: regex.inst_groupstart => return 7000 + ((g: size): i64);
case regex.inst_groupend => return 8000;
case => return 9999;
};
};
// Emitted-program pins for the fold-3 arms — deterministic, engine-
// independent: the exact inst sequence (kinds + jump/split targets)
// each metachar must compile to. Derived by hand-executing
// regex.ha:286-473 (insert-before + shift + the SIZE_MAX-sentinel
// jump fixup); the a|b row pins the whole jump_idxs pipeline
// including the sentinel overwrite at ha:470-473.
type pgmcase = struct {
expr: str,
soff: i32,
scnt: i32,
};
@test fn fold3_programs() void = {
let sigs: [25]i64 = [
// "^a": anchored — no leading skip
1097, 6000,
// "a$": skip, lit a, match(TRUE)
2000, 1097, 6001,
// "a?": split jumps OVER the lit to the match
2000, 4003, 1097, 6000,
// "a*": split to match; jump back to the split
2000, 4004, 1097, 5001, 6000,
// "a+": split back to the lit
2000, 1097, 4001, 6000,
// "a|b": leading split to the second branch's skip; the
// first branch's jump lands on the epilogue match (the
// fixed-up SIZE_MAX sentinel)
4004, 2000, 1097, 5006, 2000, 1098, 6000,
];
let rows: [6]pgmcase = [
pgmcase { expr = "^a", soff = 0, scnt = 2 },
pgmcase { expr = "a$", soff = 2, scnt = 3 },
pgmcase { expr = "a?", soff = 5, scnt = 4 },
pgmcase { expr = "a*", soff = 9, scnt = 5 },
pgmcase { expr = "a+", soff = 14, scnt = 4 },
pgmcase { expr = "a|b", soff = 18, scnt = 7 },
];
let i: i32 = 0;
for (i < len(rows)) {
let ex: str = rows[i].expr;
let c: (regex.regex | regex.error | nomem) = regex.compile(ex);
match (c) {
case let re: regex.regex => {
if (re.insts.len != rows[i].scnt) { fail(); };
let k: i32 = 0;
for (k < rows[i].scnt) {
if (instsig(re.insts[k])
!= sigs[rows[i].soff + k]) {
fail();
};
k += 1;
};
regex.finish(&re);
};
case => fail();
};
i += 1;
};
};
// The fold-3 compile-error surface, exact texts (regex.ha:289 / 296-299
// / 303-308 / 405-417 / 423-435 / 446-455). The "ab\|^cd" row is
// Hare's own ERROR fixture (+test.ha:634) — the escaped '|' must NOT
// set was_prev_rune_pipe, so the following '^' misplaces.
type cerow = struct {
pat: str,
want: str,
};
@test fn fold3_compile_errors() void = {
let rows: [12]cerow = [
cerow { pat = "\\", want = "Trailing backslash '\\'" },
cerow { pat = "a\\", want = "Trailing backslash '\\'" },
cerow { pat = "a^",
want = "Anchor '^' not at start of whole pattern or alternation" },
cerow { pat = "$a",
want = "Anchor '$' not at end of whole pattern or alternation" },
cerow { pat = "ab\\|^cd",
want = "Anchor '^' not at start of whole pattern or alternation" },
cerow { pat = "?", want = "Unused '?'" },
cerow { pat = "*", want = "Unused '*'" },
cerow { pat = "+", want = "Unused '+'" },
// '^' appends nothing, so insts is still empty (ha:404's
// len check, not the r_idx one)
cerow { pat = "^*", want = "Unused '*'" },
cerow { pat = "a*?", want = "Misused '?'" },
cerow { pat = "a**", want = "Misused '*'" },
cerow { pat = "a*+", want = "Misused '+'" },
];
let i: i32 = 0;
for (i < len(rows)) {
let p: str = rows[i].pat;
match (regex.compile(p)) {
case let e: regex.error => {
let w: str = rows[i].want;
if (strings.compare((e: str), w) != 0) { fail(); };
};
case => fail();
};
i += 1;
};
};
// find_last_groupstart (regex.ha:104-119) — driven directly (private
// fn): no inst_groupstart exists in any fold-3 program, so the error
// arm is the live one; pin its exact text. A hand-built groupstart
// row pins the success arm the group fold will rely on.
@test fn find_last_groupstart_cases() void = {
let insts: []regex.inst = [];
append(insts, ('a': regex.inst_lit));
match (find_last_groupstart(insts)) {
case let e: regex.error => {
if (strings.compare((e: str), "Unmatched ')'") != 0) {
fail();
};
};
case => fail();
};
append(insts, ((1: size): regex.inst_groupstart));
append(insts, ('b': regex.inst_lit));
match (find_last_groupstart(insts)) {
case let sz: size => { if (sz != 1) { fail(); }; };
case => fail();
};
};
// shift (regex.ha:123-133) — driven directly over a sub-slice view:
// jump/split payloads in the view bump by one, the element before the
// view and non-jump kinds are untouched (the PE3/PE4 shapes).
@test fn shift_direct() void = {
let insts: []regex.inst = [];
append(insts, ((3: size): regex.inst_jump));
append(insts, ('a': regex.inst_lit));
append(insts, ((5: size): regex.inst_split));
append(insts, ((7: size): regex.inst_jump));
shift(insts[1:]);
if (instsig(insts[0]) != 5003) { fail(); };
if (instsig(insts[1]) != 1097) { fail(); };
if (instsig(insts[2]) != 4006) { fail(); };
if (instsig(insts[3]) != 5008) { fail(); };
};
// fold-3 find/test rows — the group-free subset of Hare's own table
// (+test.ha:221-256 anchors/postfix, :622-650 whole-expression and
// multiple alternation; end == -1 resolved to rune-length per
// +test.ha:693-697) plus rob's dedup/leftmost/longest riders:
// `a*` over "aaaa" must yield ONE (0,4) (split spawns same-pc threads
// every step — the ha:872-889 dedup pin gone observable), `b+` over
// "abab" pins the leftmost trim (1,2 not 3,4), `b*`/`^b*` over
// "aaaabbbb" pin longest-pick vs anchored zero-length. The multibyte
// `b+` row keeps every idx != bytesize (B4). Reuses the fcase shape;
// every row also cross-pins test() == (find() matched).
@test fn fold3_find_cases() void = {
let rows: [42]fcase = [
fcase { expr = "^abc$", input = "abc", matches = true,
start = 0, sb = 0, end = 3, eb = 3, content = "abc" },
fcase { expr = "^abc$", input = "axc", matches = false, ... },
fcase { expr = "^.$", input = "x", matches = true,
start = 0, sb = 0, end = 1, eb = 1, content = "x" },
fcase { expr = "^.$", input = "", matches = false, ... },
fcase { expr = "^a+$", input = "a", matches = true,
start = 0, sb = 0, end = 1, eb = 1, content = "a" },
fcase { expr = "^a+$", input = "aaa", matches = true,
start = 0, sb = 0, end = 3, eb = 3, content = "aaa" },
fcase { expr = "^a+$", input = "", matches = false, ... },
fcase { expr = "^a*$", input = "", matches = true,
start = 0, sb = 0, end = 0, eb = 0, content = "" },
fcase { expr = "^a*$", input = "aaaa", matches = true,
start = 0, sb = 0, end = 4, eb = 4, content = "aaaa" },
fcase { expr = "^a*$", input = "b", matches = false, ... },
fcase { expr = "^a?$", input = "", matches = true,
start = 0, sb = 0, end = 0, eb = 0, content = "" },
fcase { expr = "^a?$", input = "a", matches = true,
start = 0, sb = 0, end = 1, eb = 1, content = "a" },
fcase { expr = "^a?$", input = "b", matches = false, ... },
fcase { expr = "^a*", input = "aaaa", matches = true,
start = 0, sb = 0, end = 4, eb = 4, content = "aaaa" },
fcase { expr = "a*$", input = "aaaa", matches = true,
start = 0, sb = 0, end = 4, eb = 4, content = "aaaa" },
fcase { expr = "a*", input = "aaaa", matches = true,
start = 0, sb = 0, end = 4, eb = 4, content = "aaaa" },
fcase { expr = "b*", input = "aaaabbbb", matches = true,
start = 4, sb = 4, end = 8, eb = 8, content = "bbbb" },
fcase { expr = "^b*", input = "aaaabbbb", matches = true,
start = 0, sb = 0, end = 0, eb = 0, content = "" },
fcase { expr = "b*$", input = "aaaabbbb", matches = true,
start = 4, sb = 4, end = 8, eb = 8, content = "bbbb" },
fcase { expr = "b+", input = "abab", matches = true,
start = 1, sb = 1, end = 2, eb = 2, content = "b" },
// multibyte rider: 2-byte ß before the b's splits every
// idx from its bytesize
fcase { expr = "b+", input = "aßbb", matches = true,
start = 2, sb = 3, end = 4, eb = 5, content = "bb" },
fcase { expr = "ab|cd", input = "cd", matches = true,
start = 0, sb = 0, end = 2, eb = 2, content = "cd" },
fcase { expr = "ab|cd", input = "abc", matches = true,
start = 0, sb = 0, end = 2, eb = 2, content = "ab" },
fcase { expr = "ab|cd", input = "abcd", matches = true,
start = 0, sb = 0, end = 2, eb = 2, content = "ab" },
fcase { expr = "ab|cd", input = "bcd", matches = true,
start = 1, sb = 1, end = 3, eb = 3, content = "cd" },
fcase { expr = "^ab|cd", input = "bcd", matches = true,
start = 1, sb = 1, end = 3, eb = 3, content = "cd" },
fcase { expr = "^ab|cd", input = "zab", matches = false, ... },
fcase { expr = "ab$|cd", input = "ab", matches = true,
start = 0, sb = 0, end = 2, eb = 2, content = "ab" },
fcase { expr = "ab$|cd", input = "abc", matches = false, ... },
fcase { expr = "ab|cd$", input = "cde", matches = false, ... },
fcase { expr = "ab|^cd", input = "bcd", matches = false, ... },
fcase { expr = "ab|^cd", input = "cde", matches = true,
start = 0, sb = 0, end = 2, eb = 2, content = "cd" },
fcase { expr = "a|b|c|d|e", input = "e", matches = true,
start = 0, sb = 0, end = 1, eb = 1, content = "e" },
fcase { expr = "a|b|c|d|e", input = "xe", matches = true,
start = 1, sb = 1, end = 2, eb = 2, content = "e" },
fcase { expr = "a|b$|c$|d$|e", input = "cd", matches = true,
start = 1, sb = 1, end = 2, eb = 2, content = "d" },
fcase { expr = "a|b$|c$|d$|e", input = "ax", matches = true,
start = 0, sb = 0, end = 1, eb = 1, content = "a" },
fcase { expr = "a|b$|c$|d$|e", input = "cx", matches = false, ... },
fcase { expr = "a|b$|c$|d$|e", input = "ex", matches = true,
start = 0, sb = 0, end = 1, eb = 1, content = "e" },
fcase { expr = "a|^b|^c|^d|e", input = "cd", matches = true,
start = 0, sb = 0, end = 1, eb = 1, content = "c" },
fcase { expr = "a|^b|^c|^d|e", input = "xa", matches = true,
start = 1, sb = 1, end = 2, eb = 2, content = "a" },
fcase { expr = "a|^b|^c|^d|e", input = "xc", matches = false, ... },
fcase { expr = "a|^b|^c|^d|e", input = "xe", matches = true,
start = 1, sb = 1, end = 2, eb = 2, content = "e" },
];
let i: i32 = 0;
for (i < len(rows)) {
let ex: str = rows[i].expr;
let inp: str = rows[i].input;
let c: (regex.regex | regex.error | nomem) = regex.compile(ex);
match (c) {
case let re: regex.regex => {
let fr: (regex.result | nomem) = regex.find(&re, inp);
if (!(fr is regex.result)) { fail(); };
let res: regex.result = fr as regex.result;
if (rows[i].matches) {
if (len(res) != 1) { fail(); };
if (res[0].start != rows[i].start) { fail(); };
if (res[0].start_bytesize != rows[i].sb) {
fail();
};
if (res[0].end != rows[i].end) { fail(); };
if (res[0].end_bytesize != rows[i].eb) {
fail();
};
let wc: str = rows[i].content;
if (strings.compare(res[0].content, wc) != 0) {
fail();
};
} else {
if (len(res) != 0) { fail(); };
};
let tr: (bool | nomem) = regex.test(&re, inp);
if (!(tr is bool)) { fail(); };
if ((tr as bool) != (len(res) != 0)) { fail(); };
regex.result_free(res);
regex.finish(&re);
};
case => fail();
};
i += 1;
};
};
export fn main() i32 = {
signalled = 1; lit_and_match();
signalled = 2; size_aliases_distinct();
signalled = 3; void_aliases_distinct();
signalled = 4; charset_payload();
signalled = 5; repeat_payload();
signalled = 6; struct_shapes_and_finish();
signalled = 7; compile_literal_program();
signalled = 8; compile_any_program();
signalled = 9; compile_empty_program();
signalled = 10; compile_metachar_loud();
signalled = 11; thread_shape();
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();
signalled = 18; run_thread_literal_program();
signalled = 19; run_thread_anchored_route();
signalled = 20; search_matches();
signalled = 21; search_early_exit();
signalled = 22; search_no_match();
signalled = 23; test_matches();
signalled = 24; find_cases();
signalled = 25; findall_content();
signalled = 26; findall_fields();
signalled = 27; fold3_programs();
signalled = 28; fold3_compile_errors();
signalled = 29; find_last_groupstart_cases();
signalled = 30; shift_direct();
signalled = 31; fold3_find_cases();
return 0;
};