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,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,
};
};