lib/regex: fold-2b tranche B4 — run_thread (thread-machine core)

First end-to-end engine execution in tree: compile("ab")'s program
runs through skip-spawn / lit advance / match capture under the @test
drivers (search and the exec surface stay tranche C/D). Ported
Hare-verbatim from ref/hare/regex/regex.ha:589-742 — the #40 arg
wiring carries the ha:602 loop condition with no let-bind; arm bodies
stay verbatim so the group/repeat fold pastes straight into this
match. Arms compile() cannot emit are one loud not-yet-ported abort
each (the fold boundary); Hare's bare unreachable abort()s carry a
message because os.ww's private abort(msg) shadows the builtin
cross-module (filed, ww-core #45). The (anchored: bool)/(lit: rune)
casts are checker-required (ww aliases are nominal where Hare relies
on transparency), WHY-cited at site.
This commit is contained in:
2026-06-04 13:16:22 +09:00
parent d642017643
commit e481cb86bd
2 changed files with 233 additions and 20 deletions

View File

@@ -2,14 +2,13 @@
// ref/hare/regex/regex.ha. Fold 1 = the data model; fold 2a = the
// compile() literal core (lit/any/match + the leading skip); fold 2b
// tranche A = the thread-machine scaffolding (thread/newmatch types,
// result_free, strerror); fold 2b tranche B = the engine leaf fns
// (delete_thread/is_consuming_inst/add_thread — dead until search
// lands, fold-1 precedent). Every other metacharacter arm, the rest
// of the engine (run_thread, see its deferral note below; search on
// F5 element-copy + F2 len-builtin, tranche C) and the exec surface
// (test/find on the C6 multi-success `?` gate, tranche D; replace)
// are DEFERRED behind compiler fixes — probed pre-port, pA*/pB*/PB*
// probes. They land with those fixes.
// result_free, strerror); fold 2b tranche B = the engine
// (delete_thread/is_consuming_inst/add_thread/run_thread — dead
// until search lands, fold-1 precedent). Every other metacharacter
// arm, search (F5 element-copy + F2 len-builtin, tranche C) and the
// exec surface (test/find on the C6 multi-success `?` gate, tranche
// D; replace) are DEFERRED behind compiler fixes — probed pre-port,
// pA*/pB*/PB* probes. They land with those fixes.
//
// One fold-1 construct is held back behind a filed compiler/fidelity
// gap (see the charclass_map site below):
@@ -19,6 +18,7 @@
// uses exact type_eq, no element decay).
package regex;
import io;
import strings;
import encoding.utf8;
@@ -275,16 +275,107 @@ fn add_thread(threads: *[]thread, parent_idx: size, new_pc: size) (void | nomem)
return;
};
// run_thread (regex.ha:589-742) is deferred behind ONE remaining
// compiler boundary: its loop condition
// `is_consuming_inst(re.insts[threads[i].pc])` passes the 56B inst
// BY VALUE from a slice-element source, which the #38b arg wiring
// loud-rejects ("unsupported source kind 12" — ident sources landed
// with #19; the slice-element extension is task #40, in flight).
// Probed pre-port (PB6c); ruled: run_thread lands Hare-VERBATIM once
// #40 wires the source. Every other run_thread shape is probe-proven
// at HEAD (PB6b match-arm store + composed frombytes bound, P7
// composed scrutinee + compound pc step, P8 newmatch return).
// ref/hare/regex/regex.ha:589-742. Only the arms fold-2a's compile()
// can emit execute (skip + match non-consuming; lit + any consuming);
// every other inst arm is one loud not-yet-ported abort — the fold
// boundary, the compile() metachar discipline. The "unreachable"
// aborts on lit/any inside the non-consuming loop (ha:604-605) and
// the trailing default (ha:738) are Hare's own unreachable arms;
// Hare spells them bare `abort()`, ww carries a message — the
// zero-arg builtin form is shadowed in any combined unit that
// declares its own abort fn (os.ww:16 is private yet shadows
// cross-module; filed, ww-core #45). Hare's loop match omits inst_charset
// (consuming — the loop condition excludes it); ww has no
// match-exhaustiveness analysis, so the omission becomes the loud
// default arm.
fn run_thread(
i: size,
re: *regex,
string: str,
threads: *[]thread,
r_or_end: (rune | io.eof),
str_idx: size,
str_bytesize: size,
) (void | newmatch | nomem) = {
let str_bytes: []u8 = strings.toutf8(string);
if ((*threads)[i].matched) {
return;
};
for (!is_consuming_inst(re.insts[(*threads)[i].pc])) {
match (re.insts[(*threads)[i].pc]) {
case inst_lit => abort("regex: unreachable");
case inst_any => abort("regex: unreachable");
case inst_split =>
abort("regex: inst_split not yet ported");
case inst_jump =>
abort("regex: inst_jump not yet ported");
case inst_skip => {
let new_pc: size = (*threads)[i].pc + 1;
(*threads)[i].start_idx = str_idx;
(*threads)[i].start_bytesize = str_bytesize;
add_thread(threads, i, new_pc)?;
break;
};
case let anchored: inst_match => {
// Do not match if we need an end-anchored match, but we
// have not exhausted our string
//
// Hare spells `anchored` bare (ha:621) — alias
// transparency; ww named aliases are nominal in bool /
// comparison position, so this cast and the consuming
// arm's (lit: rune) are checker-required.
if ((anchored: bool) && !(r_or_end is io.eof)) {
(*threads)[i].failed = true;
return;
};
let content: str = strings.frombytes(str_bytes[
(*threads)[i].start_bytesize:str_bytesize]);
(*threads)[i].root_capture = capture {
start = (*threads)[i].start_idx,
start_bytesize = (*threads)[i].start_bytesize,
end = str_idx,
end_bytesize = str_bytesize,
content = content,
};
(*threads)[i].matched = true;
let nm: newmatch;
return nm;
};
case inst_groupstart =>
abort("regex: inst_groupstart not yet ported");
case inst_groupend =>
abort("regex: inst_groupend not yet ported");
case inst_repeat =>
abort("regex: inst_repeat not yet ported");
case => abort("regex: unreachable");
};
};
// From now on, we're only matching consuming instructions, and these
// can't do anything without another rune.
if (r_or_end is io.eof) {
(*threads)[i].failed = true;
return;
};
let r: rune = r_or_end as rune;
match (re.insts[(*threads)[i].pc]) {
case inst_skip => return;
case let lit: inst_lit => {
if (r != (lit: rune)) {
(*threads)[i].failed = true;
};
};
case inst_any => void;
case inst_charset =>
abort("regex: inst_charset not yet ported");
case => abort("regex: unreachable"); // unreachable (ha:738)
};
(*threads)[i].pc += 1;
return;
};
// Frees a [[result]].
//