@ptolemy2002/rgx 5.5.0 → 5.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -54,7 +54,7 @@ type RGXTokenFromType<T extends RGXTokenTypeGuardInput> =
54
54
  // ... see source for full definition
55
55
  ;
56
56
 
57
- 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';
57
+ 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';
58
58
 
59
59
  type RangeObject = {
60
60
  min?: number | null;
@@ -78,6 +78,23 @@ type RGXGroupTokenArgs = {
78
78
  name?: string | null;
79
79
  capturing?: boolean;
80
80
  };
81
+
82
+ type RGXPartEventType = "pre-capture" | "post-capture";
83
+ type RGXPartOptions<R, T=string> = {
84
+ transform: (captured: string) => T;
85
+ onEvent: ((part: RGXPart<R, T>, eventType: RGXPartEventType, walker: RGXWalker<R>) => void) | null;
86
+ };
87
+
88
+ type RGXWalkerOptions<R> = {
89
+ startingSourcePosition?: number;
90
+ reducedCurrent?: R;
91
+ };
92
+
93
+ type RGXWalkerFlags = {
94
+ stopped: boolean;
95
+ skipped: boolean;
96
+ nonCapture: boolean;
97
+ };
81
98
  ```
82
99
 
83
100
  ## Classes
@@ -257,6 +274,31 @@ constructor(reason?: string | null, message?: string | null)
257
274
  #### Properties
258
275
  - `reason` (`string | null`): The reason the insertion was rejected, or `null` if no reason was provided.
259
276
 
277
+ ### RGXRegexNotMatchedAtPositionError extends RGXError
278
+ A specific error class for regex match failures at a given position. This error is thrown when a regex is expected to match at a specific position in a string but does not (e.g., via `assertRegexMatchesAtPosition`). The error code is set to `REGEX_NOT_MATCHED_AT_POSITION` on instantiation.
279
+
280
+ #### Constructor
281
+ ```typescript
282
+ constructor(message: string, pattern: RegExp, source: string, position: number, contextSize?: number | null)
283
+ ```
284
+ - `message` (`string`): The error message.
285
+ - `pattern` (`RegExp`): The regex pattern that failed to match.
286
+ - `source` (`string`): The string that was being matched against.
287
+ - `position` (`number`): The zero-based index in the source string where the match was expected. Must be >= 0 and < `source.length`, or an `RGXOutOfBoundsError` will be thrown.
288
+ - `contextSize` (`number | null`, optional): The number of characters on each side of the position to include in contextual output. Defaults to `null` (full source shown).
289
+
290
+ #### Properties
291
+ - `pattern` (`RegExp`): The regex pattern that failed to match.
292
+ - `source` (`string`): The string that was being matched against.
293
+ - `position` (`number`): The position where the match was expected. Setting this validates that the value is >= 0 and < `source.length`, throwing `RGXOutOfBoundsError` if not.
294
+ - `contextSize` (`number | null`): The number of characters on each side of the position to include in contextual output, or `null` for the full source.
295
+
296
+ #### Methods
297
+ - `sourceContext() => string`: Returns the relevant portion of the source string around the position. When `contextSize` is `null` or covers the entire string, returns the full source. Otherwise, returns a substring from `max(0, position - contextSize)` to `min(source.length, position + contextSize)`.
298
+ - `hasLeftContext() => boolean`: Returns `true` if the context window starts after the beginning of the source string (i.e., there is truncated content on the left). Returns `false` when `contextSize` is `null`.
299
+ - `hasRightContext() => boolean`: Returns `true` if the context window ends before the end of the source string (i.e., there is truncated content on the right). Returns `false` when `contextSize` is `null`.
300
+ - `hasFullContext() => boolean`: Returns `true` when the full source is shown (neither side is truncated). This is the case when `contextSize` is `null` or when the context window covers the entire source string.
301
+
260
302
  ### RGXOutOfBoundsError extends RGXError
261
303
  A specific error class for out-of-bounds values. This error is thrown when a numeric value falls outside an expected range. The error code is set to `OUT_OF_BOUNDS` on instantiation.
262
304
 
@@ -536,6 +578,87 @@ constructor(pattern: string | RegExp, flags?: string)
536
578
  #### Static Properties
537
579
  - `[Symbol.species]` (`RegExpConstructor`): Returns `ExtRegExp`, ensuring that derived `RegExp` methods (like those returning new regex instances) produce `ExtRegExp` instances rather than plain `RegExp`.
538
580
 
581
+ ### 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.
583
+
584
+ A function `rgxPart` is provided with the same parameters as this class' constructor, for easier instantiation without needing to use the `new` keyword.
585
+
586
+ #### Static Properties
587
+ - `check(value: unknown): value is RGXPart`: A type guard that checks if the given value is an instance of `RGXPart`.
588
+ - `assert(value: unknown): asserts value is RGXPart`: An assertion that checks if the given value is an instance of `RGXPart`. If the assertion fails, an `RGXInvalidTokenError` will be thrown.
589
+
590
+ #### Constructor
591
+ ```typescript
592
+ constructor(token: RGXToken, options?: Partial<RGXPartOptions<R, T>>)
593
+ ```
594
+ - `token` (`RGXToken`): The token to wrap.
595
+ - `options` (`Partial<RGXPartOptions<R, T>>`, optional): Configuration options. Defaults to `{}`.
596
+ - `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`.
598
+
599
+ #### Properties
600
+ - `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`.
605
+ - `rgxIsGroup` (`boolean`): Delegates to the wrapped token's group status via `isRGXGroupedToken`.
606
+ - `rgxIsRepeatable` (`boolean`): If the wrapped token is an `RGXConvertibleToken`, delegates to its `rgxIsRepeatable` property (defaulting to `true` if not present). Otherwise, returns `true`.
607
+
608
+ #### 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
+ - `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.
612
+
613
+ ### 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).
615
+
616
+ A function `rgxWalker` is provided with the same parameters as this class' constructor, for easier instantiation without needing to use the `new` keyword.
617
+
618
+ #### Static Properties
619
+ - `check(value: unknown): value is RGXWalker`: A type guard that checks if the given value is an instance of `RGXWalker`.
620
+ - `assert(value: unknown): asserts value is RGXWalker`: An assertion that checks if the given value is an instance of `RGXWalker`. If the assertion fails, an `RGXInvalidTokenError` will be thrown.
621
+
622
+ #### Constructor
623
+ ```typescript
624
+ constructor(source: string, tokens: RGXTokenCollectionInput, options?: RGXWalkerOptions<R>)
625
+ ```
626
+ - `source` (`string`): The string to walk through, matching tokens against.
627
+ - `tokens` (`RGXTokenCollectionInput`): The tokens to match sequentially. Internally stored as an `RGXTokenCollection` in 'concat' mode.
628
+ - `options` (`RGXWalkerOptions<R>`, optional): Configuration options. Defaults to `{}`.
629
+ - `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`.
631
+
632
+ #### Properties
633
+ - `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.
635
+ - `tokens` (`RGXTokenCollection`): The internal collection of tokens in 'concat' mode (readonly).
636
+ - `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`.
640
+
641
+ #### 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`.
646
+ - `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.
659
+ - `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.
661
+
539
662
  ## Functions
540
663
  The following functions are exported by the library:
541
664
 
@@ -1287,10 +1410,25 @@ Creates a new `ExtRegExp` from an existing one with additional or replaced flags
1287
1410
 
1288
1411
  ### regexMatchAtPosition
1289
1412
  ```typescript
1290
- function regexMatchAtPosition(regex: RegExp, str: string, position: number): boolean
1413
+ function regexMatchAtPosition(regex: RegExp, str: string, position: number): string | null
1291
1414
  ```
1292
1415
 
1293
- Tests whether the given regular expression matches at a specific position in the string. This is done by creating a sticky (`y` flag) copy of the regex and setting its `lastIndex` to the desired position. The position must be within the bounds of the string (>= 0 and < string length), or an `RGXOutOfBoundsError` will be thrown.
1416
+ Attempts to match the given regular expression at a specific position in the string and returns the matched string, or `null` if there is no match. This is done by creating a sticky (`y` flag) copy of the regex and setting its `lastIndex` to the desired position. The position must be within the bounds of the string (>= 0 and < string length), or an `RGXOutOfBoundsError` will be thrown.
1417
+
1418
+ #### Parameters
1419
+ - `regex` (`RegExp`): The regular expression to match.
1420
+ - `str` (`string`): The string to match against.
1421
+ - `position` (`number`): The zero-based index in the string at which to attempt the match. Must be >= 0 and < `str.length`.
1422
+
1423
+ #### Returns
1424
+ - `string | null`: The matched string if the regex matches at the specified position, otherwise `null`.
1425
+
1426
+ ### doesRegexMatchAtPosition
1427
+ ```typescript
1428
+ function doesRegexMatchAtPosition(regex: RegExp, str: string, position: number): boolean
1429
+ ```
1430
+
1431
+ Tests whether the given regular expression matches at a specific position in the string. This is a boolean wrapper around `regexMatchAtPosition`, returning `true` if the match is non-null.
1294
1432
 
1295
1433
  #### Parameters
1296
1434
  - `regex` (`RegExp`): The regular expression to test.
@@ -1300,6 +1438,22 @@ Tests whether the given regular expression matches at a specific position in the
1300
1438
  #### Returns
1301
1439
  - `boolean`: `true` if the regex matches at the specified position, otherwise `false`.
1302
1440
 
1441
+ ### assertRegexMatchesAtPosition
1442
+ ```typescript
1443
+ function assertRegexMatchesAtPosition(regex: RegExp, str: string, position: number, contextSize?: number | null): string
1444
+ ```
1445
+
1446
+ Asserts that the given regular expression matches at a specific position in the string, throwing an `RGXRegexNotMatchedAtPositionError` if it does not. On success, returns the matched string.
1447
+
1448
+ #### Parameters
1449
+ - `regex` (`RegExp`): The regular expression to match.
1450
+ - `str` (`string`): The string to match against.
1451
+ - `position` (`number`): The zero-based index in the string at which to assert the match. Must be >= 0 and < `str.length`.
1452
+ - `contextSize` (`number | null`, optional): The number of characters on each side of the position to include in the error's context output. Defaults to `10`.
1453
+
1454
+ #### Returns
1455
+ - `string`: The matched string if the regex matches at the specified position. Throws `RGXRegexNotMatchedAtPositionError` if there is no match.
1456
+
1303
1457
  ### cloneRGXToken
1304
1458
  ```typescript
1305
1459
  function cloneRGXTokeN<T extends RGXToken>(token: T, depth: CloneDepth="max"): T
@@ -1,9 +1,9 @@
1
- import { RGXToken, ValidRegexString } from "./types";
1
+ import { RGXConvertibleToken, RGXToken, ValidRegexString } from "./types";
2
2
  import { CloneDepth } from "@ptolemy2002/immutability-utils";
3
3
  import { Collection } from "@ptolemy2002/ts-utils";
4
4
  export type RGXTokenCollectionMode = 'union' | 'concat';
5
5
  export type RGXTokenCollectionInput = RGXToken | RGXTokenCollection;
6
- export declare class RGXTokenCollection implements Collection<RGXToken> {
6
+ export declare class RGXTokenCollection implements Collection<RGXToken>, RGXConvertibleToken {
7
7
  mode: RGXTokenCollectionMode;
8
8
  tokens: RGXToken[];
9
9
  static check: (value: unknown) => value is 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';
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';
2
2
  export declare class RGXError extends Error {
3
3
  _message: string;
4
4
  code: RGXErrorCode;
@@ -12,3 +12,4 @@ export * from './notSupported';
12
12
  export * from './insertionRejected';
13
13
  export * from './constantConflict';
14
14
  export * from './invalidConstantKey';
15
+ export * from './regexNotMatchedAtPosition';
@@ -28,3 +28,4 @@ __exportStar(require("./notSupported"), exports);
28
28
  __exportStar(require("./insertionRejected"), exports);
29
29
  __exportStar(require("./constantConflict"), exports);
30
30
  __exportStar(require("./invalidConstantKey"), exports);
31
+ __exportStar(require("./regexNotMatchedAtPosition"), exports);
@@ -0,0 +1,15 @@
1
+ import { RGXError } from "./base";
2
+ export declare class RGXRegexNotMatchedAtPositionError 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.RGXRegexNotMatchedAtPositionError = void 0;
4
+ const base_1 = require("./base");
5
+ const outOfBounds_1 = require("./outOfBounds");
6
+ class RGXRegexNotMatchedAtPositionError extends base_1.RGXError {
7
+ set position(value) {
8
+ (0, outOfBounds_1.assertInRange)(value, { min: 0, max: this.source.length, inclusiveRight: false }, "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_AT_POSITION');
16
+ this.name = 'RGXRegexNotMatchedAtPositionError';
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.RGXRegexNotMatchedAtPositionError = RGXRegexNotMatchedAtPositionError;
package/dist/index.d.ts CHANGED
@@ -12,5 +12,6 @@ export * from "./ExtRegExp";
12
12
  export * from "./flag-transformer";
13
13
  export * from "./clone";
14
14
  export * from "./constants";
15
+ export * from "./walker";
15
16
  export declare function rgxa(tokens: t.RGXToken[], flags?: string): ExtRegExp;
16
17
  export default function rgx(flags?: string, multiline?: boolean): (strings: TemplateStringsArray, ...tokens: t.RGXToken[]) => ExtRegExp;
package/dist/index.js CHANGED
@@ -33,6 +33,7 @@ __exportStar(require("./ExtRegExp"), exports);
33
33
  __exportStar(require("./flag-transformer"), exports);
34
34
  __exportStar(require("./clone"), exports);
35
35
  __exportStar(require("./constants"), exports);
36
+ __exportStar(require("./walker"), exports);
36
37
  // Call this for certain class methods to work correctly
37
38
  (0, class_1.rgxClassInit)();
38
39
  // Call this for our custom flags to work correctly
@@ -1 +1,3 @@
1
- export declare function regexMatchAtPosition(regex: RegExp, str: string, position: number): boolean;
1
+ export declare function regexMatchAtPosition(regex: RegExp, str: string, position: number): string | null;
2
+ export declare function doesRegexMatchAtPosition(regex: RegExp, str: string, position: number): boolean;
3
+ export declare function assertRegexMatchesAtPosition(regex: RegExp, str: string, position: number, contextSize?: number | null): string;
@@ -1,8 +1,11 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.regexMatchAtPosition = regexMatchAtPosition;
4
+ exports.doesRegexMatchAtPosition = doesRegexMatchAtPosition;
5
+ exports.assertRegexMatchesAtPosition = assertRegexMatchesAtPosition;
4
6
  const outOfBounds_1 = require("../errors/outOfBounds");
5
7
  const regexWithFlags_1 = require("./regexWithFlags");
8
+ const errors_1 = require("../errors");
6
9
  function regexMatchAtPosition(regex, str, position) {
7
10
  /*
8
11
  The y flag means sticky mode, which means the next match must start at
@@ -12,5 +15,15 @@ function regexMatchAtPosition(regex, str, position) {
12
15
  (0, outOfBounds_1.assertInRange)(position, { min: 0, max: str.length, inclusiveRight: false }, 'String index out of bounds');
13
16
  const stickyRegex = (0, regexWithFlags_1.regexWithFlags)(regex, "y");
14
17
  stickyRegex.lastIndex = position;
15
- return stickyRegex.test(str);
18
+ const match = stickyRegex.exec(str);
19
+ return match ? match[0] : null;
20
+ }
21
+ function doesRegexMatchAtPosition(regex, str, position) {
22
+ return regexMatchAtPosition(regex, str, position) !== null;
23
+ }
24
+ function assertRegexMatchesAtPosition(regex, str, position, contextSize = 10) {
25
+ if (!doesRegexMatchAtPosition(regex, str, position)) {
26
+ throw new errors_1.RGXRegexNotMatchedAtPositionError("Regex not matched at index", regex, str, position, contextSize);
27
+ }
28
+ return regexMatchAtPosition(regex, str, position);
16
29
  }
@@ -0,0 +1,49 @@
1
+ import { CloneDepth } from "@ptolemy2002/immutability-utils";
2
+ import { RGXTokenCollection, RGXTokenCollectionInput } from "../collection";
3
+ import { RGXConvertibleToken, RGXToken } from "../types";
4
+ import { RGXPart } from "./part";
5
+ export type RGXWalkerOptions<R> = {
6
+ startingSourcePosition?: number;
7
+ reducedCurrent?: R;
8
+ };
9
+ export type RGXWalkerFlags = {
10
+ stopped: boolean;
11
+ skipped: boolean;
12
+ nonCapture: boolean;
13
+ };
14
+ export declare class RGXWalker<R> implements RGXConvertibleToken {
15
+ readonly source: string;
16
+ _sourcePosition: number;
17
+ readonly tokens: RGXTokenCollection;
18
+ _tokenPosition: number;
19
+ reducedCurrent: R;
20
+ capturedStrings: string[];
21
+ flags: RGXWalkerFlags;
22
+ static check: (value: unknown) => value is RGXWalker<unknown>;
23
+ static assert: (value: unknown) => asserts value is RGXWalker<unknown>;
24
+ get sourcePosition(): number;
25
+ set sourcePosition(value: number);
26
+ get tokenPosition(): number;
27
+ set tokenPosition(value: number);
28
+ constructor(source: string, tokens: RGXTokenCollectionInput, options?: RGXWalkerOptions<R>);
29
+ resetFlags(): void;
30
+ stop(): void;
31
+ skip(): void;
32
+ preventCapture(): void;
33
+ atTokenEnd(): boolean;
34
+ hasNextToken(predicate?: (token: RGXToken) => boolean): boolean;
35
+ atSourceEnd(): boolean;
36
+ hasNextSource(predicate?: (rest: string) => boolean): boolean;
37
+ hasCapturedStrings(minCount?: number): boolean;
38
+ getLastCapturedString(): string | null | undefined;
39
+ nextToken(): RGXToken;
40
+ remainingSource(): string | null;
41
+ capture(token: RGXToken): string;
42
+ step(flagReset?: boolean): string | null;
43
+ stepToToken(predicate: (token: RGXToken) => boolean): void;
44
+ stepToPart(predicate?: (part: RGXPart<R>) => boolean): void;
45
+ walk(): void;
46
+ toRgx(): RGXTokenCollection;
47
+ clone(depth?: CloneDepth): RGXWalker<R>;
48
+ }
49
+ export declare function rgxWalker<R>(...args: ConstructorParameters<typeof RGXWalker<R>>): RGXWalker<R>;
@@ -0,0 +1,156 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.RGXWalker = void 0;
4
+ exports.rgxWalker = rgxWalker;
5
+ const immutability_utils_1 = require("@ptolemy2002/immutability-utils");
6
+ const collection_1 = require("../collection");
7
+ const errors_1 = require("../errors");
8
+ const part_1 = require("./part");
9
+ const index_1 = require("../index");
10
+ const internal_1 = require("../internal");
11
+ class RGXWalker {
12
+ get sourcePosition() {
13
+ return this._sourcePosition;
14
+ }
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");
17
+ this._sourcePosition = value;
18
+ }
19
+ get tokenPosition() {
20
+ return this._tokenPosition;
21
+ }
22
+ set tokenPosition(value) {
23
+ (0, errors_1.assertInRange)(value, { min: 0, max: this.tokens.length }, "tokenPosition is outside the bounds of the token collection");
24
+ this._tokenPosition = value;
25
+ }
26
+ constructor(source, tokens, options = {}) {
27
+ this.capturedStrings = [];
28
+ this.flags = {
29
+ stopped: false,
30
+ skipped: false,
31
+ nonCapture: false
32
+ };
33
+ this.source = source;
34
+ this.sourcePosition = options.startingSourcePosition ?? 0;
35
+ this.tokens = new collection_1.RGXTokenCollection(tokens, "concat");
36
+ 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;
43
+ }
44
+ stop() {
45
+ this.flags.stopped = true;
46
+ }
47
+ skip() {
48
+ this.flags.skipped = true;
49
+ }
50
+ preventCapture() {
51
+ this.flags.nonCapture = true;
52
+ }
53
+ atTokenEnd() {
54
+ return this.tokenPosition >= this.tokens.length;
55
+ }
56
+ hasNextToken(predicate = () => true) {
57
+ return !this.atTokenEnd() && predicate(this.nextToken());
58
+ }
59
+ atSourceEnd() {
60
+ return this.sourcePosition >= this.source.length - 1;
61
+ }
62
+ hasNextSource(predicate = () => true) {
63
+ return !this.atSourceEnd() && predicate(this.remainingSource());
64
+ }
65
+ hasCapturedStrings(minCount = 1) {
66
+ return this.capturedStrings.length >= minCount;
67
+ }
68
+ getLastCapturedString() {
69
+ if (!this.hasCapturedStrings())
70
+ return null;
71
+ return this.capturedStrings[this.capturedStrings.length - 1];
72
+ }
73
+ nextToken() {
74
+ if (this.tokenPosition >= this.tokens.length)
75
+ return null;
76
+ return this.tokens.at(this.tokenPosition);
77
+ }
78
+ remainingSource() {
79
+ if (this.sourcePosition >= this.source.length - 1)
80
+ return null;
81
+ return this.source.slice(this.sourcePosition);
82
+ }
83
+ capture(token) {
84
+ const regex = (0, index_1.rgxa)([token]);
85
+ 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
92
+ return match;
93
+ }
94
+ step(flagReset = true) {
95
+ if (flagReset)
96
+ this.resetFlags();
97
+ if (!this.hasNextToken())
98
+ 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);
107
+ }
108
+ this.tokenPosition++;
109
+ return captured;
110
+ }
111
+ stepToToken(predicate) {
112
+ while (this.hasNextToken()) {
113
+ this.resetFlags();
114
+ if (predicate(this.nextToken()))
115
+ break;
116
+ this.step(false);
117
+ if (this.flags.stopped)
118
+ break;
119
+ }
120
+ }
121
+ 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)
124
+ this.step();
125
+ if (this.flags.stopped)
126
+ return;
127
+ this.stepToToken(token => token instanceof part_1.RGXPart && predicate(token));
128
+ }
129
+ walk() {
130
+ return this.stepToToken(() => false);
131
+ }
132
+ // When used as a convertible token, just treat the walker as
133
+ // a collection.
134
+ toRgx() {
135
+ return this.tokens;
136
+ }
137
+ // Clone method
138
+ clone(depth = "max") {
139
+ if (depth === 0)
140
+ return this;
141
+ const clone = new RGXWalker(this.source, this.tokens.clone((0, immutability_utils_1.depthDecrement)(1)), {
142
+ startingSourcePosition: this.sourcePosition,
143
+ reducedCurrent: (0, immutability_utils_1.extClone)(this.reducedCurrent, (0, immutability_utils_1.depthDecrement)(1))
144
+ });
145
+ 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));
148
+ return clone;
149
+ }
150
+ }
151
+ exports.RGXWalker = RGXWalker;
152
+ RGXWalker.check = (0, internal_1.createClassGuardFunction)(RGXWalker);
153
+ RGXWalker.assert = (0, internal_1.createAssertClassGuardFunction)(RGXWalker);
154
+ function rgxWalker(...args) {
155
+ return new RGXWalker(...args);
156
+ }
@@ -0,0 +1,2 @@
1
+ export * from "./base";
2
+ export * from "./part";
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./base"), exports);
18
+ __exportStar(require("./part"), exports);
@@ -0,0 +1,24 @@
1
+ import { RGXConvertibleToken, RGXToken } from "../types";
2
+ import type { RGXWalker } from "./base";
3
+ import { CloneDepth } from "@ptolemy2002/immutability-utils";
4
+ export type RGXPartEventType = "pre-capture" | "post-capture";
5
+ export type RGXPartOptions<R, T = string> = {
6
+ transform: (captured: string) => T;
7
+ onEvent: ((part: RGXPart<R, T>, eventType: RGXPartEventType, walker: RGXWalker<R>) => void) | null;
8
+ };
9
+ export declare class RGXPart<R, T = string> implements RGXConvertibleToken {
10
+ token: RGXToken;
11
+ capturedString: string | null;
12
+ capturedValue: T | null;
13
+ readonly transform: RGXPartOptions<R, T>["transform"];
14
+ readonly onEvent: RGXPartOptions<R, T>["onEvent"];
15
+ static check: (value: unknown) => value is RGXPart<unknown, unknown>;
16
+ static assert: (value: unknown) => asserts value is RGXPart<unknown, unknown>;
17
+ constructor(token: RGXToken, options?: Partial<RGXPartOptions<R, T>>);
18
+ triggerEvent(eventType: RGXPartEventType, walker: RGXWalker<R>): void;
19
+ get rgxIsGroup(): boolean;
20
+ get rgxIsRepeatable(): boolean;
21
+ toRgx(): RGXToken;
22
+ clone(depth?: CloneDepth): RGXPart<R, T>;
23
+ }
24
+ export declare function rgxPart<R, T = string>(...args: ConstructorParameters<typeof RGXPart<R, T>>): RGXPart<R, T>;
@@ -0,0 +1,52 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.RGXPart = void 0;
4
+ exports.rgxPart = rgxPart;
5
+ const typeGuards_1 = require("../typeGuards");
6
+ const internal_1 = require("../internal");
7
+ const clone_1 = require("../clone");
8
+ const immutability_utils_1 = require("@ptolemy2002/immutability-utils");
9
+ class RGXPart {
10
+ constructor(token, options = {}) {
11
+ this.capturedString = null;
12
+ this.capturedValue = null;
13
+ this.token = token;
14
+ this.transform = options.transform ?? ((captured) => captured);
15
+ this.onEvent = options.onEvent ?? null;
16
+ }
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);
26
+ }
27
+ // Properties used for conversion to an RGXToken
28
+ get rgxIsGroup() {
29
+ return (0, typeGuards_1.isRGXGroupedToken)(this.token);
30
+ }
31
+ get rgxIsRepeatable() {
32
+ if ((0, typeGuards_1.isRGXToken)(this.token, 'convertible'))
33
+ return this.token.rgxIsRepeatable ?? true;
34
+ // Assume any other token is repeatable, since we don't know its implementation.
35
+ return true;
36
+ }
37
+ toRgx() {
38
+ return this.token;
39
+ }
40
+ // Clone method
41
+ clone(depth = "max") {
42
+ if (depth === 0)
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 });
45
+ }
46
+ }
47
+ exports.RGXPart = RGXPart;
48
+ RGXPart.check = (0, internal_1.createClassGuardFunction)(RGXPart);
49
+ RGXPart.assert = (0, internal_1.createAssertClassGuardFunction)(RGXPart);
50
+ function rgxPart(...args) {
51
+ return new RGXPart(...args);
52
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ptolemy2002/rgx",
3
- "version": "5.5.0",
3
+ "version": "5.6.0",
4
4
  "private": false,
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",