lib/regex: fold-2c — findall/result_freeall
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).
This commit is contained in:
@@ -2,9 +2,9 @@
|
||||
// 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, and the tranche-D exec
|
||||
// surface (test/find). Run with
|
||||
// `out/bin/ww run lib/regex/regex_test.ww`.
|
||||
// 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
|
||||
@@ -900,6 +900,157 @@ type fcase = struct {
|
||||
};
|
||||
};
|
||||
|
||||
// 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: [10]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" },
|
||||
];
|
||||
let rows: [7]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 },
|
||||
];
|
||||
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;
|
||||
};
|
||||
};
|
||||
|
||||
export fn main() i32 = {
|
||||
signalled = 1; lit_and_match();
|
||||
signalled = 2; size_aliases_distinct();
|
||||
@@ -925,5 +1076,7 @@ export fn main() i32 = {
|
||||
signalled = 22; search_no_match();
|
||||
signalled = 23; test_matches();
|
||||
signalled = 24; find_cases();
|
||||
signalled = 25; findall_content();
|
||||
signalled = 26; findall_fields();
|
||||
return 0;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user