Port is_consuming_inst (regex.ha:553-555), delete_thread (ha:547-551) and add_thread (ha:557-587) per the tranche-B scope (drew §9b). D12 chained-|| spells the multi-type is (parity task #13); D9 index loops replace the &.. ranges (#11); the dedup-scan bound reads .len, not the len() builtin (FB1, #41 — len(*p) loads the data pointer as the length). add_thread's capture/rep_counter dup is loud-bounded per the rob-ratified ruling: every ww route into the dup is blocked at HEAD (#35 spread source, #34 element source, #7 element let-copy), and fold-2a compile() cannot emit inst_groupstart/inst_repeat, so both parent slices are provably empty in every reachable program; the verbatim dup lands with the group/repeat fold (#3). run_thread (B4) stays out: its loop condition passes the 56B inst by value from a slice-element source, gated on the #38b extension (#40, in flight). Fixture cases 15-17 drive the three fns directly (package regex): all-10-kind consuming table, delete at middle/last/0-to-empty, dedup suppress/strict-</matched-guard + inheritance + zeroed headers. Byte-cmp on the regenerated combined holds the FC0-only baseline; both drivers run the fixture green.
567 lines
19 KiB
Plaintext
567 lines
19 KiB
Plaintext
// regex_test — exercises the lib/regex fold-1 data model (the type
|
|
// model + finish()), the fold-2a compile() literal core, and the
|
|
// fold-2b tranche-A/B thread machine (thread/newmatch + result_free
|
|
// + strerror; delete_thread/is_consuming_inst/add_thread; run_thread
|
|
// and the exec surface are deferred — see regex.ww). Run with
|
|
// `out/bin/ww run lib/regex/regex_test.ww`.
|
|
//
|
|
// Private symbols (thread, newmatch) are reached unqualified: this
|
|
// file declares `package regex`, so the import unifies it with the
|
|
// lib sources (the decimaltest precedent,
|
|
// lib/strconv/test/decimaltest.ww).
|
|
//
|
|
// 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
|
|
// discrimination (including the nominally-distinct same-underlying
|
|
// inst_split/inst_jump/inst_groupstart `size` aliases and the
|
|
// inst_any/inst_skip/inst_groupend `void` aliases), payload extraction,
|
|
// the regex/capture struct shapes, and finish(). Same
|
|
// signalled-then-fail()-with-+10 pattern as the rest of the stdlib
|
|
// run-tests; the non-zero exit pinpoints the failing case.
|
|
//
|
|
// Struct literals below name the type UNQUALIFIED (`inst_charset { … }`,
|
|
// not `regex.inst_charset { … }`): the parser rejects a module-qualified
|
|
// name in struct-literal position (#29), and the imported type
|
|
// is in scope unqualified.
|
|
package regex;
|
|
|
|
import regex;
|
|
import os;
|
|
import strings;
|
|
|
|
let signalled: i32 = 0;
|
|
fn fail() void = { os.exit(signalled + 10); };
|
|
|
|
// inst_lit / inst_match carry distinguishable payloads (rune / bool).
|
|
@test fn lit_and_match() void = {
|
|
let a: regex.inst = ('a': regex.inst_lit);
|
|
match (a) {
|
|
case let l: regex.inst_lit => { if ((l: rune) != 'a') { fail(); }; };
|
|
case => fail();
|
|
};
|
|
|
|
let m: regex.inst = (true: regex.inst_match);
|
|
match (m) {
|
|
case let b: regex.inst_match => { if (!(b: bool)) { fail(); }; };
|
|
case => fail();
|
|
};
|
|
};
|
|
|
|
// The three `size`-aliased variants are nominally distinct: a value
|
|
// built as inst_split must match inst_split, never inst_jump /
|
|
// inst_groupstart, despite identical underlying storage.
|
|
@test fn size_aliases_distinct() void = {
|
|
let sp: regex.inst = ((5: size): regex.inst_split);
|
|
match (sp) {
|
|
case let s: regex.inst_split => { if ((s: size) != (5: size)) { fail(); }; };
|
|
case let j: regex.inst_jump => fail();
|
|
case let g: regex.inst_groupstart => fail();
|
|
case => fail();
|
|
};
|
|
|
|
let jp: regex.inst = ((9: size): regex.inst_jump);
|
|
match (jp) {
|
|
case let j: regex.inst_jump => { if ((j: size) != (9: size)) { fail(); }; };
|
|
case let s: regex.inst_split => fail();
|
|
case => fail();
|
|
};
|
|
|
|
let gs: regex.inst = ((2: size): regex.inst_groupstart);
|
|
match (gs) {
|
|
case let g: regex.inst_groupstart => { if ((g: size) != (2: size)) { fail(); }; };
|
|
case let s: regex.inst_split => fail();
|
|
case => fail();
|
|
};
|
|
};
|
|
|
|
// The `void`-aliased variants are likewise nominally distinct.
|
|
@test fn void_aliases_distinct() void = {
|
|
let av: regex.inst_any;
|
|
let an: regex.inst = av;
|
|
match (an) {
|
|
case let a: regex.inst_any => void;
|
|
case let k: regex.inst_skip => fail();
|
|
case let e: regex.inst_groupend => fail();
|
|
case => fail();
|
|
};
|
|
|
|
let sv: regex.inst_skip;
|
|
let sk: regex.inst = sv;
|
|
match (sk) {
|
|
case let k: regex.inst_skip => void;
|
|
case let a: regex.inst_any => fail();
|
|
case => fail();
|
|
};
|
|
|
|
let gv: regex.inst_groupend;
|
|
let ge: regex.inst = gv;
|
|
match (ge) {
|
|
case let e: regex.inst_groupend => void;
|
|
case let a: regex.inst_any => fail();
|
|
case let k: regex.inst_skip => fail();
|
|
case => fail();
|
|
};
|
|
};
|
|
|
|
// inst_charset carries a struct payload; its fields survive the union
|
|
// round-trip.
|
|
@test fn charset_payload() void = {
|
|
let c: regex.inst = (inst_charset { idx = 3, is_positive = true });
|
|
match (c) {
|
|
case let cs: regex.inst_charset => {
|
|
if (cs.idx != (3: size)) { fail(); };
|
|
if (!cs.is_positive) { fail(); };
|
|
};
|
|
case => fail();
|
|
};
|
|
};
|
|
|
|
// inst_repeat round-trips through the inst union with its plain `size`
|
|
// fields intact. Matching the nested (void | size) min/max bounds back
|
|
// out is DEFERRED: `match` on a tagged-union-typed struct field
|
|
// diverges cs≠ww (#26 — the wwstage frames it wider), so
|
|
// asserting the bounds here would seed a rule-10-divergent fixture.
|
|
@test fn repeat_payload() void = {
|
|
let r: regex.inst = (inst_repeat {
|
|
id = 1, origin = 4, min = (2: size), max = void,
|
|
});
|
|
match (r) {
|
|
case let rp: regex.inst_repeat => {
|
|
if (rp.id != (1: size)) { fail(); };
|
|
if (rp.origin != (4: size)) { fail(); };
|
|
};
|
|
case => fail();
|
|
};
|
|
};
|
|
|
|
// The regex/capture structs hold their fields; finish() is a no-op
|
|
// (no-free runtime) and must accept a built regex.
|
|
@test fn struct_shapes_and_finish() void = {
|
|
let cap: regex.capture = capture {
|
|
content = "abc",
|
|
start = 0,
|
|
start_bytesize = 0,
|
|
end = 3,
|
|
end_bytesize = 3,
|
|
};
|
|
if (cap.content.len != 3) { fail(); };
|
|
if (cap.end != (3: size)) { fail(); };
|
|
|
|
// regex's insts/charsets ([]inst / []charset) are left empty here:
|
|
// fold 1 ports no compile() to populate them, an empty `[]` literal
|
|
// is unspellable as a typed slice (#25 — array→slice
|
|
// element-coercion gap), and a struct-literal slice-field store
|
|
// drops len/cap (#24). Declaring the regex zeroes both
|
|
// slice headers to {0,0,0}; only n_reps is set explicitly.
|
|
let re: regex.regex;
|
|
re.n_reps = 0;
|
|
if (re.n_reps != (0: size)) { fail(); };
|
|
if (re.insts.len != 0) { fail(); };
|
|
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();
|
|
};
|
|
};
|
|
|
|
// Every deferred metacharacter is a LOUD error carrying the exact
|
|
// fold-boundary text — falling through to the literal default would
|
|
// silently compile a wrong program, and any OTHER error text would
|
|
// mean an arm was half-ported. One pattern per deferred arm so the
|
|
// fold that ports an arm consciously deletes its row. '^' leads its
|
|
// pattern: the r_idx==0 skip gate (regex.ha:261) must compose with
|
|
// the loud arm, not bypass it.
|
|
@test fn compile_metachar_loud() void = {
|
|
let pats: [11]str = [
|
|
"a\\", "^a", "a$", "a[", "a(", "a)",
|
|
"a|", "a{", "a?", "a*", "a+",
|
|
];
|
|
let i: i32 = 0;
|
|
for (i < len(pats)) {
|
|
match (regex.compile(pats[i])) {
|
|
case let e: regex.error => {
|
|
if (strings.compare((e: str),
|
|
"regex: metacharacter not yet ported") != 0) {
|
|
fail();
|
|
};
|
|
};
|
|
case => fail();
|
|
};
|
|
i += 1;
|
|
};
|
|
};
|
|
|
|
// The thread struct (regex.ha:55-64) is in tree ahead of its engine
|
|
// consumers so the pending #15/#17 fix probes exercise the real type.
|
|
// Pin the field layout via the P6-proven wide-literal append + a
|
|
// depth-1 read-back row per appended thread; the Hare `...` partial
|
|
// fill (P5) must zero everything the second row's literal omits.
|
|
// root_capture has NO row: every read route into it is
|
|
// compiler-blocked today — the depth-2 chain behind the index links
|
|
// the field as a global (#6 F4), the element let-copy is #7 F5, and
|
|
// a probed `&threads[i].root_capture` deref segfaults byte-id on
|
|
// both stages — so its row lands with those fixes.
|
|
type texp = struct {
|
|
pc: size,
|
|
start_idx: size,
|
|
start_bytesize: size,
|
|
matched: bool,
|
|
failed: bool,
|
|
// .len reads as i32 (check.c:1239), so the count columns match it
|
|
ncaps: i32,
|
|
nreps: i32,
|
|
};
|
|
|
|
@test fn thread_shape() void = {
|
|
let rc: capture = capture {
|
|
content = "ab", start = 1, start_bytesize = 1,
|
|
end = 2, end_bytesize = 2,
|
|
};
|
|
let pcaps: []capture = [];
|
|
append(pcaps, rc);
|
|
let prep: []size = [];
|
|
append(prep, (7: size));
|
|
let threads: []thread = [];
|
|
append(threads, thread {
|
|
pc = 5,
|
|
start_idx = 6,
|
|
start_bytesize = 7,
|
|
root_capture = rc,
|
|
captures = pcaps,
|
|
rep_counters = prep,
|
|
matched = false,
|
|
failed = true,
|
|
});
|
|
append(threads, thread { pc = 9, ... });
|
|
let want: [2]texp = [
|
|
texp { pc = 5, start_idx = 6, start_bytesize = 7,
|
|
matched = false, failed = true,
|
|
ncaps = 1, nreps = 1 },
|
|
texp { pc = 9, start_idx = 0, start_bytesize = 0,
|
|
matched = false, failed = false,
|
|
ncaps = 0, nreps = 0 },
|
|
];
|
|
if (len(threads) != len(want)) { fail(); };
|
|
let i: i32 = 0;
|
|
for (i < len(want)) {
|
|
if (threads[i].pc != want[i].pc) { fail(); };
|
|
if (threads[i].start_idx != want[i].start_idx) { fail(); };
|
|
if (threads[i].start_bytesize != want[i].start_bytesize) {
|
|
fail();
|
|
};
|
|
if (threads[i].matched != want[i].matched) { fail(); };
|
|
if (threads[i].failed != want[i].failed) { fail(); };
|
|
if (threads[i].captures.len != want[i].ncaps) { fail(); };
|
|
if (threads[i].rep_counters.len != want[i].nreps) { fail(); };
|
|
i += 1;
|
|
};
|
|
};
|
|
|
|
// newmatch (regex.ha:66) must discriminate nominally against plain
|
|
// void — and against nomem, the third payload-free member — across
|
|
// run_thread's (void | newmatch | nomem) return boundary: the P8
|
|
// shape on the real lib type, one row per returned member.
|
|
fn nm_probe(x: i32) (void | newmatch | nomem) = {
|
|
if (x == 1) {
|
|
let nm: newmatch;
|
|
return nm;
|
|
};
|
|
if (x == 2) {
|
|
let n: nomem;
|
|
return n;
|
|
};
|
|
return;
|
|
};
|
|
|
|
type nmexp = struct {
|
|
arg: i32,
|
|
want_nm: bool,
|
|
want_void: bool,
|
|
want_nomem: bool,
|
|
};
|
|
|
|
@test fn newmatch_discriminates() void = {
|
|
let rows: [3]nmexp = [
|
|
nmexp { arg = 1, want_nm = true, want_void = false,
|
|
want_nomem = false },
|
|
nmexp { arg = 0, want_nm = false, want_void = true,
|
|
want_nomem = false },
|
|
nmexp { arg = 2, want_nm = false, want_void = false,
|
|
want_nomem = true },
|
|
];
|
|
let i: i32 = 0;
|
|
for (i < len(rows)) {
|
|
let r: (void | newmatch | nomem) = nm_probe(rows[i].arg);
|
|
if ((r is newmatch) != rows[i].want_nm) { fail(); };
|
|
if ((r is void) != rows[i].want_void) { fail(); };
|
|
if ((r is nomem) != rows[i].want_nomem) { fail(); };
|
|
i += 1;
|
|
};
|
|
};
|
|
|
|
// result_free (regex.ha:1114-1116) accepts a built result; free() is
|
|
// the documented no-op (no-free runtime), so the header must stay
|
|
// readable after — a future real free changes this row consciously.
|
|
// The local is spelled []regex.capture, not the regex.result alias:
|
|
// wwstage falsely loud-bails appending a struct literal onto an
|
|
// alias-typed dst (#20); the alias + signature stay exercised by the
|
|
// result_free call itself. Reverts to `regex.result` when #20 lands.
|
|
@test fn result_free_noop() void = {
|
|
let res: []regex.capture;
|
|
append(res, capture {
|
|
content = "x", start = 0, start_bytesize = 0,
|
|
end = 1, end_bytesize = 1,
|
|
});
|
|
regex.result_free(res);
|
|
if (len(res) != 1) { fail(); };
|
|
if (res[0].end != (1: size)) { fail(); };
|
|
// The zero-header edge: find()'s no-match path returns an empty
|
|
// result (regex.ha:915-916) the caller still result_free()s. The
|
|
// bare decl is alias-typed — the #20 dodge above is append-only,
|
|
// so the alias stays exercised in value position here.
|
|
let empty: regex.result;
|
|
regex.result_free(empty);
|
|
if (len(empty) != 0) { fail(); };
|
|
};
|
|
|
|
// strerror (regex.ha:1127) is identity on the boundary text — routed
|
|
// through a REAL compile() error, completing the exported error
|
|
// surface end to end.
|
|
@test fn strerror_identity() void = {
|
|
match (regex.compile("a*")) {
|
|
case let e: regex.error => {
|
|
if (strings.compare(regex.strerror(e),
|
|
"regex: metacharacter not yet ported") != 0) {
|
|
fail();
|
|
};
|
|
};
|
|
case => fail();
|
|
};
|
|
};
|
|
|
|
// is_consuming_inst must discriminate the three consuming kinds from
|
|
// the seven non-consuming ones across all 10 inst variants
|
|
// (regex.ha:553-555) — the tranche-A-deferred row, graduated by the
|
|
// #19 >48B by-value arg wiring. Sequential typed-let + helper calls,
|
|
// not a [10](inst, bool) table: tagged-element array literals
|
|
// under-copy (#12), and a cast/literal rvalue arg source is
|
|
// #38b-unwired, so each value goes through a typed let (the
|
|
// #19-landed ident source).
|
|
fn ic_one(v: regex.inst, want: bool) void = {
|
|
if (is_consuming_inst(v) != want) { fail(); };
|
|
};
|
|
|
|
@test fn is_consuming_kinds() void = {
|
|
let lit: regex.inst = ('a': regex.inst_lit);
|
|
ic_one(lit, true);
|
|
let av: regex.inst_any;
|
|
let any: regex.inst = av;
|
|
ic_one(any, true);
|
|
let cs: regex.inst = (inst_charset { idx = 0, is_positive = true });
|
|
ic_one(cs, true);
|
|
let kv: regex.inst_skip;
|
|
let sk: regex.inst = kv;
|
|
ic_one(sk, false);
|
|
let sp: regex.inst = ((5: size): regex.inst_split);
|
|
ic_one(sp, false);
|
|
let jm: regex.inst = ((6: size): regex.inst_jump);
|
|
ic_one(jm, false);
|
|
let mt: regex.inst = (false: regex.inst_match);
|
|
ic_one(mt, false);
|
|
let gs: regex.inst = ((2: size): regex.inst_groupstart);
|
|
ic_one(gs, false);
|
|
let gv: regex.inst_groupend;
|
|
let ge: regex.inst = gv;
|
|
ic_one(ge, false);
|
|
let rp: regex.inst = (inst_repeat {
|
|
id = 1, origin = 4, min = (2: size), max = void,
|
|
});
|
|
ic_one(rp, false);
|
|
};
|
|
|
|
// delete_thread (regex.ha:547-551) removes exactly the indexed
|
|
// element and preserves order; its frees are no-ops (no-free
|
|
// runtime), so the survivors' capture headers stay readable.
|
|
@test fn delete_thread_middle() void = {
|
|
let caps: []regex.capture = [];
|
|
append(caps, capture {
|
|
content = "x", start = 0, start_bytesize = 0,
|
|
end = 1, end_bytesize = 1,
|
|
});
|
|
let ts: []thread = [];
|
|
append(ts, thread { pc = 1, start_idx = 11, captures = caps, ... });
|
|
append(ts, thread { pc = 2, start_idx = 22, ... });
|
|
append(ts, thread { pc = 3, start_idx = 33, ... });
|
|
delete_thread(1, &ts);
|
|
if (len(ts) != 2) { fail(); };
|
|
if (ts[0].pc != (1: size)) { fail(); };
|
|
if (ts[0].start_idx != (11: size)) { fail(); };
|
|
if (ts[0].captures.len != 1) { fail(); };
|
|
if (ts[1].pc != (3: size)) { fail(); };
|
|
if (ts[1].start_idx != (33: size)) { fail(); };
|
|
if (ts[1].captures.len != 0) { fail(); };
|
|
// boundary rows: delete at the last index, then at index 0 down
|
|
// to empty — the failed-sweep loop (regex.ha:891-896) deletes at
|
|
// every position including both ends.
|
|
delete_thread(1, &ts);
|
|
if (len(ts) != 1) { fail(); };
|
|
if (ts[0].pc != (1: size)) { fail(); };
|
|
delete_thread(0, &ts);
|
|
if (len(ts) != 0) { fail(); };
|
|
};
|
|
|
|
// add_thread (regex.ha:557-587): same-pc dedup suppression fires only
|
|
// when the existing thread is unmatched AND started strictly earlier
|
|
// than the parent (ha:561-565); otherwise the child appends,
|
|
// inheriting the parent's start/matched/failed with fresh empty
|
|
// capture/rep_counter headers and a zeroed root_capture. The
|
|
// capture-dup loud bound must NOT fire on these empty-caps parents.
|
|
@test fn add_thread_dedup_inherit() void = {
|
|
let ts: []thread = [];
|
|
append(ts, thread { pc = 0, start_idx = 5, start_bytesize = 4,
|
|
matched = false, failed = true, ... });
|
|
// inherit: fresh pc, parent fields copied, rest zeroed
|
|
let r: (void | nomem) = add_thread(&ts, 0, 7);
|
|
if (!(r is void)) { fail(); };
|
|
if (len(ts) != 2) { fail(); };
|
|
if (ts[1].pc != (7: size)) { fail(); };
|
|
if (ts[1].start_idx != (5: size)) { fail(); };
|
|
if (ts[1].start_bytesize != (4: size)) { fail(); };
|
|
if (ts[1].matched) { fail(); };
|
|
if (!ts[1].failed) { fail(); };
|
|
if (ts[1].captures.len != 0) { fail(); };
|
|
if (ts[1].rep_counters.len != 0) { fail(); };
|
|
if (ts[1].root_capture.content.len != 0) { fail(); };
|
|
if (ts[1].root_capture.end != (0: size)) { fail(); };
|
|
// same-pc same-start does NOT suppress (strict <, ha:563-565)
|
|
let r2: (void | nomem) = add_thread(&ts, 0, 7);
|
|
if (!(r2 is void)) { fail(); };
|
|
if (len(ts) != 3) { fail(); };
|
|
// an earlier-started unmatched existing thread DOES suppress
|
|
let ts2: []thread = [];
|
|
append(ts2, thread { pc = 0, start_idx = 5, ... });
|
|
append(ts2, thread { pc = 7, start_idx = 2, ... });
|
|
let r3: (void | nomem) = add_thread(&ts2, 0, 7);
|
|
if (!(r3 is void)) { fail(); };
|
|
if (len(ts2) != 2) { fail(); };
|
|
// a MATCHED existing thread never suppresses
|
|
let ts3: []thread = [];
|
|
append(ts3, thread { pc = 0, start_idx = 5, ... });
|
|
append(ts3, thread { pc = 7, start_idx = 2, matched = true, ... });
|
|
let r4: (void | nomem) = add_thread(&ts3, 0, 7);
|
|
if (!(r4 is void)) { fail(); };
|
|
if (len(ts3) != 3) { fail(); };
|
|
if (ts3[2].pc != (7: size)) { fail(); };
|
|
if (ts3[2].start_idx != (5: size)) { fail(); };
|
|
};
|
|
|
|
export fn main() i32 = {
|
|
signalled = 1; lit_and_match();
|
|
signalled = 2; size_aliases_distinct();
|
|
signalled = 3; void_aliases_distinct();
|
|
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();
|
|
signalled = 11; thread_shape();
|
|
signalled = 12; newmatch_discriminates();
|
|
signalled = 13; result_free_noop();
|
|
signalled = 14; strerror_identity();
|
|
signalled = 15; is_consuming_kinds();
|
|
signalled = 16; delete_thread_middle();
|
|
signalled = 17; add_thread_dedup_inherit();
|
|
return 0;
|
|
};
|