@ptolemy2002/rgx 5.6.0 → 6.0.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/README.md CHANGED
@@ -79,21 +79,23 @@ type RGXGroupTokenArgs = {
79
79
  capturing?: boolean;
80
80
  };
81
81
 
82
- type RGXPartEventType = "pre-capture" | "post-capture";
82
+ type RGXPartControl = "skip" | "stop" | "silent" | void;
83
+
84
+ type RGXCapture<T = unknown> = {
85
+ raw: string;
86
+ value: T;
87
+ };
88
+
83
89
  type RGXPartOptions<R, T=string> = {
84
90
  transform: (captured: string) => T;
85
- onEvent: ((part: RGXPart<R, T>, eventType: RGXPartEventType, walker: RGXWalker<R>) => void) | null;
91
+ validate: (captured: RGXCapture<T>, part: RGXPart<R, T>, walker: RGXWalker<R>) => boolean | string;
92
+ beforeCapture: ((part: RGXPart<R, T>, walker: RGXWalker<R>) => RGXPartControl) | null;
93
+ afterCapture: ((capture: RGXCapture<T>, part: RGXPart<R, T>, walker: RGXWalker<R>) => void) | null;
86
94
  };
87
95
 
88
96
  type RGXWalkerOptions<R> = {
89
97
  startingSourcePosition?: number;
90
- reducedCurrent?: R;
91
- };
92
-
93
- type RGXWalkerFlags = {
94
- stopped: boolean;
95
- skipped: boolean;
96
- nonCapture: boolean;
98
+ reduced?: R;
97
99
  };
98
100
  ```
99
101
 
@@ -325,6 +327,21 @@ constructor(message: string, got: number, { min, max, inclusiveLeft, inclusiveRi
325
327
  - `failedAtMax() => boolean`: Returns `true` if the `got` value is above the maximum bound (respecting `inclusiveRight`), otherwise `false`. Returns `false` if `max` is `null`.
326
328
  - `failedAtAny() => boolean`: Returns `true` if the value failed at either the minimum or maximum bound.
327
329
 
330
+ ### RGXPartValidationFailedError extends RGXError
331
+ A specific error class for RGX part validation failures. This error is thrown when a captured value fails validation in a custom part's `validate` function. The error code is set to `PART_VALIDATION_FAILED` on instantiation.
332
+
333
+ #### Constructor
334
+ ```typescript
335
+ constructor(message: string, gotRaw: string, gotTransformed: unknown)
336
+ ```
337
+ - `message` (`string`): The error message.
338
+ - `gotRaw` (`string`): The raw captured string value that failed validation.
339
+ - `gotTransformed` (`unknown`): The transformed value that was produced by the part's `transform` function, which also failed validation.
340
+
341
+ #### Properties
342
+ - `gotRaw` (`string`): The raw captured string value that failed validation.
343
+ - `gotTransformed` (`unknown`): The transformed value that was produced by the part's `transform` function, which also failed validation.
344
+
328
345
  ### RGXTokenCollection
329
346
  A class representing a collection of RGX tokens. This class manages collections of RGX tokens like an array, but with additional metadata about the collection mode (union or concat). Since `toRgx()` returns a `RegExp`, instances of this class satisfy the `RGXConvertibleToken` interface and can be used directly as tokens in `rgx`, `rgxa`, and other token-accepting functions.
330
347
 
@@ -579,7 +596,7 @@ constructor(pattern: string | RegExp, flags?: string)
579
596
  - `[Symbol.species]` (`RegExpConstructor`): Returns `ExtRegExp`, ensuring that derived `RegExp` methods (like those returning new regex instances) produce `ExtRegExp` instances rather than plain `RegExp`.
580
597
 
581
598
  ### RGXPart\<R, T=string\>
582
- A class that wraps an `RGXToken` and captures the matched string when used within an `RGXWalker`. It implements `RGXConvertibleToken`, delegating `rgxIsGroup` and `rgxIsRepeatable` to the wrapped token. After a walker captures a match for this part, the `capturedString` and `capturedValue` properties are populated, with the latter produced by the `transform` function.
599
+ A class that wraps an `RGXToken` with optional callbacks for use within an `RGXWalker`. It implements `RGXConvertibleToken`, delegating `rgxIsGroup` and `rgxIsRepeatable` to the wrapped token. Unlike plain tokens, Parts can control walker behavior via `beforeCapture` (returning an `RGXPartControl` value) and react to captures via `afterCapture`. Parts are purely definitions and do not store capture state — all captures are stored on the walker as `RGXCapture` objects.
583
600
 
584
601
  A function `rgxPart` is provided with the same parameters as this class' constructor, for easier instantiation without needing to use the `new` keyword.
585
602
 
@@ -594,24 +611,25 @@ constructor(token: RGXToken, options?: Partial<RGXPartOptions<R, T>>)
594
611
  - `token` (`RGXToken`): The token to wrap.
595
612
  - `options` (`Partial<RGXPartOptions<R, T>>`, optional): Configuration options. Defaults to `{}`.
596
613
  - `transform` (`(captured: string) => T`, optional): A function that transforms the captured string into the desired type `T`. Defaults to an identity function that casts the string to `T`.
597
- - `onEvent` (`((part: RGXPart<R, T>, eventType: RGXPartEventType, walker: RGXWalker<R>) => void) | null`, optional): A callback invoked when an event is triggered on this part during walking. Defaults to `null`.
614
+ - `beforeCapture` (`((part: RGXPart<R, T>, walker: RGXWalker<R>) => RGXPartControl) | null`, optional): A callback invoked before capturing this part during walking. Returns an `RGXPartControl` value to control walker behavior: `"skip"` to skip this token without capturing, `"silent"` to capture but not record in `captures`, `"stop"` to halt immediately without capturing or advancing, or `void`/`undefined` to proceed normally. Defaults to `null`.
615
+ - `afterCapture` (`((capture: RGXCapture<T>, part: RGXPart<R, T>, walker: RGXWalker<R>) => void) | null`, optional): A callback invoked after capturing this part during walking. Receives the typed `RGXCapture<T>` result. Can call `walker.stop()` to halt walking after this capture. Defaults to `null`.
616
+ - `validate` (`((capture: RGXCapture<T>, walker: RGXWalker<R>) => boolean | string) | null`, optional): A callback invoked during validation after capturing and transforming, but before `afterCapture`. Returns `true` if validation passes, `false` to fail with a generic error, or a string to fail with that string as the error message. Defaults to `null`.
598
617
 
599
618
  #### Properties
600
619
  - `token` (`RGXToken`): The wrapped token.
601
- - `capturedString` (`string | null`): The raw string captured by the walker for this part, or `null` if not yet captured.
602
- - `capturedValue` (`T | null`): The transformed value produced by applying `transform` to `capturedString`, or `null` if not yet captured.
603
- - `transform` (`(captured: string) => T`): The transform function used to convert captured strings to values of type `T`.
604
- - `onEvent` (`((part: RGXPart<R, T>, eventType: RGXPartEventType, walker: RGXWalker<R>) => void) | null`): The event callback, or `null`.
620
+ - `transform` (`(captured: string) => T`, readonly): The transform function used to convert captured strings to values of type `T`.
621
+ - `beforeCapture` (`((part: RGXPart<R, T>, walker: RGXWalker<R>) => RGXPartControl) | null`, readonly): The before-capture callback, or `null`.
622
+ - `afterCapture` (`((capture: RGXCapture<T>, part: RGXPart<R, T>, walker: RGXWalker<R>) => void) | null`, readonly): The after-capture callback, or `null`.
605
623
  - `rgxIsGroup` (`boolean`): Delegates to the wrapped token's group status via `isRGXGroupedToken`.
606
624
  - `rgxIsRepeatable` (`boolean`): If the wrapped token is an `RGXConvertibleToken`, delegates to its `rgxIsRepeatable` property (defaulting to `true` if not present). Otherwise, returns `true`.
607
625
 
608
626
  #### Methods
609
- - `triggerEvent(eventType: RGXPartEventType, walker: RGXWalker<R>) => void`: Triggers an event on this part. For `"post-capture"` events, sets `capturedString` to the walker's last captured string and `capturedValue` to the result of `transform`. Then calls the `onEvent` callback if present.
610
627
  - `toRgx() => RGXToken`: Returns the wrapped token.
611
- - `clone(depth: CloneDepth = "max") => RGXPart`: Creates a clone of this part. When `depth` is `0`, returns `this`; otherwise, returns a new `RGXPart` with a cloned token and the same `transform` and `onEvent` references.
628
+ - `clone(depth: CloneDepth = "max") => RGXPart`: Creates a clone of this part. When `depth` is `0`, returns `this`; otherwise, returns a new `RGXPart` with a cloned token and the same `transform`, `beforeCapture`, and `afterCapture` references.
629
+ - `validate(capture: RGXCapture<T>, walker: RGXWalker<R>) => void`: A method that calls the inner passed validation logic for this part, if any. If it returns `false`, a generic `RGXPartValidationFailedError` is thrown. If it returns a string, an `RGXPartValidationFailedError` is thrown with that string as the message. If it returns `true`, validation passed. This is called internally by the walker after capturing and transforming a part, before invoking `afterCapture`.
612
630
 
613
631
  ### RGXWalker\<R\>
614
- A class that walks through a sequence of RGX tokens, matching each token against a source string at the current position. It implements `RGXConvertibleToken`, delegating to its internal `RGXTokenCollection`. The walker maintains a source position and a token position, advancing through both as tokens are matched. When an `RGXPart` is encountered, its event callbacks are triggered around the capture. The generic type `R` represents a user-defined "reduced" value that can accumulate state during walking (e.g., via `RGXPart` event callbacks).
632
+ A class that walks through a sequence of RGX tokens, matching each token against a source string at the current position. It implements `RGXConvertibleToken`, delegating to its internal `RGXTokenCollection`. The walker maintains a source position and a token position, advancing through both as tokens are matched. When an `RGXPart` is encountered, its `beforeCapture` callback can control behavior via return values (`RGXPartControl`), and its `afterCapture` callback is invoked with the typed capture result. All captures are stored as structured `RGXCapture` objects on the walker. The generic type `R` represents a user-defined "reduced" value that can accumulate state during walking (e.g., via `RGXPart` callbacks).
615
633
 
616
634
  A function `rgxWalker` is provided with the same parameters as this class' constructor, for easier instantiation without needing to use the `new` keyword.
617
635
 
@@ -627,37 +645,33 @@ constructor(source: string, tokens: RGXTokenCollectionInput, options?: RGXWalker
627
645
  - `tokens` (`RGXTokenCollectionInput`): The tokens to match sequentially. Internally stored as an `RGXTokenCollection` in 'concat' mode.
628
646
  - `options` (`RGXWalkerOptions<R>`, optional): Configuration options. Defaults to `{}`.
629
647
  - `startingSourcePosition` (`number`, optional): The starting index in the source string. Defaults to `0`.
630
- - `reducedCurrent` (`R`, optional): The initial value for the `reducedCurrent` accumulator. Defaults to `null`.
648
+ - `reduced` (`R`, optional): The initial value for the `reduced` accumulator. Defaults to `null`.
631
649
 
632
650
  #### Properties
633
651
  - `source` (`string`): The source string being walked (readonly).
634
- - `sourcePosition` (`number`): The current index in the source string. Setting this validates that the value is >= 0 and < `source.length`, throwing `RGXOutOfBoundsError` if not.
652
+ - `sourcePosition` (`number`): The current index in the source string. Range is `[0, source.length]` inclusive, where `source.length` represents "fully consumed". Setting this validates that the value is >= 0 and <= `source.length`, throwing `RGXOutOfBoundsError` if not.
635
653
  - `tokens` (`RGXTokenCollection`): The internal collection of tokens in 'concat' mode (readonly).
636
654
  - `tokenPosition` (`number`): The current index in the token collection. Setting this validates that the value is >= 0 and <= `tokens.length`, throwing `RGXOutOfBoundsError` if not.
637
- - `reducedCurrent` (`R`): A user-defined accumulator value, typically updated by `RGXPart` event callbacks during walking.
638
- - `capturedStrings` (`string[]`): An array of all strings captured during walking (excluding those captured with the `nonCapture` flag set).
639
- - `flags` (`RGXWalkerFlags`): The current walker flags: `stopped` halts `stepToToken`/`stepToPart`/`walk`, `skipped` causes `step` to skip the current token without capturing, and `nonCapture` prevents the captured string from being added to `capturedStrings`.
655
+ - `reduced` (`R`): A user-defined accumulator value, typically updated by `RGXPart` callbacks during walking.
656
+ - `captures` (`RGXCapture[]`): An array of structured capture results recorded during walking. Each entry has a `raw` string and a `value` (the transform result for Parts, or the raw string for plain tokens).
657
+ - `stopped` (`boolean`, readonly): Whether the walker has been stopped, either by a Part's `beforeCapture` returning `"stop"` or by calling `stop()` in an `afterCapture` callback.
640
658
 
641
659
  #### Methods
642
- - `resetFlags() => void`: Resets all flags (`stopped`, `skipped`, `nonCapture`) to `false`.
643
- - `stop() => void`: Sets the `stopped` flag to `true`, causing any active `stepToToken`, `stepToPart`, or `walk` loop to halt after the current iteration.
644
- - `skip() => void`: Sets the `skipped` flag to `true`, causing the current `step` call to skip capturing the current token.
645
- - `preventCapture() => void`: Sets the `nonCapture` flag to `true`, causing the current `step` call to capture the token but not add the matched string to `capturedStrings`.
660
+ - `stop() => void`: Sets `stopped` to `true`, causing any active `stepToToken`, `stepToPart`, or `walk` loop to halt after the current iteration. Typically called from an `afterCapture` callback to stop walking after the current capture.
646
661
  - `atTokenEnd() => boolean`: Returns `true` if the token position is at or past the end of the token collection.
647
- - `hasNextToken(predicate?: (token: RGXToken) => boolean) => boolean`: Returns `true` if there is a next token and it satisfies the optional predicate (defaults to `() => true`).
648
- - `atSourceEnd() => boolean`: Returns `true` if the source position is at the last character of the source string.
649
- - `hasNextSource(predicate?: (rest: string) => boolean) => boolean`: Returns `true` if the source position is not at the end and the remaining source satisfies the optional predicate (defaults to `() => true`).
650
- - `hasCapturedStrings(minCount?: number) => boolean`: Returns `true` if `capturedStrings` has at least `minCount` entries (defaults to `1`).
651
- - `getLastCapturedString() => string | null`: Returns the last entry in `capturedStrings`, or `null` if empty.
652
- - `nextToken() => RGXToken | null`: Returns the token at the current token position, or `null` if at the end.
653
- - `remainingSource() => string | null`: Returns the remaining source string from the current position onward, or `null` if at the end.
654
- - `capture(token: RGXToken) => string`: Resolves the token to a regex, asserts that it matches at the current source position (throwing `RGXRegexNotMatchedAtPositionError` if not), captures the matched string (unless `nonCapture` is set), and advances the source position by the match length. Returns the matched string.
655
- - `step(flagReset?: boolean) => string | null`: Steps through the next token in the collection. If `flagReset` is `true` (the default), resets flags first. If the token is an `RGXPart`, triggers `"pre-capture"` before capturing and `"post-capture"` after (when not skipped or non-capturing). Advances the token position and returns the captured string, or `null` if there are no more tokens or the step was skipped.
656
- - `stepToToken(predicate: (token: RGXToken) => boolean) => void`: Steps through tokens until the predicate returns `true` for the next token or the walker is stopped. The matching token is not consumed.
657
- - `stepToPart(predicate?: (part: RGXPart<R>) => boolean) => void`: Steps through tokens until the next `RGXPart` satisfying the predicate is reached. If already at a part, steps once first to move past it. The matching part is not consumed.
658
- - `walk() => void`: Steps through all remaining tokens until the end of the token collection.
662
+ - `hasNextToken(predicate?: (token: RGXToken) => boolean) => boolean`: Returns `true` if there is a current token and it satisfies the optional predicate (defaults to `() => true`).
663
+ - `atSourceEnd() => boolean`: Returns `true` if the source has been fully consumed (`sourcePosition >= source.length`).
664
+ - `hasNextSource(predicate?: (rest: string) => boolean) => boolean`: Returns `true` if the source is not fully consumed and the remaining source satisfies the optional predicate (defaults to `() => true`).
665
+ - `lastCapture() => RGXCapture | null`: Returns the last entry in `captures`, or `null` if empty.
666
+ - `currentToken() => RGXToken | null`: Returns the token at the current token position, or `null` if at the end.
667
+ - `remainingSource() => string | null`: Returns the remaining source string from the current position onward, or `null` if the source is fully consumed.
668
+ - `capture(token: RGXToken) => string`: Resolves the token to a regex, asserts that it matches at the current source position (throwing `RGXRegexNotMatchedAtPositionError` if not), and advances the source position by the match length. Returns the matched string.
669
+ - `step() => RGXCapture | null`: Steps through the next token in the collection. If the token is an `RGXPart`, calls `beforeCapture` first — if it returns `"stop"`, sets `stopped` and returns `null` without advancing; if `"skip"`, advances the token position and returns `null` without capturing; if `"silent"`, captures but does not add to `captures`. After capturing, validates. After validating, calls `afterCapture` if present. Returns the `RGXCapture` result, or `null` if there are no more tokens, the step was skipped, or the walker was stopped.
670
+ - `stepToToken(predicate: (token: RGXToken) => boolean) => void`: Steps through tokens until the predicate returns `true` for the current token or the walker is stopped. The matching token is not consumed.
671
+ - `stepToPart(predicate?: (part: RGXPart<R>) => boolean) => void`: Steps through tokens until the next `RGXPart` satisfying the predicate is reached. If already at a Part, steps once first to move past it. The matching Part is not consumed.
672
+ - `walk() => void`: Steps through all remaining tokens until the end of the token collection or the walker is stopped.
659
673
  - `toRgx() => RGXToken`: Returns the internal `RGXTokenCollection`, allowing the walker to be used as a convertible token.
660
- - `clone(depth: CloneDepth = "max") => RGXWalker`: Creates a clone of the walker. When `depth` is `0`, returns `this`; otherwise, creates a new `RGXWalker` with cloned tokens, source position, reduced value, captured strings, and flags.
674
+ - `clone(depth: CloneDepth = "max") => RGXWalker`: Creates a clone of the walker. When `depth` is `0`, returns `this`; otherwise, creates a new `RGXWalker` with cloned tokens, source position, reduced value, captures, and stopped state.
661
675
 
662
676
  ## Functions
663
677
  The following functions are exported by the library:
@@ -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';
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';
2
2
  export declare class RGXError extends Error {
3
3
  _message: string;
4
4
  code: RGXErrorCode;
@@ -13,3 +13,4 @@ export * from './insertionRejected';
13
13
  export * from './constantConflict';
14
14
  export * from './invalidConstantKey';
15
15
  export * from './regexNotMatchedAtPosition';
16
+ export * from './partValidationFailed';
@@ -29,3 +29,4 @@ __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("./partValidationFailed"), exports);
@@ -0,0 +1,7 @@
1
+ import { RGXError } from "./base";
2
+ export declare class RGXPartValidationFailedError extends RGXError {
3
+ gotRaw: string;
4
+ gotTransformed: unknown;
5
+ constructor(message: string, gotRaw: string, gotTransformed: unknown);
6
+ calcMessage(message: string): string;
7
+ }
@@ -0,0 +1,16 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.RGXPartValidationFailedError = void 0;
4
+ const base_1 = require("./base");
5
+ class RGXPartValidationFailedError extends base_1.RGXError {
6
+ constructor(message, gotRaw, gotTransformed) {
7
+ super(message, 'PART_VALIDATION_FAILED');
8
+ this.name = 'RGXPartValidationFailedError';
9
+ this.gotRaw = gotRaw;
10
+ this.gotTransformed = gotTransformed;
11
+ }
12
+ calcMessage(message) {
13
+ return `${message}; Got: ${this.gotRaw} (transformed: ${JSON.stringify(this.gotTransformed)})`;
14
+ }
15
+ }
16
+ exports.RGXPartValidationFailedError = RGXPartValidationFailedError;
@@ -22,8 +22,9 @@ function doesRegexMatchAtPosition(regex, str, position) {
22
22
  return regexMatchAtPosition(regex, str, position) !== null;
23
23
  }
24
24
  function assertRegexMatchesAtPosition(regex, str, position, contextSize = 10) {
25
- if (!doesRegexMatchAtPosition(regex, str, position)) {
25
+ const result = regexMatchAtPosition(regex, str, position);
26
+ if (result === null) {
26
27
  throw new errors_1.RGXRegexNotMatchedAtPositionError("Regex not matched at index", regex, str, position, contextSize);
27
28
  }
28
- return regexMatchAtPosition(regex, str, position);
29
+ return result;
29
30
  }
@@ -1,45 +1,37 @@
1
1
  import { CloneDepth } from "@ptolemy2002/immutability-utils";
2
2
  import { RGXTokenCollection, RGXTokenCollectionInput } from "../collection";
3
3
  import { RGXConvertibleToken, RGXToken } from "../types";
4
- import { RGXPart } from "./part";
4
+ import { RGXPart, RGXCapture } from "./part";
5
5
  export type RGXWalkerOptions<R> = {
6
6
  startingSourcePosition?: number;
7
- reducedCurrent?: R;
8
- };
9
- export type RGXWalkerFlags = {
10
- stopped: boolean;
11
- skipped: boolean;
12
- nonCapture: boolean;
7
+ reduced?: R;
13
8
  };
14
9
  export declare class RGXWalker<R> implements RGXConvertibleToken {
15
10
  readonly source: string;
16
11
  _sourcePosition: number;
17
12
  readonly tokens: RGXTokenCollection;
18
13
  _tokenPosition: number;
19
- reducedCurrent: R;
20
- capturedStrings: string[];
21
- flags: RGXWalkerFlags;
14
+ reduced: R;
15
+ captures: RGXCapture[];
16
+ private _stopped;
22
17
  static check: (value: unknown) => value is RGXWalker<unknown>;
23
18
  static assert: (value: unknown) => asserts value is RGXWalker<unknown>;
24
19
  get sourcePosition(): number;
25
20
  set sourcePosition(value: number);
26
21
  get tokenPosition(): number;
27
22
  set tokenPosition(value: number);
23
+ get stopped(): boolean;
28
24
  constructor(source: string, tokens: RGXTokenCollectionInput, options?: RGXWalkerOptions<R>);
29
- resetFlags(): void;
30
25
  stop(): void;
31
- skip(): void;
32
- preventCapture(): void;
33
26
  atTokenEnd(): boolean;
34
27
  hasNextToken(predicate?: (token: RGXToken) => boolean): boolean;
35
28
  atSourceEnd(): boolean;
36
29
  hasNextSource(predicate?: (rest: string) => boolean): boolean;
37
- hasCapturedStrings(minCount?: number): boolean;
38
- getLastCapturedString(): string | null | undefined;
39
- nextToken(): RGXToken;
30
+ lastCapture(): RGXCapture | null;
31
+ currentToken(): RGXToken;
40
32
  remainingSource(): string | null;
41
33
  capture(token: RGXToken): string;
42
- step(flagReset?: boolean): string | null;
34
+ step(): RGXCapture | null;
43
35
  stepToToken(predicate: (token: RGXToken) => boolean): void;
44
36
  stepToPart(predicate?: (part: RGXPart<R>) => boolean): void;
45
37
  walk(): void;
@@ -13,7 +13,7 @@ class RGXWalker {
13
13
  return this._sourcePosition;
14
14
  }
15
15
  set sourcePosition(value) {
16
- (0, errors_1.assertInRange)(value, { min: 0, max: this.source.length, inclusiveRight: false }, "sourcePosition is outside the bounds of the source string");
16
+ (0, errors_1.assertInRange)(value, { min: 0, max: this.source.length }, "sourcePosition is outside the bounds of the source string");
17
17
  this._sourcePosition = value;
18
18
  }
19
19
  get tokenPosition() {
@@ -23,114 +23,119 @@ class RGXWalker {
23
23
  (0, errors_1.assertInRange)(value, { min: 0, max: this.tokens.length }, "tokenPosition is outside the bounds of the token collection");
24
24
  this._tokenPosition = value;
25
25
  }
26
+ get stopped() {
27
+ return this._stopped;
28
+ }
26
29
  constructor(source, tokens, options = {}) {
27
- this.capturedStrings = [];
28
- this.flags = {
29
- stopped: false,
30
- skipped: false,
31
- nonCapture: false
32
- };
30
+ this.captures = [];
31
+ this._stopped = false;
33
32
  this.source = source;
34
33
  this.sourcePosition = options.startingSourcePosition ?? 0;
35
34
  this.tokens = new collection_1.RGXTokenCollection(tokens, "concat");
36
35
  this.tokenPosition = 0;
37
- this.reducedCurrent = options.reducedCurrent ?? null;
38
- }
39
- resetFlags() {
40
- this.flags.stopped = false;
41
- this.flags.skipped = false;
42
- this.flags.nonCapture = false;
36
+ this.reduced = options.reduced ?? null;
43
37
  }
44
38
  stop() {
45
- this.flags.stopped = true;
46
- }
47
- skip() {
48
- this.flags.skipped = true;
49
- }
50
- preventCapture() {
51
- this.flags.nonCapture = true;
39
+ this._stopped = true;
52
40
  }
53
41
  atTokenEnd() {
54
42
  return this.tokenPosition >= this.tokens.length;
55
43
  }
56
44
  hasNextToken(predicate = () => true) {
57
- return !this.atTokenEnd() && predicate(this.nextToken());
45
+ return !this.atTokenEnd() && predicate(this.currentToken());
58
46
  }
59
47
  atSourceEnd() {
60
- return this.sourcePosition >= this.source.length - 1;
48
+ return this.sourcePosition >= this.source.length;
61
49
  }
62
50
  hasNextSource(predicate = () => true) {
63
51
  return !this.atSourceEnd() && predicate(this.remainingSource());
64
52
  }
65
- hasCapturedStrings(minCount = 1) {
66
- return this.capturedStrings.length >= minCount;
67
- }
68
- getLastCapturedString() {
69
- if (!this.hasCapturedStrings())
53
+ lastCapture() {
54
+ if (this.captures.length === 0)
70
55
  return null;
71
- return this.capturedStrings[this.capturedStrings.length - 1];
56
+ return this.captures[this.captures.length - 1];
72
57
  }
73
- nextToken() {
74
- if (this.tokenPosition >= this.tokens.length)
58
+ currentToken() {
59
+ if (this.atTokenEnd())
75
60
  return null;
76
61
  return this.tokens.at(this.tokenPosition);
77
62
  }
78
63
  remainingSource() {
79
- if (this.sourcePosition >= this.source.length - 1)
64
+ if (this.atSourceEnd())
80
65
  return null;
81
66
  return this.source.slice(this.sourcePosition);
82
67
  }
83
68
  capture(token) {
84
69
  const regex = (0, index_1.rgxa)([token]);
85
70
  const match = (0, index_1.assertRegexMatchesAtPosition)(regex, this.source, this.sourcePosition);
86
- if (!this.flags.nonCapture)
87
- this.capturedStrings.push(match);
88
- if (this.sourcePosition < this.source.length - match.length)
89
- this.sourcePosition += match.length;
90
- else
91
- this.sourcePosition = this.source.length - 1; // Move to the end of the source if the match goes beyond it
71
+ this.sourcePosition += match.length;
92
72
  return match;
93
73
  }
94
- step(flagReset = true) {
95
- if (flagReset)
96
- this.resetFlags();
97
- if (!this.hasNextToken())
74
+ step() {
75
+ if (this.atTokenEnd())
98
76
  return null;
99
- const token = this.nextToken();
100
- if (token instanceof part_1.RGXPart)
101
- token.triggerEvent("pre-capture", this);
102
- let captured = null;
103
- if (!this.flags.skipped) {
104
- captured = this.capture(token);
105
- if (token instanceof part_1.RGXPart && !this.flags.nonCapture)
106
- token.triggerEvent("post-capture", this);
77
+ const token = this.currentToken();
78
+ const isPart = token instanceof part_1.RGXPart;
79
+ let silent = false;
80
+ // Ask Part what to do — control flow via return values, not flags.
81
+ if (isPart) {
82
+ const control = token.beforeCapture?.(token, this);
83
+ if (control === "stop") {
84
+ this._stopped = true;
85
+ return null;
86
+ }
87
+ if (control === "skip") {
88
+ this.tokenPosition++;
89
+ return null;
90
+ }
91
+ if (control === "silent") {
92
+ silent = true;
93
+ }
94
+ }
95
+ // Capture the match
96
+ const raw = this.capture(token);
97
+ const value = isPart ? token.transform(raw) : raw;
98
+ const captureResult = { raw, value };
99
+ // Validate the part. If validation fails, it will throw an error, so nothing below will run.
100
+ if (isPart) {
101
+ token.validate(captureResult, this);
102
+ }
103
+ // Skip adding the capture if in silent mode.
104
+ if (!silent) {
105
+ this.captures.push(captureResult);
106
+ }
107
+ // Notify Part after capture
108
+ if (isPart) {
109
+ token.afterCapture?.(captureResult, token, this);
107
110
  }
108
111
  this.tokenPosition++;
109
- return captured;
112
+ return captureResult;
110
113
  }
111
114
  stepToToken(predicate) {
112
115
  while (this.hasNextToken()) {
113
- this.resetFlags();
114
- if (predicate(this.nextToken()))
116
+ this._stopped = false;
117
+ if (predicate(this.currentToken()))
115
118
  break;
116
- this.step(false);
117
- if (this.flags.stopped)
119
+ this.step();
120
+ if (this._stopped)
118
121
  break;
119
122
  }
120
123
  }
121
124
  stepToPart(predicate = () => true) {
122
- // If we're currently at a part, step once so that we can get to the next one.
123
- if (this.nextToken() instanceof part_1.RGXPart)
125
+ // If currently at a Part, step past it first so repeated
126
+ // calls advance to the next Part rather than getting stuck.
127
+ if (this.currentToken() instanceof part_1.RGXPart) {
128
+ this._stopped = false;
124
129
  this.step();
125
- if (this.flags.stopped)
126
- return;
130
+ if (this._stopped)
131
+ return;
132
+ }
127
133
  this.stepToToken(token => token instanceof part_1.RGXPart && predicate(token));
128
134
  }
129
135
  walk() {
130
136
  return this.stepToToken(() => false);
131
137
  }
132
- // When used as a convertible token, just treat the walker as
133
- // a collection.
138
+ // When used as a convertible token, treat the walker as its collection.
134
139
  toRgx() {
135
140
  return this.tokens;
136
141
  }
@@ -140,11 +145,11 @@ class RGXWalker {
140
145
  return this;
141
146
  const clone = new RGXWalker(this.source, this.tokens.clone((0, immutability_utils_1.depthDecrement)(1)), {
142
147
  startingSourcePosition: this.sourcePosition,
143
- reducedCurrent: (0, immutability_utils_1.extClone)(this.reducedCurrent, (0, immutability_utils_1.depthDecrement)(1))
148
+ reduced: (0, immutability_utils_1.extClone)(this.reduced, (0, immutability_utils_1.depthDecrement)(1))
144
149
  });
145
150
  clone._tokenPosition = this.tokenPosition;
146
- clone.capturedStrings = (0, immutability_utils_1.extClone)(this.capturedStrings, (0, immutability_utils_1.depthDecrement)(1));
147
- clone.flags = (0, immutability_utils_1.extClone)(this.flags, (0, immutability_utils_1.depthDecrement)(1));
151
+ clone.captures = (0, immutability_utils_1.extClone)(this.captures, (0, immutability_utils_1.depthDecrement)(1));
152
+ clone._stopped = this._stopped;
148
153
  return clone;
149
154
  }
150
155
  }
@@ -1,21 +1,27 @@
1
1
  import { RGXConvertibleToken, RGXToken } from "../types";
2
2
  import type { RGXWalker } from "./base";
3
3
  import { CloneDepth } from "@ptolemy2002/immutability-utils";
4
- export type RGXPartEventType = "pre-capture" | "post-capture";
4
+ export type RGXPartControl = "skip" | "stop" | "silent" | void;
5
+ export type RGXCapture<T = unknown> = {
6
+ raw: string;
7
+ value: T;
8
+ };
5
9
  export type RGXPartOptions<R, T = string> = {
6
10
  transform: (captured: string) => T;
7
- onEvent: ((part: RGXPart<R, T>, eventType: RGXPartEventType, walker: RGXWalker<R>) => void) | null;
11
+ validate: (captured: RGXCapture<T>, part: RGXPart<R, T>, walker: RGXWalker<R>) => boolean | string;
12
+ beforeCapture: ((part: RGXPart<R, T>, walker: RGXWalker<R>) => RGXPartControl) | null;
13
+ afterCapture: ((capture: RGXCapture<T>, part: RGXPart<R, T>, walker: RGXWalker<R>) => void) | null;
8
14
  };
9
15
  export declare class RGXPart<R, T = string> implements RGXConvertibleToken {
10
16
  token: RGXToken;
11
- capturedString: string | null;
12
- capturedValue: T | null;
13
17
  readonly transform: RGXPartOptions<R, T>["transform"];
14
- readonly onEvent: RGXPartOptions<R, T>["onEvent"];
18
+ private readonly _validate;
19
+ readonly beforeCapture: RGXPartOptions<R, T>["beforeCapture"];
20
+ readonly afterCapture: RGXPartOptions<R, T>["afterCapture"];
15
21
  static check: (value: unknown) => value is RGXPart<unknown, unknown>;
16
22
  static assert: (value: unknown) => asserts value is RGXPart<unknown, unknown>;
17
23
  constructor(token: RGXToken, options?: Partial<RGXPartOptions<R, T>>);
18
- triggerEvent(eventType: RGXPartEventType, walker: RGXWalker<R>): void;
24
+ validate(capture: RGXCapture<T>, walker: RGXWalker<R>): void;
19
25
  get rgxIsGroup(): boolean;
20
26
  get rgxIsRepeatable(): boolean;
21
27
  toRgx(): RGXToken;
@@ -6,23 +6,23 @@ const typeGuards_1 = require("../typeGuards");
6
6
  const internal_1 = require("../internal");
7
7
  const clone_1 = require("../clone");
8
8
  const immutability_utils_1 = require("@ptolemy2002/immutability-utils");
9
+ const errors_1 = require("../errors");
10
+ // A Part is purely a definition: a token + optional callbacks.
11
+ // It does NOT store capture state — that lives on the walker.
9
12
  class RGXPart {
10
13
  constructor(token, options = {}) {
11
- this.capturedString = null;
12
- this.capturedValue = null;
13
14
  this.token = token;
14
15
  this.transform = options.transform ?? ((captured) => captured);
15
- this.onEvent = options.onEvent ?? null;
16
+ this._validate = options.validate ?? (() => true);
17
+ this.beforeCapture = options.beforeCapture ?? null;
18
+ this.afterCapture = options.afterCapture ?? null;
16
19
  }
17
- triggerEvent(eventType, walker) {
18
- switch (eventType) {
19
- case "post-capture": {
20
- this.capturedString = walker.getLastCapturedString();
21
- this.capturedValue = this.transform(this.capturedString);
22
- break;
23
- }
24
- }
25
- this.onEvent?.(this, eventType, walker);
20
+ validate(capture, walker) {
21
+ const result = this._validate(capture, this, walker);
22
+ if (result === true)
23
+ return;
24
+ const message = typeof result === "string" ? result : "Part Validation Failed";
25
+ throw new errors_1.RGXPartValidationFailedError(message, capture.raw, capture.value);
26
26
  }
27
27
  // Properties used for conversion to an RGXToken
28
28
  get rgxIsGroup() {
@@ -41,7 +41,11 @@ class RGXPart {
41
41
  clone(depth = "max") {
42
42
  if (depth === 0)
43
43
  return this;
44
- return new RGXPart((0, clone_1.cloneRGXToken)(this.token, (0, immutability_utils_1.depthDecrement)(depth, 1)), { transform: this.transform, onEvent: this.onEvent });
44
+ return new RGXPart((0, clone_1.cloneRGXToken)(this.token, (0, immutability_utils_1.depthDecrement)(depth, 1)), {
45
+ transform: this.transform,
46
+ beforeCapture: this.beforeCapture,
47
+ afterCapture: this.afterCapture,
48
+ });
45
49
  }
46
50
  }
47
51
  exports.RGXPart = RGXPart;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ptolemy2002/rgx",
3
- "version": "5.6.0",
3
+ "version": "6.0.0",
4
4
  "private": false,
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",