// regex — POSIX extended regular expressions. Port of // 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. // // One fold-1 construct is held back behind a filed compiler/fidelity // gap (see the charclass_map site below): // - charclass_map (regex.ha:74-87) — a module-level const slice of // (str, *fn(rune) bool) tuples. Blocked on the array-literal→slice // element-coercion checker gap (#25; type.c:402-404 #258 borrow // uses exact type_eq, no element decay). package regex; import strings; import encoding.utf8; // ref/hare/regex/regex.ha:14 — an error string describing a compilation // error. export type error = !str; // ref/hare/regex/regex.ha:16-30. export type inst_lit = rune; export type inst_charset = struct { idx: size, is_positive: bool }; export type inst_any = void; export type inst_split = size; export type inst_jump = size; export type inst_skip = void; export type inst_match = bool; export type inst_groupstart = size; export type inst_groupend = void; export type inst_repeat = struct { id: size, origin: size, min: (void | size), max: (void | size), }; // ref/hare/regex/regex.ha:32-35. export type inst = (inst_lit | inst_any | inst_split | inst_jump | inst_skip | inst_match | inst_charset | inst_groupstart | inst_groupend | inst_repeat); // The resulting match of a [[regex]] applied to a string. // // The first [[capture]] corresponds to the implicit zeroth capture // group, i.e. the whole expression. // // The rest of the [[capture]]s correspond to the rest of the capture // groups, i.e. the sub-expressions. // ref/hare/regex/regex.ha:44. export type result = []capture; // A (sub)match corresponding to a regular expression's capture group. // ref/hare/regex/regex.ha:47-53. export type capture = struct { content: str, start: size, start_bytesize: size, end: size, end_bytesize: size, }; // ref/hare/regex/regex.ha:68-72. export type charset = [](charset_lit_item | charset_range_item | charset_class_item); export type charset_lit_item = rune; export type charset_range_item = (u32, u32); export type charset_class_item = (str, *fn(c: rune) bool); // ref/hare/regex/regex.ha:74-87 — charclass_map: the const // [](str, *fn(rune) bool) table mapping POSIX class tokens to the // matching ascii predicate. DEFERRED: the array-literal→slice // assignability check (type.c:402-404, the #258 borrow) compares // element types with exact type_eq and applies NO element coercion, so // the literal `[(":alnum:]", &ascii.isalnum), ...]` (typed // `[N](untyped_str, *fn(rune) bool)`) is rejected against the declared // `[](str, *fn(rune) bool)`. Minimal repro: `let xs: [](size, size) = // [(1, 2)];`. Reshaping to a fixed `[12](...)` array would compile but // is an unfaithful workaround (CLAUDE.md rule-7), so the table — and // the `import ascii;` it needs — land with the consuming fold (compile) // once the checker gap is fixed. // ref/hare/regex/regex.ha:89-93. export type regex = struct { insts: []inst, charsets: []charset, n_reps: size, }; // Frees resources associated with a [[regex]]. // // ref/hare/regex/regex.ha:96-102, verbatim. The free() builtin is a // documented no-op (#27 landed): ww is a no-free runtime (rt/alloc.s:30 // — the bump allocator can't reclaim, process-exit does), so each free // below evaluates its operand and reclaims nothing. Kept verbatim for // API + source parity with the Hare surface. export fn finish(re: *regex) void = { free(re.insts); for (let charset .. re.charsets) { free(charset); }; free(re.charsets); }; // 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 the free() // builtin is a no-op anyway (#27 — ww is a no-free runtime), so the // cleanup would reclaim nothing. export fn compile(expr: str) (regex | error | nomem) = { // Hare `let insts: []inst = [];` — a bare ww slice declaration // zeroes the header (cgen.c:9836 no-rhs multi-word composite // zero-fill, symmetric in cgenstmt.ww). 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. // Hare appends the bare type name (`inst_skip`) as // the void-variant value; in ww that resolves as a // symbol ref (cf the alloc(T) rule), so void // variants go through a typed let. 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 append(insts, (r: inst_lit)); case '\\', '^', '$', '[', '(', ')', '|', '{', '?', '*', '+': // fold-2a boundary: regex.ha:286-459 arms deferred. return "regex: metacharacter not yet ported": error; case: // regex.ha:462-463 append(insts, (r: inst_lit)); }; 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)) { append(insts, (false: inst_match)); }; // regex.ha:479-484. The alternation fixup (ha:470-473) drops with // jump_idxs. return regex { insts = insts, charsets = charsets, n_reps = n_reps, }; };