@stll/regex-set-wasm 1.0.9 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/regex-set.single-thread.wasm +0 -0
- package/dist/regex-set.wasm32-wasi.wasm +0 -0
- package/dist/single-thread-inline.d.mts +123 -0
- package/dist/single-thread-inline.mjs +1003 -0
- package/dist/single-thread-inline.mjs.map +1 -0
- package/dist/single-thread-size.json +5 -0
- package/dist/single-thread.d.mts +125 -0
- package/dist/single-thread.mjs +1015 -0
- package/dist/single-thread.mjs.map +1 -0
- package/dist/vite.mjs.map +1 -1
- package/dist/wasi-worker-browser.mjs.map +1 -1
- package/dist/wasm.mjs +105 -3
- package/dist/wasm.mjs.map +1 -1
- package/package.json +15 -4
|
@@ -0,0 +1,1015 @@
|
|
|
1
|
+
//#region src/core.ts
|
|
2
|
+
const encoder$1 = new TextEncoder();
|
|
3
|
+
const ALTERNATION_CHUNK_MIN_BRANCHES = 64;
|
|
4
|
+
const ALTERNATION_CHUNK_SIZE = 20;
|
|
5
|
+
const ALTERNATION_CHUNK_MAX_BODY_START = 32;
|
|
6
|
+
let binding;
|
|
7
|
+
/** Set the native backend. Must be called once
|
|
8
|
+
* before any class constructor. */
|
|
9
|
+
const initBinding = (b) => {
|
|
10
|
+
binding = b;
|
|
11
|
+
};
|
|
12
|
+
function unpack(packed, haystack, names, indexMap) {
|
|
13
|
+
const len = packed.length;
|
|
14
|
+
const matches = new Array(len / 3);
|
|
15
|
+
for (let i = 0, j = 0; i < len; i += 3, j++) {
|
|
16
|
+
const idx = packed[i];
|
|
17
|
+
const s = packed[i + 1];
|
|
18
|
+
const e = packed[i + 2];
|
|
19
|
+
if (idx === void 0 || s === void 0 || e === void 0) throw new Error(`Corrupt packed match data at offset ${i}`);
|
|
20
|
+
const pattern = indexMap ? indexMap[idx] : idx;
|
|
21
|
+
if (pattern === void 0) throw new Error(`Missing native index map ${idx}`);
|
|
22
|
+
const m = {
|
|
23
|
+
pattern,
|
|
24
|
+
start: s,
|
|
25
|
+
end: e,
|
|
26
|
+
text: haystack.slice(s, e)
|
|
27
|
+
};
|
|
28
|
+
if (names && names[pattern] !== void 0) m.name = names[pattern];
|
|
29
|
+
matches[j] = m;
|
|
30
|
+
}
|
|
31
|
+
return matches;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Replace unescaped `\b` and `\B` with their
|
|
35
|
+
* ASCII-only equivalents `(?-u:\b)` / `(?-u:\B)`.
|
|
36
|
+
* Skips character classes `[...]` (where `\b` means
|
|
37
|
+
* backspace) and escaped backslashes `\\b`.
|
|
38
|
+
*/
|
|
39
|
+
function asciiBoundaries(src) {
|
|
40
|
+
let result = "";
|
|
41
|
+
let inClass = false;
|
|
42
|
+
let i = 0;
|
|
43
|
+
while (i < src.length) if (src.charAt(i) === "\\" && i + 1 < src.length) {
|
|
44
|
+
const next = src.charAt(i + 1);
|
|
45
|
+
if (!inClass && (next === "b" || next === "B")) result += `(?-u:\\${next})`;
|
|
46
|
+
else result += src.charAt(i) + src.charAt(i + 1);
|
|
47
|
+
i += 2;
|
|
48
|
+
} else {
|
|
49
|
+
if (src.charAt(i) === "[") inClass = true;
|
|
50
|
+
if (src.charAt(i) === "]") inClass = false;
|
|
51
|
+
result += src.charAt(i);
|
|
52
|
+
i++;
|
|
53
|
+
}
|
|
54
|
+
return result;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Convert a RegExp to Rust regex syntax string.
|
|
58
|
+
*/
|
|
59
|
+
function regexpToRust(re) {
|
|
60
|
+
let flags = "";
|
|
61
|
+
if (re.flags.includes("i")) flags += "i";
|
|
62
|
+
if (re.flags.includes("m")) flags += "m";
|
|
63
|
+
if (re.flags.includes("s")) flags += "s";
|
|
64
|
+
if (!flags) return javascriptCharacterClasses(re);
|
|
65
|
+
if (!flags.includes("i")) return `(?${flags})${javascriptCharacterClasses(re)}`;
|
|
66
|
+
let src = re.unicode || re.unicodeSets ? javascriptCharacterClasses(re) : javascriptLegacyCaseFolding(re);
|
|
67
|
+
let leading = "";
|
|
68
|
+
let trailing = "";
|
|
69
|
+
if (src.startsWith("\\b")) {
|
|
70
|
+
leading = "\\b";
|
|
71
|
+
src = src.slice(2);
|
|
72
|
+
} else if (src.startsWith("\\B")) {
|
|
73
|
+
leading = "\\B";
|
|
74
|
+
src = src.slice(2);
|
|
75
|
+
}
|
|
76
|
+
if (src.length >= 2) {
|
|
77
|
+
const last = src.at(-1);
|
|
78
|
+
if (last === "b" || last === "B") {
|
|
79
|
+
let bs = 0;
|
|
80
|
+
let k = src.length - 2;
|
|
81
|
+
while (k >= 0 && src[k] === "\\") {
|
|
82
|
+
bs++;
|
|
83
|
+
k--;
|
|
84
|
+
}
|
|
85
|
+
if (bs > 0 && bs % 2 === 1) {
|
|
86
|
+
trailing = "\\" + last;
|
|
87
|
+
src = src.slice(0, -2);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return `${leading}(?${flags}:${src})${trailing}`;
|
|
92
|
+
}
|
|
93
|
+
const JS_WHITESPACE = "\\x{0009}-\\x{000D}\\x{0020}\\x{00A0}\\x{1680}\\x{2000}-\\x{200A}\\x{2028}\\x{2029}\\x{202F}\\x{205F}\\x{3000}\\x{FEFF}";
|
|
94
|
+
function javascriptCharacterClasses(re) {
|
|
95
|
+
let result = "";
|
|
96
|
+
let classDepth = 0;
|
|
97
|
+
const unicodeCase = re.ignoreCase && (re.unicode || re.unicodeSets);
|
|
98
|
+
const word = "a-zA-Z0-9_" + (unicodeCase ? "\\x{017F}\\x{212A}" : "");
|
|
99
|
+
for (let i = 0; i < re.source.length; i++) {
|
|
100
|
+
const char = re.source.charAt(i);
|
|
101
|
+
if (char !== "\\") {
|
|
102
|
+
if (char === "[" && (classDepth === 0 || re.unicodeSets)) classDepth++;
|
|
103
|
+
if (char === "]" && classDepth > 0) classDepth--;
|
|
104
|
+
result += char;
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
const escaped = re.source.charAt(++i);
|
|
108
|
+
if (escaped === "b" || escaped === "B") {
|
|
109
|
+
if (classDepth > 0) {
|
|
110
|
+
result += escaped === "b" ? "\\x{0008}" : "B";
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
if (!unicodeCase) {
|
|
114
|
+
result += `(?-u:\\${escaped})`;
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
const beforeWord = `(?<=[${word}])`;
|
|
118
|
+
const beforeNonword = `(?<![${word}])`;
|
|
119
|
+
const afterWord = `(?=[${word}])`;
|
|
120
|
+
const afterNonword = `(?![${word}])`;
|
|
121
|
+
result += escaped === "b" ? `(?-i:(?:${beforeWord}${afterNonword}|${beforeNonword}${afterWord}))` : `(?-i:(?:${beforeWord}${afterWord}|${beforeNonword}${afterNonword}))`;
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
let members;
|
|
125
|
+
switch (escaped.toLowerCase()) {
|
|
126
|
+
case "s":
|
|
127
|
+
members = JS_WHITESPACE;
|
|
128
|
+
break;
|
|
129
|
+
case "d":
|
|
130
|
+
members = "0-9";
|
|
131
|
+
break;
|
|
132
|
+
case "w":
|
|
133
|
+
members = word;
|
|
134
|
+
break;
|
|
135
|
+
default:
|
|
136
|
+
result += "\\" + escaped;
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
const negation = escaped === escaped.toUpperCase() ? "^" : "";
|
|
140
|
+
result += `[${negation}${members}]`;
|
|
141
|
+
}
|
|
142
|
+
return result;
|
|
143
|
+
}
|
|
144
|
+
let legacyUnicodeFolds;
|
|
145
|
+
/** Legacy JS folding uses one UTF-16 unit's uppercase, excluding ASCII folds. */
|
|
146
|
+
function getLegacyUnicodeFolds() {
|
|
147
|
+
if (legacyUnicodeFolds) return legacyUnicodeFolds;
|
|
148
|
+
const groups = /* @__PURE__ */ new Map();
|
|
149
|
+
for (let code = 128; code <= 65535; code++) {
|
|
150
|
+
const char = String.fromCharCode(code);
|
|
151
|
+
const canonical = char.toUpperCase();
|
|
152
|
+
if (canonical.length !== 1 || canonical === char || canonical.charCodeAt(0) < 128) continue;
|
|
153
|
+
const members = groups.get(canonical);
|
|
154
|
+
if (members) members.push(char);
|
|
155
|
+
else groups.set(canonical, [canonical, char]);
|
|
156
|
+
}
|
|
157
|
+
legacyUnicodeFolds = groups;
|
|
158
|
+
return groups;
|
|
159
|
+
}
|
|
160
|
+
function legacyLiteralPattern(literal) {
|
|
161
|
+
if (/[a-z]/i.test(literal)) return `(?-i:[${literal.toLowerCase()}${literal.toUpperCase()}])`;
|
|
162
|
+
const code = literal.codePointAt(0);
|
|
163
|
+
if (code === void 0) throw new Error("Missing literal");
|
|
164
|
+
const canonical = literal.toUpperCase();
|
|
165
|
+
const members = canonical.length === 1 && canonical.charCodeAt(0) >= 128 ? getLegacyUnicodeFolds().get(canonical) : void 0;
|
|
166
|
+
return members ? `(?-i:[${members.join("")}])` : `(?-i:\\x{${code.toString(16)}})`;
|
|
167
|
+
}
|
|
168
|
+
/** Keep legacy /i folding local so dots and complements remain Unicode-safe. */
|
|
169
|
+
function javascriptLegacyCaseFolding(re) {
|
|
170
|
+
let result = "";
|
|
171
|
+
for (let i = 0; i < re.source.length; i++) {
|
|
172
|
+
const char = re.source.charAt(i);
|
|
173
|
+
if (char === "\\") {
|
|
174
|
+
const escaped = re.source.charAt(++i);
|
|
175
|
+
if ("sSwWdDbB".includes(escaped)) {
|
|
176
|
+
const token = new RegExp("\\" + escaped, "i");
|
|
177
|
+
result += `(?-i:${javascriptCharacterClasses(token)})`;
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
const hex = re.source.slice(i).match(/^(?:x[\da-f]{2}|u[\da-f]{4})/i)?.[0];
|
|
181
|
+
if (hex) {
|
|
182
|
+
const literal = String.fromCharCode(Number.parseInt(hex.slice(1), 16));
|
|
183
|
+
result += legacyLiteralPattern(literal);
|
|
184
|
+
i += hex.length - 1;
|
|
185
|
+
continue;
|
|
186
|
+
}
|
|
187
|
+
if (escaped === "k" && re.source.charAt(i + 1) === "<") {
|
|
188
|
+
const end = re.source.indexOf(">", i + 2);
|
|
189
|
+
if (end !== -1) {
|
|
190
|
+
result += re.source.slice(i - 1, end + 1);
|
|
191
|
+
i = end;
|
|
192
|
+
continue;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
result += "\\" + escaped;
|
|
196
|
+
continue;
|
|
197
|
+
}
|
|
198
|
+
if (re.source.startsWith("(?<", i) && !"=!".includes(re.source.charAt(i + 3))) {
|
|
199
|
+
const end = re.source.indexOf(">", i + 3);
|
|
200
|
+
if (end !== -1) {
|
|
201
|
+
result += re.source.slice(i, end + 1);
|
|
202
|
+
i = end;
|
|
203
|
+
continue;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
if (char !== "[") {
|
|
207
|
+
const code = re.source.codePointAt(i);
|
|
208
|
+
if (code !== void 0 && code >= 128) {
|
|
209
|
+
const literal = String.fromCodePoint(code);
|
|
210
|
+
result += legacyLiteralPattern(literal);
|
|
211
|
+
i += literal.length - 1;
|
|
212
|
+
} else result += /[a-z]/i.test(char) ? legacyLiteralPattern(char) : char;
|
|
213
|
+
continue;
|
|
214
|
+
}
|
|
215
|
+
const start = i;
|
|
216
|
+
while (++i < re.source.length) {
|
|
217
|
+
const member = re.source.charAt(i);
|
|
218
|
+
if (member === "]") break;
|
|
219
|
+
if (member !== "\\") continue;
|
|
220
|
+
i++;
|
|
221
|
+
}
|
|
222
|
+
const original = re.source.slice(start, i + 1);
|
|
223
|
+
let normalized = "";
|
|
224
|
+
let previousShortcut = false;
|
|
225
|
+
for (let j = 0; j < original.length; j++) {
|
|
226
|
+
const member = original.charAt(j);
|
|
227
|
+
if (member === "\\") {
|
|
228
|
+
const escaped = original.charAt(++j);
|
|
229
|
+
normalized += "\\" + escaped;
|
|
230
|
+
previousShortcut = "sSwWdD".includes(escaped);
|
|
231
|
+
continue;
|
|
232
|
+
}
|
|
233
|
+
const nextShortcut = original.charAt(j + 1) === "\\" && "sSwWdD".includes(original.charAt(j + 2));
|
|
234
|
+
normalized += member === "-" && (previousShortcut || nextShortcut) ? "\\-" : member;
|
|
235
|
+
previousShortcut = false;
|
|
236
|
+
}
|
|
237
|
+
const classRegexp = new RegExp(normalized, "i");
|
|
238
|
+
const escapedNonAscii = [...normalized.matchAll(/\\(?:x([\da-f]{2})|u([\da-f]{4}))/gi)].some((match) => Number.parseInt(match[1] ?? match[2] ?? "0", 16) >= 128);
|
|
239
|
+
const body = original.slice(original.startsWith("[^") ? 2 : 1, -1);
|
|
240
|
+
const positive = new RegExp(`[${body}]`, "i");
|
|
241
|
+
let folds = "";
|
|
242
|
+
for (const letter of "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz") if (positive.test(letter)) folds += letter;
|
|
243
|
+
if (hasNonAscii(original) || escapedNonAscii) {
|
|
244
|
+
for (const [canonical, members] of getLegacyUnicodeFolds()) if (positive.test(canonical)) folds += members.join("");
|
|
245
|
+
}
|
|
246
|
+
const translated = javascriptCharacterClasses(classRegexp);
|
|
247
|
+
result += `(?-i:${translated.slice(0, -1)}${folds}])`;
|
|
248
|
+
}
|
|
249
|
+
return result;
|
|
250
|
+
}
|
|
251
|
+
/**
|
|
252
|
+
* Check if content uses character class shortcuts
|
|
253
|
+
* (\w, \W, \d, \D, \s, \S, \b, \B) that have
|
|
254
|
+
* Unicode-aware versions. Only these benefit from
|
|
255
|
+
* -u (ASCII-only mode). Literal strings like
|
|
256
|
+
* "dollars" produce identical DFAs with or without
|
|
257
|
+
* -u, so skipping -u for them is zero-cost.
|
|
258
|
+
*/
|
|
259
|
+
function needsAsciiMode(s) {
|
|
260
|
+
for (let i = 0; i < s.length; i++) {
|
|
261
|
+
if (s.charAt(i) !== "\\") continue;
|
|
262
|
+
const escaped = s.charAt(++i);
|
|
263
|
+
if ("wWdDsSbB".includes(escaped)) return true;
|
|
264
|
+
}
|
|
265
|
+
return false;
|
|
266
|
+
}
|
|
267
|
+
/**
|
|
268
|
+
* Check if a string contains non-ASCII characters.
|
|
269
|
+
* When true, -u MUST NOT be added: regex-automata
|
|
270
|
+
* rejects (?-u) alongside non-ASCII content like
|
|
271
|
+
* [ACDE] or literal zlotych.
|
|
272
|
+
*/
|
|
273
|
+
function hasNonAscii(s) {
|
|
274
|
+
for (let i = 0; i < s.length; i++) if (s.charCodeAt(i) > 127) return true;
|
|
275
|
+
return false;
|
|
276
|
+
}
|
|
277
|
+
/**
|
|
278
|
+
* Convert inline (?i) flags to (?i-u) for ASCII
|
|
279
|
+
* case folding. Handles bare and scoped groups.
|
|
280
|
+
*
|
|
281
|
+
* Bare (?i) at the start of a pattern is converted
|
|
282
|
+
* to a scoped group (?i-u:...) with edge \b pulled
|
|
283
|
+
* outside, matching the RegExp path behaviour. This
|
|
284
|
+
* prevents -u from affecting \b word boundary
|
|
285
|
+
* semantics (which should remain Unicode when
|
|
286
|
+
* unicodeBoundaries is true).
|
|
287
|
+
*
|
|
288
|
+
* NOTE: -u also disables Unicode character classes
|
|
289
|
+
* (\w, \d, \s become ASCII-only), matching the
|
|
290
|
+
* behaviour of regexpToRust() for non-Unicode /i RegExps.
|
|
291
|
+
*/
|
|
292
|
+
function scopeInlineFlags(src) {
|
|
293
|
+
const leadingBare = src.match(/^\(\?([ims]+)(?:-([imsu]+))?\)/);
|
|
294
|
+
const enableGroup = leadingBare?.[1];
|
|
295
|
+
if (leadingBare && enableGroup?.includes("i")) {
|
|
296
|
+
const enable = enableGroup;
|
|
297
|
+
const disable = leadingBare[2] ?? "";
|
|
298
|
+
let rest = src.slice(leadingBare[0].length);
|
|
299
|
+
let leading = "";
|
|
300
|
+
let trailing = "";
|
|
301
|
+
if (rest.startsWith("\\b")) {
|
|
302
|
+
leading = "\\b";
|
|
303
|
+
rest = rest.slice(2);
|
|
304
|
+
} else if (rest.startsWith("\\B")) {
|
|
305
|
+
leading = "\\B";
|
|
306
|
+
rest = rest.slice(2);
|
|
307
|
+
}
|
|
308
|
+
if (rest.length >= 2) {
|
|
309
|
+
const last = rest.at(-1);
|
|
310
|
+
if (last === "b" || last === "B") {
|
|
311
|
+
let bs = 0;
|
|
312
|
+
let k = rest.length - 2;
|
|
313
|
+
while (k >= 0 && rest[k] === "\\") {
|
|
314
|
+
bs++;
|
|
315
|
+
k--;
|
|
316
|
+
}
|
|
317
|
+
if (bs > 0 && bs % 2 === 1) {
|
|
318
|
+
trailing = "\\" + last;
|
|
319
|
+
rest = rest.slice(0, -2);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
const inner = scopeInnerFlags(rest);
|
|
324
|
+
const merged = needsAsciiMode(rest) && !hasNonAscii(rest) && !disable.includes("u") ? disable + "u" : disable;
|
|
325
|
+
const disablePart = merged ? `-${merged}` : "";
|
|
326
|
+
return `${leading}(?${enable}${disablePart}:${inner})${trailing}`;
|
|
327
|
+
}
|
|
328
|
+
return scopeInnerFlags(src);
|
|
329
|
+
}
|
|
330
|
+
/**
|
|
331
|
+
* Replace inline (?i) / (?i:...) groups with -u
|
|
332
|
+
* variants. Does not handle leading bare flags
|
|
333
|
+
* (that's done by scopeInlineFlags above).
|
|
334
|
+
*/
|
|
335
|
+
function scopeInnerFlags(src) {
|
|
336
|
+
let result = "";
|
|
337
|
+
let inClass = false;
|
|
338
|
+
let i = 0;
|
|
339
|
+
while (i < src.length) {
|
|
340
|
+
if (src[i] === "\\" && i + 1 < src.length) {
|
|
341
|
+
result += src.charAt(i) + src.charAt(i + 1);
|
|
342
|
+
i += 2;
|
|
343
|
+
continue;
|
|
344
|
+
}
|
|
345
|
+
if (src.charAt(i) === "[") inClass = true;
|
|
346
|
+
if (src.charAt(i) === "]") inClass = false;
|
|
347
|
+
if (!inClass && src.charAt(i) === "(" && src.charAt(i + 1) === "?") {
|
|
348
|
+
let j = i + 2;
|
|
349
|
+
let enable = "";
|
|
350
|
+
while (j < src.length && "ims".includes(src.charAt(j))) {
|
|
351
|
+
enable += src.charAt(j);
|
|
352
|
+
j++;
|
|
353
|
+
}
|
|
354
|
+
let disable = "";
|
|
355
|
+
if (j < src.length && src.charAt(j) === "-") {
|
|
356
|
+
j++;
|
|
357
|
+
while (j < src.length && "imsu".includes(src.charAt(j))) {
|
|
358
|
+
disable += src.charAt(j);
|
|
359
|
+
j++;
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
if (enable.length > 0 && (src.charAt(j) === ")" || src.charAt(j) === ":")) {
|
|
363
|
+
if (enable.includes("i")) {
|
|
364
|
+
if (disable.length > 0) result += `(?${enable}-${disable}${src.charAt(j)}`;
|
|
365
|
+
else result += `(?${enable}${src.charAt(j)}`;
|
|
366
|
+
} else if (disable.length > 0) result += `(?${enable}-${disable}${src.charAt(j)}`;
|
|
367
|
+
else result += `(?${enable}${src.charAt(j)}`;
|
|
368
|
+
i = j + 1;
|
|
369
|
+
continue;
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
result += src.charAt(i);
|
|
373
|
+
i++;
|
|
374
|
+
}
|
|
375
|
+
return result;
|
|
376
|
+
}
|
|
377
|
+
/**
|
|
378
|
+
* Normalize a pattern entry to { pattern, name }.
|
|
379
|
+
*/
|
|
380
|
+
function normalizeEntry(p, i) {
|
|
381
|
+
if (typeof p === "string") return {
|
|
382
|
+
pattern: scopeInlineFlags(p),
|
|
383
|
+
name: void 0
|
|
384
|
+
};
|
|
385
|
+
if (p instanceof RegExp) return {
|
|
386
|
+
pattern: regexpToRust(p),
|
|
387
|
+
name: void 0
|
|
388
|
+
};
|
|
389
|
+
if (typeof p !== "object" || p === null || !("pattern" in p)) throw new TypeError(`Pattern at index ${i} must be a string, RegExp, or { pattern, name }`);
|
|
390
|
+
const pattern = p.pattern;
|
|
391
|
+
if (typeof pattern !== "string" && !(pattern instanceof RegExp)) throw new TypeError(`Pattern at index ${i}: "pattern" field must be a string or RegExp`);
|
|
392
|
+
const name = "name" in p ? p.name : void 0;
|
|
393
|
+
if (name !== void 0 && typeof name !== "string") throw new TypeError(`Pattern at index ${i}: "name" field must be a string`);
|
|
394
|
+
return {
|
|
395
|
+
pattern: pattern instanceof RegExp ? regexpToRust(pattern) : scopeInlineFlags(pattern),
|
|
396
|
+
name
|
|
397
|
+
};
|
|
398
|
+
}
|
|
399
|
+
function expandLargeAlternation(pattern) {
|
|
400
|
+
const group = findLargestChunkableAlternation(pattern);
|
|
401
|
+
if (!group) return [pattern];
|
|
402
|
+
if (group.branches.length < ALTERNATION_CHUNK_MIN_BRANCHES) return [pattern];
|
|
403
|
+
const expanded = [];
|
|
404
|
+
for (let i = 0; i < group.branches.length; i += ALTERNATION_CHUNK_SIZE) expanded.push(pattern.slice(0, group.bodyStart) + group.branches.slice(i, i + ALTERNATION_CHUNK_SIZE).join("|") + pattern.slice(group.bodyEnd));
|
|
405
|
+
return expanded;
|
|
406
|
+
}
|
|
407
|
+
function findLargestChunkableAlternation(pattern) {
|
|
408
|
+
let best;
|
|
409
|
+
let inClass = false;
|
|
410
|
+
for (let i = 0; i < pattern.length; i++) {
|
|
411
|
+
const ch = pattern.charAt(i);
|
|
412
|
+
if (ch === "\\") {
|
|
413
|
+
i++;
|
|
414
|
+
continue;
|
|
415
|
+
}
|
|
416
|
+
if (ch === "[") {
|
|
417
|
+
inClass = true;
|
|
418
|
+
continue;
|
|
419
|
+
}
|
|
420
|
+
if (ch === "]") {
|
|
421
|
+
inClass = false;
|
|
422
|
+
continue;
|
|
423
|
+
}
|
|
424
|
+
if (inClass || ch !== "(" || pattern.charAt(i + 1) !== "?" || pattern.charAt(i + 2) !== ":") continue;
|
|
425
|
+
const bodyStart = i + 3;
|
|
426
|
+
const bodyEnd = findGroupEnd(pattern, bodyStart);
|
|
427
|
+
if (bodyEnd === void 0) continue;
|
|
428
|
+
const branches = splitTopLevelAlternatives(pattern.slice(bodyStart, bodyEnd));
|
|
429
|
+
if (bodyStart <= ALTERNATION_CHUNK_MAX_BODY_START && branches.length >= ALTERNATION_CHUNK_MIN_BRANCHES && pattern.slice(bodyStart, bodyEnd).includes("(?i:") && (!best || branches.length > best.branches.length)) best = {
|
|
430
|
+
bodyStart,
|
|
431
|
+
bodyEnd,
|
|
432
|
+
branches
|
|
433
|
+
};
|
|
434
|
+
}
|
|
435
|
+
return best;
|
|
436
|
+
}
|
|
437
|
+
function findGroupEnd(pattern, bodyStart) {
|
|
438
|
+
let depth = 0;
|
|
439
|
+
let inClass = false;
|
|
440
|
+
for (let i = bodyStart; i < pattern.length; i++) {
|
|
441
|
+
const ch = pattern.charAt(i);
|
|
442
|
+
if (ch === "\\") {
|
|
443
|
+
i++;
|
|
444
|
+
continue;
|
|
445
|
+
}
|
|
446
|
+
if (ch === "[") {
|
|
447
|
+
inClass = true;
|
|
448
|
+
continue;
|
|
449
|
+
}
|
|
450
|
+
if (ch === "]") {
|
|
451
|
+
inClass = false;
|
|
452
|
+
continue;
|
|
453
|
+
}
|
|
454
|
+
if (inClass) continue;
|
|
455
|
+
if (ch === "(") {
|
|
456
|
+
depth++;
|
|
457
|
+
continue;
|
|
458
|
+
}
|
|
459
|
+
if (ch !== ")") continue;
|
|
460
|
+
if (depth === 0) return i;
|
|
461
|
+
depth--;
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
function splitTopLevelAlternatives(body) {
|
|
465
|
+
const branches = [];
|
|
466
|
+
let depth = 0;
|
|
467
|
+
let inClass = false;
|
|
468
|
+
let start = 0;
|
|
469
|
+
for (let i = 0; i < body.length; i++) {
|
|
470
|
+
const ch = body.charAt(i);
|
|
471
|
+
if (ch === "\\") {
|
|
472
|
+
i++;
|
|
473
|
+
continue;
|
|
474
|
+
}
|
|
475
|
+
if (ch === "[") {
|
|
476
|
+
inClass = true;
|
|
477
|
+
continue;
|
|
478
|
+
}
|
|
479
|
+
if (ch === "]") {
|
|
480
|
+
inClass = false;
|
|
481
|
+
continue;
|
|
482
|
+
}
|
|
483
|
+
if (inClass) continue;
|
|
484
|
+
if (ch === "(") {
|
|
485
|
+
depth++;
|
|
486
|
+
continue;
|
|
487
|
+
}
|
|
488
|
+
if (ch === ")") {
|
|
489
|
+
depth--;
|
|
490
|
+
continue;
|
|
491
|
+
}
|
|
492
|
+
if (ch === "|" && depth === 0) {
|
|
493
|
+
branches.push(body.slice(start, i));
|
|
494
|
+
start = i + 1;
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
branches.push(body.slice(start));
|
|
498
|
+
return branches;
|
|
499
|
+
}
|
|
500
|
+
function buildNativeInput(patterns, options) {
|
|
501
|
+
const entries = patterns.map(normalizeEntry);
|
|
502
|
+
const names = entries.map((e) => e.name);
|
|
503
|
+
const unicode = options?.unicodeBoundaries ?? true;
|
|
504
|
+
const wholeWords = options?.wholeWords ?? false;
|
|
505
|
+
const ci = options?.caseInsensitive ?? false;
|
|
506
|
+
let processed = entries.map((e) => e.pattern);
|
|
507
|
+
if (ci) processed = processed.map(caseInsensitivePattern);
|
|
508
|
+
if (!unicode) processed = processed.map(asciiBoundaries);
|
|
509
|
+
const nativeOpts = options ? {
|
|
510
|
+
...options.wholeWords !== void 0 ? { wholeWords: options.wholeWords } : {},
|
|
511
|
+
...options.unicodeBoundaries !== void 0 ? { unicodeBoundaries: options.unicodeBoundaries } : {}
|
|
512
|
+
} : void 0;
|
|
513
|
+
const nativePatterns = [];
|
|
514
|
+
const nativeIndexMap = [];
|
|
515
|
+
const jsFallbacks = [];
|
|
516
|
+
for (let i = 0; i < processed.length; i++) {
|
|
517
|
+
const pattern = processed[i];
|
|
518
|
+
if (pattern === void 0) throw new Error(`Missing processed pattern ${i}`);
|
|
519
|
+
const expanded = expandLargeAlternation(pattern);
|
|
520
|
+
for (const expandedPattern of expanded) {
|
|
521
|
+
const jsFallback = jsFallbackRegExp(expandedPattern, unicode);
|
|
522
|
+
if (jsFallback) jsFallbacks.push({
|
|
523
|
+
pattern: i,
|
|
524
|
+
re: jsFallback
|
|
525
|
+
});
|
|
526
|
+
else {
|
|
527
|
+
nativeIndexMap.push(i);
|
|
528
|
+
nativePatterns.push(expandedPattern);
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
return {
|
|
533
|
+
names,
|
|
534
|
+
hasNames: entries.some((e) => e.name !== void 0),
|
|
535
|
+
patternCount: entries.length,
|
|
536
|
+
nativePatterns,
|
|
537
|
+
nativeOpts,
|
|
538
|
+
nativeIndexMap,
|
|
539
|
+
jsFallbacks,
|
|
540
|
+
boundaryOptions: {
|
|
541
|
+
wholeWords,
|
|
542
|
+
unicodeBoundaries: unicode
|
|
543
|
+
}
|
|
544
|
+
};
|
|
545
|
+
}
|
|
546
|
+
function caseInsensitivePattern(p) {
|
|
547
|
+
if (/^(?:\\[bB]|\(\?[ims]+(?:-[imsu]+)?\))*\(\?[ims]*i[ims]*(?:-[imsu]+)?[:(]/.test(p)) return p;
|
|
548
|
+
let src = p;
|
|
549
|
+
let flagPrefix = "";
|
|
550
|
+
const bareFlagMatch = src.match(/^\(\?[ims]+(?:-[imsu]+)?\)/);
|
|
551
|
+
if (bareFlagMatch) {
|
|
552
|
+
flagPrefix = bareFlagMatch[0];
|
|
553
|
+
src = src.slice(flagPrefix.length);
|
|
554
|
+
}
|
|
555
|
+
let leading = "";
|
|
556
|
+
let trailing = "";
|
|
557
|
+
if (src.startsWith("\\b")) {
|
|
558
|
+
leading = "\\b";
|
|
559
|
+
src = src.slice(2);
|
|
560
|
+
} else if (src.startsWith("\\B")) {
|
|
561
|
+
leading = "\\B";
|
|
562
|
+
src = src.slice(2);
|
|
563
|
+
}
|
|
564
|
+
if (src.length >= 2) {
|
|
565
|
+
const last = src.at(-1);
|
|
566
|
+
if (last === "b" || last === "B") {
|
|
567
|
+
let bs = 0;
|
|
568
|
+
let k = src.length - 2;
|
|
569
|
+
while (k >= 0 && src[k] === "\\") {
|
|
570
|
+
bs++;
|
|
571
|
+
k--;
|
|
572
|
+
}
|
|
573
|
+
if (bs > 0 && bs % 2 === 1) {
|
|
574
|
+
trailing = "\\" + last;
|
|
575
|
+
src = src.slice(0, -2);
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
const uFlag = needsAsciiMode(src) && !hasNonAscii(src) ? "-u" : "";
|
|
580
|
+
return `${flagPrefix}${leading}(?i${uFlag}:${src})${trailing}`;
|
|
581
|
+
}
|
|
582
|
+
/**
|
|
583
|
+
* Multi-pattern regex matcher.
|
|
584
|
+
*
|
|
585
|
+
* Compiles multiple regex patterns into a single
|
|
586
|
+
* automaton for the main matching path. Standard
|
|
587
|
+
* scans use Rust's DFA engine without catastrophic
|
|
588
|
+
* backtracking. Some lookaround cases may use a
|
|
589
|
+
* targeted fallback verifier to preserve
|
|
590
|
+
* correctness. Uses Rust regex syntax for string
|
|
591
|
+
* patterns (no lookaheads/backreferences).
|
|
592
|
+
*
|
|
593
|
+
* @example
|
|
594
|
+
* ```ts
|
|
595
|
+
* // Simple
|
|
596
|
+
* new RegexSet([/\d{8}/, "\\+?\\d{9,12}"]);
|
|
597
|
+
*
|
|
598
|
+
* // Named
|
|
599
|
+
* new RegexSet([
|
|
600
|
+
* { pattern: /\d{8}/, name: "ico" },
|
|
601
|
+
* { pattern: /\d{2}\.\d{2}\.\d{4}/, name: "date" },
|
|
602
|
+
* ]);
|
|
603
|
+
* // match.name === "date"
|
|
604
|
+
* ```
|
|
605
|
+
*/
|
|
606
|
+
var RegexSet = class {
|
|
607
|
+
_inner;
|
|
608
|
+
_names;
|
|
609
|
+
_hasNames;
|
|
610
|
+
_patternCount;
|
|
611
|
+
_nativeIndexMap;
|
|
612
|
+
_jsFallbacks;
|
|
613
|
+
_nativeSingles;
|
|
614
|
+
_boundaryOptions;
|
|
615
|
+
constructor(patterns, options, prepared) {
|
|
616
|
+
const nativeInput = buildNativeInput(patterns, options);
|
|
617
|
+
this._names = nativeInput.names;
|
|
618
|
+
this._hasNames = nativeInput.hasNames;
|
|
619
|
+
this._patternCount = nativeInput.patternCount;
|
|
620
|
+
this._boundaryOptions = nativeInput.boundaryOptions;
|
|
621
|
+
this._nativeIndexMap = nativeInput.nativeIndexMap;
|
|
622
|
+
this._jsFallbacks = nativeInput.jsFallbacks;
|
|
623
|
+
this._nativeSingles = [];
|
|
624
|
+
this._inner = new binding.RegexSet(nativeInput.nativePatterns, nativeInput.nativeOpts, prepared);
|
|
625
|
+
if (this._jsFallbacks.length > 0) this._nativeSingles = nativeInput.nativePatterns.map((pattern, i) => {
|
|
626
|
+
const original = this._nativeIndexMap[i];
|
|
627
|
+
if (original === void 0) throw new Error(`Missing native index map ${i}`);
|
|
628
|
+
return {
|
|
629
|
+
pattern: original,
|
|
630
|
+
inner: new binding.RegexSet([pattern], nativeInput.nativeOpts)
|
|
631
|
+
};
|
|
632
|
+
});
|
|
633
|
+
}
|
|
634
|
+
static prepare(patterns, options) {
|
|
635
|
+
const nativeInput = buildNativeInput(patterns, options);
|
|
636
|
+
return binding._prepareRegexSet(nativeInput.nativePatterns, nativeInput.nativeOpts);
|
|
637
|
+
}
|
|
638
|
+
/** Number of patterns. */
|
|
639
|
+
get patternCount() {
|
|
640
|
+
return this._patternCount;
|
|
641
|
+
}
|
|
642
|
+
/** Internal diagnostics: fast DFA, verified DFA, and fallback pattern counts. */
|
|
643
|
+
_enginePathCounts() {
|
|
644
|
+
return [...this._inner._enginePathCounts()];
|
|
645
|
+
}
|
|
646
|
+
/** Returns `true` if any pattern matches. */
|
|
647
|
+
isMatch(haystack) {
|
|
648
|
+
if (this._inner._isMatchBuf(encoder$1.encode(haystack))) return true;
|
|
649
|
+
for (const fb of this._jsFallbacks) if (this.findFirstJsFallback(haystack, fb)) return true;
|
|
650
|
+
return false;
|
|
651
|
+
}
|
|
652
|
+
/** Find all non-overlapping matches. */
|
|
653
|
+
findIter(haystack) {
|
|
654
|
+
if (this._jsFallbacks.length > 0) return selectNonOverlapping(this.findNativeSingles(haystack).concat(this.findJsFallbacks(haystack)));
|
|
655
|
+
return unpack(this._inner._findIterPackedBuf(encoder$1.encode(haystack)), haystack, this._hasNames ? this._names : null, this._nativeIndexMap);
|
|
656
|
+
}
|
|
657
|
+
/** Which pattern indices matched (not where). */
|
|
658
|
+
whichMatch(haystack) {
|
|
659
|
+
const seen = /* @__PURE__ */ new Set();
|
|
660
|
+
for (const pattern of this._inner.whichMatch(haystack)) {
|
|
661
|
+
const original = this._nativeIndexMap[pattern];
|
|
662
|
+
if (original === void 0) throw new Error(`Missing native index map ${pattern}`);
|
|
663
|
+
seen.add(original);
|
|
664
|
+
}
|
|
665
|
+
for (const fb of this._jsFallbacks) if (this.findFirstJsFallback(haystack, fb)) seen.add(fb.pattern);
|
|
666
|
+
return [...seen];
|
|
667
|
+
}
|
|
668
|
+
/**
|
|
669
|
+
* Replace all non-overlapping matches.
|
|
670
|
+
* `replacements[i]` replaces pattern `i`.
|
|
671
|
+
*/
|
|
672
|
+
replaceAll(haystack, replacements) {
|
|
673
|
+
if (this._jsFallbacks.length === 0 && this._nativeIndexMap.length === this._patternCount && this._nativeIndexMap.every((idx, i) => idx === i)) return this._inner.replaceAll(haystack, replacements);
|
|
674
|
+
if (replacements.length !== this._patternCount) throw new Error(`Expected ${this._patternCount} replacements, got ${replacements.length}`);
|
|
675
|
+
const matches = this.findIter(haystack);
|
|
676
|
+
let result = "";
|
|
677
|
+
let last = 0;
|
|
678
|
+
for (const m of matches) {
|
|
679
|
+
result += haystack.slice(last, m.start);
|
|
680
|
+
const replacement = replacements[m.pattern];
|
|
681
|
+
if (replacement === void 0) throw new Error(`Missing replacement for pattern ${m.pattern}`);
|
|
682
|
+
result += replacement;
|
|
683
|
+
last = m.end;
|
|
684
|
+
}
|
|
685
|
+
result += haystack.slice(last);
|
|
686
|
+
return result;
|
|
687
|
+
}
|
|
688
|
+
findJsFallbacks(haystack) {
|
|
689
|
+
const matches = [];
|
|
690
|
+
for (const fb of this._jsFallbacks) {
|
|
691
|
+
fb.re.lastIndex = 0;
|
|
692
|
+
for (;;) {
|
|
693
|
+
const m = fb.re.exec(haystack);
|
|
694
|
+
if (!m) break;
|
|
695
|
+
const text = m[0];
|
|
696
|
+
const start = m.index;
|
|
697
|
+
const end = start + text.length;
|
|
698
|
+
if (this.acceptJsFallbackMatch(haystack, start, end)) {
|
|
699
|
+
const match = {
|
|
700
|
+
pattern: fb.pattern,
|
|
701
|
+
start,
|
|
702
|
+
end,
|
|
703
|
+
text
|
|
704
|
+
};
|
|
705
|
+
const name = this._names[fb.pattern];
|
|
706
|
+
if (name !== void 0) match.name = name;
|
|
707
|
+
matches.push(match);
|
|
708
|
+
if (text.length === 0) fb.re.lastIndex = nextRegexStart(haystack, start);
|
|
709
|
+
} else fb.re.lastIndex = nextRegexStart(haystack, start);
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
return matches;
|
|
713
|
+
}
|
|
714
|
+
findNativeSingles(haystack) {
|
|
715
|
+
const encoded = encoder$1.encode(haystack);
|
|
716
|
+
const matches = [];
|
|
717
|
+
for (const single of this._nativeSingles) matches.push(...unpack(single.inner._findIterPackedBuf(encoded), haystack, this._hasNames ? this._names : null, [single.pattern]));
|
|
718
|
+
return matches;
|
|
719
|
+
}
|
|
720
|
+
findFirstJsFallback(haystack, fb) {
|
|
721
|
+
fb.re.lastIndex = 0;
|
|
722
|
+
for (;;) {
|
|
723
|
+
const m = fb.re.exec(haystack);
|
|
724
|
+
if (!m) return false;
|
|
725
|
+
const text = m[0];
|
|
726
|
+
const start = m.index;
|
|
727
|
+
const end = start + text.length;
|
|
728
|
+
if (this.acceptJsFallbackMatch(haystack, start, end)) return true;
|
|
729
|
+
fb.re.lastIndex = nextRegexStart(haystack, start);
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
acceptJsFallbackMatch(haystack, start, end) {
|
|
733
|
+
if (!this._boundaryOptions.wholeWords) return true;
|
|
734
|
+
return isWordBoundary(haystack, start, this._boundaryOptions.unicodeBoundaries) && isWordBoundary(haystack, end, this._boundaryOptions.unicodeBoundaries);
|
|
735
|
+
}
|
|
736
|
+
};
|
|
737
|
+
function jsFallbackRegExp(pattern, unicodeBoundaries) {
|
|
738
|
+
if (!hasLookaround(pattern) || unicodeBoundaries && hasRegexWordBoundary(pattern) || hasRustUnicodeShorthand(pattern) || hasRustClassSetOperation(pattern) || countAlternations(pattern) < 128) return;
|
|
739
|
+
try {
|
|
740
|
+
return new RegExp(pattern, "gu");
|
|
741
|
+
} catch {
|
|
742
|
+
return;
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
function hasLookaround(pattern) {
|
|
746
|
+
return pattern.includes("(?=") || pattern.includes("(?!") || pattern.includes("(?<=") || pattern.includes("(?<!");
|
|
747
|
+
}
|
|
748
|
+
function countAlternations(pattern) {
|
|
749
|
+
let count = 0;
|
|
750
|
+
let inClass = false;
|
|
751
|
+
for (let i = 0; i < pattern.length; i++) {
|
|
752
|
+
const ch = pattern[i];
|
|
753
|
+
if (ch === "\\") {
|
|
754
|
+
i++;
|
|
755
|
+
continue;
|
|
756
|
+
}
|
|
757
|
+
if (ch === "[") inClass = true;
|
|
758
|
+
else if (ch === "]") inClass = false;
|
|
759
|
+
else if (ch === "|" && !inClass) count++;
|
|
760
|
+
}
|
|
761
|
+
return count;
|
|
762
|
+
}
|
|
763
|
+
function hasRegexWordBoundary(pattern) {
|
|
764
|
+
let inClass = false;
|
|
765
|
+
for (let i = 0; i < pattern.length; i++) {
|
|
766
|
+
const ch = pattern[i];
|
|
767
|
+
if (ch === "\\") {
|
|
768
|
+
const next = pattern[i + 1];
|
|
769
|
+
if (!inClass && (next === "b" || next === "B")) return true;
|
|
770
|
+
i++;
|
|
771
|
+
continue;
|
|
772
|
+
}
|
|
773
|
+
if (ch === "[") inClass = true;
|
|
774
|
+
else if (ch === "]") inClass = false;
|
|
775
|
+
}
|
|
776
|
+
return false;
|
|
777
|
+
}
|
|
778
|
+
function hasRustUnicodeShorthand(pattern) {
|
|
779
|
+
for (let i = 0; i < pattern.length; i++) {
|
|
780
|
+
if (pattern[i] !== "\\") continue;
|
|
781
|
+
const next = pattern[i + 1];
|
|
782
|
+
if (next === "d" || next === "D" || next === "w" || next === "W" || next === "s" || next === "S") return true;
|
|
783
|
+
i++;
|
|
784
|
+
}
|
|
785
|
+
return false;
|
|
786
|
+
}
|
|
787
|
+
function hasRustClassSetOperation(pattern) {
|
|
788
|
+
let inClass = false;
|
|
789
|
+
for (let i = 0; i < pattern.length; i++) {
|
|
790
|
+
const ch = pattern[i];
|
|
791
|
+
if (ch === "\\") {
|
|
792
|
+
i++;
|
|
793
|
+
continue;
|
|
794
|
+
}
|
|
795
|
+
if (ch === "[") {
|
|
796
|
+
inClass = true;
|
|
797
|
+
continue;
|
|
798
|
+
}
|
|
799
|
+
if (ch === "]") {
|
|
800
|
+
inClass = false;
|
|
801
|
+
continue;
|
|
802
|
+
}
|
|
803
|
+
if (inClass && i + 1 < pattern.length && (ch === "&" && pattern[i + 1] === "&" || ch === "-" && pattern[i + 1] === "-" || ch === "~" && pattern[i + 1] === "~")) return true;
|
|
804
|
+
}
|
|
805
|
+
return false;
|
|
806
|
+
}
|
|
807
|
+
function nextRegexStart(text, index) {
|
|
808
|
+
if (index >= text.length) return index + 1;
|
|
809
|
+
const first = text.charCodeAt(index);
|
|
810
|
+
if (first >= 55296 && first <= 56319 && index + 1 < text.length) {
|
|
811
|
+
const second = text.charCodeAt(index + 1);
|
|
812
|
+
if (second >= 56320 && second <= 57343) return index + 2;
|
|
813
|
+
}
|
|
814
|
+
return index + 1;
|
|
815
|
+
}
|
|
816
|
+
function selectNonOverlapping(matches) {
|
|
817
|
+
if (matches.length <= 1) return matches;
|
|
818
|
+
matches.sort((a, b) => {
|
|
819
|
+
if (a.start !== b.start) return a.start - b.start;
|
|
820
|
+
const lengthOrder = b.end - b.start - (a.end - a.start);
|
|
821
|
+
if (lengthOrder !== 0) return lengthOrder;
|
|
822
|
+
return a.pattern - b.pattern;
|
|
823
|
+
});
|
|
824
|
+
const selected = [];
|
|
825
|
+
let lastEnd = 0;
|
|
826
|
+
for (const m of matches) if (m.start >= lastEnd) {
|
|
827
|
+
selected.push(m);
|
|
828
|
+
lastEnd = m.end;
|
|
829
|
+
}
|
|
830
|
+
return selected;
|
|
831
|
+
}
|
|
832
|
+
function isWordBoundary(text, pos, unicode) {
|
|
833
|
+
const before = previousCodePoint(text, pos);
|
|
834
|
+
const after = nextCodePoint(text, pos);
|
|
835
|
+
return isWordChar(before, unicode) !== isWordChar(after, unicode);
|
|
836
|
+
}
|
|
837
|
+
function previousCodePoint(text, pos) {
|
|
838
|
+
if (pos <= 0) return void 0;
|
|
839
|
+
return Array.from(text.slice(0, pos)).at(-1);
|
|
840
|
+
}
|
|
841
|
+
function nextCodePoint(text, pos) {
|
|
842
|
+
if (pos >= text.length) return void 0;
|
|
843
|
+
const cp = text.codePointAt(pos);
|
|
844
|
+
return cp === void 0 ? void 0 : String.fromCodePoint(cp);
|
|
845
|
+
}
|
|
846
|
+
function isWordChar(ch, unicode) {
|
|
847
|
+
if (ch === void 0) return false;
|
|
848
|
+
return unicode ? /^[\p{Alphabetic}\p{Number}_]$/u.test(ch) : /^[A-Za-z0-9_]$/.test(ch);
|
|
849
|
+
}
|
|
850
|
+
//#endregion
|
|
851
|
+
//#region src/single-thread-binding.ts
|
|
852
|
+
const encoder = new TextEncoder();
|
|
853
|
+
const decoder = new TextDecoder();
|
|
854
|
+
const LONE_SURROGATE = /[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/gu;
|
|
855
|
+
const REINITIALIZE = "The single-thread WebAssembly instance trapped; call init() or initSync() again";
|
|
856
|
+
const napiString = (value) => value.replaceAll(LONE_SURROGATE, "�");
|
|
857
|
+
const createSingleThreadBinding = (wasm, onTrap) => {
|
|
858
|
+
let trapped = false;
|
|
859
|
+
const request = (body) => {
|
|
860
|
+
if (trapped) throw new Error(REINITIALIZE);
|
|
861
|
+
const bytes = encoder.encode(JSON.stringify(body, (_key, value) => typeof value === "string" ? napiString(value) : value));
|
|
862
|
+
try {
|
|
863
|
+
const pointer = wasm.alloc(bytes.length);
|
|
864
|
+
new Uint8Array(wasm.memory.buffer, pointer, bytes.length).set(bytes);
|
|
865
|
+
wasm.dispatch(pointer, bytes.length);
|
|
866
|
+
const output = new Uint8Array(wasm.memory.buffer, wasm.output_pointer(), wasm.output_length());
|
|
867
|
+
const reply = JSON.parse(decoder.decode(output));
|
|
868
|
+
if (!reply.ok) throw new Error(reply.error ?? "WASM operation failed");
|
|
869
|
+
return reply.value;
|
|
870
|
+
} catch (error) {
|
|
871
|
+
if (error instanceof WebAssembly.RuntimeError) {
|
|
872
|
+
trapped = true;
|
|
873
|
+
onTrap();
|
|
874
|
+
}
|
|
875
|
+
throw error;
|
|
876
|
+
}
|
|
877
|
+
};
|
|
878
|
+
const numbers = (value) => {
|
|
879
|
+
if (!Array.isArray(value) || !value.every(Number.isInteger)) throw new TypeError("Invalid numeric response from single-thread WebAssembly");
|
|
880
|
+
return value;
|
|
881
|
+
};
|
|
882
|
+
const bytes = (value) => [...value];
|
|
883
|
+
const options = (opts) => ({
|
|
884
|
+
whole_words: opts?.wholeWords ?? false,
|
|
885
|
+
unicode_boundaries: opts?.unicodeBoundaries ?? true
|
|
886
|
+
});
|
|
887
|
+
class WasmRegexSet {
|
|
888
|
+
handle;
|
|
889
|
+
patternCount;
|
|
890
|
+
constructor(patterns, opts, prepared) {
|
|
891
|
+
this.patternCount = patterns.length;
|
|
892
|
+
const value = request({
|
|
893
|
+
op: "create",
|
|
894
|
+
patterns,
|
|
895
|
+
...options(opts),
|
|
896
|
+
prepared: prepared ? bytes(prepared) : null
|
|
897
|
+
});
|
|
898
|
+
if (!Number.isInteger(value)) throw new TypeError("Invalid matcher handle from single-thread WebAssembly");
|
|
899
|
+
this.handle = Number(value);
|
|
900
|
+
finalizers.register(this, this.handle);
|
|
901
|
+
}
|
|
902
|
+
_enginePathCounts() {
|
|
903
|
+
return Uint32Array.from(numbers(request({
|
|
904
|
+
op: "counts",
|
|
905
|
+
handle: this.handle
|
|
906
|
+
})));
|
|
907
|
+
}
|
|
908
|
+
isMatch(haystack) {
|
|
909
|
+
return Boolean(request({
|
|
910
|
+
op: "is_match",
|
|
911
|
+
handle: this.handle,
|
|
912
|
+
haystack
|
|
913
|
+
}));
|
|
914
|
+
}
|
|
915
|
+
_isMatchBuf(haystack) {
|
|
916
|
+
return Boolean(request({
|
|
917
|
+
op: "is_match_buf",
|
|
918
|
+
handle: this.handle,
|
|
919
|
+
haystack: bytes(haystack)
|
|
920
|
+
}));
|
|
921
|
+
}
|
|
922
|
+
_findIterPacked(haystack) {
|
|
923
|
+
return Uint32Array.from(numbers(request({
|
|
924
|
+
op: "find",
|
|
925
|
+
handle: this.handle,
|
|
926
|
+
haystack
|
|
927
|
+
})));
|
|
928
|
+
}
|
|
929
|
+
_findIterPackedBuf(haystack) {
|
|
930
|
+
return Uint32Array.from(numbers(request({
|
|
931
|
+
op: "find_buf",
|
|
932
|
+
handle: this.handle,
|
|
933
|
+
haystack: bytes(haystack)
|
|
934
|
+
})));
|
|
935
|
+
}
|
|
936
|
+
whichMatch(haystack) {
|
|
937
|
+
return numbers(request({
|
|
938
|
+
op: "which",
|
|
939
|
+
handle: this.handle,
|
|
940
|
+
haystack
|
|
941
|
+
}));
|
|
942
|
+
}
|
|
943
|
+
replaceAll(haystack, replacements) {
|
|
944
|
+
const value = request({
|
|
945
|
+
op: "replace",
|
|
946
|
+
handle: this.handle,
|
|
947
|
+
haystack,
|
|
948
|
+
replacements
|
|
949
|
+
});
|
|
950
|
+
if (typeof value !== "string") throw new TypeError("Invalid replacement response from single-thread WebAssembly");
|
|
951
|
+
return value;
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
const finalizers = new FinalizationRegistry((handle) => {
|
|
955
|
+
if (!trapped) request({
|
|
956
|
+
op: "free",
|
|
957
|
+
handle
|
|
958
|
+
});
|
|
959
|
+
});
|
|
960
|
+
return {
|
|
961
|
+
RegexSet: WasmRegexSet,
|
|
962
|
+
_prepareRegexSet(patterns, opts) {
|
|
963
|
+
return Uint8Array.from(numbers(request({
|
|
964
|
+
op: "prepare",
|
|
965
|
+
patterns,
|
|
966
|
+
...options(opts)
|
|
967
|
+
})));
|
|
968
|
+
},
|
|
969
|
+
_uax29Boundaries(haystack) {
|
|
970
|
+
return numbers(request({
|
|
971
|
+
op: "boundaries",
|
|
972
|
+
haystack: bytes(haystack)
|
|
973
|
+
}));
|
|
974
|
+
}
|
|
975
|
+
};
|
|
976
|
+
};
|
|
977
|
+
//#endregion
|
|
978
|
+
//#region src/single-thread-runtime.ts
|
|
979
|
+
const isWasmExports = (value) => value["memory"] instanceof WebAssembly.Memory && typeof value["alloc"] === "function" && typeof value["dispatch"] === "function" && typeof value["output_pointer"] === "function" && typeof value["output_length"] === "function";
|
|
980
|
+
const requireWasmExports = (instance) => {
|
|
981
|
+
if (!isWasmExports(instance.exports)) throw new TypeError("Invalid single-thread WebAssembly exports: expected memory and alloc, dispatch, output_pointer, and output_length functions");
|
|
982
|
+
return instance.exports;
|
|
983
|
+
};
|
|
984
|
+
//#endregion
|
|
985
|
+
//#region src/single-thread.ts
|
|
986
|
+
let generation = 0;
|
|
987
|
+
const install = (instance) => {
|
|
988
|
+
const current = ++generation;
|
|
989
|
+
initBinding(createSingleThreadBinding(requireWasmExports(instance), () => {
|
|
990
|
+
if (generation === current) generation++;
|
|
991
|
+
}));
|
|
992
|
+
};
|
|
993
|
+
const isBufferSource = (source) => Object.prototype.toString.call(source) === "[object ArrayBuffer]" || ArrayBuffer.isView(source);
|
|
994
|
+
const isResponse = (source) => typeof source === "object" && "arrayBuffer" in source && "ok" in source && "status" in source;
|
|
995
|
+
const initSync = (source) => {
|
|
996
|
+
install(new WebAssembly.Instance(new WebAssembly.Module(source), {}));
|
|
997
|
+
};
|
|
998
|
+
const init = async (source) => {
|
|
999
|
+
let bytes;
|
|
1000
|
+
if (isBufferSource(source)) bytes = source;
|
|
1001
|
+
else if (isResponse(source)) {
|
|
1002
|
+
if (!source.ok) throw new Error(`Unable to load WebAssembly: HTTP ${source.status}`);
|
|
1003
|
+
bytes = await source.arrayBuffer();
|
|
1004
|
+
} else {
|
|
1005
|
+
const response = await fetch(source);
|
|
1006
|
+
if (!response.ok) throw new Error(`Unable to load WebAssembly: HTTP ${response.status}`);
|
|
1007
|
+
bytes = await response.arrayBuffer();
|
|
1008
|
+
}
|
|
1009
|
+
const result = await WebAssembly.instantiate(bytes, {});
|
|
1010
|
+
install(result.instance);
|
|
1011
|
+
};
|
|
1012
|
+
//#endregion
|
|
1013
|
+
export { RegexSet, init, initSync };
|
|
1014
|
+
|
|
1015
|
+
//# sourceMappingURL=single-thread.mjs.map
|