@ptolemy2002/rgx 12.5.0 → 12.6.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.
@@ -9,6 +9,7 @@ export type RGXWalkerOptions<R, S = unknown> = {
9
9
  looping?: boolean;
10
10
  };
11
11
  export type RGXTokenOrPart<R, S = unknown, T = unknown> = RGXToken | RGXPart<R, S, T>;
12
+ export type RGXWalkerStepDirective = "stop" | "skip" | "silent";
12
13
  export declare class RGXWalker<R, S = unknown> {
13
14
  readonly source: string;
14
15
  _sourcePosition: number;
@@ -39,6 +40,14 @@ export declare class RGXWalker<R, S = unknown> {
39
40
  remainingSource(): string | null;
40
41
  capture(token: RGXTokenOrPart<R, S>, includeMatch: true): RegExpExecArray;
41
42
  capture(token: RGXTokenOrPart<R, S>, includeMatch?: false): string;
43
+ private advanceToken;
44
+ private determineBranch;
45
+ private registerCapture;
46
+ private unregisterLastCapture;
47
+ private handleBeforeCapture;
48
+ private attemptCapture;
49
+ private validateCapture;
50
+ private handleAfterCapture;
42
51
  step(): RGXCapture | null;
43
52
  stepToToken(predicate: (token: RGXTokenOrPart<R>) => boolean): this;
44
53
  stepToPart(predicate?: (part: RGXPart<R, S, unknown>) => boolean): this;
@@ -96,6 +96,106 @@ class RGXWalker {
96
96
  this.sourcePosition += match[0].length;
97
97
  return includeMatch ? match : match[0];
98
98
  }
99
+ advanceToken() {
100
+ if (!this.infinite || this.tokenPosition < this.tokens.length - 1) {
101
+ this.tokenPosition++;
102
+ if (this.looping && this.atTokenEnd()) {
103
+ this.tokenPosition = 0;
104
+ }
105
+ }
106
+ }
107
+ determineBranch(capture) {
108
+ for (let i = 0; i < capture.length - 1; i++) {
109
+ if (capture.groups?.[`rgx_branch_${i}`] !== undefined)
110
+ return i;
111
+ }
112
+ return 0;
113
+ }
114
+ registerCapture(captureResult, token) {
115
+ this.captures.push(captureResult);
116
+ if (token instanceof part_1.RGXPart && token.hasId()) {
117
+ if (!(token.id in this.namedCaptures))
118
+ this.namedCaptures[token.id] = [];
119
+ this.namedCaptures[token.id].push(captureResult);
120
+ }
121
+ }
122
+ unregisterLastCapture(token) {
123
+ this.captures.pop();
124
+ if (token instanceof part_1.RGXPart && token.hasId()) {
125
+ this.namedCaptures[token.id].pop();
126
+ }
127
+ }
128
+ handleBeforeCapture(token) {
129
+ const control = token.beforeCapture?.({ part: token, walker: this });
130
+ // If this happens, beforeCapture itself stopped the walker, so we just need to respect that.
131
+ if (this.stopped)
132
+ return "stop";
133
+ if (control === "stop" || control === "stop-silent")
134
+ return "stop";
135
+ if (control === "skip")
136
+ return "skip";
137
+ if (control === "silent")
138
+ return "silent";
139
+ return null;
140
+ }
141
+ // Returns the capture on success, or a directive on handled failure. Unhandled errors are rethrown.
142
+ attemptCapture(branchedToken, part) {
143
+ try {
144
+ return this.capture(branchedToken, true);
145
+ }
146
+ catch (e) {
147
+ if (part !== null && e instanceof errors_1.RGXRegexNotMatchedAtPositionError) {
148
+ const control = part.afterFailure?.(e, { part, walker: this });
149
+ // If this happens, afterFailure itself stopped the walker, so we just need to respect that.
150
+ if (this.stopped)
151
+ return "stop";
152
+ if (control === "stop" || control === "stop-silent")
153
+ return "stop";
154
+ if (control === "skip")
155
+ return "skip";
156
+ // Handling silent is pointless here since it won't add a capture in either case, so we don't check for it.
157
+ }
158
+ throw e;
159
+ }
160
+ }
161
+ // Returns a directive on handled validation failure, null on success. Unhandled errors are rethrown.
162
+ validateCapture(token, captureResult, start) {
163
+ try {
164
+ token.validate(captureResult, { part: token, walker: this });
165
+ return null;
166
+ }
167
+ catch (e) {
168
+ this.sourcePosition = start; // Reset source position on validation failure
169
+ if (e instanceof errors_1.RGXPartValidationFailedError) {
170
+ const control = token.afterValidationFailure?.(e, { part: token, walker: this });
171
+ // If this happens, afterValidationFailure itself stopped the walker, so we just need to respect that.
172
+ if (this.stopped)
173
+ return "stop";
174
+ if (control === "stop" || control === "stop-silent")
175
+ return "stop";
176
+ if (control === "skip")
177
+ return "skip";
178
+ // Handling silent is pointless here since it won't add a capture in either case, so we don't check for it.
179
+ }
180
+ throw e;
181
+ }
182
+ }
183
+ handleAfterCapture(token, captureResult, silent, start) {
184
+ const control = token.afterCapture?.(captureResult, { part: token, walker: this });
185
+ // If this happens, afterCapture itself stopped the walker, so we just need to respect that.
186
+ if (this.stopped)
187
+ return "stop";
188
+ if (!silent && (control === "skip" || control === "silent" || control === "stop-silent")) {
189
+ this.unregisterLastCapture(token);
190
+ }
191
+ if (control === "skip") {
192
+ this.sourcePosition = start;
193
+ return "skip";
194
+ }
195
+ if (control === "stop" || control === "stop-silent")
196
+ return "stop";
197
+ return null;
198
+ }
99
199
  step() {
100
200
  if (!this.infinite && !this.looping && this.atTokenEnd()) {
101
201
  this._stopped = true;
@@ -109,106 +209,64 @@ class RGXWalker {
109
209
  const token = this.currentToken();
110
210
  const isPart = token instanceof part_1.RGXPart;
111
211
  let silent = false;
112
- // Ask Part what to do — control flow via return values, not flags.
113
212
  if (isPart) {
114
- const control = token.beforeCapture?.({ part: token, walker: this });
115
- if (control === "stop") {
213
+ const dir = this.handleBeforeCapture(token);
214
+ if (dir === "stop") {
116
215
  this._stopped = true;
117
216
  return null;
118
217
  }
119
- if (control === "skip") {
120
- this.tokenPosition++;
218
+ if (dir === "skip") {
219
+ this.advanceToken();
121
220
  return null;
122
221
  }
123
- if (control === "silent") {
124
- silent = true;
125
- }
222
+ silent = dir === "silent";
126
223
  }
127
- // Capture the match
128
224
  const start = this.sourcePosition;
129
- let branchedToken;
130
- if (isPart)
131
- branchedToken = createBranchGroups(token.token);
132
- else
133
- branchedToken = createBranchGroups(token);
134
- let capture;
135
- try {
136
- capture = this.capture(branchedToken, true);
225
+ const branchedToken = isPart ? createBranchGroups(token.token) : createBranchGroups(token);
226
+ const captureAttempt = this.attemptCapture(branchedToken, isPart ? token : null);
227
+ if (captureAttempt === "stop") {
228
+ this._stopped = true;
229
+ return null;
137
230
  }
138
- catch (e) {
139
- if (isPart && e instanceof errors_1.RGXRegexNotMatchedAtPositionError) {
140
- const control = token.afterFailure?.(e, { part: token, walker: this });
141
- if (control === "stop") {
142
- this._stopped = true;
143
- return null;
144
- }
145
- if (control === "skip") {
146
- this.tokenPosition++;
147
- return null;
148
- }
149
- // Handling silent is pointless here since it won't add a capture in either case, so we don't check for it.
150
- }
151
- throw e;
231
+ if (captureAttempt === "skip") {
232
+ this.advanceToken();
233
+ return null;
152
234
  }
153
- const raw = isPart ? token.rawTransform(capture[0]) : capture[0];
235
+ const raw = isPart ? token.rawTransform(captureAttempt[0]) : captureAttempt[0];
154
236
  const end = this.sourcePosition;
155
237
  const value = isPart ? token.transform(raw) : raw;
156
- let branch = 0;
157
- // Determine branch index for captureResult by finding the first index
158
- // with non-undefined match group.
159
- for (let i = 0; i < capture.length - 1; i++) {
160
- const branchKey = `rgx_branch_${i}`;
161
- if (capture.groups && capture.groups[branchKey] !== undefined) {
162
- branch = i;
163
- break;
164
- }
165
- }
166
238
  const captureResult = {
167
- raw, value, start, end, branch,
239
+ raw, value, start, end,
240
+ branch: this.determineBranch(captureAttempt),
168
241
  ownerId: isPart && token.hasId() ? token.id : null,
169
- groups: capture.groups ?? null
242
+ groups: captureAttempt.groups ?? null
170
243
  };
171
- // Validate the part. If validation fails, it will throw an error, so nothing below will run.
172
244
  if (isPart) {
173
- try {
174
- token.validate(captureResult, { part: token, walker: this });
175
- }
176
- catch (e) {
177
- this.sourcePosition = start; // Reset source position on validation failure
178
- if (e instanceof errors_1.RGXPartValidationFailedError) {
179
- const control = token.afterValidationFailure?.(e, { part: token, walker: this });
180
- if (control === "stop") {
181
- this._stopped = true;
182
- return null;
183
- }
184
- if (control === "skip") {
185
- this.tokenPosition++;
186
- return null;
187
- }
188
- // Handling silent is pointless here since it won't add a capture in either case, so we don't check for it.
189
- }
190
- throw e;
245
+ const dir = this.validateCapture(token, captureResult, start);
246
+ if (dir === "stop") {
247
+ this._stopped = true;
248
+ return null;
191
249
  }
192
- }
193
- // Skip adding the capture if in silent mode.
194
- if (!silent) {
195
- this.captures.push(captureResult);
196
- if (isPart && token.hasId()) {
197
- if (!(token.id in this.namedCaptures))
198
- this.namedCaptures[token.id] = [];
199
- this.namedCaptures[token.id].push(captureResult);
250
+ if (dir === "skip") {
251
+ this.advanceToken();
252
+ return null;
200
253
  }
201
254
  }
202
- // Notify Part after capture
255
+ if (!silent)
256
+ this.registerCapture(captureResult, token);
203
257
  if (isPart) {
204
- token.afterCapture?.(captureResult, { part: token, walker: this });
205
- }
206
- if (!this.infinite || this.tokenPosition < this.tokens.length - 1) {
207
- this.tokenPosition++;
208
- if (this.looping && this.atTokenEnd()) {
209
- this.tokenPosition = 0;
258
+ const dir = this.handleAfterCapture(token, captureResult, silent, start);
259
+ if (dir === "stop") {
260
+ this.advanceToken();
261
+ this._stopped = true;
262
+ return null;
263
+ }
264
+ if (dir === "skip") {
265
+ this.advanceToken();
266
+ return null;
210
267
  }
211
268
  }
269
+ this.advanceToken();
212
270
  return captureResult;
213
271
  }
214
272
  stepToToken(predicate) {
@@ -2,7 +2,7 @@ import { RGXToken } from "../types";
2
2
  import type { RGXWalker } from "./base";
3
3
  import { CloneDepth } from "@ptolemy2002/immutability-utils";
4
4
  import { RGXPartValidationFailedError, RGXRegexNotMatchedAtPositionError } from "../errors";
5
- export type RGXPartControl = "skip" | "stop" | "silent" | void;
5
+ export type RGXPartControl = "skip" | "stop" | "silent" | "stop-silent" | void;
6
6
  export type RGXCapture<T = unknown> = {
7
7
  raw: string;
8
8
  value: T;
@@ -22,7 +22,7 @@ export type RGXPartOptions<R, S = unknown, T = string> = {
22
22
  transform: (captured: string) => T;
23
23
  validate: (captured: RGXCapture<T>, context: RGXPartContext<R, S, T>) => boolean | string;
24
24
  beforeCapture: ((context: RGXPartContext<R, S, T>) => RGXPartControl) | null;
25
- afterCapture: ((capture: RGXCapture<T>, context: RGXPartContext<R, S, T>) => void) | null;
25
+ afterCapture: ((capture: RGXCapture<T>, context: RGXPartContext<R, S, T>) => RGXPartControl) | null;
26
26
  afterFailure: ((e: RGXRegexNotMatchedAtPositionError, context: RGXPartContext<R, S, T>) => RGXPartControl) | null;
27
27
  afterValidationFailure: ((e: RGXPartValidationFailedError, context: RGXPartContext<R, S, T>) => RGXPartControl) | null;
28
28
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ptolemy2002/rgx",
3
- "version": "12.5.0",
3
+ "version": "12.6.0",
4
4
  "private": false,
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",