poe-code 14.0.74 → 14.0.76
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/index.js +1283 -144
- package/dist/index.js.map +4 -4
- package/dist/metafile.json +1 -1
- package/package.json +1 -1
- package/packages/safe-js/dist/chunks/{chunk-YEZOXLY4.js → chunk-UZYQWFMY.js} +1233 -143
- package/packages/safe-js/dist/chunks/chunk-UZYQWFMY.js.map +7 -0
- package/packages/safe-js/dist/chunks/{chunk-2J6W3YGB.js → chunk-VD2AS4E4.js} +2 -2
- package/packages/safe-js/dist/cli.js +2 -2
- package/packages/safe-js/dist/core.js +1 -1
- package/packages/safe-js/dist/index.js +2 -2
- package/packages/safe-js/dist/index.js.map +1 -1
- package/packages/safe-js/dist/interp/bigint-operators.d.ts +3 -0
- package/packages/safe-js/dist/interp/bigint-operators.js +62 -0
- package/packages/safe-js/dist/interp/boxed.d.ts +2 -2
- package/packages/safe-js/dist/interp/boxed.js +4 -1
- package/packages/safe-js/dist/interp/globals/bigint.d.ts +2 -0
- package/packages/safe-js/dist/interp/globals/bigint.js +85 -0
- package/packages/safe-js/dist/interp/globals/console-json.js +57 -24
- package/packages/safe-js/dist/interp/globals/object-array.js +1 -1
- package/packages/safe-js/dist/interp/globals/regex.js +108 -2
- package/packages/safe-js/dist/interp/globals.d.ts +1 -0
- package/packages/safe-js/dist/interp/globals.js +2 -0
- package/packages/safe-js/dist/interp/host-bridge.js +2 -0
- package/packages/safe-js/dist/interp/interpreter.js +59 -12
- package/packages/safe-js/dist/interp/iteration.js +5 -2
- package/packages/safe-js/dist/interp/methods/number.js +1 -1
- package/packages/safe-js/dist/interp/methods/regex-split.d.ts +3 -0
- package/packages/safe-js/dist/interp/methods/regex-split.js +102 -0
- package/packages/safe-js/dist/interp/methods/regex.d.ts +1 -1
- package/packages/safe-js/dist/interp/methods/regex.js +16 -5
- package/packages/safe-js/dist/interp/methods/regexp-iterator.d.ts +5 -1
- package/packages/safe-js/dist/interp/methods/regexp-iterator.js +59 -7
- package/packages/safe-js/dist/interp/methods/string.js +9 -9
- package/packages/safe-js/dist/interp/object-model.d.ts +1 -0
- package/packages/safe-js/dist/interp/object-model.js +4 -1
- package/packages/safe-js/dist/interp/regex/compile-guard.js +26 -4
- package/packages/safe-js/dist/interp/regex/engine.d.ts +4 -0
- package/packages/safe-js/dist/interp/regex/engine.js +230 -30
- package/packages/safe-js/dist/interp/regex/parse.d.ts +42 -4
- package/packages/safe-js/dist/interp/regex/parse.js +429 -25
- package/packages/safe-js/dist/interp/regexp-iterator.d.ts +4 -2
- package/packages/safe-js/dist/interp/regexp-iterator.js +6 -1
- package/packages/safe-js/dist/interp/string-coercion.d.ts +1 -1
- package/packages/safe-js/dist/interp/string-coercion.js +8 -3
- package/packages/safe-js/dist/interp/values.d.ts +1 -1
- package/packages/safe-js/dist/interp/values.js +10 -3
- package/packages/safe-js/dist/lint/rules/AS-async-not-needed.js +2 -0
- package/packages/safe-js/dist/lint/rules/AS-missing-async.js +2 -0
- package/packages/safe-js/dist/lint/rules/known-globals.d.ts +1 -1
- package/packages/safe-js/dist/lint/rules/known-globals.js +1 -0
- package/packages/safe-js/dist/parse/parser.d.ts +6 -1
- package/packages/safe-js/dist/parse/parser.js +8 -0
- package/packages/safe-js/dist/parse/tokenizer.js +12 -4
- package/packages/safe-js/dist/parse.d.ts +1 -1
- package/packages/safe-js/dist/snapshot/bigint.d.ts +1 -0
- package/packages/safe-js/dist/snapshot/bigint.js +14 -0
- package/packages/safe-js/dist/snapshot/dump-format.js +12 -2
- package/packages/safe-js/dist/snapshot/replay-data.d.ts +6 -0
- package/packages/safe-js/dist/snapshot/replay-data.js +23 -5
- package/packages/safe-js/dist/snapshot/restore.js +10 -2
- package/packages/safe-js/dist/snapshot/serialize.d.ts +4 -1
- package/packages/safe-js/dist/snapshot/serialize.js +6 -1
- package/packages/safe-js/dist/snapshot/validation.js +27 -0
- package/packages/safe-js/dist/chunks/chunk-YEZOXLY4.js.map +0 -7
- /package/packages/safe-js/dist/chunks/{chunk-2J6W3YGB.js.map → chunk-VD2AS4E4.js.map} +0 -0
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { RegexCompileGuard } from "./compile-guard.js";
|
|
2
|
+
const groupNameStart = /^[$_\p{ID_Start}]$/u;
|
|
3
|
+
const groupNamePart = /^[$\u200c\u200d\p{ID_Continue}]$/u;
|
|
2
4
|
export function parseRegex(source, flags = "", compilation, valueUnits = 0) {
|
|
3
5
|
const guard = new RegexCompileGuard(compilation);
|
|
4
6
|
try {
|
|
@@ -6,10 +8,12 @@ export function parseRegex(source, flags = "", compilation, valueUnits = 0) {
|
|
|
6
8
|
guard.checkLength(flags.length, true);
|
|
7
9
|
guard.allocate(5 + valueUnits);
|
|
8
10
|
const parsedFlags = parseFlags(flags, guard);
|
|
9
|
-
const parser = new RegexParser(source, guard);
|
|
11
|
+
const parser = new RegexParser(source, guard, parsedFlags.unicode || parsedFlags.unicodeSets, parsedFlags.unicodeSets);
|
|
10
12
|
const body = parser.parse();
|
|
11
13
|
guard.allocate(5 + source.length);
|
|
12
14
|
const pattern = { source, flags: parsedFlags, captureCount: parser.captureCount, body };
|
|
15
|
+
if (Object.keys(parser.namedGroups).length > 0)
|
|
16
|
+
pattern.groups = parser.namedGroups;
|
|
13
17
|
guard.retain(pattern, valueUnits);
|
|
14
18
|
return pattern;
|
|
15
19
|
}
|
|
@@ -20,11 +24,18 @@ export function parseRegex(source, flags = "", compilation, valueUnits = 0) {
|
|
|
20
24
|
class RegexParser {
|
|
21
25
|
source;
|
|
22
26
|
guard;
|
|
27
|
+
unicode;
|
|
28
|
+
unicodeSets;
|
|
23
29
|
captureCount = 0;
|
|
24
30
|
cursor = 0;
|
|
25
|
-
|
|
31
|
+
totalCaptureCount;
|
|
32
|
+
hasNamedCaptures = false;
|
|
33
|
+
namedGroups = Object.create(null);
|
|
34
|
+
constructor(source, guard, unicode, unicodeSets) {
|
|
26
35
|
this.source = source;
|
|
27
36
|
this.guard = guard;
|
|
37
|
+
this.unicode = unicode;
|
|
38
|
+
this.unicodeSets = unicodeSets;
|
|
28
39
|
}
|
|
29
40
|
get position() {
|
|
30
41
|
return this.cursor;
|
|
@@ -41,6 +52,8 @@ class RegexParser {
|
|
|
41
52
|
}
|
|
42
53
|
this.fail(`Unexpected character '${this.peek()}'`);
|
|
43
54
|
}
|
|
55
|
+
if (this.unicode || Object.keys(this.namedGroups).length > 0)
|
|
56
|
+
this.validateNamedGroups(body);
|
|
44
57
|
return body;
|
|
45
58
|
}
|
|
46
59
|
parseAlternation() {
|
|
@@ -76,15 +89,22 @@ class RegexParser {
|
|
|
76
89
|
parseQuantifiedAtom() {
|
|
77
90
|
const quantifierStart = this.position;
|
|
78
91
|
const current = this.peek();
|
|
79
|
-
if (current === "*" || current === "+" || current === "?" || current === "{") {
|
|
92
|
+
if (current === "*" || current === "+" || current === "?" || (current === "{" && this.unicode)) {
|
|
80
93
|
this.fail("Nothing to repeat", quantifierStart);
|
|
81
94
|
}
|
|
95
|
+
if (current === "{") {
|
|
96
|
+
const quantifier = this.parseQuantifier();
|
|
97
|
+
this.position = quantifierStart;
|
|
98
|
+
if (quantifier !== undefined)
|
|
99
|
+
this.fail("Nothing to repeat", quantifierStart);
|
|
100
|
+
}
|
|
82
101
|
const body = this.parseAtom();
|
|
83
102
|
const quantifier = this.parseQuantifier();
|
|
84
103
|
if (quantifier === undefined) {
|
|
85
104
|
return body;
|
|
86
105
|
}
|
|
87
|
-
if (body.type === "anchor" || body.type === "wordBoundary"
|
|
106
|
+
if (body.type === "anchor" || body.type === "wordBoundary" || body.type === "lookbehind" ||
|
|
107
|
+
(this.unicode && body.type === "lookahead")) {
|
|
88
108
|
this.fail("Invalid quantifier target", quantifierStart);
|
|
89
109
|
}
|
|
90
110
|
const greedy = this.peek() !== "?";
|
|
@@ -95,7 +115,7 @@ class RegexParser {
|
|
|
95
115
|
return { type: "quantifier", body, ...quantifier, greedy };
|
|
96
116
|
}
|
|
97
117
|
parseAtom() {
|
|
98
|
-
const character = this.
|
|
118
|
+
const character = this.takeCharacter();
|
|
99
119
|
switch (character) {
|
|
100
120
|
case ".":
|
|
101
121
|
this.guard.allocate(2);
|
|
@@ -113,12 +133,18 @@ class RegexParser {
|
|
|
113
133
|
case "\\":
|
|
114
134
|
return this.parseEscape(false, this.position - 1);
|
|
115
135
|
default:
|
|
136
|
+
if (this.unicode && (character === "]" || character === "}"))
|
|
137
|
+
this.fail("Invalid Unicode pattern character");
|
|
116
138
|
this.guard.allocate(3 + character.length);
|
|
117
139
|
return { type: "literal", value: character };
|
|
118
140
|
}
|
|
119
141
|
}
|
|
120
142
|
parseGroup(start) {
|
|
121
143
|
let capturing = true;
|
|
144
|
+
let name;
|
|
145
|
+
let modifiers;
|
|
146
|
+
let assertionNegated;
|
|
147
|
+
let lookbehind = false;
|
|
122
148
|
if (this.peek() === "?") {
|
|
123
149
|
this.guard.allocate(Math.min(3, this.source.length - this.position));
|
|
124
150
|
this.guard.work(Math.min(3, this.source.length - this.position));
|
|
@@ -128,13 +154,24 @@ class RegexParser {
|
|
|
128
154
|
this.position += 2;
|
|
129
155
|
}
|
|
130
156
|
else if (extension.startsWith("?=") || extension.startsWith("?!")) {
|
|
131
|
-
|
|
157
|
+
capturing = false;
|
|
158
|
+
assertionNegated = extension.startsWith("?!");
|
|
159
|
+
this.position += 2;
|
|
132
160
|
}
|
|
133
161
|
else if (extension.startsWith("?<=") || extension.startsWith("?<!")) {
|
|
134
|
-
|
|
162
|
+
capturing = false;
|
|
163
|
+
assertionNegated = extension.startsWith("?<!");
|
|
164
|
+
lookbehind = true;
|
|
165
|
+
this.position += 3;
|
|
135
166
|
}
|
|
136
167
|
else if (extension.startsWith("?<")) {
|
|
137
|
-
this.
|
|
168
|
+
this.position += 2;
|
|
169
|
+
name = this.parseGroupName(start);
|
|
170
|
+
}
|
|
171
|
+
else if ("ims-".includes(extension[1] ?? "") && extension.length > 1) {
|
|
172
|
+
capturing = false;
|
|
173
|
+
this.position++;
|
|
174
|
+
modifiers = this.parseModifiers(start);
|
|
138
175
|
}
|
|
139
176
|
else {
|
|
140
177
|
this.fail("Unsupported group construct", start);
|
|
@@ -143,6 +180,12 @@ class RegexParser {
|
|
|
143
180
|
if (capturing)
|
|
144
181
|
this.guard.allocate(1);
|
|
145
182
|
const index = capturing ? ++this.captureCount : undefined;
|
|
183
|
+
if (name !== undefined) {
|
|
184
|
+
this.guard.allocate(name.length + 2);
|
|
185
|
+
const indices = this.namedGroups[name] ??= [];
|
|
186
|
+
this.guard.array(indices.length + 1);
|
|
187
|
+
indices.push(index);
|
|
188
|
+
}
|
|
146
189
|
this.guard.enterGroup();
|
|
147
190
|
let body;
|
|
148
191
|
try {
|
|
@@ -155,10 +198,37 @@ class RegexParser {
|
|
|
155
198
|
this.fail("Unterminated group", start);
|
|
156
199
|
}
|
|
157
200
|
this.position += 1;
|
|
201
|
+
if (assertionNegated !== undefined) {
|
|
202
|
+
this.guard.allocate(4);
|
|
203
|
+
return { type: lookbehind ? "lookbehind" : "lookahead", negated: assertionNegated, body };
|
|
204
|
+
}
|
|
158
205
|
this.guard.allocate(5);
|
|
159
|
-
return { type: "group", capturing, index, body }
|
|
206
|
+
return { type: "group", capturing, index, body, ...(name === undefined ? {} : { name }),
|
|
207
|
+
...(modifiers === undefined ? {} : { modifiers }) };
|
|
208
|
+
}
|
|
209
|
+
parseModifiers(start) {
|
|
210
|
+
this.guard.allocate(2);
|
|
211
|
+
const modifiers = {};
|
|
212
|
+
let enabled = true;
|
|
213
|
+
while (!this.atEnd() && this.peek() !== ":") {
|
|
214
|
+
const flag = this.take();
|
|
215
|
+
if (flag === "-" && enabled) {
|
|
216
|
+
enabled = false;
|
|
217
|
+
continue;
|
|
218
|
+
}
|
|
219
|
+
const name = flag === "i" ? "ignoreCase" : flag === "m" ? "multiline" : flag === "s" ? "dotAll" : undefined;
|
|
220
|
+
if (name === undefined || Object.hasOwn(modifiers, name))
|
|
221
|
+
this.fail("Invalid regex modifiers", start);
|
|
222
|
+
this.guard.allocate(1);
|
|
223
|
+
modifiers[name] = enabled;
|
|
224
|
+
}
|
|
225
|
+
if (this.take() !== ":" || Object.keys(modifiers).length === 0)
|
|
226
|
+
this.fail("Invalid regex modifiers", start);
|
|
227
|
+
return modifiers;
|
|
160
228
|
}
|
|
161
229
|
parseCharacterClass(start) {
|
|
230
|
+
if (this.unicodeSets)
|
|
231
|
+
return { type: "characterClass", ...this.parseUnicodeSet(start) };
|
|
162
232
|
const negated = this.peek() === "^";
|
|
163
233
|
if (negated) {
|
|
164
234
|
this.position += 1;
|
|
@@ -178,9 +248,14 @@ class RegexParser {
|
|
|
178
248
|
this.position += 1;
|
|
179
249
|
const right = this.parseClassItem(start);
|
|
180
250
|
if (left.type !== "character" || right.type !== "character") {
|
|
181
|
-
this.
|
|
251
|
+
if (this.unicode)
|
|
252
|
+
this.fail("Character class ranges require literal endpoints", rangePosition);
|
|
253
|
+
this.guard.array(items.length + 3);
|
|
254
|
+
this.guard.allocate(4);
|
|
255
|
+
items.push(left, { type: "character", value: "-" }, right);
|
|
256
|
+
continue;
|
|
182
257
|
}
|
|
183
|
-
if (left.value.
|
|
258
|
+
if (left.value.codePointAt(0) > right.value.codePointAt(0)) {
|
|
184
259
|
this.fail("Character class range is out of order", rangePosition);
|
|
185
260
|
}
|
|
186
261
|
this.guard.allocate(4 + left.value.length + right.value.length);
|
|
@@ -210,26 +285,226 @@ class RegexParser {
|
|
|
210
285
|
this.fail("Unsupported character class escape", escapeStart);
|
|
211
286
|
}
|
|
212
287
|
this.guard.allocate(4);
|
|
213
|
-
return { type: "character", value: this.
|
|
288
|
+
return { type: "character", value: this.takeCharacter() };
|
|
289
|
+
}
|
|
290
|
+
parseUnicodeSet(start) {
|
|
291
|
+
this.guard.enterGroup();
|
|
292
|
+
try {
|
|
293
|
+
const negated = this.peek() === "^";
|
|
294
|
+
if (negated)
|
|
295
|
+
this.position++;
|
|
296
|
+
this.guard.allocate(5);
|
|
297
|
+
const items = [];
|
|
298
|
+
let operation;
|
|
299
|
+
while (!this.atEnd() && this.peek() !== "]") {
|
|
300
|
+
this.guard.array(items.length + 1);
|
|
301
|
+
let item = this.parseUnicodeSetItem(start);
|
|
302
|
+
if (this.peek() === "-" && this.source[this.position + 1] !== "-") {
|
|
303
|
+
this.position++;
|
|
304
|
+
const right = this.parseUnicodeSetItem(start);
|
|
305
|
+
if (item.type !== "character" || right.type !== "character")
|
|
306
|
+
this.fail("Invalid set range", start);
|
|
307
|
+
if (item.value.codePointAt(0) > right.value.codePointAt(0))
|
|
308
|
+
this.fail("Set range is out of order", start);
|
|
309
|
+
this.guard.allocate(4 + item.value.length + right.value.length);
|
|
310
|
+
item = { type: "range", from: item.value, to: right.value };
|
|
311
|
+
}
|
|
312
|
+
if (operation !== undefined && item.type === "range")
|
|
313
|
+
this.fail("Set operations require nested ranges", start);
|
|
314
|
+
items.push(item);
|
|
315
|
+
const operator = this.source.slice(this.position, this.position + 2);
|
|
316
|
+
if (operator === "&&" || operator === "--") {
|
|
317
|
+
const next = operator === "&&" ? "intersection" : "subtraction";
|
|
318
|
+
if ((operation === undefined && (items.length !== 1 || item.type === "range")) ||
|
|
319
|
+
(operation !== undefined && operation !== next))
|
|
320
|
+
this.fail("Mixed set operations", start);
|
|
321
|
+
if (operation === undefined)
|
|
322
|
+
this.guard.allocate(1 + next.length);
|
|
323
|
+
operation = next;
|
|
324
|
+
this.position += 2;
|
|
325
|
+
if (this.peek() === "]" || this.atEnd())
|
|
326
|
+
this.fail("Missing set operand", start);
|
|
327
|
+
}
|
|
328
|
+
else if (operation !== undefined && this.peek() !== "]")
|
|
329
|
+
this.fail("Missing set operator", start);
|
|
330
|
+
}
|
|
331
|
+
if (this.take() !== "]")
|
|
332
|
+
this.fail("Unterminated character set", start);
|
|
333
|
+
const mayContainStrings = (item) => {
|
|
334
|
+
if (item.type === "set")
|
|
335
|
+
return item.value.strings === true;
|
|
336
|
+
if (item.type === "property")
|
|
337
|
+
return item.strings === true;
|
|
338
|
+
if (item.type === "strings")
|
|
339
|
+
return item.values.some(value => value.length !== ((value.codePointAt(0) ?? 0) > 0xffff ? 2 : 1));
|
|
340
|
+
return false;
|
|
341
|
+
};
|
|
342
|
+
const strings = operation === "intersection" ? items.every(mayContainStrings)
|
|
343
|
+
: operation === "subtraction" ? mayContainStrings(items[0]) : items.some(mayContainStrings);
|
|
344
|
+
if (negated && strings)
|
|
345
|
+
this.fail("Cannot complement a set containing strings", start);
|
|
346
|
+
if (strings)
|
|
347
|
+
this.guard.allocate(1);
|
|
348
|
+
return { negated, items, ...(operation === undefined ? {} : { operation }), ...(strings ? { strings: true } : {}) };
|
|
349
|
+
}
|
|
350
|
+
finally {
|
|
351
|
+
this.guard.leaveGroup();
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
parseUnicodeSetItem(start) {
|
|
355
|
+
if (this.peek() === "[") {
|
|
356
|
+
this.position++;
|
|
357
|
+
this.guard.allocate(3);
|
|
358
|
+
return { type: "set", value: this.parseUnicodeSet(start) };
|
|
359
|
+
}
|
|
360
|
+
if (this.source.startsWith("\\q{", this.position)) {
|
|
361
|
+
this.position += 3;
|
|
362
|
+
this.guard.allocate(4);
|
|
363
|
+
const values = [];
|
|
364
|
+
let value = "";
|
|
365
|
+
while (!this.atEnd()) {
|
|
366
|
+
if (this.peek() === "|" || this.peek() === "}") {
|
|
367
|
+
this.guard.array(values.length + 1);
|
|
368
|
+
this.guard.allocate(1);
|
|
369
|
+
values.push(value);
|
|
370
|
+
value = "";
|
|
371
|
+
if (this.take() === "}")
|
|
372
|
+
return { type: "strings", values };
|
|
373
|
+
}
|
|
374
|
+
else {
|
|
375
|
+
const item = this.parseSetCharacter(start);
|
|
376
|
+
this.guard.allocate(item.length);
|
|
377
|
+
value += item;
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
this.fail("Unterminated class string", start);
|
|
381
|
+
}
|
|
382
|
+
if (this.peek() === "\\")
|
|
383
|
+
return this.parseClassItem(start);
|
|
384
|
+
const value = this.parseSetCharacter(start);
|
|
385
|
+
this.guard.allocate(3 + value.length);
|
|
386
|
+
return { type: "character", value };
|
|
387
|
+
}
|
|
388
|
+
parseSetCharacter(start) {
|
|
389
|
+
if (this.peek() === "\\") {
|
|
390
|
+
this.position++;
|
|
391
|
+
const node = this.parseEscape(true, start);
|
|
392
|
+
if (node.type !== "literal")
|
|
393
|
+
this.fail("Class strings require literal characters", start);
|
|
394
|
+
return node.value;
|
|
395
|
+
}
|
|
396
|
+
const character = this.takeCharacter();
|
|
397
|
+
if (!character || "()[]{}/-|".includes(character) ||
|
|
398
|
+
("!#$%&*+,.:;<=>?@^`~".includes(character) && this.peek() === character))
|
|
399
|
+
this.fail("Invalid character in Unicode set", start);
|
|
400
|
+
return character;
|
|
214
401
|
}
|
|
215
402
|
parseEscape(inCharacterClass, start) {
|
|
216
403
|
if (this.atEnd()) {
|
|
217
404
|
this.fail("Trailing escape", start);
|
|
218
405
|
}
|
|
219
406
|
const escaped = this.take();
|
|
220
|
-
if (escaped
|
|
221
|
-
this.
|
|
407
|
+
if (escaped === "k") {
|
|
408
|
+
this.totalCaptureCount ??= this.countAllCaptures();
|
|
409
|
+
if (this.unicode || this.hasNamedCaptures) {
|
|
410
|
+
if (inCharacterClass || this.take() !== "<")
|
|
411
|
+
this.fail("Invalid named backreference", start);
|
|
412
|
+
const name = this.parseGroupName(start);
|
|
413
|
+
this.guard.allocate(3 + name.length);
|
|
414
|
+
return { type: "namedBackreference", name };
|
|
415
|
+
}
|
|
222
416
|
}
|
|
223
|
-
if (escaped
|
|
224
|
-
|
|
417
|
+
if (isDecimalDigit(escaped)) {
|
|
418
|
+
if (!inCharacterClass && escaped !== "0") {
|
|
419
|
+
let end = this.position;
|
|
420
|
+
let index = Number(escaped);
|
|
421
|
+
while (isDecimalDigit(this.source[end] ?? "")) {
|
|
422
|
+
this.guard.work(1);
|
|
423
|
+
index = index * 10 + Number(this.source[end++]);
|
|
424
|
+
}
|
|
425
|
+
this.totalCaptureCount ??= this.countAllCaptures();
|
|
426
|
+
if (index <= this.totalCaptureCount) {
|
|
427
|
+
this.position = end;
|
|
428
|
+
this.guard.allocate(3);
|
|
429
|
+
return { type: "backreference", index };
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
if (this.unicode && (escaped !== "0" || isDecimalDigit(this.peek())))
|
|
433
|
+
this.fail("Invalid decimal escape", start);
|
|
434
|
+
this.guard.allocate(4);
|
|
435
|
+
if (escaped === "8" || escaped === "9")
|
|
436
|
+
return { type: "literal", value: escaped };
|
|
437
|
+
let code = Number(escaped);
|
|
438
|
+
const maximum = escaped <= "3" ? 3 : 2;
|
|
439
|
+
for (let digits = 1; digits < maximum && this.peek() >= "0" && this.peek() <= "7"; digits++) {
|
|
440
|
+
code = code * 8 + Number(this.take());
|
|
441
|
+
}
|
|
442
|
+
return { type: "literal", value: String.fromCharCode(code) };
|
|
443
|
+
}
|
|
444
|
+
if (this.unicode && (escaped === "p" || escaped === "P")) {
|
|
445
|
+
if (this.take() !== "{")
|
|
446
|
+
this.fail("Invalid Unicode property escape", start);
|
|
447
|
+
const begin = this.position;
|
|
448
|
+
while (!this.atEnd() && this.peek() !== "}") {
|
|
449
|
+
const character = this.take();
|
|
450
|
+
if (!isDecimalDigit(character) && !(character >= "a" && character <= "z") &&
|
|
451
|
+
!(character >= "A" && character <= "Z") && character !== "_" && character !== "=")
|
|
452
|
+
this.fail("Invalid Unicode property escape", start);
|
|
453
|
+
}
|
|
454
|
+
this.guard.allocate(this.position - begin + 9);
|
|
455
|
+
const value = this.source.slice(begin, this.position);
|
|
456
|
+
if (this.take() !== "}")
|
|
457
|
+
this.fail("Unterminated Unicode property escape", start);
|
|
458
|
+
// Only a validated property token reaches the host's single-character classifier.
|
|
459
|
+
try {
|
|
460
|
+
new RegExp(`\\${escaped}{${value}}`, this.unicodeSets ? "v" : "u");
|
|
461
|
+
}
|
|
462
|
+
catch {
|
|
463
|
+
this.fail("Unknown Unicode property", start);
|
|
464
|
+
}
|
|
465
|
+
let strings = false;
|
|
466
|
+
if (this.unicodeSets) {
|
|
467
|
+
try {
|
|
468
|
+
new RegExp(`\\p{${value}}`, "u");
|
|
469
|
+
}
|
|
470
|
+
catch {
|
|
471
|
+
strings = true;
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
this.guard.array(1);
|
|
475
|
+
return { type: "characterClass", negated: false, items: [{ type: "property", value, negated: escaped === "P", ...(strings ? { strings: true } : {}) }] };
|
|
225
476
|
}
|
|
226
477
|
if (escaped === "x") {
|
|
227
478
|
this.guard.allocate(4);
|
|
228
|
-
return { type: "literal", value: this.parseHexEscape(2, "hexadecimal", start) };
|
|
479
|
+
return { type: "literal", value: this.parseHexEscape(2, "hexadecimal", start, this.unicode ? undefined : "x") };
|
|
229
480
|
}
|
|
230
481
|
if (escaped === "u") {
|
|
231
482
|
this.guard.allocate(4);
|
|
232
|
-
|
|
483
|
+
if (this.unicode && this.peek() === "{") {
|
|
484
|
+
this.position++;
|
|
485
|
+
const begin = this.position;
|
|
486
|
+
while (!this.atEnd() && this.peek() !== "}")
|
|
487
|
+
this.position++;
|
|
488
|
+
this.guard.allocate(this.position - begin);
|
|
489
|
+
const digits = this.source.slice(begin, this.position);
|
|
490
|
+
const point = Number.parseInt(digits, 16);
|
|
491
|
+
if (this.take() !== "}" || digits.length === 0 || !allHexDigits(digits) || point > 0x10ffff)
|
|
492
|
+
this.fail("Invalid Unicode escape", start);
|
|
493
|
+
return { type: "literal", value: String.fromCodePoint(point) };
|
|
494
|
+
}
|
|
495
|
+
let value = this.parseHexEscape(4, "Unicode", start, this.unicode ? undefined : "u");
|
|
496
|
+
if (this.unicode && value.charCodeAt(0) >= 0xd800 && value.charCodeAt(0) <= 0xdbff &&
|
|
497
|
+
this.source.startsWith("\\u", this.position)) {
|
|
498
|
+
const digits = this.source.slice(this.position + 2, this.position + 6);
|
|
499
|
+
const point = Number.parseInt(digits, 16);
|
|
500
|
+
this.guard.work(digits.length);
|
|
501
|
+
this.guard.allocate(digits.length);
|
|
502
|
+
if (digits.length === 4 && allHexDigits(digits) && point >= 0xdc00 && point <= 0xdfff) {
|
|
503
|
+
value += String.fromCharCode(point);
|
|
504
|
+
this.position += 6;
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
return { type: "literal", value };
|
|
233
508
|
}
|
|
234
509
|
this.guard.allocate(25);
|
|
235
510
|
const kinds = {
|
|
@@ -260,20 +535,124 @@ class RegexParser {
|
|
|
260
535
|
v: "\v",
|
|
261
536
|
"0": "\0"
|
|
262
537
|
};
|
|
538
|
+
if (escaped === "c") {
|
|
539
|
+
const letter = this.peek();
|
|
540
|
+
this.guard.allocate(4);
|
|
541
|
+
if ((letter >= "a" && letter <= "z") || (letter >= "A" && letter <= "Z") ||
|
|
542
|
+
(!this.unicode && inCharacterClass && (isDecimalDigit(letter) || letter === "_"))) {
|
|
543
|
+
this.position++;
|
|
544
|
+
return { type: "literal", value: String.fromCharCode(letter.charCodeAt(0) % 32) };
|
|
545
|
+
}
|
|
546
|
+
if (this.unicode)
|
|
547
|
+
this.fail("Invalid control escape", start);
|
|
548
|
+
// The unmatched backslash is literal; leave c as the next atom so a
|
|
549
|
+
// following quantifier binds to c rather than to the pair.
|
|
550
|
+
this.position--;
|
|
551
|
+
return { type: "literal", value: "\\" };
|
|
552
|
+
}
|
|
553
|
+
if (this.unicode && controls[escaped] === undefined &&
|
|
554
|
+
!"^$\\.*+?()[]{}|/".includes(escaped) && !(inCharacterClass && (escaped === "-" ||
|
|
555
|
+
(this.unicodeSets && "!#$%&+,.:;<=>?@`~".includes(escaped)))))
|
|
556
|
+
this.fail("Invalid identity escape", start);
|
|
263
557
|
this.guard.allocate(4);
|
|
264
558
|
return { type: "literal", value: controls[escaped] ?? escaped };
|
|
265
559
|
}
|
|
266
|
-
parseHexEscape(length, name, start) {
|
|
560
|
+
parseHexEscape(length, name, start, identity) {
|
|
267
561
|
const end = this.position + length;
|
|
268
562
|
this.guard.allocate(Math.min(length, this.source.length - this.position));
|
|
269
563
|
this.guard.work(Math.min(length, this.source.length - this.position));
|
|
270
564
|
const digits = this.source.slice(this.position, end);
|
|
271
565
|
if (digits.length !== length || !allHexDigits(digits)) {
|
|
566
|
+
if (identity !== undefined)
|
|
567
|
+
return identity;
|
|
272
568
|
this.fail(`Invalid ${name} escape`, start);
|
|
273
569
|
}
|
|
274
570
|
this.position = end;
|
|
275
571
|
return String.fromCharCode(Number.parseInt(digits, 16));
|
|
276
572
|
}
|
|
573
|
+
countAllCaptures() {
|
|
574
|
+
let count = 0;
|
|
575
|
+
let escaped = false;
|
|
576
|
+
let characterClass = false;
|
|
577
|
+
for (let index = 0; index < this.source.length; index++) {
|
|
578
|
+
this.guard.work(1);
|
|
579
|
+
const character = this.source[index];
|
|
580
|
+
if (escaped)
|
|
581
|
+
escaped = false;
|
|
582
|
+
else if (character === "\\")
|
|
583
|
+
escaped = true;
|
|
584
|
+
else if (character === "[")
|
|
585
|
+
characterClass = true;
|
|
586
|
+
else if (character === "]")
|
|
587
|
+
characterClass = false;
|
|
588
|
+
else if (!characterClass && character === "(") {
|
|
589
|
+
const named = this.source[index + 1] === "?" && this.source[index + 2] === "<" &&
|
|
590
|
+
this.source[index + 3] !== "=" && this.source[index + 3] !== "!";
|
|
591
|
+
if (named)
|
|
592
|
+
this.hasNamedCaptures = true;
|
|
593
|
+
if (named || this.source[index + 1] !== "?")
|
|
594
|
+
count++;
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
return count;
|
|
598
|
+
}
|
|
599
|
+
parseGroupName(start) {
|
|
600
|
+
let name = "";
|
|
601
|
+
while (!this.atEnd() && this.peek() !== ">") {
|
|
602
|
+
let character = this.take();
|
|
603
|
+
if (character === "\\") {
|
|
604
|
+
if (this.take() !== "u")
|
|
605
|
+
this.fail("Invalid group name escape", start);
|
|
606
|
+
if (this.peek() === "{") {
|
|
607
|
+
this.position++;
|
|
608
|
+
const begin = this.position;
|
|
609
|
+
while (!this.atEnd() && this.peek() !== "}")
|
|
610
|
+
this.position++;
|
|
611
|
+
this.guard.allocate(this.position - begin);
|
|
612
|
+
const digits = this.source.slice(begin, this.position);
|
|
613
|
+
const point = Number.parseInt(digits, 16);
|
|
614
|
+
if (this.take() !== "}" || digits.length === 0 || !allHexDigits(digits) || point > 0x10ffff)
|
|
615
|
+
this.fail("Invalid group name escape", start);
|
|
616
|
+
character = String.fromCodePoint(point);
|
|
617
|
+
}
|
|
618
|
+
else
|
|
619
|
+
character = this.parseHexEscape(4, "Unicode", start);
|
|
620
|
+
}
|
|
621
|
+
this.guard.allocate(character.length);
|
|
622
|
+
name += character;
|
|
623
|
+
}
|
|
624
|
+
if (this.take() !== ">" || name.length === 0)
|
|
625
|
+
this.fail("Invalid group name", start);
|
|
626
|
+
let first = true;
|
|
627
|
+
for (const character of name) {
|
|
628
|
+
this.guard.work(1);
|
|
629
|
+
if (!(first ? groupNameStart : groupNamePart).test(character))
|
|
630
|
+
this.fail("Invalid group name", start);
|
|
631
|
+
first = false;
|
|
632
|
+
}
|
|
633
|
+
return name;
|
|
634
|
+
}
|
|
635
|
+
validateNamedGroups(node) {
|
|
636
|
+
this.guard.work(1);
|
|
637
|
+
this.guard.allocate(1);
|
|
638
|
+
const names = new Set();
|
|
639
|
+
if (node.type === "namedBackreference" && !Object.hasOwn(this.namedGroups, node.name))
|
|
640
|
+
this.fail("Unknown named backreference");
|
|
641
|
+
if (node.type === "group" && node.name !== undefined)
|
|
642
|
+
names.add(node.name);
|
|
643
|
+
const children = node.type === "sequence" ? node.elements : node.type === "alternation" ? node.alternatives
|
|
644
|
+
: node.type === "group" || node.type === "quantifier" || node.type === "lookahead" || node.type === "lookbehind" ? [node.body] : [];
|
|
645
|
+
for (const child of children) {
|
|
646
|
+
for (const name of this.validateNamedGroups(child)) {
|
|
647
|
+
this.guard.work(1);
|
|
648
|
+
if (names.has(name) && node.type !== "alternation")
|
|
649
|
+
this.fail("Duplicate capture group name");
|
|
650
|
+
this.guard.allocate(1);
|
|
651
|
+
names.add(name);
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
return names;
|
|
655
|
+
}
|
|
277
656
|
parseQuantifier() {
|
|
278
657
|
const character = this.peek();
|
|
279
658
|
if (character === "*") {
|
|
@@ -303,18 +682,30 @@ class RegexParser {
|
|
|
303
682
|
}
|
|
304
683
|
if (this.peek() === "}") {
|
|
305
684
|
this.position += 1;
|
|
685
|
+
if (!Number.isSafeInteger(min))
|
|
686
|
+
this.fail("Quantifier is too large", start);
|
|
306
687
|
this.guard.allocate(3);
|
|
307
688
|
return { min, max: min };
|
|
308
689
|
}
|
|
309
690
|
if (this.peek() !== ",") {
|
|
691
|
+
if (!this.unicode) {
|
|
692
|
+
this.position = start;
|
|
693
|
+
return undefined;
|
|
694
|
+
}
|
|
310
695
|
this.fail("Invalid quantifier", start);
|
|
311
696
|
}
|
|
312
697
|
this.position += 1;
|
|
313
698
|
const max = this.parseDecimal();
|
|
314
699
|
if (this.peek() !== "}") {
|
|
700
|
+
if (!this.unicode) {
|
|
701
|
+
this.position = start;
|
|
702
|
+
return undefined;
|
|
703
|
+
}
|
|
315
704
|
this.fail("Unterminated quantifier", start);
|
|
316
705
|
}
|
|
317
706
|
this.position += 1;
|
|
707
|
+
if (!Number.isSafeInteger(min) || (max !== undefined && !Number.isSafeInteger(max)))
|
|
708
|
+
this.fail("Quantifier is too large", start);
|
|
318
709
|
if (max !== undefined && min > max) {
|
|
319
710
|
this.fail("Quantifier range is out of order", start);
|
|
320
711
|
}
|
|
@@ -331,11 +722,7 @@ class RegexParser {
|
|
|
331
722
|
}
|
|
332
723
|
this.guard.allocate(this.position - start);
|
|
333
724
|
this.guard.work(this.position - start);
|
|
334
|
-
|
|
335
|
-
if (!Number.isSafeInteger(value)) {
|
|
336
|
-
this.fail("Quantifier is too large", start);
|
|
337
|
-
}
|
|
338
|
-
return value;
|
|
725
|
+
return Number(this.source.slice(start, this.position));
|
|
339
726
|
}
|
|
340
727
|
peek() {
|
|
341
728
|
return this.source[this.position] ?? "";
|
|
@@ -345,6 +732,13 @@ class RegexParser {
|
|
|
345
732
|
this.position += 1;
|
|
346
733
|
return character;
|
|
347
734
|
}
|
|
735
|
+
takeCharacter() {
|
|
736
|
+
if (!this.unicode || this.atEnd())
|
|
737
|
+
return this.take();
|
|
738
|
+
const character = String.fromCodePoint(this.source.codePointAt(this.position));
|
|
739
|
+
this.position += character.length;
|
|
740
|
+
return character;
|
|
741
|
+
}
|
|
348
742
|
atEnd() {
|
|
349
743
|
return this.position >= this.source.length;
|
|
350
744
|
}
|
|
@@ -355,16 +749,24 @@ class RegexParser {
|
|
|
355
749
|
function parseFlags(flags, guard) {
|
|
356
750
|
guard.allocate(10);
|
|
357
751
|
const parsed = {
|
|
752
|
+
hasIndices: false,
|
|
358
753
|
global: false,
|
|
754
|
+
sticky: false,
|
|
755
|
+
unicode: false,
|
|
756
|
+
unicodeSets: false,
|
|
359
757
|
ignoreCase: false,
|
|
360
758
|
multiline: false,
|
|
361
759
|
dotAll: false
|
|
362
760
|
};
|
|
363
761
|
const names = {
|
|
762
|
+
d: "hasIndices",
|
|
364
763
|
g: "global",
|
|
365
764
|
i: "ignoreCase",
|
|
366
765
|
m: "multiline",
|
|
367
|
-
s: "dotAll"
|
|
766
|
+
s: "dotAll",
|
|
767
|
+
y: "sticky",
|
|
768
|
+
u: "unicode",
|
|
769
|
+
v: "unicodeSets"
|
|
368
770
|
};
|
|
369
771
|
for (let position = 0; position < flags.length; position += 1) {
|
|
370
772
|
guard.work(1);
|
|
@@ -378,6 +780,8 @@ function parseFlags(flags, guard) {
|
|
|
378
780
|
}
|
|
379
781
|
parsed[name] = true;
|
|
380
782
|
}
|
|
783
|
+
if (parsed.unicode && parsed.unicodeSets)
|
|
784
|
+
throw new SyntaxError("Unicode flags u and v cannot be combined");
|
|
381
785
|
return parsed;
|
|
382
786
|
}
|
|
383
787
|
function isDecimalDigit(character) {
|
|
@@ -1,12 +1,14 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { SandboxValue } from "./values.js";
|
|
2
2
|
declare const regexpIteratorBrand: unique symbol;
|
|
3
3
|
export type SandboxRegExpIterator = {
|
|
4
4
|
readonly [regexpIteratorBrand]: true;
|
|
5
5
|
};
|
|
6
6
|
export type RegExpIteratorState = {
|
|
7
|
-
matcher:
|
|
7
|
+
matcher: SandboxValue;
|
|
8
8
|
input: string | undefined;
|
|
9
9
|
exhausted: boolean;
|
|
10
|
+
global?: boolean;
|
|
11
|
+
unicode?: boolean;
|
|
10
12
|
};
|
|
11
13
|
export declare function isSandboxRegExpIterator(value: unknown): value is SandboxRegExpIterator;
|
|
12
14
|
export declare function restoreSandboxRegExpIterator(state: RegExpIteratorState, target?: SandboxRegExpIterator): SandboxRegExpIterator;
|
|
@@ -3,10 +3,15 @@ export function isSandboxRegExpIterator(value) {
|
|
|
3
3
|
return typeof value === "object" && value !== null && states.has(value);
|
|
4
4
|
}
|
|
5
5
|
export function restoreSandboxRegExpIterator(state, target = Object.create(null)) {
|
|
6
|
+
if ((state.global !== undefined || state.unicode !== undefined) &&
|
|
7
|
+
(typeof state.global !== "boolean" || typeof state.unicode !== "boolean"))
|
|
8
|
+
throw new TypeError("Invalid RegExp iterator modes.");
|
|
9
|
+
if (state.matcher !== undefined && (state.matcher === null || typeof state.matcher !== "object"))
|
|
10
|
+
throw new TypeError("Invalid RegExp iterator matcher.");
|
|
6
11
|
if (!state.exhausted && (state.matcher === undefined || state.input === undefined))
|
|
7
12
|
throw new TypeError("A live RegExp iterator requires its matcher and input.");
|
|
8
13
|
states.set(target, state.exhausted
|
|
9
|
-
? { matcher: undefined, input: undefined, exhausted: true }
|
|
14
|
+
? { ...state, matcher: undefined, input: undefined, exhausted: true }
|
|
10
15
|
: { ...state });
|
|
11
16
|
return target;
|
|
12
17
|
}
|