@ptolemy2002/rgx 7.0.0 → 7.2.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
@@ -100,6 +100,8 @@ type RGXPartOptions<R, T=string> = {
100
100
  type RGXWalkerOptions<R> = {
101
101
  startingSourcePosition?: number;
102
102
  reduced?: R;
103
+ infinite?: boolean;
104
+ looping?: boolean;
103
105
  };
104
106
 
105
107
  type RGXWOptions<R = unknown> = Omit<RGXWalkerOptions<R>, "startingSourcePosition"> & {
@@ -657,6 +659,8 @@ constructor(source: string, tokens: RGXTokenCollectionInput, options?: RGXWalker
657
659
  - `options` (`RGXWalkerOptions<R>`, optional): Configuration options. Defaults to `{}`.
658
660
  - `startingSourcePosition` (`number`, optional): The starting index in the source string. Defaults to `0`.
659
661
  - `reduced` (`R`, optional): The initial value for the `reduced` accumulator. Defaults to `null`.
662
+ - `infinite` (`boolean`, optional): When `true`, the walker stays at the last token indefinitely rather than stopping when the token collection is exhausted, continuing to match the last token until the source is consumed. Defaults to `false`.
663
+ - `looping` (`boolean`, optional): When `true`, the walker loops back to token position `0` when the token collection is exhausted, continuing to match from the start until the source is consumed. Defaults to `false`.
660
664
 
661
665
  #### Properties
662
666
  - `source` (`string`): The source string being walked (readonly).
@@ -666,6 +670,8 @@ constructor(source: string, tokens: RGXTokenCollectionInput, options?: RGXWalker
666
670
  - `reduced` (`R`): A user-defined accumulator value, typically updated by `RGXPart` callbacks during walking.
667
671
  - `captures` (`RGXCapture[]`): An array of structured capture results recorded during walking. Each entry has a `raw` string, a `value` (the transform result for Parts, or the raw string for plain tokens), `start` and `end` indices in the source string, and an `ownerId` that is the `id` of the Part that produced it (or `null` for captures from plain tokens or parts without ids).
668
672
  - `namedCaptures` (`Record<string, RGXCapture[]>`): An object mapping capture IDs to their corresponding `RGXCapture` results. Only Parts with non-null IDs are included. The captures occur in the same order as they appear in the `captures` array.
673
+ - `infinite` (`boolean`): Whether the walker is in infinite mode — stays at the last token when the token collection is exhausted until the source is consumed.
674
+ - `looping` (`boolean`): Whether the walker is in looping mode — loops back to token position `0` when the token collection is exhausted until the source is consumed.
669
675
  - `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.
670
676
 
671
677
  #### Methods
@@ -678,12 +684,12 @@ constructor(source: string, tokens: RGXTokenCollectionInput, options?: RGXWalker
678
684
  - `currentToken() => RGXToken | null`: Returns the token at the current token position, or `null` if at the end.
679
685
  - `remainingSource() => string | null`: Returns the remaining source string from the current position onward, or `null` if the source is fully consumed.
680
686
  - `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.
681
- - `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` or `namedCaptures`. 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.
687
+ - `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` or `namedCaptures`. After capturing, validates. After validating, calls `afterCapture` if present. Returns the `RGXCapture` result, or `null` if there are no more tokens (or no more source in `infinite`/`looping` mode), the step was skipped, or the walker was stopped.
682
688
  - `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.
683
689
  - `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.
684
- - `walk() => void`: Steps through all remaining tokens until the end of the token collection or the walker is stopped.
690
+ - `walk() => void`: Steps through all remaining tokens until the end of the token collection (or until the source is consumed in `infinite`/`looping` mode) or the walker is stopped.
685
691
  - `toRgx() => RGXToken`: Returns the internal `RGXTokenCollection`, allowing the walker to be used as a convertible token.
686
- - `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.
692
+ - `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, stopped state, and the `infinite`/`looping` flags.
687
693
 
688
694
  ## Functions
689
695
  The following functions are exported by the library:
@@ -1354,6 +1360,8 @@ A pre-built `RegExpFlagTransformer` that makes a regex pattern accent-insensitiv
1354
1360
  - `o` / `O`: ó, ò, ö, ô, õ / Ó, Ò, Ö, Ô, Õ
1355
1361
  - `u` / `U`: ú, ù, ü, û / Ú, Ù, Ü, Û
1356
1362
 
1363
+ Note that this transformer intentionally excludes replacing characters preceded by an odd number of backslashes, to allow for escaping. For example, in the pattern `\\a`, the `a` is preceded by two backslashes (an even number), so it will be replaced with `(a|á|à|ä|â|ã)`. In the pattern `\a`, the `a` is preceded by one backslash (an odd number), so it will not be replaced.
1364
+
1357
1365
  #### Parameters
1358
1366
  - `exp` (`RegExp`): The regular expression to transform.
1359
1367
 
@@ -1660,6 +1668,11 @@ Since these are defined as native tokens (strings), they are automatically wrapp
1660
1668
  | `"non-word-char"` | `\W` | Any non-word character |
1661
1669
  | `"backspace"` | `[\b]` | Backspace character |
1662
1670
 
1671
+ ### Complex Constructs
1672
+ | Name | Resolves To | Description |
1673
+ | --- | --- | --- |
1674
+ | `"non-escape-bound"` | `(?<=(?<!\\)(?:\\\\)*)(?=[^\\]\|$)` | Matches a position that is not preceded by an odd number of backslashes, i.e., the next character is not escaped. Note that this doesn't match when the next character is a backslash, since allowing it to do that would cause non-escaped backslashes within a series of backslashes to be treated as escaped. For example, in the string `\\\a`, the first and third backslashes would be treated as escaped. |
1675
+
1663
1676
  ## Peer Dependencies
1664
1677
  - `@ptolemy2002/immutability-utils` ^2.0.0
1665
1678
  - `@ptolemy2002/js-utils` ^3.2.2
@@ -32,7 +32,7 @@ class RGXRepeatToken extends base_1.RGXClassToken {
32
32
  return this._token;
33
33
  }
34
34
  set token(value) {
35
- if ((0, typeGuards_1.isRGXToken)(value, "convertible") && !value.rgxIsRepeatable) {
35
+ if ((0, typeGuards_1.isRGXToken)(value, "convertible") && typeof value.rgxIsRepeatable === "boolean" && !value.rgxIsRepeatable) {
36
36
  if ((0, typeGuards_1.isRGXToken)(value, "class")) {
37
37
  throw new errors_1.RGXNotSupportedError(`Repeating ${value.constructor.name} tokens`, "The token was manually marked as non-repeatable.");
38
38
  }
package/dist/constants.js CHANGED
@@ -191,3 +191,12 @@ defineRGXConstant("backspace", {
191
191
  return /[\b]/;
192
192
  }
193
193
  });
194
+ // Complex Constructs
195
+ // Put this before any pattern to ensure that the pattern is not escaped, i.e., not preceded by an odd number of backslashes.
196
+ defineRGXConstant("non-escape-bound", {
197
+ rgxGroupWrap: false,
198
+ rgxIsRepeatable: false,
199
+ toRgx() {
200
+ return /(?<=(?<!\\)(?:\\\\)*)(?=[^\\]|$)/;
201
+ }
202
+ });
@@ -1,6 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.accentInsensitiveFlagTransformer = void 0;
4
+ const constants_1 = require("../constants");
5
+ const resolve_1 = require("../resolve");
4
6
  const accentPatterns = [
5
7
  "(a|á|à|ä|â|ã)", "(A|Á|À|Ä|Â|Ã)",
6
8
  "(e|é|è|ë|ê)", "(E|É|È|Ë|Ê)",
@@ -11,9 +13,10 @@ const accentPatterns = [
11
13
  const accentInsensitiveFlagTransformer = function (exp) {
12
14
  let source = exp.source;
13
15
  const flags = exp.flags;
16
+ const nonEscapeBound = (0, resolve_1.resolveRGXToken)((0, constants_1.rgxConstant)("non-escape-bound"));
14
17
  accentPatterns.forEach((pattern) => {
15
18
  // Replace any of the characters in the pattern with the pattern itself
16
- source = source.replaceAll(new RegExp(pattern, "g"), pattern);
19
+ source = source.replaceAll(new RegExp(nonEscapeBound + pattern, "g"), pattern);
17
20
  });
18
21
  return new RegExp(source, flags);
19
22
  };
@@ -5,6 +5,8 @@ import { RGXPart, RGXCapture } from "./part";
5
5
  export type RGXWalkerOptions<R> = {
6
6
  startingSourcePosition?: number;
7
7
  reduced?: R;
8
+ infinite?: boolean;
9
+ looping?: boolean;
8
10
  };
9
11
  export declare class RGXWalker<R> implements RGXConvertibleToken {
10
12
  readonly source: string;
@@ -14,6 +16,8 @@ export declare class RGXWalker<R> implements RGXConvertibleToken {
14
16
  reduced: R;
15
17
  captures: RGXCapture[];
16
18
  namedCaptures: Record<string, RGXCapture[]>;
19
+ infinite: boolean;
20
+ looping: boolean;
17
21
  private _stopped;
18
22
  static check: (value: unknown) => value is RGXWalker<unknown>;
19
23
  static assert: (value: unknown) => asserts value is RGXWalker<unknown>;
@@ -35,6 +35,8 @@ class RGXWalker {
35
35
  this.tokens = new collection_1.RGXTokenCollection(tokens, "concat");
36
36
  this.tokenPosition = 0;
37
37
  this.reduced = options.reduced ?? null;
38
+ this.infinite = options.infinite ?? false;
39
+ this.looping = options.looping ?? false;
38
40
  }
39
41
  stop() {
40
42
  this._stopped = true;
@@ -73,8 +75,15 @@ class RGXWalker {
73
75
  return match;
74
76
  }
75
77
  step() {
76
- if (this.atTokenEnd())
78
+ if (!this.infinite && !this.looping && this.atTokenEnd()) {
79
+ this._stopped = true;
80
+ return null;
81
+ }
82
+ // If we're infinite, we need to manually stop when all is exhausted.
83
+ if ((this.infinite || this.looping) && this.atSourceEnd()) {
84
+ this._stopped = true;
77
85
  return null;
86
+ }
78
87
  const token = this.currentToken();
79
88
  const isPart = token instanceof part_1.RGXPart;
80
89
  let silent = false;
@@ -116,7 +125,12 @@ class RGXWalker {
116
125
  if (isPart) {
117
126
  token.afterCapture?.(captureResult, token, this);
118
127
  }
119
- this.tokenPosition++;
128
+ if (!this.infinite || this.tokenPosition < this.tokens.length - 1) {
129
+ this.tokenPosition++;
130
+ if (this.looping && this.atTokenEnd()) {
131
+ this.tokenPosition = 0;
132
+ }
133
+ }
120
134
  return captureResult;
121
135
  }
122
136
  stepToToken(predicate) {
@@ -153,7 +167,9 @@ class RGXWalker {
153
167
  return this;
154
168
  const clone = new RGXWalker(this.source, this.tokens.clone((0, immutability_utils_1.depthDecrement)(1)), {
155
169
  startingSourcePosition: this.sourcePosition,
156
- reduced: (0, immutability_utils_1.extClone)(this.reduced, (0, immutability_utils_1.depthDecrement)(1))
170
+ reduced: (0, immutability_utils_1.extClone)(this.reduced, (0, immutability_utils_1.depthDecrement)(1)),
171
+ infinite: this.infinite,
172
+ looping: this.looping
157
173
  });
158
174
  clone._tokenPosition = this.tokenPosition;
159
175
  clone.captures = (0, immutability_utils_1.extClone)(this.captures, (0, immutability_utils_1.depthDecrement)(1));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ptolemy2002/rgx",
3
- "version": "7.0.0",
3
+ "version": "7.2.0",
4
4
  "private": false,
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",