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:
@@ -1,6 +1,8 @@
|
||||
// regex — POSIX extended regular expressions. Port of
|
||||
// ref/hare/regex/regex.ha. Fold 1 = the data model only; compile() /
|
||||
// exec / find / replace are DEFERRED to later folds.
|
||||
// ref/hare/regex/regex.ha. Fold 1 = the data model; fold 2a = the
|
||||
// compile() literal core (lit/any/match + the leading skip); every
|
||||
// other metacharacter arm, exec / find / replace are DEFERRED to
|
||||
// later folds.
|
||||
//
|
||||
// Two fold-1 constructs are held back behind filed compiler/fidelity
|
||||
// gaps (see the charclass_map and finish() sites below):
|
||||
@@ -11,6 +13,9 @@
|
||||
// - finish() (regex.ha:96-102) — frees; ww is a no-free runtime (#27).
|
||||
package regex;
|
||||
|
||||
import strings;
|
||||
import encoding.utf8;
|
||||
|
||||
// ref/hare/regex/regex.ha:14 — an error string describing a compilation
|
||||
// error.
|
||||
export type error = !str;
|
||||
@@ -96,3 +101,80 @@ export type regex = struct {
|
||||
// no-op, after which this ports the Hare frees VERBATIM (the no-op
|
||||
// builtin reclaims nothing, same end state).
|
||||
export fn finish(re: *regex) void = { };
|
||||
|
||||
// Compiles a regular expression string into a [[regex]].
|
||||
//
|
||||
// ref/hare/regex/regex.ha:227-263. Fold 2a ports the literal core:
|
||||
// inst_lit / inst_any / inst_match plus the leading inst_skip
|
||||
// (regex.ha:261-263 — unanchored exec depends on it). Every other
|
||||
// metacharacter arm is one loud not-yet-ported error — the explicit
|
||||
// fold boundary; falling to literal would be a silent semantic lie.
|
||||
// State serving only the deferred arms is dropped with them:
|
||||
// jump_idxs (ha:241-248), the bracket quad (ha:249-252),
|
||||
// was_prev_rune_pipe / group_level / capture_idx (ha:253-256).
|
||||
//
|
||||
// Hare's `defer if (!ok) free(...)` cleanup (ha:231-237) is omitted:
|
||||
// ww has no `defer if` (cf lib/strings/strings.ww:85) and is a no-free
|
||||
// runtime (#27) — the port drops every Hare free().
|
||||
export fn compile(expr: str) (regex | error | nomem) = {
|
||||
// Hare `let insts: []inst = [];` — a bare ww slice declaration
|
||||
// zeroes the header (cf lib/shlex/shlex.ww:215).
|
||||
let insts: []inst;
|
||||
let charsets: []charset; // stays empty until the '[' fold
|
||||
let iter: strings.iterator = strings.iter(expr);
|
||||
let r_idx: size = 0;
|
||||
let n_reps: size = 0;
|
||||
|
||||
for (true) {
|
||||
let next: (rune | utf8.done) = strings.next(&iter);
|
||||
|
||||
if (r_idx == 0 && next is rune && (next as rune) != '^') {
|
||||
// Bare append: ww append returns void; Hare's
|
||||
// `append(...)?` nomem propagation is filed #36.
|
||||
let sk: inst_skip;
|
||||
let v: inst = sk;
|
||||
append(insts, v);
|
||||
};
|
||||
|
||||
// regex.ha:277-284 minus the group_level check (it rides
|
||||
// the deferred '(' arm's state).
|
||||
let r: rune = match (next) {
|
||||
case utf8.done => break;
|
||||
case let x: rune => yield x;
|
||||
};
|
||||
|
||||
switch (r) {
|
||||
case '.': { // regex.ha:460-461
|
||||
let av: inst_any;
|
||||
let v: inst = av;
|
||||
append(insts, v);
|
||||
};
|
||||
case ']': { // regex.ha:315-316 — literal outside a bracket
|
||||
let v: inst = (r: inst_lit);
|
||||
append(insts, v);
|
||||
};
|
||||
case '\\', '^', '$', '[', '(', ')', '|', '{', '?', '*', '+':
|
||||
// fold-2a boundary: regex.ha:286-459 arms deferred.
|
||||
return "regex: metacharacter not yet ported": error;
|
||||
case: { // regex.ha:462-463
|
||||
let v: inst = (r: inst_lit);
|
||||
append(insts, v);
|
||||
};
|
||||
};
|
||||
r_idx += 1;
|
||||
};
|
||||
|
||||
// regex.ha:475-477. `$` appends true: inst_match — deferred, so
|
||||
// the guard can only see no-match today; kept verbatim.
|
||||
if (insts.len == 0 || !(insts[insts.len - 1] is inst_match)) {
|
||||
let m: inst = (false: inst_match);
|
||||
append(insts, m);
|
||||
};
|
||||
// regex.ha:479-484. The alternation fixup (ha:470-473) drops with
|
||||
// jump_idxs.
|
||||
return regex {
|
||||
insts = insts,
|
||||
charsets = charsets,
|
||||
n_reps = n_reps,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user