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:
2026-06-04 15:49:38 +09:00
parent 532b0a88ae
commit 48904e5ef3
3 changed files with 238 additions and 6 deletions

View File

@@ -6,8 +6,9 @@
// (delete_thread/is_consuming_inst/add_thread/run_thread); fold 2b
// tranche C = search, the first end-to-end match; fold 2b tranche D
// = the exec surface (test/find — the D13 explicit-match spelling of
// Hare's multi-success `?`, ww-core #14). Every other metacharacter
// arm (and replace/findall) is DEFERRED behind compiler fixes —
// Hare's multi-success `?`, ww-core #14); fold 2c =
// findall/result_freeall over the memio seeker. Every other
// metacharacter arm (and replace) is DEFERRED behind compiler fixes —
// probed pre-port, pA*/pB*/PB*/PC*/PD* probes. They land with those
// fixes.
//
@@ -642,6 +643,68 @@ export fn find(re: *regex, string: str) (result | nomem) = {
};
};
// Attempts to match a [[regex]] against a string and returns all
// non-overlapping matches as a slice of [[result]]s. The caller must
// free the return value with [[result_freeall]].
//
// ref/hare/regex/regex.ha:920-960. Hare's ok-flag + `defer if (!ok)
// result_freeall(res)` (ha:924-926) is omitted: the flag's only use
// is the defer-if, ww defer takes a single expression, and the frees
// are the documented no-op (#27) — compile()'s omitted `defer if`
// precedent. The `search(...)?` unwrap (ha:933) is the same explicit
// D13 lowering as test()/find() (ww-core #14); the nomem arm
// propagates verbatim.
export fn findall(re: *regex, string: str) ([]result | nomem) = {
let res: []result;
let str_idx: size = 0;
let str_bytesize: size = 0;
let strm: memio.stream = memio.fixed(strings.toutf8(string));
let str_bytes: []u8 = strings.toutf8(string);
for (true) {
let substring: str =
strings.frombytes(str_bytes[str_bytesize:]);
let r: (void | []capture | nomem) =
search(re, substring, &strm.vt, true);
match (r) {
case let m: []capture => {
// Hare appends m and THEN fixes m[0] up from
// substring- to whole-string-relative (ha:935-939):
// the appended header shares m's backing, so the
// mutations below are visible through res. Kept
// verbatim — the aliasing is the subtle bit.
append(res, m);
m[0].start += str_idx;
m[0].end += str_idx;
m[0].start_bytesize += str_bytesize;
m[0].end_bytesize += str_bytesize;
str_idx = m[0].end;
str_bytesize = m[0].end_bytesize;
if (m[0].start_bytesize == (len(str_bytes): size)) {
// end-of-string reached
break;
};
if (m[0].start_bytesize == m[0].end_bytesize) {
// zero-length match: forward rune and byte
// indices (ha:946-952 — the guard against
// the classic findall infinite loop)
str_idx += 1;
str_bytesize += (utf8.utf8sz(
str_bytes[str_bytesize])!: size);
};
// ha:953-954: each search call's scanner buffers
// past what scanrune consumed; the absolute SET
// repositions the underlying stream before the
// next call builds a fresh scanner.
io.seek(&strm.vt, (str_bytesize: io.off),
io.whence.SET)!;
};
case void => break;
case let n: nomem => return n;
};
};
return res;
};
// Frees a [[result]].
//
// ref/hare/regex/regex.ha:1113-1116, verbatim — the free() builtin is
@@ -650,6 +713,17 @@ export fn result_free(s: result) void = {
free(s);
};
// Frees a slice of [[result]]s.
//
// ref/hare/regex/regex.ha:1119-1124, verbatim — both frees are the
// documented no-op (#27).
export fn result_freeall(s: []result) void = {
for (let r .. s) {
result_free(r);
};
free(s);
};
// Converts an [[error]] into a user-friendly string.
//
// ref/hare/regex/regex.ha:1126-1127 (expression-bodied in Hare; ww