lib/regex: compile() literal core — lit/any/match + loud fold boundary (regex port fold 2a)

Ports ref/hare/regex/regex.ha:227-263 literal arms: leading unanchored
inst_skip, inst_lit / inst_any, epilogue inst_match(false). Every
deferred metacharacter arm returns a loud not-yet-ported error (the
fold boundary); state serving only deferred arms drops with them.
Hare free()/defer-if cleanup omitted (no-free runtime, #27); bare
append per #36. 4 new @test rows pin the emitted programs incl. the
empty-input and loud-boundary cases; compile()'s >24B tagged return
doubles as a #38 sret consumer. Rides #34/#38/#44/#45/#48 — all five
fold-2a blockers now closed on master.
This commit is contained in:
2026-06-04 05:36:01 +09:00
parent 288b21b1e9
commit 3cffe204d1
2 changed files with 191 additions and 4 deletions

View File

@@ -1,7 +1,10 @@
// regex_test — exercises the lib/regex fold-1 data model (the type
// model + finish()). Run with `out/bin/ww run lib/regex/regex_test.ww`.
// model + finish()) and the fold-2a compile() literal core. Run with
// `out/bin/ww run lib/regex/regex_test.ww`.
//
// Fold 1 ports the data model only; compile()/exec live in later folds.
// 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
@@ -152,6 +155,104 @@ fn fail() void = { os.exit(signalled + 10); };
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();
};
};
// Any deferred metacharacter is a LOUD error — pins the fold-2a
// boundary so the fold that ports '*' consciously deletes this row.
@test fn compile_metachar_loud() void = {
match (regex.compile("a*")) {
case let e: regex.error => { if ((e: str).len == 0) { fail(); }; };
case => fail();
};
};
export fn main() i32 = {
signalled = 1; lit_and_match();
signalled = 2; size_aliases_distinct();
@@ -159,5 +260,9 @@ export fn main() i32 = {
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();
return 0;
};