mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-11-08 08:44:37 +08:00
A global replace or replaceAll of a regex literal called the compiled matcher once per match: each call set up its frame and seek again, and the runtime then copied the gap and the replacement. A substitution of a literal inside the compiled subset with an ASCII replacement without $ patterns now gets a replacer built for that pattern and replacement. It appends each gap and the replacement to the output and continues the search after the match, as the runtime's loop does (one past an empty match), without returning. Gaps of up to 16 bytes are one 16-byte copy, and the search after a match is a 16-byte scan instead of a memchr call. scr_regex_replace_with runs the replacer on an ASCII subject; a subject outside ASCII keeps the runtime's loop. Plain-text patterns, which the runtime searches as strings, get no replacer. New corpus program: 8351-regex-literal-replacers.ts.
43 lines
1.6 KiB
TypeScript
43 lines
1.6 KiB
TypeScript
import { mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
|
|
import { tmpdir } from "node:os";
|
|
import { join } from "node:path";
|
|
import { expect, test } from "vitest";
|
|
import { compile } from "../src/index.js";
|
|
|
|
async function emitLlvm(source: string): Promise<string> {
|
|
const dir = await mkdtemp(join(tmpdir(), "scriptc-regex-replacers-"));
|
|
try {
|
|
const entry = join(dir, "main.ts");
|
|
const outPath = join(dir, "main.ll");
|
|
await writeFile(entry, source);
|
|
const result = await compile(entry, { outDir: dir, outPath, outputKind: "llvm" });
|
|
expect(result.ok, JSON.stringify(result.diagnostics)).toBe(true);
|
|
return await readFile(outPath, "utf8");
|
|
} finally {
|
|
await rm(dir, { recursive: true, force: true });
|
|
}
|
|
}
|
|
|
|
test("global substitutions of regex literals with literal replacements run a replacer", async () => {
|
|
const llvm = await emitLlvm(`
|
|
const s = process.argv[2] ?? "a<b>c";
|
|
const rep = process.argv[3] ?? "-";
|
|
console.log(
|
|
s.replace(/<[^>]*>/g, "|"),
|
|
s.replaceAll(/a[bc]/g, "<2>"),
|
|
s.replace(/<[^>]*>/g, "|"),
|
|
s.replace(/<[^>]*>/g, "$&$&"),
|
|
s.replace(/<[^>]*>/, "|"),
|
|
s.replace(/<[^>]*>/gy, "|"),
|
|
s.replace(/<[^>]*>/g, rep),
|
|
);
|
|
`);
|
|
const replacers = llvm.match(/^define internal i32 @sc_rr_\d+\(/gm) ?? [];
|
|
// One replacer per (pattern, flags, replacement): the repeated site shares it.
|
|
expect(replacers).toHaveLength(2);
|
|
expect(llvm.match(/call ptr @scr_regex_replace_with\(/g)).toHaveLength(3);
|
|
// $ patterns, non-global and sticky patterns, and computed replacements
|
|
// keep the runtime's loop.
|
|
expect(llvm.match(/call ptr @scr_regex_replace\(/g)!.length).toBeGreaterThanOrEqual(4);
|
|
});
|