@ptolemy2002/rgx 13.3.3 → 13.4.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/resolve.js CHANGED
@@ -58,9 +58,39 @@ function localizableVanillaRegexFlagDiff(prev, next) {
58
58
  return `${added}`;
59
59
  return `${added}-${removed}`;
60
60
  }
61
+ function hasParenErrors(pattern) {
62
+ let depth = 0;
63
+ let charClassDepth = 0;
64
+ for (let i = 0; i < pattern.length; i++) {
65
+ const ch = pattern[i];
66
+ if (ch === '\\') {
67
+ i++;
68
+ continue;
69
+ }
70
+ if (charClassDepth > 0) {
71
+ if (ch === '[')
72
+ charClassDepth++;
73
+ else if (ch === ']')
74
+ charClassDepth--;
75
+ continue;
76
+ }
77
+ if (ch === '[') {
78
+ charClassDepth++;
79
+ }
80
+ else if (ch === '(') {
81
+ depth++;
82
+ }
83
+ else if (ch === ')') {
84
+ depth--;
85
+ if (depth < 0)
86
+ return true;
87
+ }
88
+ }
89
+ return depth !== 0;
90
+ }
61
91
  function resolveRGXToken(token, { groupWrap = true, topLevel = true, currentFlags = '' } = {}) {
62
92
  (0, ExtRegExp_1.assertValidRegexFlags)(currentFlags);
63
- let acceptUnterminatedGroup = false;
93
+ let acceptParenErrors = false;
64
94
  const innerResolve = () => {
65
95
  if (tg.isRGXNoOpToken(token))
66
96
  return '';
@@ -86,7 +116,7 @@ function resolveRGXToken(token, { groupWrap = true, topLevel = true, currentFlag
86
116
  // will be caught by one of the checks that the entire resolved string
87
117
  // is a valid regex string.
88
118
  if (token.rgxInterpolate) {
89
- acceptUnterminatedGroup = true;
119
+ acceptParenErrors = true;
90
120
  return String(token.toRgx());
91
121
  }
92
122
  // The top-level group-wrapping preference propogates to a direct convertible token, but after that
@@ -113,21 +143,7 @@ function resolveRGXToken(token, { groupWrap = true, topLevel = true, currentFlag
113
143
  throw new e.RGXInvalidTokenError(`Invalid RGX token: ${token}`, null, token);
114
144
  };
115
145
  const result = innerResolve();
116
- try {
146
+ if (!(acceptParenErrors && hasParenErrors(result)))
117
147
  tg.assertValidRegexString(result);
118
- }
119
- catch (err) {
120
- if (err instanceof e.RGXInvalidRegexStringError) {
121
- if (acceptUnterminatedGroup &&
122
- (err.cause.message.endsWith('Unterminated group') ||
123
- err.cause.message.endsWith("Unmatched ')'"))) {
124
- return result;
125
- }
126
- }
127
- // This is ignored because I don't know what kind of
128
- // unexpected errors might happen.
129
- /* istanbul ignore next */
130
- throw err;
131
- }
132
148
  return result;
133
149
  }
@@ -14,6 +14,7 @@ export type RGXTryWalkOptions = {
14
14
  revertShare?: boolean;
15
15
  revertCaptures?: boolean;
16
16
  };
17
+ export type RGXWalkerSnapshotKey = "sourcePosition" | "tokenPosition" | "reduced" | "share" | "captures" | "namedCaptures";
17
18
  export type RGXTokenOrPart<R, S = unknown, T = any> = RGXToken | RGXPart<R, S, T>;
18
19
  export type RGXWalkerStepDirective = "stop" | "skip" | "silent";
19
20
  export declare class RGXWalker<R, S = unknown> {
@@ -30,6 +31,7 @@ export declare class RGXWalker<R, S = unknown> {
30
31
  contiguous: boolean;
31
32
  private _stopped;
32
33
  private _didReachEnd;
34
+ private _snapshots;
33
35
  static check: (value: unknown) => value is RGXWalker<unknown, unknown>;
34
36
  static assert: (value: unknown) => asserts value is RGXWalker<unknown, unknown>;
35
37
  get sourcePosition(): number;
@@ -61,6 +63,8 @@ export declare class RGXWalker<R, S = unknown> {
61
63
  stepToToken(predicate: (token: RGXTokenOrPart<R>) => boolean): this;
62
64
  stepToPart(predicate?: (part: RGXPart<R, S, unknown>) => boolean): this;
63
65
  walk(): R;
66
+ snapshot(name: string, ...keys: Array<RGXWalkerSnapshotKey | false>): this;
67
+ restore(name: string): this;
64
68
  tryWalk({ revertReduced, revertShare, revertCaptures }?: RGXTryWalkOptions): boolean;
65
69
  clone(depth?: CloneDepth): RGXWalker<R, S>;
66
70
  }
@@ -55,6 +55,7 @@ class RGXWalker {
55
55
  this._stopped = false;
56
56
  // Only relevant in infinite mode, tracking whether we've reached the end yet.
57
57
  this._didReachEnd = false;
58
+ this._snapshots = new Map();
58
59
  this.source = source;
59
60
  this.sourcePosition = options.startingSourcePosition ?? 0;
60
61
  this.tokens = tokens;
@@ -184,13 +185,13 @@ class RGXWalker {
184
185
  }
185
186
  }
186
187
  // Returns a directive on handled validation failure, null on success. Unhandled errors are rethrown.
187
- validateCapture(token, captureResult, start) {
188
+ validateCapture(token, captureResult) {
188
189
  try {
189
190
  token.validate(captureResult, { part: token, walker: this });
190
191
  return null;
191
192
  }
192
193
  catch (e) {
193
- this.sourcePosition = start; // Reset source position on validation failure
194
+ this.restore("step"); // Reset source position on validation failure
194
195
  if (e instanceof errors_1.RGXPartValidationFailedError) {
195
196
  const control = token.afterValidationFailure?.(e, { part: token, walker: this });
196
197
  // If this happens, afterValidationFailure itself stopped the walker, so we just need to respect that.
@@ -205,7 +206,7 @@ class RGXWalker {
205
206
  throw e;
206
207
  }
207
208
  }
208
- handleAfterCapture(token, captureResult, silent, start) {
209
+ handleAfterCapture(token, captureResult, silent) {
209
210
  const control = token.afterCapture?.(captureResult, { part: token, walker: this });
210
211
  if (!silent && (control === "skip" || control === "silent" || control === "stop-silent")) {
211
212
  this.unregisterLastCapture(token);
@@ -214,7 +215,7 @@ class RGXWalker {
214
215
  if (this.stopped)
215
216
  return "stop";
216
217
  if (control === "skip") {
217
- this.sourcePosition = start;
218
+ this.restore("step");
218
219
  return "skip";
219
220
  }
220
221
  if (control === "stop" || control === "stop-silent")
@@ -247,9 +248,9 @@ class RGXWalker {
247
248
  silent = dir === "silent";
248
249
  }
249
250
  const branchedToken = isPart ? createBranchGroups(token.token) : createBranchGroups(token);
250
- // Still track the previous source position,
251
- // because if we have to skip, we need to reset to it.
252
- const prevSourcePosition = this.sourcePosition;
251
+ // Snapshot the source position so validateCapture and handleAfterCapture
252
+ // can restore it on skip/failure.
253
+ this.snapshot("step", "sourcePosition");
253
254
  let captureAttempt;
254
255
  try {
255
256
  captureAttempt = this.attemptCapture(branchedToken, isPart ? token : null);
@@ -287,7 +288,7 @@ class RGXWalker {
287
288
  groups: captureAttempt.groups ?? null
288
289
  };
289
290
  if (isPart) {
290
- const dir = this.validateCapture(token, captureResult, prevSourcePosition);
291
+ const dir = this.validateCapture(token, captureResult);
291
292
  if (dir === "stop") {
292
293
  this._stopped = true;
293
294
  return null;
@@ -300,7 +301,7 @@ class RGXWalker {
300
301
  if (!silent)
301
302
  this.registerCapture(captureResult, token);
302
303
  if (isPart) {
303
- const dir = this.handleAfterCapture(token, captureResult, silent, prevSourcePosition);
304
+ const dir = this.handleAfterCapture(token, captureResult, silent);
304
305
  if (dir === "stop") {
305
306
  this.advanceToken();
306
307
  this._stopped = true;
@@ -341,31 +342,48 @@ class RGXWalker {
341
342
  this.stepToToken(() => false);
342
343
  return this.reduced;
343
344
  }
345
+ snapshot(name, ...keys) {
346
+ const filteredKeys = keys.filter((k) => k !== false);
347
+ const snap = {};
348
+ for (const key of filteredKeys) {
349
+ if (key === "sourcePosition" || key === "tokenPosition") {
350
+ snap[key] = this[key];
351
+ }
352
+ else {
353
+ snap[key] = (0, immutability_utils_1.extClone)(this[key], "max");
354
+ }
355
+ }
356
+ this._snapshots.set(name, snap);
357
+ return this;
358
+ }
359
+ restore(name) {
360
+ const snap = this._snapshots.get(name);
361
+ if (!snap)
362
+ return this;
363
+ if ("sourcePosition" in snap)
364
+ this.sourcePosition = snap.sourcePosition;
365
+ if ("tokenPosition" in snap)
366
+ this.tokenPosition = snap.tokenPosition;
367
+ if ("reduced" in snap)
368
+ this.reduced = snap.reduced;
369
+ if ("share" in snap)
370
+ this.share = snap.share;
371
+ if ("captures" in snap)
372
+ this.captures = snap.captures;
373
+ if ("namedCaptures" in snap)
374
+ this.namedCaptures = snap.namedCaptures;
375
+ return this;
376
+ }
344
377
  tryWalk({ revertReduced = false, revertShare = false, revertCaptures = false } = {}) {
345
- const prevSourcePosition = this.sourcePosition;
346
- const prevTokenPosition = this.tokenPosition;
347
- const prevReduced = revertReduced ? (0, immutability_utils_1.extClone)(this.reduced, "max") : this.reduced;
348
- const prevShare = revertShare ? (0, immutability_utils_1.extClone)(this.share, "max") : this.share;
349
- const prevCaptures = revertCaptures ? (0, immutability_utils_1.extClone)(this.captures, "max") : this.captures;
350
- const prevNamedCaptures = revertCaptures ? (0, immutability_utils_1.extClone)(this.namedCaptures, "max") : this.namedCaptures;
378
+ this.snapshot("tryWalk", "sourcePosition", "tokenPosition", revertReduced && "reduced", revertShare && "share", revertCaptures && "captures", revertCaptures && "namedCaptures");
351
379
  try {
352
380
  this.walk();
353
381
  return true;
354
382
  }
355
383
  catch (e) {
356
- this.sourcePosition = prevSourcePosition;
357
- this.tokenPosition = prevTokenPosition;
358
- if (revertReduced)
359
- this.reduced = prevReduced;
360
- if (revertShare)
361
- this.share = prevShare;
362
- if (revertCaptures)
363
- this.captures = prevCaptures;
364
- if (revertCaptures)
365
- this.namedCaptures = prevNamedCaptures;
366
- if (isMatchError(e)) {
384
+ this.restore("tryWalk");
385
+ if (isMatchError(e))
367
386
  return false;
368
- }
369
387
  throw e;
370
388
  }
371
389
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ptolemy2002/rgx",
3
- "version": "13.3.3",
3
+ "version": "13.4.0",
4
4
  "private": false,
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",