feat: add multi-engine regex flavour support

This commit is contained in:
2026-07-27 11:43:51 +02:00
parent 7079cde15f
commit 7f3a91ad37
340 changed files with 643286 additions and 483 deletions

View File

@@ -0,0 +1,172 @@
// @vitest-environment node
import { readFile } from "node:fs/promises";
import path from "node:path";
import { pathToFileURL } from "node:url";
import { beforeAll, describe, expect, it } from "vitest";
import {
CppEngineAdapter,
CPP_ENGINE_VERSION,
CPP_FLAVOUR_VERSION,
} from "../../src/regex/execution/adapters/cpp/CppEngineAdapter";
import type { CppRegexModuleFactory } from "../../src/regex/execution/adapters/cpp/cpp-module";
import type {
RegexExecutionRequest,
RegexReplacementRequest,
} from "../../src/regex/model/match";
const pack = path.resolve("public/engines/cpp");
let adapter: CppEngineAdapter;
function request(
pattern: string,
subject: string,
overrides: Partial<RegexExecutionRequest> = {},
): RegexExecutionRequest {
return {
flavour: "cpp",
flavourVersion: CPP_FLAVOUR_VERSION,
pattern,
flags: ["g"],
options: { grammar: "ECMAScript" },
subject,
captureMetadata: [],
scanAll: false,
maximumMatches: 100,
maximumCaptureRows: 1_000,
...overrides,
};
}
function replacement(
pattern: string,
subject: string,
replacementText: string,
overrides: Partial<RegexReplacementRequest> = {},
): RegexReplacementRequest {
return {
...request(pattern, subject),
replacement: replacementText,
maximumOutputBytes: 1_024,
...overrides,
};
}
beforeAll(async () => {
const imported = (await import(
`${pathToFileURL(path.join(pack, "cpp-regex.mjs")).href}?conformance`
)) as { default: CppRegexModuleFactory };
const module = await imported.default({
locateFile: (name) => path.join(pack, name),
print: () => undefined,
printErr: () => undefined,
});
adapter = new CppEngineAdapter(module, "Node.js conformance");
await adapter.load();
});
describe("C++ Emscripten libc++ std::wregex conformance", () => {
it("loads the exact libc++ and compiler identity", async () => {
await expect(adapter.load()).resolves.toEqual(
expect.objectContaining({
flavour: "cpp",
engineName: "C++ std::wregex (Emscripten libc++)",
engineVersion: CPP_ENGINE_VERSION,
runtimeVersion: expect.stringContaining(
"32-bit wchar_t code-point bridge",
),
offsetUnit: "code-point",
}),
);
});
it("returns code-point-native ranges without losing editor UTF-16 offsets", async () => {
const result = await adapter.execute(request("(\\w+)", "😀 one two"));
expect(result.accepted).toBe(true);
expect(
result.matches.map(({ nativeRange, range, value }) => ({
nativeRange,
range,
value,
})),
).toEqual([
{
nativeRange: { start: 2, end: 5, unit: "code-point" },
range: { startUtf16: 3, endUtf16: 6 },
value: "one",
},
{
nativeRange: { start: 6, end: 9, unit: "code-point" },
range: { startUtf16: 7, endUtf16: 10 },
value: "two",
},
]);
});
it("uses native std::regex replacement and grammar selection", async () => {
const replaced = await adapter.replace(
replacement("(\\w+)", "one two", "<$1>"),
);
expect(replaced.execution.accepted).toBe(true);
expect(replaced.output).toBe("<one> <two>");
const basic = await adapter.execute(
request("\\(ab\\)", "xxabyy", {
options: { grammar: "basic" },
}),
);
expect(basic.matches[0]?.value).toBe("ab");
});
it("bounds capture amplification and preserves the untouched partial suffix", async () => {
const bounded = await adapter.replace(
replacement("(a+)", "a".repeat(256 * 1024), "$1".repeat(4_096), {
maximumOutputBytes: 5,
}),
);
expect(bounded.execution.accepted).toBe(true);
expect(bounded.output).toBe("aaaaa");
expect(bounded.outputBytes).toBe(5);
expect(bounded.outputTruncated).toBe(true);
const partial = await adapter.replace(
replacement("a", "aaa", "x", { maximumMatches: 1 }),
);
expect(partial.execution.matches).toHaveLength(1);
expect(partial.execution.truncated).toBe(true);
expect(partial.output).toBe("xaa");
expect(partial.outputTruncated).toBe(false);
});
it("reports syntax that libc++ std::regex does not accept", async () => {
const result = await adapter.execute(request("(?<name>a)", "a"));
expect(result.accepted).toBe(false);
expect(result.diagnostics[0]).toEqual(
expect.objectContaining({
source: "execution-engine",
severity: "error",
}),
);
});
it("ships a fixed bridge module without dynamic source evaluation", async () => {
const module = await readFile(path.join(pack, "cpp-regex.mjs"), "utf8");
expect(module).not.toContain("new Function");
expect(module).not.toMatch(/\beval\s*\(/u);
const metadata = JSON.parse(
await readFile(path.join(pack, "engine-metadata.json"), "utf8"),
) as {
bridge: { dynamicExecutionDisabled: boolean };
toolchain: { version: string; commitSha1: string };
};
expect(metadata.bridge.dynamicExecutionDisabled).toBe(true);
expect(metadata.toolchain).toEqual(
expect.objectContaining({
version: "6.0.4",
commitSha1: "fe5be6afdff43ad58860d821fcc8572a23f92d19",
}),
);
});
});