Files
ww/lib/regex/regex_test.ww
Hojun-Cho b6a41ae063 lib/regex: type model + finish() (regex port fold 1, partial)
Port of ref/hare/regex/regex.ha fold 1 (the data model). Lands the
full type model — error, the inst_* variants + 10-variant inst union
(the nominally-distinct same-underlying size/void aliases included),
result/capture, charset + items, the regex struct — plus finish().
Test 989_regex_run pins variant discrimination, payload extraction,
struct shapes, and finish() on cstage; w6c == w6c_ww byte-identical.

Two fold-1 constructs are held back behind filed compiler/fidelity
gaps, documented at their sites (regex tasks A–D):

  - charclass_map (regex.ha:74-87): const [](str, *fn(rune) bool)
    table — blocked on the array-literal->slice element-coercion
    checker gap (type.c:402-404 #258 borrow uses exact type_eq,
    no element decay). It needs `import ascii;`, so both land with
    the consuming fold (compile) once the gap is fixed.

  - finish() free()s; ww is a no-free runtime (rt/alloc.s:30), so the
    faithful body drops the frees, as the port drops every Hare
    free(). Kept as a no-op for API parity.

DEFERRED to later folds: compile()/exec/find/replace.
2026-06-03 23:44:44 +09:00

164 lines
5.2 KiB
Plaintext

// 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`.
//
// Fold 1 ports the data model only; compile()/exec live in later folds.
// 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 os;
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);
};
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();
return 0;
};