Compile regex literals to native matchers

This commit is contained in:
Chris Tate
2026-10-13 11:30:19 -05:00
parent 284eb17938
commit bf40cf4255
30 changed files with 2544 additions and 22 deletions
+1 -1
View File
@@ -70,7 +70,7 @@ Without `-o`, artifacts are written to `.scriptc/` next to the input. Different
### External linking
Program objects define `main` and leave selected `scr_*` runtime functions undefined. A strong reference to `scr_runtime_abi_v16` checks runtime compatibility at link time.
Program objects define `main` and leave selected `scr_*` runtime functions undefined. A strong reference to `scr_runtime_abi_v17` checks runtime compatibility at link time.
`--print=native-link-info` emits the program object and a JSON recipe containing the runtime pack and link order for external builds. This object ABI is experimental and requires an exact runtime version. See [Native Program Objects](/docs/native-objects) for examples.
+1 -1
View File
@@ -15,4 +15,4 @@ $ ./app-ld
`cc` uses the C compiler as a linker driver. `ld` links the same precompiled runtime objects directly with the selected Apple SDK. The object defines `main`; it is a complete program object, not a library to load into another process. Use `scriptc build --lib --profile ...` for a host-callable static library.
The external object ABI is experimental. Always consume the runtime pack at the exact `runtime_pack.version` reported by the same scriptc installation. The object requires `scr_runtime_abi_v16`, so a mismatched runtime fails during the link instead of starting with an incompatible ABI.
The external object ABI is experimental. Always consume the runtime pack at the exact `runtime_pack.version` reported by the same scriptc installation. The object requires `scr_runtime_abi_v17`, so a mismatched runtime fails during the link instead of starting with an incompatible ABI.
+2 -2
View File
@@ -72,7 +72,7 @@ describe.runIf(supported)("macOS arm64 native link info", () => {
minimum_os: "14.0",
},
program: { object, entry_symbol: "main" },
runtime_abi: { version: 16, marker: "scr_runtime_abi_v16" },
runtime_abi: { version: 17, marker: "scr_runtime_abi_v17" },
runtime_pack: {
kind: "precompiled",
package: "@scriptc/runtime-darwin-arm64",
@@ -165,6 +165,6 @@ describe.runIf(supported)("macOS arm64 native link info", () => {
() => null,
(failure: { stderr?: string }) => failure,
);
expect(error?.stderr).toContain("scr_runtime_abi_v16");
expect(error?.stderr).toContain("scr_runtime_abi_v17");
});
});
+26 -6
View File
@@ -1,4 +1,5 @@
import { emitLlvmLayouts } from "./layouts.js";
import { compileRegexMatcher, type RegexMatcherCode } from "./regex-matchers.js";
import { FIRST_EMITTED_CYC_KIND, inlineAllocSupported } from "./alloc.js";
import { llvmBytes as llBytes } from "../literals.js";
import { InternalCompilerError } from "../../errors.js";
@@ -552,7 +553,10 @@ export class LlEmitter {
* (pattern, flags) pair; the bytecode slot starts null and the runtime
* compiles it lazily on first use. The source/flags strings intern at
* REGISTRATION (bodies emit before the literal table flushes). */
readonly regexInstances = new Map<string, { sym: string; src: string; fl: string }>();
readonly regexInstances = new Map<
string,
{ sym: string; src: string; fl: string; pattern: string; flags: string }
>();
/** Interned tagged-template strings objects: per-site key → { symbol,
* interned cooked-literal refs }. One immortal ScrArr of string slots
* per template SITE (the spec's per-occurrence identity — the C
@@ -1851,6 +1855,20 @@ export class LlEmitter {
];
if (this.constantNumericTables.size > 0)
this.declare(`declare double @scr_arr_get_number(ptr, double)`);
// Native matchers of the regex literals inside the compiled subset.
const regexMatchers = new Map<string, RegexMatcherCode>();
for (const [key, re] of this.regexInstances) {
const code = compileRegexMatcher(
`${re.sym}_match`,
re.pattern,
re.flags,
this.sizeType,
!this.wasi,
);
if (code === null) continue;
regexMatchers.set(key, code);
for (const decl of code.decls) this.declare(decl);
}
const out: string[] = [
`; Generated by scriptc (LLVM backend) from ${this.mod.sourceFile}. Do not edit.`,
@@ -1867,7 +1885,7 @@ export class LlEmitter {
`%ScrUnion = type { ${this.sizeType}, i32, ptr, ptr, ptr, i64 }`,
`%ScrClosure = type { ${this.sizeType}, ptr, ${this.sizeType}, ptr, i32, i32, ptr, ptr }`,
`%ScrFfiTable = type { ptr, ${this.sizeType}, ${this.sizeType}, ptr, i8, ptr, ptr, ${this.sizeType}, ${this.sizeType}, ${this.sizeType}, ptr, ptr }`,
`%ScrRegex = type { ${this.sizeType}, ptr, ptr, ptr, double, ptr, ptr }`,
`%ScrRegex = type { ${this.sizeType}, ptr, ptr, ptr, double, ptr, ptr, ptr }`,
// ScrArr mirrors scr_runtime.h field-for-field: { rc, len, cap, elem,
// cyc, spare, filled, ops, data, present, ext } (field numbers in
// SCR_ARR_FIELD), where filled bounds the hole-free prefix, ops points at the element type's constant RC descriptor
@@ -2048,14 +2066,16 @@ export class LlEmitter {
}
for (const [key, re] of this.regexInstances) {
// One immortal ScrRegex per (pattern, flags) literal, pointing at
// the interned source/flags strings. The bc and native-matcher slots
// start null (lazy compile, cached by the runtime) — a mutable
// global, not constant.
// the interned source/flags strings. The bc and translated-program
// slots start null (lazy compile, cached by the runtime) — a mutable
// global, not constant. The last slot is the build-time matcher.
const matcher = regexMatchers.has(key) ? `@${re.sym}_match` : "null";
out.push(
`@${re.sym} = internal ${tl}global %ScrRegex { ${this.sizeType} -1, ptr ${re.src}, ptr ${re.fl}, ptr null, double 0.0, ptr null, ptr null } ; ${key.replace(/\n/g, "\\n")}`,
`@${re.sym} = internal ${tl}global %ScrRegex { ${this.sizeType} -1, ptr ${re.src}, ptr ${re.fl}, ptr null, double 0.0, ptr null, ptr null, ptr ${matcher} } ; ${key.replace(/\n/g, "\\n")}`,
);
}
if (this.regexInstances.size > 0) out.push(``);
for (const code of regexMatchers.values()) out.push(...code.lines, ``);
for (const [, inst] of this.templateStringsInstances) {
// One immortal ScrArr per tagged-template site: a [N x ptr] data
// global of interned cooked-string literals, and the ScrArr header
@@ -498,6 +498,8 @@ export function regexLiteralTemplate(
sym: `sc_re_${host.regexInstances.size}`,
src: host.internLiteral(e.pattern),
fl: host.internLiteral(e.flags),
pattern: e.pattern,
flags: e.flags,
};
host.regexInstances.set(key, re);
}
@@ -0,0 +1,110 @@
import { describe, expect, test } from "vitest";
import {
compileRegexMatcher,
regexLiteralBailReason,
regexLiteralCompiles,
} from "./regex-matchers.js";
function body(pattern: string, flags = "", vector = true): string {
const code = compileRegexMatcher("m", pattern, flags, "i64", vector);
if (code === null) throw new Error(`/${pattern}/${flags} did not compile`);
return code.lines.join("\n");
}
describe("regex literal matcher subset", () => {
test.each([
['^(\\d+\\.\\d+\\.\\d+\\.\\d+) - - \\[([^\\]]+)\\] "(\\w+) ([^ ?"]+)(?:\\?([^ "]*))?$', ""],
["curl|bot|spider", "i"],
["\\d+", "g"],
["(?<year>\\d{4})-(?<month>\\d\\d)", ""],
["[\\b\\-\\]\\\\.]+", ""],
["\\x41\\u0042\\0\\t\\/\\-", ""],
["a{2,}?b{0,3}c*?", "y"],
["^\\s*$", "m"],
["\\bfoo\\B", "su"],
["[^]", ""],
])("/%s/%s compiles", (pattern, flags) => {
expect(regexLiteralBailReason(pattern, flags)).toBe("");
});
test.each([
["(a)\\1", ""],
["a(?=b)", ""],
["a(?!b)", ""],
["(?<=a)b", ""],
["\\k<n>(?<n>a)", ""],
["\\p{L}", "u"],
["é", ""],
["\\u00e9", ""],
["[é]", "i"],
["\\cA", ""],
["\\01", ""],
["a{", ""],
["]", ""],
["\\a", ""],
["(?i:a)", ""],
["a{1,200000}", ""],
["^*", ""],
])("/%s/%s keeps the interpreter", (pattern, flags) => {
expect(regexLiteralCompiles(pattern, flags)).toBe(false);
});
test("large patterns keep the interpreter", () => {
expect(regexLiteralCompiles("(?:ab|cd)".repeat(20), "")).toBe(true);
expect(regexLiteralCompiles("(?:ab|cd)".repeat(200), "")).toBe(false);
});
});
describe("regex literal matcher code", () => {
test("reports its capture count for a negative length", () => {
expect(body("(a)(?:b)(?<c>c)")).toContain("ret i32 3");
});
test("a single possible first byte searches with memchr", () => {
const code = compileRegexMatcher("m", "x\\d+", "g", "i64", true);
expect(code?.decls).toContain("declare ptr @memchr(ptr, i32, i64)");
expect(code?.lines.join("\n")).toContain("call ptr @memchr(ptr %from, i32 120, i64 %rest.w)");
});
test("a sticky pattern makes one attempt", () => {
const text = body("\\d+", "y");
expect(text).not.toContain("@memchr");
expect(text).toMatch(/attempt_fail:\n {2}br label %nomatch/);
});
test("a non-multiline ^ attempts only position 0", () => {
expect(body("^ab")).toContain("br i1 %a0, label %attempt, label %nomatch");
});
test("first-character sets and long runs use 16-byte scans", () => {
const text = body("\\d+");
expect(text).toContain("load <16 x i8>");
expect(text).toContain("@llvm.cttz.i16");
expect(body("\\d+", "", false)).not.toContain("<16 x i8>");
// A bounded count below 32 stays scalar.
expect(body("a\\d{1,3}")).not.toContain("<16 x i8>");
});
test("loops no continuation can share push no backtrack entries", () => {
// \d+ then a dot; [^\]]+ then ]; the run at the end of the pattern.
expect(body("\\d+\\.\\d+")).not.toContain("@scr_regex_stack_grow");
expect(body("\\[[^\\]]+\\]")).not.toContain("@scr_regex_stack_grow");
expect(body("a.*")).not.toContain("@scr_regex_stack_grow");
// .* followed by b can give characters back.
expect(body("a.*b")).toContain("@scr_regex_stack_grow");
// $ is zero-width: \d+ may still give back before it.
expect(body("\\d+$")).toContain("@scr_regex_stack_grow");
});
test("class tests use compares for few ranges and a table otherwise", () => {
expect(body("[a-c]x")).not.toContain("constant [128 x i8]");
expect(body("[aceg]x")).toContain("private unnamed_addr constant [128 x i8]");
});
test("case folding closes ASCII letters only", () => {
// [^a]/i excludes both cases: compares 0-64, 66-96, 98-127 as ranges.
const text = body("x[^a]", "i");
expect(text).toContain("icmp ule i32");
expect(text).not.toContain("constant [128 x i8]");
});
});
File diff suppressed because it is too large Load Diff
+2 -2
View File
@@ -2,8 +2,8 @@
* of the link contract: a program object keeps this symbol undefined and the
* matching runtime defines it, so incompatible manual links fail before the
* program can start. */
export const RUNTIME_ABI_VERSION = 16 as const;
export const RUNTIME_ABI_MARKER = "scr_runtime_abi_v16" as const;
export const RUNTIME_ABI_VERSION = 17 as const;
export const RUNTIME_ABI_MARKER = "scr_runtime_abi_v17" as const;
/** Object consumption is useful today, but the complete scr_* surface may
* still change before 1.0. The versioned marker prevents accidental mixing;
@@ -52,7 +52,7 @@ async function fixture(target: NativeTargetSpec, workers = false) {
object_format: target.objectFormat,
minimum_os: target.minimumOs,
},
runtime_abi: { version: 16, marker: "scr_runtime_abi_v16" },
runtime_abi: { version: 17, marker: "scr_runtime_abi_v17" },
compiler: { command: "fixture", identity: "fixture", target: target.llvmTriple },
macros: { executable: [], excluded: ["SCR_LIB"], sanitizer: "external-toolchain-required" },
flavors: {
@@ -116,7 +116,7 @@ async function fixture(target: NativeTargetSpec = MACOS_ARM64_TARGET) {
object_format: target.objectFormat,
minimum_os: target.minimumOs,
},
runtime_abi: { version: 16, marker: "scr_runtime_abi_v16" },
runtime_abi: { version: 17, marker: "scr_runtime_abi_v17" },
compiler: {
command: "clang",
identity: "fixture clang",
@@ -312,7 +312,7 @@ ${[0, 1, 2, 3, 4, 5].map((index) => ` call void @add${index}(i64 ${index + 2})`
expect(await symbols(helperObject, ["-u"])).toEqual(await symbols(clangObject, ["-u"]));
expect(await symbols(helperObject, ["-gU"])).toEqual(await symbols(clangObject, ["-gU"]));
expect(await symbols(helperObject, ["-gU"])).toEqual(["0000000000000000 T _main"]);
expect(await symbols(helperObject, ["-u"])).toContain("_scr_runtime_abi_v16");
expect(await symbols(helperObject, ["-u"])).toContain("_scr_runtime_abi_v17");
const clangExe = join(dir, "clang-program");
const linkDriver = join(dir, "clang-driver.c");
@@ -646,7 +646,7 @@ ${[0, 1, 2, 3, 4, 5].map((index) => ` call void @add${index}(i64 ${index + 2})`
.split("\n");
expect(undefinedSymbols).toContain("_sf_scale");
expect(undefinedSymbols).toContain("_sf_callback_mix");
expect(undefinedSymbols).toContain("_scr_runtime_abi_v16");
expect(undefinedSymbols).toContain("_scr_runtime_abi_v17");
});
test("helper and clang object paths retain the same outbound FFI C ABI", async () => {
@@ -0,0 +1,6 @@
{
"order": [
"<repo>/tests/corpus/4950-regex-compiled-captures.ts"
],
"diags": []
}
@@ -0,0 +1,6 @@
{
"order": [
"<repo>/tests/corpus/4951-regex-compiled-last-index.ts"
],
"diags": []
}
@@ -0,0 +1,6 @@
{
"order": [
"<repo>/tests/corpus/4952-regex-compiled-anchors-classes.ts"
],
"diags": []
}
@@ -0,0 +1,6 @@
{
"order": [
"<repo>/tests/corpus/4953-regex-compiled-backtracking.ts"
],
"diags": []
}
@@ -0,0 +1,6 @@
{
"order": [
"<repo>/tests/corpus/4954-regex-log-report.ts"
],
"diags": []
}
@@ -0,0 +1,6 @@
{
"order": [
"<repo>/tests/corpus/4955-regex-compiled-differential.ts"
],
"diags": []
}
@@ -306,7 +306,7 @@ async function build() {
package: packageManifest.name,
version: packageManifest.version,
target: RUNTIME_PACK_MATRIX.target,
runtime_abi: { version: 16, marker: "scr_runtime_abi_v16" },
runtime_abi: { version: 17, marker: "scr_runtime_abi_v17" },
compiler: {
command: compiler,
identity: compilerVersion,
@@ -402,7 +402,7 @@ async function build() {
package: packageManifest.name,
version: packageManifest.version,
target: matrix.target,
runtime_abi: { version: 16, marker: "scr_runtime_abi_v16" },
runtime_abi: { version: 17, marker: "scr_runtime_abi_v17" },
compiler: {
command: compiler,
identity: compilerVersion,
+1 -1
View File
@@ -126,7 +126,7 @@ static void scr_enable_utf8_console_output(void) {
}
#endif
void scr_runtime_abi_v16(void) {}
void scr_runtime_abi_v17(void) {}
void scr_init(void) {
#ifdef SCR_WORKERS
+40 -1
View File
@@ -221,10 +221,37 @@ static uint8_t *scr_regex_bc(ScrRegex *re) {
}
re->bc = bc;
re->native = scr_regex_native_build(re, bc);
/* A build-time matcher must agree with the bytecode's capture count. */
if (re->matcher && re->matcher(NULL, -1, 0, NULL) != lre_get_capture_count(bc))
re->matcher = NULL;
scr_note_compiled(re);
return bc;
}
void scr_regex_stack_grow(int32_t **stack, int32_t *cap, int32_t *inline_buf, int32_t need) {
size_t ncap = (size_t)*cap * 2;
if (ncap < (size_t)need) ncap = (size_t)need;
if (ncap > (size_t)INT32_MAX) scr_regex_oom();
int32_t *grown;
if (*stack == inline_buf) {
grown = malloc(ncap * sizeof *grown);
if (grown) memcpy(grown, *stack, (size_t)*cap * sizeof *grown);
} else {
grown = realloc(*stack, ncap * sizeof *grown);
}
if (!grown) scr_regex_oom();
*stack = grown;
*cap = (int32_t)ncap;
}
void scr_regex_stack_free(int32_t *stack) { free(stack); }
/* The build-time matcher of a regex (literal instances use their
* template's). */
static inline ScrRegexMatcherFn scr_regex_matcher(const ScrRegex *re) {
return re->literal ? re->literal->matcher : re->matcher;
}
/* ── RC entry points ──────────────────────────────────────────────────
* Literal instances borrow bytecode from their immortal templates;
* constructed regexes own their compiled bytecode. */
@@ -418,6 +445,7 @@ typedef struct ScrRegexInput {
const uint8_t *data;
uint16_t *utf16;
const ScrReNative *native; /* one-byte subjects of translated patterns */
ScrRegexMatcherFn matcher; /* one-byte subjects of build-time matchers */
int len;
int wide;
bool borrowed_utf16; /* utf16 belongs to the subject cache below */
@@ -451,6 +479,7 @@ static void scr_regex_input_init(ScrRegexInput *input, const ScrRegex *re,
if (s->len > (size_t)INT32_MAX - 1) scr_regex_oom();
input->wide = !scr_text_ascii(s);
input->native = input->wide ? NULL : (const ScrReNative *)re->native;
input->matcher = input->wide ? NULL : scr_regex_matcher(re);
input->utf16 = input->wide ? scr_to_utf16(s, &input->len) : NULL;
input->borrowed_utf16 = false;
input->data = input->wide ? (const uint8_t *)input->utf16 : (const uint8_t *)s->data;
@@ -507,6 +536,7 @@ static void scr_regex_input_init_cached(ScrRegexInput *input, const ScrRegex *re
}
input->wide = scr_subject_wide;
input->native = input->wide ? NULL : (const ScrReNative *)re->native;
input->matcher = input->wide ? NULL : scr_regex_matcher(re);
input->utf16 = scr_subject_utf16;
input->borrowed_utf16 = true;
input->data = input->wide ? (const uint8_t *)input->utf16 : (const uint8_t *)s->data;
@@ -519,7 +549,10 @@ static void scr_regex_input_dispose(ScrRegexInput *input) {
if (!input->borrowed_utf16) free(input->utf16);
}
static int scr_exec(ScrRegexInput *input, const uint8_t *bc, int index) {
/* The bytecode engines; kept out of line so a build-time matcher call does
* not pay their frame. */
__attribute__((noinline)) static int scr_exec_engine(ScrRegexInput *input, const uint8_t *bc,
int index) {
int rc = input->native
? scr_re_native_exec(input->native, input->capture, input->data, index, input->len)
: lre_exec(input->capture, bc, input->data, index, input->len, input->wide, lre_opaque());
@@ -530,6 +563,12 @@ static int scr_exec(ScrRegexInput *input, const uint8_t *bc, int index) {
return rc;
}
/* Build-time matchers trap on allocation failure themselves. */
static inline int scr_exec(ScrRegexInput *input, const uint8_t *bc, int index) {
if (input->matcher) return input->matcher(input->data, input->len, index, input->capture);
return scr_exec_engine(input, bc, index);
}
static int scr_capture_index(const ScrRegexInput *input, const uint8_t *capture) {
return (int)((capture - input->data) >> input->wide);
}
+14 -1
View File
@@ -74,7 +74,7 @@ void scr_init(void);
/* Program objects emitted by the bundled LLVM helper reference this symbol.
* Its versioned spelling makes a mismatched manual runtime link fail before
* the program can start. */
void scr_runtime_abi_v16(void);
void scr_runtime_abi_v17(void);
void scr_runtime_workers_v8(void);
/* ── the trap funnel (scr_console.c; scr_library.c under -DSCR_LIB) ──────
@@ -1903,6 +1903,9 @@ ScrStr *scr_arr_join(ScrArr *a, ScrStr *sep);
* missing ones skip. Borrows both; +1 result; never throws. */
ScrStr *scr_str_raw(ScrArr *raw, ScrArr *subs);
typedef int32_t (*ScrRegexMatcherFn)(const uint8_t *s, int32_t len, int32_t start,
uint8_t **capture);
typedef struct ScrRegex {
size_t rc; /* SIZE_MAX = immortal literal template */
ScrStr *source; /* pattern text between the slashes */
@@ -1911,8 +1914,18 @@ typedef struct ScrRegex {
double last_index;
struct ScrRegex *literal; /* immutable bytecode owner for literal instances */
void *native; /* one-byte-subject matcher built with bc (NULL: lre_exec only) */
/* Build-time matcher of a literal template (NULL: none). Searches the
* ASCII bytes s[0..len) from start (one anchored attempt when sticky);
* on a match stores 2 * ncap pointers into s (NULL when unset) and
* returns 1, otherwise 0. A negative len returns ncap. */
ScrRegexMatcherFn matcher;
} ScrRegex;
/* Backtrack stack of build-time regex matchers: grow *stack (initially the
* matcher's inline buffer) to at least `need` words; traps on OOM. */
void scr_regex_stack_grow(int32_t **stack, int32_t *cap, int32_t *inline_buf, int32_t need);
void scr_regex_stack_free(int32_t *stack);
static inline ScrRegex *scr_regex_retain(ScrRegex *re) {
if (re->rc != SIZE_MAX) re->rc++;
return re;
+55
View File
@@ -346,6 +346,60 @@ static void test_exec_groups(void) {
scr_str_release(s);
}
/* A build-time matcher stand-in for /(b)/: the first 'b'. Counts its calls,
* and grows the backtrack stack once to exercise the runtime helper. */
static int fake_calls = 0;
static int32_t fake_b_matcher(const uint8_t *s, int32_t len, int32_t start, uint8_t **capture) {
if (len < 0) return 2;
fake_calls++;
int32_t inline_buf[4];
int32_t *stack = inline_buf, cap = 4;
for (int32_t i = 0; i < 4; i++) inline_buf[i] = i;
scr_regex_stack_grow(&stack, &cap, inline_buf, 9);
bool kept = stack != inline_buf && cap >= 9 && stack[3] == 3;
scr_regex_stack_free(stack);
if (!kept) return 0;
for (int32_t i = start; i < len; i++) {
if (s[i] == 'b') {
capture[0] = capture[2] = (uint8_t *)s + i;
capture[1] = capture[3] = (uint8_t *)s + i + 1;
return 1;
}
}
return 0;
}
static int32_t fake_wrong_count(const uint8_t *s, int32_t len, int32_t start, uint8_t **capture) {
(void)s;
(void)start;
(void)capture;
if (len < 0) return 7;
fake_calls++;
return 0;
}
RE(re_matched, "(b)", "");
RE(re_mismatched, "(b)", "");
static void test_build_time_matcher(void) {
re_matched.matcher = fake_b_matcher;
re_mismatched.matcher = fake_wrong_count;
ScrStr *ascii = S("abc");
ScrStr *wide = S("\xC3\xA9" "b");
ScrArr *m = scr_regex_exec(ascii, &re_matched);
ScrStr *g = m ? scr_arr_get_ref(m, 1) : NULL;
check(fake_calls == 1 && g && str_is(g, "b"), "matcher: ASCII subjects use the build-time matcher");
scr_str_release(g);
scr_arr_release(m);
check(scr_regex_test(&re_matched, wide) && fake_calls == 1,
"matcher: non-ASCII subjects keep the bytecode engine");
ScrRegex *instance = scr_regex_literal(&re_matched);
check(scr_regex_test(instance, ascii) && fake_calls == 2, "matcher: literal instances use the template's");
scr_regex_release(instance);
check(scr_regex_test(&re_mismatched, ascii) && fake_calls == 2 && re_mismatched.matcher == NULL,
"matcher: a capture-count mismatch falls back to the bytecode engine");
scr_str_release(ascii);
scr_str_release(wide);
}
static void test_string_replace(void) {
/* String search values: one pass, GetSubstitution without captures. */
ScrStr *s = S("a.b.c");
@@ -388,6 +442,7 @@ int main(int argc, char **argv) {
test_capture_capacity();
test_exec_groups();
test_string_replace();
test_build_time_matcher();
#ifdef SCR_RC_AUDIT
check(scr_str_live_count() == 0, "no live heap strings at exit (RC audit)");
@@ -0,0 +1,76 @@
// Regex literals that compile to native matchers: capture groups that do
// not participate stay undefined, quantified groups reset their captures on
// every iteration, alternation is ordered, and optional iterations that
// match nothing stop the loop.
export {};
function show(m: RegExpExecArray | null): string {
if (m === null) return "null";
const parts: string[] = [];
for (let i = 0; i < m.length; i++) parts.push(m[i] === undefined ? "undefined" : JSON.stringify(m[i]));
return `${m.index}: ${parts.join(", ")}`;
}
// Nonparticipating captures.
console.log(show(/a(x)?c/.exec("ac")));
console.log(show(/a(x)?c/.exec("axc")));
console.log(show(/(a)|(b)/.exec("b")));
console.log(show(/(?:(a)|b)(c)?/.exec("bd")));
console.log(show(/^(\w+)(?:=(\w*))?$/.exec("key")));
console.log(show(/^(\w+)(?:=(\w*))?$/.exec("key=")));
console.log(show(/^(\w+)(?:=(\w*))?$/.exec("key=value")));
// Capture reset on each iteration (RepeatMatcher step 4).
console.log(show(/(z)((a+)?(b+)?(c))*/.exec("zaacbbbcac")));
console.log(show(/(?:(a)|b)+/.exec("ab")));
console.log(show(/(?:(a)|(b))+/.exec("aba")));
console.log(show(/(?:(\d)|([a-z]))*!/.exec("1a2b!")));
console.log(show(/((a)|(b))*c/.exec("abac")));
console.log(show(/(?:x(\d)?)+/.exec("x1xx")));
// Ordered alternation and backtracking into groups.
console.log(show(/(a|ab)(c|bcd)(d*)/.exec("abcd")));
console.log(show(/(?:ab|a)(bc|c)/.exec("abc")));
console.log(show(/^(?:a|ab)+$/.exec("abab")));
console.log(show(/(foo|foobar)bar/.exec("foobarbar")));
// Greedy and lazy quantifiers, counted forms.
console.log(show(/<(.+)>/.exec("<a><b>")));
console.log(show(/<(.+?)>/.exec("<a><b>")));
console.log(show(/(a{2,3})(a*)/.exec("aaaaa")));
console.log(show(/(a{2,3}?)(a*)/.exec("aaaaa")));
console.log(show(/(\d{3})-(\d{2,})-?(\d{0,2})/.exec("123-4567-89")));
console.log(show(/(?:ab){2}(c)?/.exec("ababc")));
console.log(show(/(?:ab){2,}?(ab)*/.exec("abababab")));
console.log(show(/(x|y){1,2}?z/.exec("xyz")));
console.log(show(/(\w+?)(\d*)$/.exec("abc123")));
console.log(show(/a.*?b.*c/.exec("aXbYbZc")));
console.log(show(/(.*)(\d+)/.exec("abc12345")));
console.log(show(/(.*?)(\d+)/.exec("abc12345")));
// Empty iterations.
console.log(show(/(a*)*b/.exec("aab")));
console.log(show(/(a*)+b/.exec("b")));
console.log(show(/(a*?)*b/.exec("aab")));
console.log(show(/(?:a?)*?x/.exec("aax")));
console.log(show(/(a|)+b/.exec("aab")));
console.log(show(/(?:()|a)+$/.exec("aa")));
console.log(show(/(a?){2,}c/.exec("ac")));
console.log(show(/(?:(^)|a)+/.exec("aa")));
console.log(show(/(\b)+a/.exec("a")));
// Named groups compile too; their values come from the same captures.
const date = /(?<y>\d{4})-(?<m>\d\d)(?:-(?<d>\d\d))?/;
for (const s of ["2026-10-13", "on 2026-10", "none"]) {
const m = date.exec(s);
if (m === null) console.log("null");
else console.log(show(m), m.groups!.y, m.groups!.d);
}
// Captures read through match and replace templates.
console.log(JSON.stringify("k1=v1;k2=;k3".match(/(\w+)=(\w*)/)));
console.log("k1=v1;k2=;k3".replace(/(\w+)(?:=(\w*))?/g, "[$1|$2]"));
console.log("aaa bbb".replace(/(a)|(b)/g, "<$1$2>"));
console.log("2026-10-13".replace(/(\d+)-(\d+)-(\d+)/, "$3/$2/$1 ($&) $$ $`$'"));
for (const m of "x=1, y=, z=33".matchAll(/(\w)=(\d+)?/g)) console.log(m.index, m[0], m[1], m[2]);
console.log(JSON.stringify("a1b22c333d".split(/(\d)(\d)?/)));
@@ -0,0 +1,72 @@
// Native regex matchers under the global and sticky lastIndex protocol:
// exec/test loops, empty matches that advance by one code unit (or one code
// point under the u flag), sticky anchoring, and subjects that switch
// between ASCII and non-ASCII text on the same regex.
export {};
const word = /[a-z]+/g;
const text = "alpha beta gamma";
for (let i = 0; i < 5; i++) {
const m = word.exec(text);
console.log(m === null ? "null" : `${m[0]}@${m.index}`, word.lastIndex);
}
const digits = /\d+/g;
let count = 0;
while (digits.test("a1b22c333")) count++;
console.log("test loop", count, digits.lastIndex);
digits.lastIndex = 4;
console.log(digits.exec("a1b22c333")?.[0], digits.lastIndex);
digits.lastIndex = 99;
console.log(digits.exec("a1b22c333"), digits.lastIndex);
// Sticky: one anchored attempt at lastIndex.
const sticky = /\d+|[a-z]/y;
const parts: string[] = [];
for (let i = 0; i < 8; i++) {
const m = sticky.exec("12ab3-4");
parts.push(m === null ? `null@${sticky.lastIndex}` : `${m[0]}@${m.index}`);
}
console.log(parts.join(" "));
const stickyG = /a|b/gy;
console.log(JSON.stringify("aabxab".match(stickyG)), "aabxab".replace(stickyG, "_"));
console.log(/b/y.test("ab"), /^b/my.test("a\nb"), JSON.stringify("ab".split(/b/y)));
// Empty matches advance past the current position.
console.log(JSON.stringify("abc".match(/x*/g)));
console.log("abc".replace(/x*/g, "-"));
console.log("abc".replace(/(?:)/g, "."));
console.log(JSON.stringify("a,b,,c".split(/,*/)));
console.log(JSON.stringify("ab12cd".match(/\d*/g)));
const empties: string[] = [];
for (const m of "aXbX".matchAll(/X?/g)) empties.push(`${m[0]}@${m.index}`);
console.log(JSON.stringify(empties));
// Non-ASCII subjects run on the interpreter; ASCII ones on the native
// matcher. lastIndex is a UTF-16 index either way.
const mixed = /[a-z]+/g;
for (const s of ["hé llo", "hello", "😀ab😀cd"]) {
mixed.lastIndex = 0;
const seen: string[] = [];
for (let m = mixed.exec(s); m !== null; m = mixed.exec(s)) seen.push(`${m[0]}@${m.index}/${mixed.lastIndex}`);
console.log(JSON.stringify(s), seen.join(" "));
}
// Empty-match advancement: code units without u, code points with u.
console.log(JSON.stringify("😀a".match(/(?:)/g)), JSON.stringify("😀a".match(/(?:)/gu)));
console.log("😀a".replace(/x*/g, "-") === "-\ud83d-\ude00-a-", "😀a".replace(/x*/gu, "-"));
console.log(JSON.stringify("a😀b".split(/(?:)/u)), "a😀b".split(/(?:)/).length);
const eu = /\b|$/gu;
const idx: number[] = [];
for (let m = eu.exec("ab 😀 c"); m !== null && idx.length < 10; m = eu.exec("ab 😀 c")) {
idx.push(m.index);
if (m[0] === "") eu.lastIndex = eu.lastIndex + 1;
}
console.log(idx.join(","));
// search ignores lastIndex and leaves it alone.
const g = /c/g;
g.lastIndex = 3;
console.log("abcabc".search(g), g.lastIndex, "abc".search(/x/), "abc".search(/^/));
// Literal sites in a loop reset their own state each evaluation.
for (let i = 0; i < 3; i++) console.log(/o/g.exec("foo boo")?.[0], /o/g.test("xo"), "foo".search(/o/g));
@@ -0,0 +1,41 @@
// Native regex matchers for anchors (with and without m), word boundaries,
// character classes and their escapes, the dot with and without s, and
// ASCII case folding under i (with and without u).
export {};
function all(re: RegExp, s: string): string {
return JSON.stringify(s.match(re));
}
// $ only matches at the very end without m (also before a final newline:
// no), and at each line end with m. ^ likewise.
console.log(/a$/.test("a\n"), /a$/m.test("a\n"), /a$/m.test("a\r\nb"), /^b/m.test("a\rb"));
console.log(all(/^\w+$/gm, "one\ntwo\r\nthree\rfour\n"), all(/\w+$/g, "one\ntwo"));
console.log(all(/^/gm, "a\n\nb"), all(/$/gm, "a\n\nb"), "a\nb\n".replace(/$/gm, ";"));
console.log(all(/^\d|\d$/g, "12 34"), /^$/.test(""), /^$/m.test("a\n"), /x^/.test("x"));
// Word boundaries.
console.log(all(/\b\w/g, "hi there_you-2b"), all(/\B\w/g, "ab cd"), "a b".replace(/\b/g, "|"));
console.log(/\bfoo\b/.test("a foo."), /\bfoo\b/.test("afoo"), all(/\B/g, "ab"), all(/\b/g, ""));
// Classes, negation, ranges and escapes.
console.log(all(/[a-c]+/g, "abcdcba"), all(/[^a-c]+/g, "abcdcba"), all(/[^"]*"/g, 'x"y""'));
console.log(all(/[\d.]+/g, "v1.25 and 3."), all(/[\w-]+/g, "a-b c_d"), all(/[-a]/g, "-ba-"));
console.log(all(/[a\-z]/g, "a-mz"), all(/[\]\\]/g, "a]b\\c"), all(/[.]/g, "a.b"), all(/[^]/g, "ab"));
console.log(all(/[\b]/g, "a\bb"), all(/\x41B\t/g, "AB\tAB"), all(/\0/g, "a\0b"), /[]/.test("a"));
console.log(all(/\s+/g, "a \t\n\v\f\rb"), all(/\S+/g, "a b"), all(/\D+/g, "a1b"), all(/\W/g, "a-b c"));
console.log(all(/\/\*.*?\*\//g, "a /* x */ b /**/ c"), all(/\$\{(\w+)\}/g, "${a}-${bc}"));
// The dot.
console.log(all(/a.b/g, "a\nb a\rb axb"), all(/a.b/gs, "a\nb a\rb axb"), all(/.+/g, "x\ny"));
// ASCII case folding.
console.log(all(/abc/gi, "ABC aBc abc"), all(/[a-c]+/gi, "xABcx"), all(/[^a]/gi, "aAb"));
console.log(all(/[^\W_]+/gi, "Ab_9 x"), all(/\w+/gi, "sK ſK"), all(/[k-l]/giu, "KkLl"));
console.log(/\W/i.test("s"), /\W/iu.test("s"), /\W/iu.test("k"), /[\W]/i.test("K"));
console.log(/s/i.test("ſ"), /s/iu.test("ſ"), /k/iu.test("K"), /[a-z]/i.test("K"));
console.log(all(/GET|post/gi, "get POST Get pOsT"), "Hello World".replace(/o/gi, "0"));
// Patterns outside the compiled subset still run (on the interpreter).
console.log(all(/(\w)\1/g, "aabbcd"), all(/a(?=b)/g, "abac"), all(/(?<=\$)\d+/g, "$5 $66"));
console.log(all(/\p{L}+/gu, "héllo wörld"), all(/[à-ÿ]+/g, "àé ok"));
@@ -0,0 +1,41 @@
// Native regex matchers under heavy backtracking: nested quantifiers on
// failing inputs, long give-back runs, and backtrack stacks that grow far
// past their inline size. Results match the interpreter and Node.
export {};
function show(m: RegExpExecArray | null): string {
if (m === null) return "null";
const parts: string[] = [];
for (let i = 0; i < m.length; i++) parts.push(m[i] === undefined ? "undefined" : `${m[i]!.length}`);
return `${m.index}: ${parts.join(", ")}`;
}
// Exponential in the input length; small enough to finish quickly.
for (const n of [4, 10, 16, 18]) {
const s = "a".repeat(n);
console.log(n, /(a+)+b/.test(s), /(a|aa)+c/.test(s), show(/(a*)*$/.exec(s)), /(?:a?){12}a{12}/.test(s));
}
console.log(show(/^(\w+\s?)*$/.exec("word word word word word!")), show(/^(\w+\s?)*$/.exec("word word word")));
console.log(show(/(x+x+)+y/.exec("xxxxxxxxxxxxxx")), show(/(x+x+)+y/.exec("xxxxxy")));
// Long give-back and lazy extension.
const long = "a".repeat(5000) + "b" + "a".repeat(5000);
console.log(show(/(.*)b(.*)/.exec(long)), show(/(.*?)b/.exec(long)), show(/a*?b/.exec(long)));
console.log(show(/(a*)a{4000}b/.exec(long)), show(/^(?:a|b)*$/.exec(long)), /^(?:a|b)*c/.test(long));
console.log(show(/((?:ab)*?)c/.exec("ab".repeat(3000) + "c")), show(/(?:(a)|b)*$/.exec("ab".repeat(4000))));
console.log(show(/(\w+)\s(\w+)$/.exec("x ".repeat(2000) + "end here")));
// Many alternatives and deep nesting.
const kw = /\b(?:if|else|while|for|return|function|const|let|var|class|new|this)\b/g;
console.log(JSON.stringify("let x = new Foo(); if (x) return this; else while (1) {}".match(kw)));
console.log(show(/((((((((((a))))))))))+/.exec("aaa")), show(/(?:(?:(?:(?:a|b)c)?d)*e)+/.exec("acdbcdedebcde")));
// The same regex reused across many subjects.
const ip = /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/;
let valid = 0;
for (let i = 0; i < 2000; i++) {
const s = `${i % 300}.${(i * 7) % 260}.${i % 10}.${(i * 13) % 999}`;
const m = ip.exec(s);
if (m !== null && m.slice(1).every((x) => Number(x) < 256)) valid++;
}
console.log("valid", valid);
+88
View File
@@ -0,0 +1,88 @@
// A log report built with regex literals: access-log lines with optional
// query strings, key=value application logs, quoted CSV fields, ISO
// timestamps, and template placeholders.
export {};
let seed = 7;
function next(): number {
seed = (Math.imul(seed, 1103515245) + 12345) >>> 0;
return seed / 4294967296;
}
function pick<T>(items: T[]): T {
return items[Math.floor(next() * items.length)]!;
}
const access = /^(\S+) \S+ (\S+) \[([^\]]+)\] "([A-Z]+) ([^ "?]+)(?:\?([^ "]*))? HTTP\/(1\.[01]|2)" (\d{3}) (\d+|-)(?: "([^"]*)" "([^"]*)")?$/;
const app = /^(\d{4})-(\d\d)-(\d\d)T(\d\d):(\d\d):(\d\d)(?:\.(\d{1,3}))?Z\s+(DEBUG|INFO|WARN|ERROR)\s+\[([\w.-]+)\]\s+(.*)$/;
const pair = /(\w+)=("(?:[^"\\]|\\.)*"|\S*)/g;
const csvField = /(?:^|,)(?:"((?:[^"]|"")*)"|([^,]*))/g;
const placeholder = /\{\{\s*(\w+)(?:\s*\|\s*(\w+))?\s*\}\}/g;
const userAgentBot = /bot|crawler|spider|curl/i;
const lines: string[] = [];
for (let i = 0; i < 400; i++) {
const ip = `192.168.${Math.floor(next() * 4)}.${Math.floor(next() * 255)}`;
const user = next() < 0.8 ? "-" : pick(["ana", "bo", "cy"]);
const method = pick(["GET", "POST", "PUT", "DELETE", "PATCH"]);
const path = pick(["/", "/api/v1/items", "/login", "/static/main.css", "/search"]);
const query = next() < 0.4 ? `?q=${Math.floor(next() * 100)}&page=${Math.floor(next() * 5)}` : "";
const status = pick([200, 200, 200, 201, 301, 404, 500]);
const size = next() < 0.1 ? "-" : String(Math.floor(next() * 50000));
const tail = next() < 0.7 ? ` "https://ref.example/${i}" "${pick(["Mozilla/5.0", "Googlebot/2.1", "curl/8.0", "Spider-X"])}"` : "";
lines.push(`${ip} - ${user} [13/Oct/2026:10:${String(i % 60).padStart(2, "0")}:00 +0000] "${method} ${path}${query} HTTP/${pick(["1.0", "1.1", "2"])}" ${status} ${size}${tail}`);
}
lines.push("garbage line");
lines.push('1.2.3.4 - - [x] "GET /bad path HTTP/1.1" 200 1');
const statusCounts = new Map<string, number>();
let bytes = 0;
let bots = 0;
let queries = 0;
const users = new Set<string>();
for (const line of lines) {
const m = access.exec(line);
if (m === null) {
console.log("unparsed:", line);
continue;
}
statusCounts.set(m[8]!, (statusCounts.get(m[8]!) ?? 0) + 1);
if (m[9] !== "-") bytes += Number(m[9]);
if (m[6] !== undefined) queries += m[6].split("&").length;
if (m[11] !== undefined && userAgentBot.test(m[11])) bots++;
if (m[2] !== "-") users.add(m[2]!);
}
const statusKeys = [...statusCounts.keys()].sort();
console.log(statusKeys.map((k) => `${k}:${statusCounts.get(k)}`).join(" "), bytes, bots, queries, [...users].sort().join(","));
const appLines = [
'2026-10-13T08:00:01.123Z INFO [api.server] started port=8080 mode="production" workers=4',
"2026-10-13T08:00:02Z WARN [db-pool] slow query ms=812 table=orders",
'2026-10-13T08:00:03.5Z ERROR [api.handler] failed path=/x err="timeout \\"upstream\\"" retry=',
"2026-10-13 08:00:04 INFO [bad] no T separator",
"2026-10-13T08:00:05.999Z DEBUG [cache] hit key=user:42",
];
for (const line of appLines) {
const m = app.exec(line);
if (m === null) {
console.log("no match:", line);
continue;
}
const fields: string[] = [];
for (const p of m[10]!.matchAll(pair)) fields.push(`${p[1]}=>${p[2]}`);
console.log(m[8], m[9], m[7] ?? "000", m[4], fields.join("; "));
}
const csv = ['a,b,c', '"quoted, comma",plain,"with ""quotes"""', ",,", '"",x,', "single"];
for (const row of csv) {
const cells: string[] = [];
for (const f of row.matchAll(csvField)) cells.push(f[1] !== undefined ? f[1].replace(/""/g, '"') : f[2]!);
console.log(cells.length, JSON.stringify(cells));
}
const template = "Hello {{ name }}, you have {{count|number}} new {{ things | plural }}. {{missing}} {{ bad";
const values = new Map<string, string>([["name", "Ana"], ["count", "3"], ["things", "message"]]);
console.log(template.replace(placeholder, (_all, key: string, filter: string | undefined) => {
const v = values.get(key) ?? `<${key}>`;
return filter === "plural" ? v + "s" : filter === "number" ? `#${v}` : v;
}));
console.log(lines.slice(0, 50).join("\n").replace(/\d+/g, "#").length, lines[3]!.replace(/\b\d{1,3}(\.\d{1,3}){3}\b/, "IP"));
@@ -0,0 +1,123 @@
// Differential cases for regex literals compiled to native matchers:
// generated patterns over a small alphabet (classes, escapes, anchors,
// greedy and lazy quantifiers, alternation, capturing, non-capturing and
// named groups, all flag combinations) run through exec, test, replace,
// search, split and match on ASCII and non-ASCII subjects.
export {};
const inputs: string[] = ["bbbbbbbbbbbbbbbbbbbbb", "\naaaaaaaaaaaaaaaaaaaaaa11111111111111112SSSSSSSSSSSSSSSSSS_ .", " ", "\r\r\r\r\r\r", "\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n", "A-", " A ", "Bb\rKKKKKaaaaa_\"\n1", "A\nx\rsKxxxxxx\"\nSb K11111111111KKKKKKKKKKKKKKKcs\r-sc", " . xc-----------\nss\"AbaaaaaaaaaaaaaaaaaaaaaaBkBk__Kaaaaaaaaaaaaaa", "ssssssssssssssssssssssss", "xa\n\r2k\n", "-s--------------------\n\n\na-SAb.KSSSSSSSSSS2\n.B1kcccccccxSKB\"\"x", "\r\r\r\r\r\r\rb_\r-KssssssssssssssB"];
const patterns: RegExp[] = [
/\B\-/gm,
/([a-ca-z0-9]?([^___]+[^\W\]](_^)$)\-)([\n])?? /gy,
/(-?(?<g2>A*?\db*))??/g,
/[a-c]x+?"?/,
/((?<g2>[a] |(?<g3>(ba)x))*?-+?\-(?<g5>\sA+(\.|\D1*){0,1}){0,1}|\Ba(?:((?<g8>b)\b^[^0-9.\s])(?<g9>("??\.)\-*_{0}a?)){1,2}x)(?:(?:\/\s))??(-c{2})((?:\B(?:\B(b*a*? |.b??\D{0,1})-(aa1{2,3}|ab|.{2,2}a)){0,1} +){0,1})/my,
/\b(([b\]](?:"|(a*\W|a[\s1\d]\d*b+){2}\n(?:\s|b+){1,2}|(?:\d{1}\/b+){0,1}\S?|(\Sbb|\w)(?:a{0}?[bx-z\s]\d*?)?(?:b\d{0}a )??)a){1,1}){0,2}?[^ "]^/g,
/(?<g1>\S\/*_{1,1}?\b)[\n\sb]\/$/,
/\w/gi,
/(?:^|(?:-*?){0,1}\.*-{1,3}(-??((?:b*\-a\d){0,1}(?:\db\-){0,1}|\s*1\B)?\s{2,2}))_{2,}_[^a-c]|-*?/gsy,
/(\n$)/g,
/\B[\].]+?_b/g,
/[ba-za-z]|\.[\-aa]+(x*[_"b](b{0}[^.b\n]){0,2}?\W+|(?:\.*xb*?|a?\/)-(\/+b(?:(?<g4>a{1,1}_\d*)|(ba)))\n+?)/gyu,
/\/\//y,
/^b{2}?((?:^[\sa]??(?:\/""{1,1}))$){1,2}[^A-Zx-z]/gm,
/\-\w+(([A-Z.]{1}(?<g3>-([^ 0-90-9]{1,}?b+?)(\Db){2}[.]+)){2}\-{0,1}|\-\D*(\/\W*a(([_]ab{0}x)??){0,1})(_*1{2,3}))/m,
/c[\]]/gi,
/(?:A{1})??/g,
/[ ]*((?:\.b(Ac[^\-\w\w]|(\da\S{1,}_|baa[^ ]|A\w *){0,1}[A-Z""]{0,0}(?<g4>\d[A-Z]*\n){2}(b{2,2}?\d"|a)??|.a))\D(?:((?: {0,}?|a){0,2}?(?:b\s["]*\d)){1,2}\/+){0,1}| (.|(?:A\d\/*^)(?:[^.0-9\W]{0,}?(\d\d*)( *b{0,0}|c*?[0-9]+?\D)??(\nx)??)??|^){0,2}?x+){0,1}/my,
/(\s(\B|(?:"*[^\na\s])\/b{1,1}(A?[\n](a+?aa|A_*|b\d*?b|[^a\]]*?||a+){0,2}?))*){0,2}?((?:[x-z](?<g6>(\D?[a\]]*|b?[a])\n\s+))){2}[a"a-c]{2,3}(?:(([a-c]\s(b)?(?:c*?\d+_?\n))+?([\s\- ](?:ab{1,2}[A-Z])|\-[1a]+?(\nca{1,}a)|^(x*?1)(?:b+[^A-Z\d])(?:\d\s"+1))?){0,2}?(\n)?(a(?:[^0-9A-Z]{2,2}?(\d\n+a\d{0,2})c)(?:a[ax-z\w]{2,}?a*?|)?\w*).+)/,
/\-{2,3}\D*?A/i,
/\b\.{0,}($)(?<g2>(?<g3>[\d]*"??1?)?^)|x*|\-?^|(b\W*){2}/,
/(\b((?:\W){0,2}?\B((x\dbb+)$[\nA-Z]\w{2,2}|[^.\s0-9]*$(?<g5>a{1,2})|(?<g6>\s )+\b\d?){0,2}?\n{2}){0,1}bA)|a{1,1}|(?:([a-c1 ])._)/,
/(\/??)_{0,0}(?:[ba]\d(\.(\s+\b[\]]|(?:\d?b{2}b|["a]b\s*?)|[0-9b\-]{1,}(b\W\S)){2}|[ A-Z\s]??(?:_*?[ ]+)([^"a-z\n][^0-9][ 1a]*| ?-{1,3}?(?:\db*?[^a"]a?))(\s+|[\-a-z"]*))\w*?)/gy,
/("?_(A[\-\d]{1,}){1,2})+[._]1??/g,
/[\-]([\d"]\/*?[^\]\w]+(?:\n*?$(\.)(?:\b(?:"?\.{2,}A{0,})))|($$|[^\]a]{0,}?^(?:(\-|\.A*|b)\nx[^a_"]|(?:a){1,2}(?:[\W\nb]\/\d))){2})(?:\-*[x-zax-z]?|((?:(?<g6>A{2,}?[^\s]b{1,3}x)A*\D|A)){0,1}(?:x(([x-za-z0-9]a||_a{0,}a\-))))?\n/gy,
/\W{0,}[.b\w][^.]/mu,
/\b[^ \s](?<g1>(?:([\d\-\]]?){0}?-{0}(c+[\-_0-9]))((?<g5>(\d*?\.*-+?)(\d[^b\w]{2}?\W?b+|||bxb{0,}\S+|b){0,1}x{1,1}?[11a-c]{0,}|(?:b){2})(?: +(?:\.{0,}?a{0,}\d*)|\s[^_x-z][.]){0,2}?){0,2}?)?[\nb\-]+?/iy,
/("\d?(?<g2>([\W](?:ab{2}[^A-ZA-Z]+)\D(?:\d+?)??|)[\W\w]([^A-Z_]c(?<g5>[a-c\n\-]|\-\-+b{2,})){0,1}$)??)(?:((1*A)(?:(\.+?"\S?)|(\d? [a]\d??|aa\d+?[x-z ]){2}(b|b{2}\.?\d){0,2}?(\da{1}\n))[^A-Z\s]((?:\sa*?)\.*?(?<g13>1{0}ba{1,}c{2,3}?|1A\d)?A){0}|\W(?<g14>b+?(a)( +|b[^\wa-c]{1,}x)(b?a*){2}){1,2}(?:(?:b\d\d)\b(["\-]+b?-{2,})??| ?([x-z]*[.a-za]aa)1*)[^a-c\W]|"{2}(?:(\d+a?ba))\B)(?<g21>(a *)((a\d){0,2}?(\-\d\d*)*?(b_*)?)\D{1})((\/{0,1}){1,2})?)(?<g29>(\s{0,0}(?<g31>.{0,}x+([^_a-ca]?[.\]]?_+?)( *){0,1}){0,1}-?\b){0,2}?-?(?:[\]x-z"]+|(?:(\d??a)){1,2})){0,1}/im,
/\-/gi,
/A{0}?\.(([.\s_])a?(?: )??)\w|(a*[ .])\S1|c{2,2}/s,
/[^\n]|.\W{2,3}?\b$/g,
/\d?? +?/g,
/(((?:(a{0})??\/+?\b1)\.|[\]"])b\n*?)-*b[\]\wA-Z]{0,}/g,
/"+?(a(\Sc*\B){0}?\W{1,}[1\n]*?){2,3}[\nb]??(\/*?)?/,
/["A-Z](?<g1>\-? +)?/g,
/x-+A(?:\b((["0-9\-])(([x-zA-Z]??ab*)\s\D[\]"\n])^b)??\B|\d*?$(?:A\s)?|.\n(b($){0,1}((?<g8>a\S*?\d*\.){2}1*(a ?.-{1,1}|\S*\Wab))+(?<g10>(?:.?\s+|[ \s]\d{2,})|){0,1})){1,2}/gyu,
/([\d"\]]{2}\b[\s_]([ba\n]{2}\nc-)|\/\B((?<g4>c\B.\-){1,2}(1*([\sa-c\n].c{1,} {1,}?|a+a+)(?:b?\-+?|\-*?[1\sa]a{0}))(?:\d^1*\s*?)A{2,}){0,1})[aA-Z"]b/iy,
/(c(^\W$ |(\B1(b*a\w){0,2}?)){0,2}?((\.??( )|b(?<g8>[aa]"1*))\/){1,2})(?:[^\-\w][^\]\s]\-[.]??)/gsy,
/\w{1,}(\B\d+?)(?:a\b){0,1}(x-"|\B)/gs,
/a$ +|([^a0-9]\B^(A{1,3}?|-11)?|(?:a?(^){0,2}?- ){0,1}a{2}\B\n?)\/*(?:$[\]]+?b*(?<g4>\/*\-{1}?))/,
/(?: ?(?:x\.-){1,2}).{2,}(?:[\n"](?:\s*. +?1|\b)??(?:$[\wb]*){2}| +\.^\/+?)-|_{1,}?[^a-z]+/su,
/ /gsyu,
/1_+/gsu,
/_a([^" \W]+(?:[_x-z]([x-za\w][\s]{1,})(?<g3>(\S{0,}?)??c\D{0,2}?){1,2}a?){1,2})/m,
/(([\W]($( \/+\.x{1,})??[ 1]{0,0}_+|\.??\/(?:\d*b*\d)$){0,2}?c\b)((?<g6>\S*?1*[b])\.{1}(1[^.bx-z]+c+|([A-Z\]_]{1,3})(c\d{1}b)??([\w\n\d]{2,}?\s*){0,2}?|(bb|b*?[1a-z])?? c)|\w{2,}))[^\sa-c_]\S/gms,
/1/gy,
/\/{2}x|(?:([\]\n][\W])\/?|\-+?|$(?:\b)??[\n])(\.){0,1}/g,
/\wb{0,}c{0}(x{2,3}){0,1}/g,
/(?:\/)["a-c0-9]*^[^0-9\w1]|(aa+\b){0,2}?|\S??/g,
/\w*\w/my,
/1"/,
/\b/u,
/(?:\-*?([.]{2}?)?)??A/gis,
/\b/i,
/-*^/gyu,
/ (?:[\W\n]{0}?c *?)|_?[^a-z\s]/gsu,
/A{0,}(\.??c\.[^ \-])??b*?$/gi,
/(?:"\/(?<g1>\n((a??)[.]{2}?(b?|x*)*|\w)?(b{0,1}?\B)A{2}){0,1}){1,2}(\/*?(?:\W(?<g7>-{2,3}|1?$^(?<g8>b*a+b{2}){1,2}){0,1}\s{1,})?){0,2}?(?<g9>\s|\B(\b-(?<g11>\-[ 1]|(b){2}(?<g13>-\/*){0,2}?[^b.]?\B)|\W{2,}((\.*a[^x-z\]]{0,0}\d+)(\d.{0,})\-\n|(?:[^0-9ba])(?<g17>a?|){0,1}){2}\/??\b||_(?:-{1,1}?1\b)1*(\-{1,}(?:"){0,1}1+?(\d))??|(?<g20>_a{1,1}){0,2}?((b)(\/*a|[\-1]*[a-c]{1,}a*[\-\]])\b)|\S(?<g24>a)\/[\d_\d]{0,1}|(?<g25>\W?)\.{2,3}?[\n][^a]?)\S??[^0-9\wa-z])\.{1,}|(\b[.\dx-z]){2}[x-z]??$(?:(A{2}\/*|(?<g28>(?:\.a*|[A-Z]a){2}c\W[a-ca\-]){1,2}(x{2}\S{0,}?[a\W]{2})??_*\w|(\S|$\w)(?<g31>(a{0,}x){0,2}?\/A{1,3}){2}){0,2}?"| {1,}-_){0,2}?/,
/[^_ ]b/g,
/c?\bA{1,}/gyu,
/b(?:[\s]*?[a-z\d\n]??(?:"[a-c]*\/))?(?:(.+"+)?1{0})\//gmsu,
/"?([a-c]\W{0}\D??(?:\b))?\/{2,}|\D{2,4}?xx1+/g,
/\B|((?<g2>[^"\W]{1}?$"+\b)(?:[a-z]){0,1}|[^ ]{1,} )|-\S(\.*((?<g5>\n\bA{0,}?(-?\-+?[_a-z\n]){2}){0,1}\/)[^"a-c.]??)/m,
/(\s|\n|(a[A-Z\d]{2,}_{2}\.{0,1}){2}\.*["])([b0-9])?/gms,
/(A{2}?$^([^_\s]+?|(b_"*?(?<g4>.\d\.?))?-(A(?:A??a?|)([\saA-Z]b{0,}?\d??\s{2,}){0,1}[A-Z\d"]){2}))a[^1]?/g,
/A|(\b)??\b.*?(A(\d)(?<g4>\B(\d+[\n]{0,1}[^.\]\s])))/gi,
/(\d|\b[.]){1,2}([^\W1]b(?<g3>"{1}(^[a-c"\]]+)\S*)?)(\-(^ \b){0,2}?)/g,
/-*c .*?|_\b [^\n]/giu,
/(?<g1>[\d]){1,2}$/iy,
/[".]{2,3}?\-/is,
/(?:((?:([\W\W]?1\S*||\w??){0,1}_)??(?:(?<g3>a.+?_-{1,}|c)?x^)??(?<g4>\w*){1,2}\B|))_|a\naa+?/gis,
/\W\n(?:.$|(-?\.{0,0}\-([b]{1}){1,2})|\-+([^a-z0-9]*(?:\/){0,1}))??|[\]1]+c{2}?\-(?:[\]])/gy,
/\b(?:[\]_](_|A{1,}?(([^A-Z]_x){0}x[\]]$)\b){0,1}(\BA([\n\W]??^[a-c_]\D|){0,2}?){2}( )|\b(A[\d\-a-z][a0-9_]{1,3}\.)((([\n]a|\d*\/{0}?b){2}\sb+?)(\W+(\d?\d{1,}?[^\n]\.{1})")(?:(?:a+?b*?ab|\.b+?1[1]*){1,2}[_"\]]){0,1})(A){0,1})??|\/ /i,
/(?:_+-(((?:[^.]\d??a|\S|a{2}bb)(\d\d\d)){0,1}\D\bA{1,}?){1,2})[^x-z]/,
/x\-+/gy,
/\.*"-/,
/(?<g1>[x-za-c_]b)\.{0,}_*/,
/\.|(?<g1> ){0,2}?[b]*c+?[\-]/m,
/[\n\nA-Z]+?(?: +?([x-z]*\n?[0-9b\W]^)(?:[A-ZA-Za](\n+(?:a{0,}"??.b|\s*?c\d\/)a+))?|x-(A_?)(?<g4>\s)|c??)(?:b+[A-Za-za-c]([\d\]\]]{1,1}A){1,2}|$b)??/u,
/\n{2}?/g,
/(?:((A+$))\d\/[ \na-c])*(?:(?:(?<g3>(?:a?\/a{2,4})\/{2,3}(b_))+[.b]+?(?:[^a\s\]][ ]{0,}?([\s]??")|_+$){0,2}?){1,} )|"{2,}(\.$(a((?<g9>a-{2,3}|[a-c\W.]\-{2}\d{2})\b.*)(?:(?: \-{1,}))?)([\d0-9]{2,2}[0-9ba-z]*))"*?a*/,
/[1]{0,}((\d[a][^.a-c]|[^a\n]\Wb |c+\w( *\/^[\d]?)+?")){1,2}\s[a-c]??|(?:^){2}(\S-*|a(\b\D{1}|(("??b[A-Zx-z]\-*){2}A|(bbx[^a-z_])(?:[^\na-c]{0,2}[\]a-c1]b?-+)?(?:a[a-za-z]|)|$_)[a])b+){0,1}/g,
/$/is,
/[^x-z]/y,
/\n(\/\W{0,2}|^__*[\-\]\n]){0,1}/y,
/(\BA)??/gimy,
/[b](?:[\-_]{0,0}x[\d]+|(?:\D{1,}){0}($(?:( *-??|\d{2}A{2,3}?b+|[a-c._]\dx*){0,2}?[a-c]))(?<g3> {1} *(?:\-\n))[a\s]*?)\b/isy,
/[^0-9x-z].+c[\-b]+|_\d/gms,
/1c([bA-Z]?){2}|/s,
/$^/gs,
];
function show(m: RegExpExecArray | null): string {
if (m === null) return "-";
const parts: string[] = [];
for (let i = 0; i < m.length; i++) parts.push(m[i] === undefined ? "U" : JSON.stringify(m[i]));
return `${m.index}:${parts.join(",")}`;
}
for (const re of patterns) {
const out: string[] = [];
for (const s of inputs) {
re.lastIndex = 0;
const a = show(re.exec(s));
const b = re.lastIndex;
const c = show(re.exec(s));
re.lastIndex = 0;
out.push(`${a} ${b} ${c} ${re.lastIndex} ${re.test(s)} ${s.replace(re, "<$&|$1>")} ${s.search(re)}`);
re.lastIndex = 0;
if (!re.flags.includes("y")) out.push(JSON.stringify(s.split(re)));
if (re.flags.includes("g")) out.push(JSON.stringify(s.match(re)));
}
console.log(re.source, out.join(" "));
}
@@ -96,7 +96,7 @@ for (const backend of ["llvm"] as const) {
object_format: target.objectFormat,
minimum_os: target.minimumOs,
},
runtime_abi: { version: 16, marker: "scr_runtime_abi_v16" },
runtime_abi: { version: 17, marker: "scr_runtime_abi_v17" },
compiler: { command: "fixture", identity: "fixture", target: target.llvmTriple },
macros: { executable: [], excluded: [], sanitizer: "external-toolchain-required" },
flavors: {