@ptolemy2002/rgx 5.6.0 → 6.1.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,27 @@ 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;
98
+ reduced?: R;
91
99
  };
92
100
 
93
- type RGXWalkerFlags = {
94
- stopped: boolean;
95
- skipped: boolean;
96
- nonCapture: boolean;
101
+ type RGXWOptions<R = unknown> = Omit<RGXWalkerOptions<R>, "startingSourcePosition"> & {
102
+ multiline?: boolean;
97
103
  };
98
104
  ```
99
105
 
@@ -325,6 +331,21 @@ constructor(message: string, got: number, { min, max, inclusiveLeft, inclusiveRi
325
331
  - `failedAtMax() => boolean`: Returns `true` if the `got` value is above the maximum bound (respecting `inclusiveRight`), otherwise `false`. Returns `false` if `max` is `null`.
326
332
  - `failedAtAny() => boolean`: Returns `true` if the value failed at either the minimum or maximum bound.
327
333
 
334
+ ### RGXPartValidationFailedError extends RGXError
335
+ 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.
336
+
337
+ #### Constructor
338
+ ```typescript
339
+ constructor(message: string, gotRaw: string, gotTransformed: unknown)
340
+ ```
341
+ - `message` (`string`): The error message.
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
+
345
+ #### Properties
346
+ - `gotRaw` (`string`): The raw captured string value that failed validation.
347
+ - `gotTransformed` (`unknown`): The transformed value that was produced by the part's `transform` function, which also failed validation.
348
+
328
349
  ### RGXTokenCollection
329
350
  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
351
 
@@ -579,7 +600,7 @@ constructor(pattern: string | RegExp, flags?: string)
579
600
  - `[Symbol.species]` (`RegExpConstructor`): Returns `ExtRegExp`, ensuring that derived `RegExp` methods (like those returning new regex instances) produce `ExtRegExp` instances rather than plain `RegExp`.
580
601
 
581
602
  ### 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.
603
+ 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
604
 
584
605
  A function `rgxPart` is provided with the same parameters as this class' constructor, for easier instantiation without needing to use the `new` keyword.
585
606
 
@@ -594,24 +615,25 @@ constructor(token: RGXToken, options?: Partial<RGXPartOptions<R, T>>)
594
615
  - `token` (`RGXToken`): The token to wrap.
595
616
  - `options` (`Partial<RGXPartOptions<R, T>>`, optional): Configuration options. Defaults to `{}`.
596
617
  - `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`.
618
+ - `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`.
619
+ - `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`.
620
+ - `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
621
 
599
622
  #### Properties
600
623
  - `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`.
624
+ - `transform` (`(captured: string) => T`, readonly): The transform function used to convert captured strings to values of type `T`.
625
+ - `beforeCapture` (`((part: RGXPart<R, T>, walker: RGXWalker<R>) => RGXPartControl) | null`, readonly): The before-capture callback, or `null`.
626
+ - `afterCapture` (`((capture: RGXCapture<T>, part: RGXPart<R, T>, walker: RGXWalker<R>) => void) | null`, readonly): The after-capture callback, or `null`.
605
627
  - `rgxIsGroup` (`boolean`): Delegates to the wrapped token's group status via `isRGXGroupedToken`.
606
628
  - `rgxIsRepeatable` (`boolean`): If the wrapped token is an `RGXConvertibleToken`, delegates to its `rgxIsRepeatable` property (defaulting to `true` if not present). Otherwise, returns `true`.
607
629
 
608
630
  #### 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
631
  - `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.
632
+ - `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.
633
+ - `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
634
 
613
635
  ### 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).
636
+ 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
637
 
616
638
  A function `rgxWalker` is provided with the same parameters as this class' constructor, for easier instantiation without needing to use the `new` keyword.
617
639
 
@@ -627,37 +649,33 @@ constructor(source: string, tokens: RGXTokenCollectionInput, options?: RGXWalker
627
649
  - `tokens` (`RGXTokenCollectionInput`): The tokens to match sequentially. Internally stored as an `RGXTokenCollection` in 'concat' mode.
628
650
  - `options` (`RGXWalkerOptions<R>`, optional): Configuration options. Defaults to `{}`.
629
651
  - `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`.
652
+ - `reduced` (`R`, optional): The initial value for the `reduced` accumulator. Defaults to `null`.
631
653
 
632
654
  #### Properties
633
655
  - `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.
656
+ - `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
657
  - `tokens` (`RGXTokenCollection`): The internal collection of tokens in 'concat' mode (readonly).
636
658
  - `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`.
659
+ - `reduced` (`R`): A user-defined accumulator value, typically updated by `RGXPart` callbacks during walking.
660
+ - `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).
661
+ - `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
662
 
641
663
  #### 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`.
664
+ - `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
665
  - `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.
666
+ - `hasNextToken(predicate?: (token: RGXToken) => boolean) => boolean`: Returns `true` if there is a current token and it satisfies the optional predicate (defaults to `() => true`).
667
+ - `atSourceEnd() => boolean`: Returns `true` if the source has been fully consumed (`sourcePosition >= source.length`).
668
+ - `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`).
669
+ - `lastCapture() => RGXCapture | null`: Returns the last entry in `captures`, or `null` if empty.
670
+ - `currentToken() => RGXToken | null`: Returns the token at the current token position, or `null` if at the end.
671
+ - `remainingSource() => string | null`: Returns the remaining source string from the current position onward, or `null` if the source is fully consumed.
672
+ - `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.
673
+ - `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.
674
+ - `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.
675
+ - `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.
676
+ - `walk() => void`: Steps through all remaining tokens until the end of the token collection or the walker is stopped.
659
677
  - `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.
678
+ - `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
679
 
662
680
  ## Functions
663
681
  The following functions are exported by the library:
@@ -1137,6 +1155,37 @@ As an alternative to using the `rgx` template tag, you can directly call `rgxa`
1137
1155
  #### Returns
1138
1156
  - `ExtRegExp`: An `ExtRegExp` object constructed from the resolved tokens and the provided flags.
1139
1157
 
1158
+ ### rgxw
1159
+ ```typescript
1160
+ function rgxw<R = unknown>(source: string, {multiline=true, ...options}: RGXWOptions<R> = {}): (strings: TemplateStringsArray, ...tokens: RGXToken[]
1161
+ ) => RGXWalker<R>
1162
+ ```
1163
+ A helper function that creates an `RGXWalker` instance from an interpolation of strings and tokens. The token array is processed exactly like in `rgx`, but instead of returning an `ExtRegExp`, it returns an `RGXWalker` that can be used to walk through matches of the regex pattern in the source string.
1164
+
1165
+ #### Parameters
1166
+ - `source` (`string`): An arbitrary string value that will be included in the `source` property of the walker object.
1167
+ - `options` (`RGXWOptions<R>`, optional): Additional options for configuring the behavior of the resulting `RGXWalker`. This includes:
1168
+ - `multiline` (`boolean`, optional): Whether to strip newlines and trim leading whitespace from the literal string parts of the template. Defaults to `true`. When `true`, each literal string part is split by newlines, each line has its leading whitespace trimmed, empty lines are removed, and the remaining lines are joined together. Interpolated tokens (including string tokens via `${"..."}`) are not affected. When `false`, literal string parts are preserved exactly as written.
1169
+ - `reduced` (`R`, optional): An optional initial value for the walker's `reduced` property, which can be used to accumulate results across matches.
1170
+
1171
+ #### Returns
1172
+ - `(strings: TemplateStringsArray, ...tokens: RGXToken[]) => RGXWalker<R>`: A template tag function that takes a template literal and returns an `RGXWalker` instance configured with the provided source, the provided tokens, and the specified options.
1173
+
1174
+ ### rgxwa
1175
+ ```typescript
1176
+ function rgxwa<R = unknown>(source: string, tokens: RGXToken[], options: Omit<RGXWOptions<R>, "multiline"> = {}): RGXWalker<R>
1177
+ ```
1178
+ As an alternative to using the `rgxw` template tag, you can directly call `rgxwa` with a source string, an array of RGX tokens, and options to get an `RGXWalker` instance. This is useful in cases where you don't want to use a template literal. The token array is processed exactly like in `rgxa`, and the provided options are passed through to configure the resulting walker.
1179
+
1180
+ #### Parameters
1181
+ - `source` (`string`): An arbitrary string value that will be included in the `source` property of the walker object.
1182
+ - `tokens` (`RGXToken[]`): The RGX tokens to be resolved and concatenated to form the regex pattern for the walker.
1183
+ - `options` (`Omit<RGXWOptions<R>, "multiline">`, optional): Additional options for configuring the behavior of the resulting `RGXWalker`, excluding the `multiline` option which is not applicable when not using a template literal. This includes:
1184
+ - `reduced` (`R`, optional): An optional initial value for the walker's `reduced` property, which can be used to accumulate results across matches.
1185
+
1186
+ #### Returns
1187
+ - `RGXWalker<R>`: An `RGXWalker` instance configured with the provided source, the resolved tokens, and the specified options.
1188
+
1140
1189
  ### expandRgxUnionTokens
1141
1190
  ```typescript
1142
1191
  function expandRgxUnionTokens(...tokens: RGXTokenCollectionInput[]): RGXTokenCollection
@@ -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;
package/dist/index.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import * as t from "./types";
2
2
  import { ExtRegExp } from "./ExtRegExp";
3
+ import { RGXWalker } from "./walker";
3
4
  export * from "./errors";
4
5
  export * from "./types";
5
6
  export * from "./typeGuards";
@@ -15,3 +16,5 @@ export * from "./constants";
15
16
  export * from "./walker";
16
17
  export declare function rgxa(tokens: t.RGXToken[], flags?: string): ExtRegExp;
17
18
  export default function rgx(flags?: string, multiline?: boolean): (strings: TemplateStringsArray, ...tokens: t.RGXToken[]) => ExtRegExp;
19
+ export declare function rgxwa<R = unknown>(source: string, tokens: t.RGXToken[], options?: Omit<t.RGXWOptions<R>, "multiline">): RGXWalker<R>;
20
+ export declare function rgxw<R = unknown>(source: string, { multiline, ...options }?: t.RGXWOptions<R>): (strings: TemplateStringsArray, ...tokens: t.RGXToken[]) => RGXWalker<R>;
package/dist/index.js CHANGED
@@ -16,11 +16,14 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
17
  exports.rgxa = rgxa;
18
18
  exports.default = rgx;
19
+ exports.rgxwa = rgxwa;
20
+ exports.rgxw = rgxw;
19
21
  const class_1 = require("./class");
20
22
  const flag_transformer_1 = require("./flag-transformer");
21
23
  const concat_1 = require("./concat");
22
24
  const internal_1 = require("./internal");
23
25
  const ExtRegExp_1 = require("./ExtRegExp");
26
+ const walker_1 = require("./walker");
24
27
  __exportStar(require("./errors"), exports);
25
28
  __exportStar(require("./types"), exports);
26
29
  __exportStar(require("./typeGuards"), exports);
@@ -50,3 +53,11 @@ function rgx(flags = '', multiline = true) {
50
53
  return rgxa((0, internal_1.taggedTemplateToArray)(strings, tokens, multiline), flags);
51
54
  };
52
55
  }
56
+ function rgxwa(source, tokens, options = {}) {
57
+ return new walker_1.RGXWalker(source, tokens, options);
58
+ }
59
+ function rgxw(source, { multiline = true, ...options } = {}) {
60
+ return (strings, ...tokens) => {
61
+ return rgxwa(source, (0, internal_1.taggedTemplateToArray)(strings, tokens, multiline), options);
62
+ };
63
+ }
package/dist/types.d.ts CHANGED
@@ -2,6 +2,7 @@ import { Branded } from "@ptolemy2002/ts-brand-utils";
2
2
  import type { RGXClassToken } from "./class";
3
3
  import type { ExtRegExp } from "./ExtRegExp";
4
4
  import type { RGXTokenCollection } from "./collection";
5
+ import type { RGXWalkerOptions } from "./walker";
5
6
  export type RGXNoOpToken = null | undefined;
6
7
  export type RGXLiteralToken = RegExp;
7
8
  export type RGXNativeToken = string | number | boolean | RGXNoOpToken;
@@ -45,3 +46,6 @@ export type ValidRegexFlags = Branded<string, [ValidRegexFlagsBrandSymbol]> | Va
45
46
  export declare const validIdentifierSymbol: unique symbol;
46
47
  export type ValidIdentifierBrandSymbol = typeof validIdentifierSymbol;
47
48
  export type ValidIdentifier = Branded<string, [ValidIdentifierBrandSymbol]>;
49
+ export type RGXWOptions<R = unknown> = Omit<RGXWalkerOptions<R>, "startingSourcePosition"> & {
50
+ multiline?: boolean;
51
+ };
@@ -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.1.0",
4
4
  "private": false,
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",