mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-10-21 05:50:55 +08:00
exec/test rescanned the whole subject for ASCII (and re-converted a non-ASCII subject to UTF-16) on every call, so lastIndex-driven exec loops and sticky tokenizers were quadratic (1e5 matches: 7.9 s vs Node 43 ms). A single-entry cache keeps the last long subject's decoding; it retains the subject so the address cannot be recycled and the string is never appended in place. Only exec/test use it (no user code runs while their input is live). new RegExp recompiled its pattern on every construction (22x Node). A bounded never-evicting cache keyed by (flags, source) shares bytecode through the literal-template mechanism; invalid patterns still throw on every construction.
61 lines
2.3 KiB
TypeScript
61 lines
2.3 KiB
TypeScript
// `new RegExp(source, flags)` built repeatedly from the same pieces shares
|
|
// compiled bytecode between instances. Pins that each instance keeps its own
|
|
// lastIndex, that flags (in any spelling order) select distinct behavior,
|
|
// that invalid patterns throw every time, and that patterns past the cache
|
|
// budget or of long sources still behave.
|
|
const words = ["alpha", "beta", "gamma", "alpha", "delta", "Alpha"];
|
|
function matcher(word: string, flags: string): RegExp {
|
|
return new RegExp("^" + word + "$", flags);
|
|
}
|
|
let hits = 0;
|
|
let ci = 0;
|
|
for (let i = 0; i < 20000; i++) {
|
|
const w = words[i % words.length]!;
|
|
if (matcher("alpha", "").test(w)) hits++;
|
|
if (matcher("alpha", "i").test(w)) ci++;
|
|
}
|
|
console.log(hits, ci);
|
|
|
|
// Independent lastIndex per instance of the same pattern.
|
|
const g1 = new RegExp("\\d", "g");
|
|
const g2 = new RegExp("\\d", "g");
|
|
const subject = "a1b2c3";
|
|
g1.exec(subject);
|
|
g1.exec(subject);
|
|
console.log(g1.lastIndex, g2.lastIndex, g2.exec(subject)![0], g1.exec(subject)![0], g1.lastIndex);
|
|
|
|
// Flag spellings: "gi" and "ig" normalize to the same flags; "y" differs.
|
|
const a = new RegExp("x", "gi");
|
|
const b = new RegExp("x", "ig");
|
|
const y = new RegExp("x", "y");
|
|
console.log(a.flags, b.flags, a.test("aX"), b.test("aX"), y.test("ax"), y.lastIndex);
|
|
|
|
// Empty source, and the source/toString surface.
|
|
const empty = new RegExp("");
|
|
console.log(empty.source, String(empty), empty.test("anything"), new RegExp("a-b").source);
|
|
|
|
// Invalid patterns throw on every construction.
|
|
let thrown = 0;
|
|
for (let i = 0; i < 3; i++) {
|
|
try {
|
|
new RegExp("(unclosed", "g");
|
|
} catch (e) {
|
|
if (e instanceof SyntaxError) thrown++;
|
|
}
|
|
}
|
|
console.log(thrown);
|
|
|
|
// More distinct patterns than the cache holds, then the first ones again.
|
|
let distinct = 0;
|
|
for (let i = 0; i < 600; i++) if (new RegExp("^n" + i + "$").test("n" + i)) distinct++;
|
|
for (let i = 0; i < 600; i += 100) if (new RegExp("^n" + i + "$").test("n" + i)) distinct++;
|
|
console.log(distinct);
|
|
|
|
// A long source.
|
|
const long = "(?:" + "ab|".repeat(600) + "zz)";
|
|
const longRe = new RegExp(long, "g");
|
|
console.log(long.length, "xxzzab".replace(longRe, "_"), new RegExp(long).test("zz"));
|
|
|
|
// Non-unicode astral source (CESU-8 re-encoding) through the cache twice.
|
|
for (let i = 0; i < 2; i++) console.log(new RegExp("😀+").test("a😀😀"), new RegExp("^.$", "u").test("😀"));
|