From c0c15945be606ae92ce8f05f6c71dca44b285e88 Mon Sep 17 00:00:00 2001 From: Hojun-Cho Date: Fri, 5 Jun 2026 04:33:33 +0900 Subject: [PATCH] =?UTF-8?q?regex:=20fold=205b=20=E2=80=94=20parse=5Frepeti?= =?UTF-8?q?tion=20leaf=20(ha:486-545)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Dead until the { arm lands (tranche-A precedent); 13-row direct private-fn table, error texts byte-exact. Hare's verbatim ((void|size),(void|size),size) tuple return can't cross a union boundary yet — cstage's (tuple|error) return store is cgen-unwired and wwstage's variant-match rejects the tuple case arm (filed #47) — so it respells as the private repparts struct per the scope-fold5 §3 pre-signed fallback; graduates back to the tuple when #47 closes. Riders: call-result .N tuple read loud-rejects (filed #48, bind-first local); strings.index's standing i32 convention (#8) stays internal, widened at each size boundary; ha:494's same-name re-bind is rejected, second local feb. --- lib/regex/regex.ww | 95 +++++++++++++++++++++++++++++++++++++++++ lib/regex/regex_test.ww | 85 ++++++++++++++++++++++++++++++++++++ 2 files changed, 180 insertions(+) diff --git a/lib/regex/regex.ww b/lib/regex/regex.ww index e5677f3b..cc18818d 100644 --- a/lib/regex/regex.ww +++ b/lib/regex/regex.ww @@ -30,6 +30,7 @@ package regex; import bufio; import io; import memio; +import strconv; import strings; import types; import encoding.utf8; @@ -628,6 +629,100 @@ export fn compile(expr: str) (regex | error | nomem) = { }; }; +// parse_repetition's Hare-verbatim return is the 3-tuple +// ((void | size), (void | size), size) (regex.ha:488); a tuple with +// tagged elements can't yet cross a union boundary in ww — cstage's +// return store into (tuple | error) is cgen-unwired +// (cg_widen_tagged_store) and wwstage's variant-match rejects the +// tuple case arm (filed, ww-core #47) — so the tuple respells as this +// private struct, positionally: .0 → min, .1 → max, .2 → replen. +// GRADUATION: when #47 closes, repparts reverts to the verbatim tuple +// with the test rows unchanged. +type repparts = struct { + min: (void | size), + max: (void | size), + replen: size, +}; + +// ref/hare/regex/regex.ha:486-545, whole. strings.index returns +// (i32 | void) in ww — the standing lib-wide str-index-i32 convention +// (lib/strings/strings.ww:54, #8) vs Hare's (size | void) +// (ref/hare/strings/index.ha:10) — so the index results stay i32 +// INTERNALLY (the ha:499 comparison is same-domain) and each value +// widens explicitly at the boundary Hare types as size (the repparts +// field stores). ha:494 re-binds `first_endbrace` over itself; ww +// rejects a same-scope re-decl ("let redeclared"), so the unwrapped +// value is `feb`. +fn parse_repetition(s: str) (repparts | error) = { + let first_comma: (i32 | void) = strings.index(s, ","); + let first_endbrace: (i32 | void) = strings.index(s, "}"); + if (first_endbrace is void) { + return "Repetition expression syntax error '{n}'": error; + }; + let feb: i32 = first_endbrace as i32; + + let min_str: str = ""; + let max_str: str = ""; + let is_single_arg: bool = false; + if (first_comma is void || feb < first_comma as i32) { + let cut: (str, str) = strings.cut(s, "}"); + min_str = cut.0; + max_str = cut.0; + is_single_arg = true; + } else { + let cut: (str, str) = strings.cut(s, ","); + min_str = cut.0; + // Hare reads .0 straight off the call (ha:507); a tuple-elem + // read on a CALL result is loud-unsupported (filed, ww-core + // #48) — bound local first. + let cut2: (str, str) = strings.cut(cut.1, "}"); + max_str = cut2.0; + }; + + let min: (void | size) = void; + let max: (void | size) = void; + + if (min_str.len > 0) { + // Hare's match-EXPRESSION with if-yields (ha:514-525) + // spells as a statement match (no yield-expr; the search + // scanrune precedent, #51). stoi's base is explicit — ww + // has no default args (the newscanner precedent; cf + // lib/encoding/hex/hex.ww:131). + match (strconv.stoi(min_str, strconv.base.DEC)) { + case let res: int => { + if (res < 0) { + return "Negative repetition count '{-n}'": error; + }; + min = (res: size); + }; + case => return "Repetition expression syntax error '{n}'": error; + }; + } else { + // `min = 0;` (ha:524): an untyped-int widen-store into + // (void | size) mis-tags (#33) — cast form pinned. + min = (0: size); + }; + + if (max_str.len > 0) { + match (strconv.stoi(max_str, strconv.base.DEC)) { + case let res: int => { + if (res < 0) { + return "Negative repetition count '{-n}'": error; + }; + max = (res: size); + }; + case => return "Repetition expression syntax error '{n}'": error; + }; + }; + + // Hare's if-EXPRESSION (ha:539-544) spells as a statement. + let rep_len: size = (min_str.len: size); + if (!is_single_arg) { + rep_len = (min_str.len: size) + 1 + (max_str.len: size); + }; + return repparts { min = min, max = max, replen = rep_len }; +}; + // ref/hare/regex/regex.ha:547-551, verbatim — both free()s are the // documented no-op (#27; see finish()), kept for source parity. ww // has no pointer auto-deref, so Hare's `threads[i]` spells diff --git a/lib/regex/regex_test.ww b/lib/regex/regex_test.ww index 187017a8..c8b029f7 100644 --- a/lib/regex/regex_test.ww +++ b/lib/regex/regex_test.ww @@ -2137,6 +2137,90 @@ type smcase = struct { }; }; +// ---- fold 5b: repetition ---------------------------------------------- + +// parse_repetition rows (regex.ha:486-545) — DIRECT private-fn table +// (the leaf fn lands ahead of its `{`-arm consumer; tranche-A +// precedent). Expectations hand-executed from Hare's own code paths: +// the input is everything AFTER `{` (compile passes iterstr's rest), +// single-arg `{n}` sets max = min and replen = len(n) (ha:500-503, +// 539-541); two-arg replen = len(min) + 1 + len(max) (ha:543); an +// empty min is 0 (ha:524) while an empty max stays void (ha:527 — +// the `{n,}` open bound); a `,` BEYOND the first `}` is not a +// two-arg form (ha:499). The two error texts are byte-exact +// (ha:492/521/531-536). max_void distinguishes "expect void" from +// "expect maxv"; err != "" rows expect that exact error. +type prrow = struct { + input: str, + minv: size, + maxv: size, + max_void: bool, + replen: size, + err: str, +}; + +@test fn parse_repetition_cases() void = { + let rows: [13]prrow = [ + prrow { input = "2}", minv = 2, maxv = 2, max_void = false, + replen = 1, err = "" }, + prrow { input = "2}$", minv = 2, maxv = 2, max_void = false, + replen = 1, err = "" }, + // comma AFTER the endbrace — still single-arg (ha:499) + prrow { input = "2},5", minv = 2, maxv = 2, max_void = false, + replen = 1, err = "" }, + prrow { input = "1,2}", minv = 1, maxv = 2, max_void = false, + replen = 3, err = "" }, + prrow { input = ",2}", minv = 0, maxv = 2, max_void = false, + replen = 2, err = "" }, + prrow { input = ",0}", minv = 0, maxv = 0, max_void = false, + replen = 2, err = "" }, + prrow { input = "2,}", minv = 2, maxv = 0, max_void = true, + replen = 2, err = "" }, + prrow { input = ",}", minv = 0, maxv = 0, max_void = true, + replen = 1, err = "" }, + prrow { input = "12,34}xyz", minv = 12, maxv = 34, + max_void = false, replen = 5, err = "" }, + prrow { input = "-1,2}", minv = 0, maxv = 0, max_void = false, + replen = 0, err = "Negative repetition count '{-n}'" }, + prrow { input = "x,2}", minv = 0, maxv = 0, max_void = false, + replen = 0, + err = "Repetition expression syntax error '{n}'" }, + prrow { input = "0,-2}", minv = 0, maxv = 0, max_void = false, + replen = 0, err = "Negative repetition count '{-n}'" }, + // no endbrace at all (ha:491-493) + prrow { input = "2", minv = 0, maxv = 0, max_void = false, + replen = 0, + err = "Repetition expression syntax error '{n}'" }, + ]; + let i: i32 = 0; + for (i < len(rows)) { + let r: (repparts | error) = parse_repetition(rows[i].input); + match (r) { + case let t: repparts => { + if (rows[i].err.len > 0) { fail(); }; + // .min is always size after a successful parse + // (ha:523-525 — empty min defaults to 0) + if (!(t.min is size)) { fail(); }; + if (t.min as size != rows[i].minv) { fail(); }; + if (rows[i].max_void) { + if (!(t.max is void)) { fail(); }; + } else { + if (!(t.max is size)) { fail(); }; + if (t.max as size != rows[i].maxv) { fail(); }; + }; + if (t.replen != rows[i].replen) { fail(); }; + }; + case let e: regex.error => { + if (rows[i].err.len == 0) { fail(); }; + if (strings.compare((e: str), rows[i].err) != 0) { + fail(); + }; + }; + }; + i += 1; + }; +}; + export fn main() i32 = { signalled = 1; lit_and_match(); signalled = 2; size_aliases_distinct(); @@ -2180,5 +2264,6 @@ export fn main() i32 = { signalled = 40; fold5_find_cases(); signalled = 41; fold5_optional_group(); signalled = 42; fold5_submatches(); + signalled = 43; parse_repetition_cases(); return 0; };