@ptolemy2002/rgx 12.5.0 → 12.7.1

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/README.md CHANGED
@@ -16,6 +16,7 @@ Because there is so much to document, it has been broken up into multiple files.
16
16
  - [clone](./docs/util/clone.md) - The `cloneRGXToken` function, which creates a clone of a given RGX token to a specified depth.
17
17
  - [escapeRegex](./docs/util/escapeRegex.md) - The `escapeRegex` function, which escapes special regex characters in a given string and assures you that the result is valid Regex.
18
18
  - [regexMatchAtPosition](./docs/util/regexMatchAtPosition.md) - The `regexMatchAtPosition` function and related functions, which attempt to match a given regular expression at a specific position in a string.
19
+ - [regexMatchAfterPosition](./docs/util/regexMatchAfterPosition.md) - The `regexMatchAfterPosition` function and related functions, which search for a regular expression match at or after a specific position in a string.
19
20
  - [regexWithFlags](./docs/util/regexWithFlags.md) - The `regexWithFlags` function, which creates a new regular expression with the same source as a given regular expression but with different flags.
20
21
  - [createRegex](./docs/util/createRegex.md) - The `createRegex` function, which safely constructs a `RegExp` or `ExtRegExp` from a pattern string, converting `SyntaxError` into `RGXInvalidRegexStringError`.
21
22
  - [createRGXClassGuardFunction](./docs/util/createRGXClassGuardFunction.md) - The `createRGXClassGuardFunction` and `createAssertRGXClassGuardFunction` utilities for creating type guard and assertion functions for class instances.
@@ -1,4 +1,4 @@
1
- export type RGXErrorCode = 'UNKNOWN' | 'INVALID_RGX_TOKEN' | 'INVALID_REGEX_STRING' | 'INVALID_REGEX_FLAGS' | 'INVALID_VANILLA_REGEX_FLAGS' | 'NOT_IMPLEMENTED' | 'NOT_SUPPORTED' | 'INVALID_IDENTIFIER' | 'OUT_OF_BOUNDS' | 'INVALID_FLAG_TRANSFORMER_KEY' | 'FLAG_TRANSFORMER_CONFLICT' | 'CONSTANT_CONFLICT' | 'INVALID_CONSTANT_KEY' | 'INSERTION_REJECTED' | 'REGEX_NOT_MATCHED_AT_POSITION' | 'PART_VALIDATION_FAILED' | 'INVALID_LEXER_MODE' | 'LEXEME_NOT_MATCHED_AT_POSITION' | 'INVALID_RGX_LEXER' | 'INVALID_RGX_WALKER' | 'INVALID_RGX_PART' | 'NOT_DIRECT_REGEXP';
1
+ export type RGXErrorCode = 'UNKNOWN' | 'INVALID_RGX_TOKEN' | 'INVALID_REGEX_STRING' | 'INVALID_REGEX_FLAGS' | 'INVALID_VANILLA_REGEX_FLAGS' | 'NOT_IMPLEMENTED' | 'NOT_SUPPORTED' | 'INVALID_IDENTIFIER' | 'OUT_OF_BOUNDS' | 'INVALID_FLAG_TRANSFORMER_KEY' | 'FLAG_TRANSFORMER_CONFLICT' | 'CONSTANT_CONFLICT' | 'INVALID_CONSTANT_KEY' | 'INSERTION_REJECTED' | 'REGEX_NOT_MATCHED_AT_POSITION' | 'REGEX_NOT_MATCHED_AFTER_POSITION' | 'PART_VALIDATION_FAILED' | 'INVALID_LEXER_MODE' | 'LEXEME_NOT_MATCHED_AT_POSITION' | 'INVALID_RGX_LEXER' | 'INVALID_RGX_WALKER' | 'INVALID_RGX_PART' | 'NOT_DIRECT_REGEXP';
2
2
  export declare class RGXError extends Error {
3
3
  _message: string;
4
4
  code: RGXErrorCode;
@@ -13,6 +13,7 @@ export * from './insertionRejected';
13
13
  export * from './constantConflict';
14
14
  export * from './invalidConstantKey';
15
15
  export * from './regexNotMatchedAtPosition';
16
+ export * from './regexNotMatchedAfterPosition';
16
17
  export * from './partValidationFailed';
17
18
  export * from './invalidLexerMode';
18
19
  export * from './lexemeNotMatchedAtPosition';
@@ -29,6 +29,7 @@ __exportStar(require("./insertionRejected"), exports);
29
29
  __exportStar(require("./constantConflict"), exports);
30
30
  __exportStar(require("./invalidConstantKey"), exports);
31
31
  __exportStar(require("./regexNotMatchedAtPosition"), exports);
32
+ __exportStar(require("./regexNotMatchedAfterPosition"), exports);
32
33
  __exportStar(require("./partValidationFailed"), exports);
33
34
  __exportStar(require("./invalidLexerMode"), exports);
34
35
  __exportStar(require("./lexemeNotMatchedAtPosition"), exports);
@@ -1,5 +1,5 @@
1
- import { RGXError, RGXPartValidationFailedError, RGXRegexNotMatchedAtPositionError } from "./";
2
- export type LexemeNotMatchedCauseError = RGXRegexNotMatchedAtPositionError | RGXPartValidationFailedError;
1
+ import { RGXError, RGXPartValidationFailedError, RGXRegexNotMatchedAfterPositionError, RGXRegexNotMatchedAtPositionError } from "./";
2
+ export type LexemeNotMatchedCauseError = RGXRegexNotMatchedAtPositionError | RGXPartValidationFailedError | RGXRegexNotMatchedAfterPositionError;
3
3
  export type LexemeNotMatchedCause = {
4
4
  id: string;
5
5
  error: LexemeNotMatchedCauseError;
@@ -4,7 +4,7 @@ exports.RGXLexemeNotMatchedAtPositionError = void 0;
4
4
  exports.isLexemeNotMatchedCauseError = isLexemeNotMatchedCauseError;
5
5
  const errors_1 = require("./");
6
6
  function isLexemeNotMatchedCauseError(error) {
7
- return error instanceof errors_1.RGXRegexNotMatchedAtPositionError || error instanceof errors_1.RGXPartValidationFailedError;
7
+ return error instanceof errors_1.RGXRegexNotMatchedAtPositionError || error instanceof errors_1.RGXPartValidationFailedError || error instanceof errors_1.RGXRegexNotMatchedAfterPositionError;
8
8
  }
9
9
  class RGXLexemeNotMatchedAtPositionError extends errors_1.RGXError {
10
10
  get position() {
@@ -0,0 +1,15 @@
1
+ import { RGXError } from "./base";
2
+ export declare class RGXRegexNotMatchedAfterPositionError extends RGXError {
3
+ pattern: RegExp;
4
+ source: string;
5
+ _position: number;
6
+ contextSize: number | null;
7
+ set position(value: number);
8
+ get position(): number;
9
+ constructor(message: string, pattern: RegExp, source: string, position: number, contextSize?: number | null);
10
+ sourceContext(): string;
11
+ hasLeftContext(): boolean;
12
+ hasRightContext(): boolean;
13
+ hasFullContext(): boolean;
14
+ calcMessage(message: string): string;
15
+ }
@@ -0,0 +1,53 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.RGXRegexNotMatchedAfterPositionError = void 0;
4
+ const base_1 = require("./base");
5
+ const outOfBounds_1 = require("./outOfBounds");
6
+ class RGXRegexNotMatchedAfterPositionError extends base_1.RGXError {
7
+ set position(value) {
8
+ (0, outOfBounds_1.assertInRange)(value, { min: 0, max: this.source.length }, "position is outside the bounds of the source string");
9
+ this._position = value;
10
+ }
11
+ get position() {
12
+ return this._position;
13
+ }
14
+ constructor(message, pattern, source, position, contextSize = null) {
15
+ super(message, 'REGEX_NOT_MATCHED_AFTER_POSITION');
16
+ this.name = 'RGXRegexNotMatchedAfterPositionError';
17
+ this.pattern = pattern;
18
+ this.source = source;
19
+ this.position = position;
20
+ this.contextSize = contextSize;
21
+ }
22
+ sourceContext() {
23
+ if (this.hasFullContext())
24
+ return this.source;
25
+ const start = Math.max(0, this.position - this.contextSize);
26
+ const end = Math.min(this.source.length, this.position + this.contextSize);
27
+ return this.source.slice(start, end);
28
+ }
29
+ hasLeftContext() {
30
+ if (this.contextSize === null)
31
+ return false;
32
+ return this.position - this.contextSize >= 0;
33
+ }
34
+ hasRightContext() {
35
+ if (this.contextSize === null)
36
+ return false;
37
+ return this.position + this.contextSize <= this.source.length;
38
+ }
39
+ hasFullContext() {
40
+ return !this.hasLeftContext() && !this.hasRightContext();
41
+ }
42
+ calcMessage(message) {
43
+ let context = this.sourceContext();
44
+ if (this.contextSize !== null) {
45
+ if (this.hasLeftContext())
46
+ context = `...${context}`;
47
+ if (this.hasRightContext())
48
+ context = `${context}...`;
49
+ }
50
+ return `${message}; Pattern: ${this.pattern.toString()}, Position: ${this.position}, Context: ${context}`;
51
+ }
52
+ }
53
+ exports.RGXRegexNotMatchedAfterPositionError = RGXRegexNotMatchedAfterPositionError;
@@ -1,4 +1,5 @@
1
1
  export * from "./regexMatchAtPosition";
2
+ export * from "./regexMatchAfterPosition";
2
3
  export * from "./regexWithFlags";
3
4
  export * from "./normalizeRegexFlags";
4
5
  export * from "./createRGXClassGuardFunction";
@@ -15,6 +15,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
17
  __exportStar(require("./regexMatchAtPosition"), exports);
18
+ __exportStar(require("./regexMatchAfterPosition"), exports);
18
19
  __exportStar(require("./regexWithFlags"), exports);
19
20
  __exportStar(require("./normalizeRegexFlags"), exports);
20
21
  __exportStar(require("./createRGXClassGuardFunction"), exports);
@@ -0,0 +1,6 @@
1
+ export declare function regexMatchAfterPosition(regex: RegExp, str: string, position: number, includeMatch: true): [number, RegExpExecArray] | null;
2
+ export declare function regexMatchAfterPosition(regex: RegExp, str: string, position: number, includeMatch?: false): [number, string] | null;
3
+ export declare function doesRegexMatchAfterPosition(regex: RegExp, str: string, position: number, includeMatch: true): [number, RegExpExecArray] | false;
4
+ export declare function doesRegexMatchAfterPosition(regex: RegExp, str: string, position: number, includeMatch?: false): boolean;
5
+ export declare function assertRegexMatchesAfterPosition(regex: RegExp, str: string, position: number, contextSize?: number | null, includeMatch?: false): [number, string];
6
+ export declare function assertRegexMatchesAfterPosition(regex: RegExp, str: string, position: number, contextSize: number | null | undefined, includeMatch: true): [number, RegExpExecArray];
@@ -0,0 +1,34 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.regexMatchAfterPosition = regexMatchAfterPosition;
4
+ exports.doesRegexMatchAfterPosition = doesRegexMatchAfterPosition;
5
+ exports.assertRegexMatchesAfterPosition = assertRegexMatchesAfterPosition;
6
+ const outOfBounds_1 = require("../errors/outOfBounds");
7
+ const regexWithFlags_1 = require("./regexWithFlags");
8
+ const errors_1 = require("../errors");
9
+ function regexMatchAfterPosition(regex, str, position, includeMatch = false) {
10
+ /*
11
+ The g flag means global mode, which searches forward from lastIndex rather
12
+ than requiring a match exactly there (as the y/sticky flag does). By setting
13
+ lastIndex to the position we want to search from, exec will find the next
14
+ match at or after that position.
15
+ */
16
+ (0, outOfBounds_1.assertInRange)(position, { min: 0, max: str.length, inclusiveRight: false }, 'String index out of bounds');
17
+ const globalRegex = (0, regexWithFlags_1.regexWithFlags)(regex, "g");
18
+ globalRegex.lastIndex = position;
19
+ const match = globalRegex.exec(str);
20
+ return match ? (includeMatch ? [match.index, match] : [match.index, match[0]]) : null;
21
+ }
22
+ function doesRegexMatchAfterPosition(regex, str, position, includeMatch = false) {
23
+ const match = regexMatchAfterPosition(regex, str, position, true);
24
+ if (includeMatch)
25
+ return match ?? false;
26
+ return match !== null;
27
+ }
28
+ function assertRegexMatchesAfterPosition(regex, str, position, contextSize = 10, includeMatch = false) {
29
+ const result = regexMatchAfterPosition(regex, str, position, true);
30
+ if (result === null) {
31
+ throw new errors_1.RGXRegexNotMatchedAfterPositionError("Regex not matched after index", regex, str, position, contextSize);
32
+ }
33
+ return includeMatch ? result : [result[0], result[1][0]];
34
+ }
@@ -7,8 +7,10 @@ export type RGXWalkerOptions<R, S = unknown> = {
7
7
  share?: S;
8
8
  infinite?: boolean;
9
9
  looping?: boolean;
10
+ contiguous?: boolean;
10
11
  };
11
12
  export type RGXTokenOrPart<R, S = unknown, T = unknown> = RGXToken | RGXPart<R, S, T>;
13
+ export type RGXWalkerStepDirective = "stop" | "skip" | "silent";
12
14
  export declare class RGXWalker<R, S = unknown> {
13
15
  readonly source: string;
14
16
  _sourcePosition: number;
@@ -20,7 +22,9 @@ export declare class RGXWalker<R, S = unknown> {
20
22
  namedCaptures: Record<string, RGXCapture[]>;
21
23
  infinite: boolean;
22
24
  looping: boolean;
25
+ contiguous: boolean;
23
26
  private _stopped;
27
+ private _didReachEnd;
24
28
  static check: (value: unknown) => value is RGXWalker<unknown, unknown>;
25
29
  static assert: (value: unknown) => asserts value is RGXWalker<unknown, unknown>;
26
30
  get sourcePosition(): number;
@@ -39,6 +43,14 @@ export declare class RGXWalker<R, S = unknown> {
39
43
  remainingSource(): string | null;
40
44
  capture(token: RGXTokenOrPart<R, S>, includeMatch: true): RegExpExecArray;
41
45
  capture(token: RGXTokenOrPart<R, S>, includeMatch?: false): string;
46
+ private advanceToken;
47
+ private determineBranch;
48
+ private registerCapture;
49
+ private unregisterLastCapture;
50
+ private handleBeforeCapture;
51
+ private attemptCapture;
52
+ private validateCapture;
53
+ private handleAfterCapture;
42
54
  step(): RGXCapture | null;
43
55
  stepToToken(predicate: (token: RGXTokenOrPart<R>) => boolean): this;
44
56
  stepToPart(predicate?: (part: RGXPart<R, S, unknown>) => boolean): this;
@@ -26,7 +26,9 @@ function createBranchGroups(tokens) {
26
26
  }
27
27
  }
28
28
  function isMatchError(e) {
29
- return e instanceof errors_1.RGXRegexNotMatchedAtPositionError || e instanceof errors_1.RGXPartValidationFailedError;
29
+ return (e instanceof errors_1.RGXRegexNotMatchedAtPositionError ||
30
+ e instanceof errors_1.RGXRegexNotMatchedAfterPositionError ||
31
+ e instanceof errors_1.RGXPartValidationFailedError);
30
32
  }
31
33
  class RGXWalker {
32
34
  get sourcePosition() {
@@ -50,6 +52,8 @@ class RGXWalker {
50
52
  this.captures = [];
51
53
  this.namedCaptures = {};
52
54
  this._stopped = false;
55
+ // Only relevant in infinite mode, tracking whether we've reached the end yet.
56
+ this._didReachEnd = false;
53
57
  this.source = source;
54
58
  this.sourcePosition = options.startingSourcePosition ?? 0;
55
59
  this.tokens = tokens;
@@ -58,6 +62,7 @@ class RGXWalker {
58
62
  this.share = options.share ?? null;
59
63
  this.infinite = options.infinite ?? false;
60
64
  this.looping = options.looping ?? false;
65
+ this.contiguous = options.contiguous ?? true;
61
66
  }
62
67
  stop() {
63
68
  this._stopped = true;
@@ -92,10 +97,123 @@ class RGXWalker {
92
97
  }
93
98
  capture(token, includeMatch = false) {
94
99
  const regex = (0, utils_1.createRegex)((0, resolve_1.resolveRGXToken)(part_1.RGXPart.check(token) ? token.token : token));
95
- const match = (0, utils_1.assertRegexMatchesAtPosition)(regex, this.source, this.sourcePosition, 10, true);
96
- this.sourcePosition += match[0].length;
100
+ const args = [regex, this.source, this.sourcePosition, 10, true];
101
+ let match;
102
+ let endPosition;
103
+ if (this.contiguous) {
104
+ match = (0, utils_1.assertRegexMatchesAtPosition)(...args);
105
+ endPosition = this.sourcePosition + match[0].length;
106
+ }
107
+ else {
108
+ const [startPosition, _match] = (0, utils_1.assertRegexMatchesAfterPosition)(...args);
109
+ match = _match;
110
+ endPosition = startPosition + match[0].length;
111
+ }
112
+ this.sourcePosition = endPosition;
97
113
  return includeMatch ? match : match[0];
98
114
  }
115
+ advanceToken() {
116
+ if (this.tokenPosition === this.tokens.length - 1)
117
+ this._didReachEnd = true;
118
+ if (!this.infinite || this.tokenPosition < this.tokens.length - 1) {
119
+ this.tokenPosition++;
120
+ if (this.looping && this.atTokenEnd()) {
121
+ this.tokenPosition = 0;
122
+ }
123
+ }
124
+ }
125
+ determineBranch(capture) {
126
+ for (let i = 0; i < capture.length - 1; i++) {
127
+ if (capture.groups?.[`rgx_branch_${i}`] !== undefined)
128
+ return i;
129
+ }
130
+ return 0;
131
+ }
132
+ registerCapture(captureResult, token) {
133
+ this.captures.push(captureResult);
134
+ if (token instanceof part_1.RGXPart && token.hasId()) {
135
+ if (!(token.id in this.namedCaptures))
136
+ this.namedCaptures[token.id] = [];
137
+ this.namedCaptures[token.id].push(captureResult);
138
+ }
139
+ }
140
+ unregisterLastCapture(token) {
141
+ this.captures.pop();
142
+ if (token instanceof part_1.RGXPart && token.hasId()) {
143
+ this.namedCaptures[token.id].pop();
144
+ }
145
+ }
146
+ handleBeforeCapture(token) {
147
+ const control = token.beforeCapture?.({ part: token, walker: this });
148
+ // If this happens, beforeCapture itself stopped the walker, so we just need to respect that.
149
+ if (this.stopped)
150
+ return "stop";
151
+ if (control === "stop" || control === "stop-silent")
152
+ return "stop";
153
+ if (control === "skip")
154
+ return "skip";
155
+ if (control === "silent")
156
+ return "silent";
157
+ return null;
158
+ }
159
+ // Returns the capture on success, or a directive on handled failure. Unhandled errors are rethrown.
160
+ attemptCapture(branchedToken, part) {
161
+ try {
162
+ return this.capture(branchedToken, true);
163
+ }
164
+ catch (e) {
165
+ if (part !== null && (e instanceof errors_1.RGXRegexNotMatchedAtPositionError || e instanceof errors_1.RGXRegexNotMatchedAfterPositionError)) {
166
+ const control = part.afterFailure?.(e, { part, walker: this });
167
+ // If this happens, afterFailure itself stopped the walker, so we just need to respect that.
168
+ if (this.stopped)
169
+ return "stop";
170
+ if (control === "stop" || control === "stop-silent")
171
+ return "stop";
172
+ if (control === "skip")
173
+ return "skip";
174
+ // Handling silent is pointless here since it won't add a capture in either case, so we don't check for it.
175
+ }
176
+ throw e;
177
+ }
178
+ }
179
+ // Returns a directive on handled validation failure, null on success. Unhandled errors are rethrown.
180
+ validateCapture(token, captureResult, start) {
181
+ try {
182
+ token.validate(captureResult, { part: token, walker: this });
183
+ return null;
184
+ }
185
+ catch (e) {
186
+ this.sourcePosition = start; // Reset source position on validation failure
187
+ if (e instanceof errors_1.RGXPartValidationFailedError) {
188
+ const control = token.afterValidationFailure?.(e, { part: token, walker: this });
189
+ // If this happens, afterValidationFailure itself stopped the walker, so we just need to respect that.
190
+ if (this.stopped)
191
+ return "stop";
192
+ if (control === "stop" || control === "stop-silent")
193
+ return "stop";
194
+ if (control === "skip")
195
+ return "skip";
196
+ // Handling silent is pointless here since it won't add a capture in either case, so we don't check for it.
197
+ }
198
+ throw e;
199
+ }
200
+ }
201
+ handleAfterCapture(token, captureResult, silent, start) {
202
+ const control = token.afterCapture?.(captureResult, { part: token, walker: this });
203
+ if (!silent && (control === "skip" || control === "silent" || control === "stop-silent")) {
204
+ this.unregisterLastCapture(token);
205
+ }
206
+ // If this happens, afterCapture itself stopped the walker, so we just need to respect that.
207
+ if (this.stopped)
208
+ return "stop";
209
+ if (control === "skip") {
210
+ this.sourcePosition = start;
211
+ return "skip";
212
+ }
213
+ if (control === "stop" || control === "stop-silent")
214
+ return "stop";
215
+ return null;
216
+ }
99
217
  step() {
100
218
  if (!this.infinite && !this.looping && this.atTokenEnd()) {
101
219
  this._stopped = true;
@@ -109,106 +227,81 @@ class RGXWalker {
109
227
  const token = this.currentToken();
110
228
  const isPart = token instanceof part_1.RGXPart;
111
229
  let silent = false;
112
- // Ask Part what to do — control flow via return values, not flags.
113
230
  if (isPart) {
114
- const control = token.beforeCapture?.({ part: token, walker: this });
115
- if (control === "stop") {
231
+ const dir = this.handleBeforeCapture(token);
232
+ if (dir === "stop") {
116
233
  this._stopped = true;
117
234
  return null;
118
235
  }
119
- if (control === "skip") {
120
- this.tokenPosition++;
236
+ if (dir === "skip") {
237
+ this.advanceToken();
121
238
  return null;
122
239
  }
123
- if (control === "silent") {
124
- silent = true;
125
- }
240
+ silent = dir === "silent";
126
241
  }
127
- // Capture the match
128
- const start = this.sourcePosition;
129
- let branchedToken;
130
- if (isPart)
131
- branchedToken = createBranchGroups(token.token);
132
- else
133
- branchedToken = createBranchGroups(token);
134
- let capture;
242
+ const branchedToken = isPart ? createBranchGroups(token.token) : createBranchGroups(token);
243
+ let captureAttempt;
135
244
  try {
136
- capture = this.capture(branchedToken, true);
245
+ captureAttempt = this.attemptCapture(branchedToken, isPart ? token : null);
246
+ if (captureAttempt === "stop") {
247
+ this._stopped = true;
248
+ return null;
249
+ }
250
+ if (captureAttempt === "skip") {
251
+ this.advanceToken();
252
+ return null;
253
+ }
137
254
  }
138
255
  catch (e) {
139
- if (isPart && e instanceof errors_1.RGXRegexNotMatchedAtPositionError) {
140
- const control = token.afterFailure?.(e, { part: token, walker: this });
141
- if (control === "stop") {
256
+ if (e instanceof errors_1.RGXRegexNotMatchedAfterPositionError) {
257
+ // If we're in infinite mode, we've reached the end before, and we're at the end now,
258
+ // this is recoverable. Just stop the walker instead of throwing an error.
259
+ if (this.infinite && this._didReachEnd && this.tokenPosition === this.tokens.length - 1) {
142
260
  this._stopped = true;
143
261
  return null;
144
262
  }
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
263
  }
151
264
  throw e;
152
265
  }
153
- const raw = isPart ? token.rawTransform(capture[0]) : capture[0];
266
+ // The reason we no longer track start as the position before attempting capture
267
+ // is the possibility of non-contiguous matches. In that case, there may be a gap between
268
+ // the previous position and the actual start of the capture.
269
+ const start = this.sourcePosition - captureAttempt[0].length;
270
+ const raw = isPart ? token.rawTransform(captureAttempt[0]) : captureAttempt[0];
154
271
  const end = this.sourcePosition;
155
272
  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
273
  const captureResult = {
167
- raw, value, start, end, branch,
274
+ raw, value, start, end,
275
+ branch: this.determineBranch(captureAttempt),
168
276
  ownerId: isPart && token.hasId() ? token.id : null,
169
- groups: capture.groups ?? null
277
+ groups: captureAttempt.groups ?? null
170
278
  };
171
- // Validate the part. If validation fails, it will throw an error, so nothing below will run.
172
279
  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;
280
+ const dir = this.validateCapture(token, captureResult, start);
281
+ if (dir === "stop") {
282
+ this._stopped = true;
283
+ return null;
191
284
  }
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);
285
+ if (dir === "skip") {
286
+ this.advanceToken();
287
+ return null;
200
288
  }
201
289
  }
202
- // Notify Part after capture
290
+ if (!silent)
291
+ this.registerCapture(captureResult, token);
203
292
  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;
293
+ const dir = this.handleAfterCapture(token, captureResult, silent, start);
294
+ if (dir === "stop") {
295
+ this.advanceToken();
296
+ this._stopped = true;
297
+ return null;
298
+ }
299
+ if (dir === "skip") {
300
+ this.advanceToken();
301
+ return null;
210
302
  }
211
303
  }
304
+ this.advanceToken();
212
305
  return captureResult;
213
306
  }
214
307
  stepToToken(predicate) {
@@ -1,8 +1,8 @@
1
1
  import { RGXToken } from "../types";
2
2
  import type { RGXWalker } from "./base";
3
3
  import { CloneDepth } from "@ptolemy2002/immutability-utils";
4
- import { RGXPartValidationFailedError, RGXRegexNotMatchedAtPositionError } from "../errors";
5
- export type RGXPartControl = "skip" | "stop" | "silent" | void;
4
+ import { RGXPartValidationFailedError, RGXRegexNotMatchedAfterPositionError, RGXRegexNotMatchedAtPositionError } from "../errors";
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,8 +22,8 @@ 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;
26
- afterFailure: ((e: RGXRegexNotMatchedAtPositionError, context: RGXPartContext<R, S, T>) => RGXPartControl) | null;
25
+ afterCapture: ((capture: RGXCapture<T>, context: RGXPartContext<R, S, T>) => RGXPartControl) | null;
26
+ afterFailure: ((e: RGXRegexNotMatchedAtPositionError | RGXRegexNotMatchedAfterPositionError, context: RGXPartContext<R, S, T>) => RGXPartControl) | null;
27
27
  afterValidationFailure: ((e: RGXPartValidationFailedError, context: RGXPartContext<R, S, T>) => RGXPartControl) | null;
28
28
  };
29
29
  export declare class RGXPart<R, S = unknown, T = string> {
@@ -38,6 +38,8 @@ class RGXPart {
38
38
  transform: this.transform,
39
39
  beforeCapture: this.beforeCapture,
40
40
  afterCapture: this.afterCapture,
41
+ afterFailure: this.afterFailure,
42
+ afterValidationFailure: this.afterValidationFailure,
41
43
  });
42
44
  }
43
45
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ptolemy2002/rgx",
3
- "version": "12.5.0",
3
+ "version": "12.7.1",
4
4
  "private": false,
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",