poe-code 14.0.73 → 14.0.75

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.
Files changed (47) hide show
  1. package/dist/index.js +2732 -1188
  2. package/dist/index.js.map +4 -4
  3. package/dist/metafile.json +1 -1
  4. package/package.json +1 -1
  5. package/packages/safe-js/dist/chunks/{chunk-I2Z37MKZ.js → chunk-BAJI3K4B.js} +2 -2
  6. package/packages/safe-js/dist/chunks/{chunk-XKCTB5FR.js → chunk-EOTY4PCT.js} +2902 -1425
  7. package/packages/safe-js/dist/chunks/chunk-EOTY4PCT.js.map +7 -0
  8. package/packages/safe-js/dist/cli.js +2 -2
  9. package/packages/safe-js/dist/core.js +1 -1
  10. package/packages/safe-js/dist/graph-depth.js +3 -0
  11. package/packages/safe-js/dist/index.js +2 -2
  12. package/packages/safe-js/dist/interp/cancel.js +3 -0
  13. package/packages/safe-js/dist/interp/globals/misc.js +2 -1
  14. package/packages/safe-js/dist/interp/globals/object.js +3 -0
  15. package/packages/safe-js/dist/interp/globals/regex.js +196 -9
  16. package/packages/safe-js/dist/interp/interpreter.js +14 -7
  17. package/packages/safe-js/dist/interp/iteration.js +9 -1
  18. package/packages/safe-js/dist/interp/methods/regex-split.d.ts +3 -0
  19. package/packages/safe-js/dist/interp/methods/regex-split.js +102 -0
  20. package/packages/safe-js/dist/interp/methods/regex.d.ts +8 -4
  21. package/packages/safe-js/dist/interp/methods/regex.js +141 -24
  22. package/packages/safe-js/dist/interp/methods/regexp-iterator.d.ts +12 -0
  23. package/packages/safe-js/dist/interp/methods/regexp-iterator.js +101 -0
  24. package/packages/safe-js/dist/interp/methods/string.d.ts +2 -0
  25. package/packages/safe-js/dist/interp/methods/string.js +415 -121
  26. package/packages/safe-js/dist/interp/object-model.d.ts +3 -0
  27. package/packages/safe-js/dist/interp/object-model.js +43 -10
  28. package/packages/safe-js/dist/interp/regex/compile-guard.js +26 -4
  29. package/packages/safe-js/dist/interp/regex/engine.d.ts +4 -0
  30. package/packages/safe-js/dist/interp/regex/engine.js +230 -30
  31. package/packages/safe-js/dist/interp/regex/parse.d.ts +42 -4
  32. package/packages/safe-js/dist/interp/regex/parse.js +429 -25
  33. package/packages/safe-js/dist/interp/regexp-iterator.d.ts +16 -0
  34. package/packages/safe-js/dist/interp/regexp-iterator.js +23 -0
  35. package/packages/safe-js/dist/interp/string-coercion.js +11 -7
  36. package/packages/safe-js/dist/interp/values.d.ts +2 -1
  37. package/packages/safe-js/dist/interp/values.js +39 -2
  38. package/packages/safe-js/dist/snapshot/dump-format.js +35 -3
  39. package/packages/safe-js/dist/snapshot/replay-data.d.ts +12 -1
  40. package/packages/safe-js/dist/snapshot/replay-data.js +42 -1
  41. package/packages/safe-js/dist/snapshot/replay-inputs.js +17 -0
  42. package/packages/safe-js/dist/snapshot/restore.js +23 -1
  43. package/packages/safe-js/dist/snapshot/serialize.d.ts +13 -2
  44. package/packages/safe-js/dist/snapshot/serialize.js +30 -11
  45. package/packages/safe-js/dist/snapshot/validation.js +22 -2
  46. package/packages/safe-js/dist/chunks/chunk-XKCTB5FR.js.map +0 -7
  47. /package/packages/safe-js/dist/chunks/{chunk-I2Z37MKZ.js.map → chunk-BAJI3K4B.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
- constructor(source, guard) {
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.take();
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
- this.fail("Lookahead is not supported", start);
157
+ capturing = false;
158
+ assertionNegated = extension.startsWith("?!");
159
+ this.position += 2;
132
160
  }
133
161
  else if (extension.startsWith("?<=") || extension.startsWith("?<!")) {
134
- this.fail("Lookbehind is not supported", start);
162
+ capturing = false;
163
+ assertionNegated = extension.startsWith("?<!");
164
+ lookbehind = true;
165
+ this.position += 3;
135
166
  }
136
167
  else if (extension.startsWith("?<")) {
137
- this.fail("Named groups are not supported", start);
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.fail("Character class ranges require literal endpoints", rangePosition);
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.charCodeAt(0) > right.value.charCodeAt(0)) {
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.take() };
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 >= "1" && escaped <= "9") {
221
- this.fail("Backreferences are not supported", start);
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 === "p" || escaped === "P") {
224
- this.fail("Unicode property escapes are not supported", start);
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
- return { type: "literal", value: this.parseHexEscape(4, "Unicode", start) };
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
- const value = Number(this.source.slice(start, this.position));
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) {
@@ -0,0 +1,16 @@
1
+ import type { SandboxValue } from "./values.js";
2
+ declare const regexpIteratorBrand: unique symbol;
3
+ export type SandboxRegExpIterator = {
4
+ readonly [regexpIteratorBrand]: true;
5
+ };
6
+ export type RegExpIteratorState = {
7
+ matcher: SandboxValue;
8
+ input: string | undefined;
9
+ exhausted: boolean;
10
+ global?: boolean;
11
+ unicode?: boolean;
12
+ };
13
+ export declare function isSandboxRegExpIterator(value: unknown): value is SandboxRegExpIterator;
14
+ export declare function restoreSandboxRegExpIterator(state: RegExpIteratorState, target?: SandboxRegExpIterator): SandboxRegExpIterator;
15
+ export declare function regexpIteratorState(value: SandboxRegExpIterator): RegExpIteratorState;
16
+ export {};