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:
@@ -2,14 +2,13 @@
|
|||||||
// ref/hare/regex/regex.ha. Fold 1 = the data model; fold 2a = the
|
// ref/hare/regex/regex.ha. Fold 1 = the data model; fold 2a = the
|
||||||
// compile() literal core (lit/any/match + the leading skip); fold 2b
|
// compile() literal core (lit/any/match + the leading skip); fold 2b
|
||||||
// tranche A = the thread-machine scaffolding (thread/newmatch types,
|
// tranche A = the thread-machine scaffolding (thread/newmatch types,
|
||||||
// result_free, strerror); fold 2b tranche B = the engine leaf fns
|
// result_free, strerror); fold 2b tranche B = the engine
|
||||||
// (delete_thread/is_consuming_inst/add_thread — dead until search
|
// (delete_thread/is_consuming_inst/add_thread/run_thread — dead
|
||||||
// lands, fold-1 precedent). Every other metacharacter arm, the rest
|
// until search lands, fold-1 precedent). Every other metacharacter
|
||||||
// of the engine (run_thread, see its deferral note below; search on
|
// arm, search (F5 element-copy + F2 len-builtin, tranche C) and the
|
||||||
// F5 element-copy + F2 len-builtin, tranche C) and the exec surface
|
// exec surface (test/find on the C6 multi-success `?` gate, tranche
|
||||||
// (test/find on the C6 multi-success `?` gate, tranche D; replace)
|
// D; replace) are DEFERRED behind compiler fixes — probed pre-port,
|
||||||
// are DEFERRED behind compiler fixes — probed pre-port, pA*/pB*/PB*
|
// pA*/pB*/PB* probes. They land with those fixes.
|
||||||
// probes. They land with those fixes.
|
|
||||||
//
|
//
|
||||||
// One fold-1 construct is held back behind a filed compiler/fidelity
|
// One fold-1 construct is held back behind a filed compiler/fidelity
|
||||||
// gap (see the charclass_map site below):
|
// gap (see the charclass_map site below):
|
||||||
@@ -19,6 +18,7 @@
|
|||||||
// uses exact type_eq, no element decay).
|
// uses exact type_eq, no element decay).
|
||||||
package regex;
|
package regex;
|
||||||
|
|
||||||
|
import io;
|
||||||
import strings;
|
import strings;
|
||||||
import encoding.utf8;
|
import encoding.utf8;
|
||||||
|
|
||||||
@@ -275,16 +275,107 @@ fn add_thread(threads: *[]thread, parent_idx: size, new_pc: size) (void | nomem)
|
|||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
// run_thread (regex.ha:589-742) is deferred behind ONE remaining
|
// ref/hare/regex/regex.ha:589-742. Only the arms fold-2a's compile()
|
||||||
// compiler boundary: its loop condition
|
// can emit execute (skip + match non-consuming; lit + any consuming);
|
||||||
// `is_consuming_inst(re.insts[threads[i].pc])` passes the 56B inst
|
// every other inst arm is one loud not-yet-ported abort — the fold
|
||||||
// BY VALUE from a slice-element source, which the #38b arg wiring
|
// boundary, the compile() metachar discipline. The "unreachable"
|
||||||
// loud-rejects ("unsupported source kind 12" — ident sources landed
|
// aborts on lit/any inside the non-consuming loop (ha:604-605) and
|
||||||
// with #19; the slice-element extension is task #40, in flight).
|
// the trailing default (ha:738) are Hare's own unreachable arms;
|
||||||
// Probed pre-port (PB6c); ruled: run_thread lands Hare-VERBATIM once
|
// Hare spells them bare `abort()`, ww carries a message — the
|
||||||
// #40 wires the source. Every other run_thread shape is probe-proven
|
// zero-arg builtin form is shadowed in any combined unit that
|
||||||
// at HEAD (PB6b match-arm store + composed frombytes bound, P7
|
// declares its own abort fn (os.ww:16 is private yet shadows
|
||||||
// composed scrutinee + compound pc step, P8 newmatch return).
|
// 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]].
|
// Frees a [[result]].
|
||||||
//
|
//
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
// regex_test — exercises the lib/regex fold-1 data model (the type
|
// regex_test — exercises the lib/regex fold-1 data model (the type
|
||||||
// model + finish()), the fold-2a compile() literal core, and the
|
// model + finish()), the fold-2a compile() literal core, and the
|
||||||
// fold-2b tranche-A/B thread machine (thread/newmatch + result_free
|
// fold-2b tranche-A/B thread machine (thread/newmatch + result_free
|
||||||
// + strerror; delete_thread/is_consuming_inst/add_thread; run_thread
|
// + strerror; delete_thread/is_consuming_inst/add_thread/run_thread;
|
||||||
// and the exec surface are deferred — see regex.ww). Run with
|
// search and the exec surface are deferred — see regex.ww). Run with
|
||||||
// `out/bin/ww run lib/regex/regex_test.ww`.
|
// `out/bin/ww run lib/regex/regex_test.ww`.
|
||||||
//
|
//
|
||||||
// Private symbols (thread, newmatch) are reached unqualified: this
|
// Private symbols (thread, newmatch) are reached unqualified: this
|
||||||
@@ -30,6 +30,7 @@
|
|||||||
package regex;
|
package regex;
|
||||||
|
|
||||||
import regex;
|
import regex;
|
||||||
|
import io;
|
||||||
import os;
|
import os;
|
||||||
import strings;
|
import strings;
|
||||||
|
|
||||||
@@ -544,6 +545,125 @@ fn ic_one(v: regex.inst, want: bool) void = {
|
|||||||
if (ts3[2].start_idx != (5: size)) { fail(); };
|
if (ts3[2].start_idx != (5: size)) { fail(); };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// run_thread (regex.ha:589-742) driven directly over compile("ab")'s
|
||||||
|
// real program [skip, lit 'a', lit 'b', match(false)] — the arms
|
||||||
|
// fold-2a can emit. Phases: parked-skip spawn (len 1→2, parent pc
|
||||||
|
// unmoved — the unanchored-restart engine), lit advance, lit
|
||||||
|
// mismatch (failed=true AND pc still steps — ha:741 runs regardless
|
||||||
|
// of the arm's verdict), EOF on a consuming pc (failed, pc frozen),
|
||||||
|
// match arm (root_capture spans start_bytesize..str_bytesize +
|
||||||
|
// matched + `is newmatch`), and the matched-thread early return
|
||||||
|
// (ha:599-601).
|
||||||
|
@test fn run_thread_literal_program() void = {
|
||||||
|
// typed-let + match receive, the compile_literal_program shape
|
||||||
|
let c: (regex.regex | regex.error | nomem) = regex.compile("ab");
|
||||||
|
match (c) {
|
||||||
|
case let re: regex.regex => {
|
||||||
|
// skip spawn: thread 0 parks on the skip, child enters at pc 1
|
||||||
|
let ra: (rune | io.eof) = 'a';
|
||||||
|
let ts: []thread = [];
|
||||||
|
append(ts, thread { pc = 0, ... });
|
||||||
|
let r1: (void | newmatch | nomem) = run_thread(0, &re, "ab", &ts, ra, 0, 0);
|
||||||
|
if (!(r1 is void)) { fail(); };
|
||||||
|
if (len(ts) != 2) { fail(); };
|
||||||
|
if (ts[0].pc != (0: size)) { fail(); };
|
||||||
|
if (ts[1].pc != (1: size)) { fail(); };
|
||||||
|
if (ts[1].failed) { fail(); };
|
||||||
|
|
||||||
|
// lit match advances pc past 'a'
|
||||||
|
let r2: (void | newmatch | nomem) = run_thread(1, &re, "ab", &ts, ra, 0, 0);
|
||||||
|
if (!(r2 is void)) { fail(); };
|
||||||
|
if (ts[1].pc != (2: size)) { fail(); };
|
||||||
|
if (ts[1].failed) { fail(); };
|
||||||
|
|
||||||
|
// lit mismatch fails the thread; pc steps anyway (ha:741)
|
||||||
|
let rx: (rune | io.eof) = 'x';
|
||||||
|
let r3: (void | newmatch | nomem) = run_thread(1, &re, "ab", &ts, rx, 1, 1);
|
||||||
|
if (!(r3 is void)) { fail(); };
|
||||||
|
if (!ts[1].failed) { fail(); };
|
||||||
|
if (ts[1].pc != (3: size)) { fail(); };
|
||||||
|
|
||||||
|
// EOF on a consuming pc fails the thread before pc steps
|
||||||
|
let ev: io.eof;
|
||||||
|
let reof: (rune | io.eof) = ev;
|
||||||
|
let ts2: []thread = [];
|
||||||
|
append(ts2, thread { pc = 1, ... });
|
||||||
|
let r4: (void | newmatch | nomem) = run_thread(0, &re, "ab", &ts2, reof, 2, 2);
|
||||||
|
if (!(r4 is void)) { fail(); };
|
||||||
|
if (!ts2[0].failed) { fail(); };
|
||||||
|
if (ts2[0].pc != (1: size)) { fail(); };
|
||||||
|
|
||||||
|
// match arm: root_capture spans start_bytesize..str_bytesize,
|
||||||
|
// matched set, newmatch returned
|
||||||
|
let ts3: []thread = [];
|
||||||
|
append(ts3, thread { pc = 3, ... });
|
||||||
|
let r5: (void | newmatch | nomem) = run_thread(0, &re, "ab", &ts3, reof, 2, 2);
|
||||||
|
if (!(r5 is newmatch)) { fail(); };
|
||||||
|
if (!ts3[0].matched) { fail(); };
|
||||||
|
if (ts3[0].failed) { fail(); };
|
||||||
|
if (ts3[0].root_capture.start != (0: size)) { fail(); };
|
||||||
|
if (ts3[0].root_capture.start_bytesize != (0: size)) { fail(); };
|
||||||
|
if (ts3[0].root_capture.end != (2: size)) { fail(); };
|
||||||
|
if (ts3[0].root_capture.end_bytesize != (2: size)) { fail(); };
|
||||||
|
if (strings.compare(ts3[0].root_capture.content, "ab") != 0) { fail(); };
|
||||||
|
|
||||||
|
// an already-matched thread is inert (ha:599-601): void
|
||||||
|
// return, state untouched
|
||||||
|
let r6: (void | newmatch | nomem) = run_thread(0, &re, "ab", &ts3, ra, 3, 3);
|
||||||
|
if (!(r6 is void)) { fail(); };
|
||||||
|
if (ts3[0].root_capture.end != (2: size)) { fail(); };
|
||||||
|
|
||||||
|
// idx/bytesize split: every all-ASCII row has idx ==
|
||||||
|
// bytesize, so a port swapping start/start_bytesize (or
|
||||||
|
// end/end_bytesize) in root_capture passes them. One 2-byte
|
||||||
|
// rune ('ß') consumed before the match start makes all four
|
||||||
|
// values distinct: start=1 start_bytesize=2 end=3
|
||||||
|
// end_bytesize=4; content = bytes[2:4] = "ab".
|
||||||
|
let ts4: []thread = [];
|
||||||
|
append(ts4, thread { pc = 3, start_idx = 1, start_bytesize = 2, ... });
|
||||||
|
let r7: (void | newmatch | nomem) = run_thread(0, &re, "ßab", &ts4, reof, 3, 4);
|
||||||
|
if (!(r7 is newmatch)) { fail(); };
|
||||||
|
if (ts4[0].root_capture.start != (1: size)) { fail(); };
|
||||||
|
if (ts4[0].root_capture.start_bytesize != (2: size)) { fail(); };
|
||||||
|
if (ts4[0].root_capture.end != (3: size)) { fail(); };
|
||||||
|
if (ts4[0].root_capture.end_bytesize != (4: size)) { fail(); };
|
||||||
|
if (strings.compare(ts4[0].root_capture.content, "ab") != 0) { fail(); };
|
||||||
|
regex.finish(&re);
|
||||||
|
};
|
||||||
|
case => fail();
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// The anchored route (ha:621-624) needs a (true: inst_match) program
|
||||||
|
// — compile() can't emit `$` yet, so it is HAND-BUILT — pinned from
|
||||||
|
// both sides: anchored + string-not-exhausted fails the thread;
|
||||||
|
// anchored + EOF falls through to the match (empty content).
|
||||||
|
@test fn run_thread_anchored_route() void = {
|
||||||
|
let insts: []regex.inst = [];
|
||||||
|
append(insts, (true: regex.inst_match));
|
||||||
|
let re: regex.regex;
|
||||||
|
re.insts = insts;
|
||||||
|
re.n_reps = 0;
|
||||||
|
|
||||||
|
let ra: (rune | io.eof) = 'a';
|
||||||
|
let ts: []thread = [];
|
||||||
|
append(ts, thread { pc = 0, ... });
|
||||||
|
let r1: (void | newmatch | nomem) = run_thread(0, &re, "ab", &ts, ra, 0, 0);
|
||||||
|
if (!(r1 is void)) { fail(); };
|
||||||
|
if (!ts[0].failed) { fail(); };
|
||||||
|
if (ts[0].matched) { fail(); };
|
||||||
|
|
||||||
|
let ev: io.eof;
|
||||||
|
let reof: (rune | io.eof) = ev;
|
||||||
|
let ts2: []thread = [];
|
||||||
|
append(ts2, thread { pc = 0, ... });
|
||||||
|
let r2: (void | newmatch | nomem) = run_thread(0, &re, "", &ts2, reof, 0, 0);
|
||||||
|
if (!(r2 is newmatch)) { fail(); };
|
||||||
|
if (!ts2[0].matched) { fail(); };
|
||||||
|
if (ts2[0].root_capture.content.len != 0) { fail(); };
|
||||||
|
if (ts2[0].root_capture.end != (0: size)) { fail(); };
|
||||||
|
};
|
||||||
|
|
||||||
export fn main() i32 = {
|
export fn main() i32 = {
|
||||||
signalled = 1; lit_and_match();
|
signalled = 1; lit_and_match();
|
||||||
signalled = 2; size_aliases_distinct();
|
signalled = 2; size_aliases_distinct();
|
||||||
@@ -562,5 +682,7 @@ export fn main() i32 = {
|
|||||||
signalled = 15; is_consuming_kinds();
|
signalled = 15; is_consuming_kinds();
|
||||||
signalled = 16; delete_thread_middle();
|
signalled = 16; delete_thread_middle();
|
||||||
signalled = 17; add_thread_dedup_inherit();
|
signalled = 17; add_thread_dedup_inherit();
|
||||||
|
signalled = 18; run_thread_literal_program();
|
||||||
|
signalled = 19; run_thread_anchored_route();
|
||||||
return 0;
|
return 0;
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user