@ptolemy2002/rgx 5.0.0 → 5.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
@@ -12,12 +12,18 @@ import { CloneDepth } from "@ptolemy2002/immutability-utils";
12
12
  type RGXNoOpToken = null | undefined;
13
13
  type RGXLiteralToken = RegExp;
14
14
  type RGXNativeToken = string | number | boolean | RGXNoOpToken;
15
- type RGXConvertibleToken = { toRgx: () => RGXToken, readonly rgxGroupWrap?: boolean, readonly rgxIsGroup?: boolean, readonly rgxIsRepeatable?: boolean };
15
+ type RGXConvertibleToken = {
16
+ toRgx: () => RGXToken,
17
+ rgxAcceptInsertion?: (tokens: RGXToken[], flags: ValidRegexFlags) => string | boolean,
18
+ readonly rgxGroupWrap?: boolean,
19
+ readonly rgxIsGroup?: boolean,
20
+ readonly rgxIsRepeatable?: boolean
21
+ };
16
22
  type RGXToken = RGXNativeToken | RGXLiteralToken | RGXConvertibleToken | RGXToken[];
17
23
 
18
24
  type RGXClassTokenConstructor = new (...args: unknown[]) => RGXClassToken;
19
25
  type RGXGroupedToken = RGXToken[] | RGXLiteralToken | RGXGroupedConvertibleToken;
20
- type RGXGroupedConvertibleToken = (RGXConvertibleToken & { readonly rgxIsGroup: true }) | (Omit<RGXConvertibleToken, "toRGX"> & { toRgx: () => RGXGroupedToken, readonly rgxGroupWrap: true });
26
+ type RGXGroupedConvertibleToken = (RGXConvertibleToken & { readonly rgxIsGroup: true }) | (Omit<RGXConvertibleToken, "toRgx"> & { toRgx: () => RGXGroupedToken, readonly rgxGroupWrap: true });
21
27
 
22
28
  const validRegexSymbol = Symbol('rgx.ValidRegex');
23
29
  type ValidRegexBrandSymbol = typeof validRegexSymbol;
@@ -48,7 +54,7 @@ type RGXTokenFromType<T extends RGXTokenTypeGuardInput> =
48
54
  // ... see source for full definition
49
55
  ;
50
56
 
51
- 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';
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' | 'INSERTION_REJECTED';
52
58
 
53
59
  type RangeObject = {
54
60
  min?: number | null;
@@ -212,6 +218,19 @@ constructor(message: string, got: string)
212
218
  #### Properties
213
219
  - `got` (`string`): The conflicting key string.
214
220
 
221
+ ### RGXInsertionRejectedError extends RGXError
222
+ A specific error class for token insertion rejection. This error is thrown when a convertible token's `rgxAcceptInsertion` method returns `false` or a string (rejection reason) during pattern construction via `rgx`, `rgxa`, or `rgxConcat`. The error code is set to `INSERTION_REJECTED` on instantiation.
223
+
224
+ #### Constructor
225
+ ```typescript
226
+ constructor(reason?: string | null, message?: string | null)
227
+ ```
228
+ - `reason` (`string | null`, optional): The reason the insertion was rejected. Defaults to `null`.
229
+ - `message` (`string | null`, optional): An optional additional message providing more context. Defaults to `null`.
230
+
231
+ #### Properties
232
+ - `reason` (`string | null`): The reason the insertion was rejected, or `null` if no reason was provided.
233
+
215
234
  ### RGXOutOfBoundsError extends RGXError
216
235
  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.
217
236
 
@@ -284,10 +303,11 @@ An abstract base class for creating custom RGX token classes. Subclasses must im
284
303
  - `rgxGroupWrap` (`boolean`): Returns `true` by default. Controls whether the resolver wraps this token's resolved output in a non-capturing group. Subclasses can override this to prevent double-wrapping (e.g., when the token already wraps itself in a group).
285
304
 
286
305
  #### Methods
306
+ - `rgxAcceptInsertion(tokens: RGXToken[], flags: ValidRegexFlags) => string | boolean`: Called during pattern construction (via `rgx`, `rgxa`, or `rgxConcat`) to allow the token to reject its own insertion based on the surrounding tokens and the pattern's flags. Returns `true` to accept insertion (the default), `false` to reject with no reason, or a string to reject with a reason message. When a token rejects insertion, an `RGXInsertionRejectedError` is thrown. Subclasses can override this to enforce constraints such as requiring certain flags to be present.
287
307
  - `or(...others: RGXTokenCollectionInput[]) => RGXClassUnionToken`: Creates an `RGXClassUnionToken` that represents a union (alternation) of this token with the provided others. If any of the `others` are `RGXClassUnionToken` instances, their tokens are flattened into the union rather than nested. If `this` is already an `RGXClassUnionToken`, its existing tokens are preserved and the others are appended.
288
308
  - `group(args?: RGXGroupTokenArgs) => RGXGroupToken`: Wraps this token in an `RGXGroupToken` with the provided arguments. The `args` parameter defaults to `{}`, which creates a capturing group with no name. This is a convenience method that creates a new `RGXGroupToken` with `this` as the sole token.
289
- - `repeat(min?: number, max?: number | null) => RGXRepeatToken`: Wraps this token in an `RGXRepeatToken` with the given repetition bounds. `min` defaults to `1`, `max` defaults to `min`. Pass `null` for `max` to allow unlimited repetitions. This is a convenience method that creates a new `RGXRepeatToken` with `this` as the token. Throws `RGXNotSupportedError` if called on a token with `rgxIsRepeatable` set to `false` (e.g., `RGXLookaroundToken`).
290
- - `optional() => RGXRepeatToken`: Shorthand for `repeat(0, 1)`. Wraps this token in an `RGXRepeatToken` that matches the token zero or one times. Throws `RGXNotSupportedError` if called on a token with `rgxIsRepeatable` set to `false` (e.g., `RGXLookaroundToken`).
309
+ - `repeat(min?: number, max?: number | null, lazy?: boolean) => RGXRepeatToken`: Wraps this token in an `RGXRepeatToken` with the given repetition bounds. `min` defaults to `1`, `max` defaults to `min`, `lazy` defaults to `false`. Pass `null` for `max` to allow unlimited repetitions. When `lazy` is `true`, the resulting quantifier will be non-greedy. This is a convenience method that creates a new `RGXRepeatToken` with `this` as the token. Throws `RGXNotSupportedError` if called on a token with `rgxIsRepeatable` set to `false` (e.g., `RGXLookaroundToken`).
310
+ - `optional(lazy?: boolean) => RGXRepeatToken`: Shorthand for `repeat(0, 1, lazy)`. Wraps this token in an `RGXRepeatToken` that matches the token zero or one times. `lazy` defaults to `false`. Throws `RGXNotSupportedError` if called on a token with `rgxIsRepeatable` set to `false` (e.g., `RGXLookaroundToken`).
291
311
  - `asLookahead(positive?: boolean) => RGXLookaheadToken`: Wraps this token in an `RGXLookaheadToken`. `positive` defaults to `true`. If this token is already an `RGXLookaheadToken`, it is returned as-is without re-wrapping.
292
312
  - `asLookbehind(positive?: boolean) => RGXLookbehindToken`: Wraps this token in an `RGXLookbehindToken`. `positive` defaults to `true`. If this token is already an `RGXLookbehindToken`, it is returned as-is without re-wrapping.
293
313
  - `resolve() => ValidRegexString`: A convenience method that resolves this token by calling `resolveRGXToken(this)`, returning the resolved regex string representation. Since this method is defined on `RGXClassToken`, it is available on all subclasses including `RGXClassUnionToken`, `RGXGroupToken`, `RGXRepeatToken`, and `RGXLookaroundToken`.
@@ -354,17 +374,19 @@ A function `rgxRepeat` is provided with the same parameters as this class' const
354
374
 
355
375
  #### Constructor
356
376
  ```typescript
357
- constructor(token: RGXToken, min?: number, max?: number | null)
377
+ constructor(token: RGXToken, min?: number, max?: number | null, lazy?: boolean)
358
378
  ```
359
379
  - `token` (`RGXToken`): The token to repeat. If the token is not already a grouped token, it will be automatically wrapped in a non-capturing `RGXGroupToken`.
360
380
  - `min` (`number`, optional): The minimum number of repetitions. Must be >= 0 and <= `max` (when `max` is not `null`). Non-integer values are floored. Defaults to `1`.
361
381
  - `max` (`number | null`, optional): The maximum number of repetitions. Must be >= `min` when not `null`. Non-integer values are floored. Pass `null` for unlimited repetitions. Defaults to `min`.
382
+ - `lazy` (`boolean`, optional): Whether the quantifier should be non-greedy (lazy). Defaults to `false`.
362
383
 
363
384
  #### Properties
364
385
  - `token` (`RGXGroupedToken`): The token being repeated. Setting this will throw `RGXNotSupportedError` if the value is a convertible token with `rgxIsRepeatable` set to `false`, and will automatically wrap non-grouped tokens in a non-capturing `RGXGroupToken`.
365
386
  - `min` (`number`): The minimum number of repetitions. Setting this validates that the value is >= 0 and <= `max` (when `max` is not `null`), and floors non-integer values. Throws `RGXOutOfBoundsError` if validation fails.
366
387
  - `max` (`number | null`): The maximum number of repetitions. Setting this validates that the value is >= `min` when not `null`, and floors non-integer values. Pass `null` for unlimited. Throws `RGXOutOfBoundsError` if validation fails.
367
- - `repeaterSuffix` (`string`): Returns the regex quantifier suffix based on the current `min` and `max` values: `*` for `{0,}`, `+` for `{1,}`, `?` for `{0,1}`, `{n}` for exact repetitions, `{n,}` for minimum-only, `{n,m}` for a range, or an empty string for `{1,1}` (exactly once, no quantifier needed).
388
+ - `lazy` (`boolean`): Whether the quantifier is non-greedy (lazy). When `true`, a `?` is appended to the `repeaterSuffix` (except when the suffix is `?` or empty, since those cases don't benefit from a lazy modifier). Defaults to `false`.
389
+ - `repeaterSuffix` (`string`): Returns the regex quantifier suffix based on the current `min`, `max`, and `lazy` values: `*` for `{0,}`, `+` for `{1,}`, `?` for `{0,1}`, `{n}` for exact repetitions, `{n,}` for minimum-only, `{n,m}` for a range, or an empty string for `{1,1}` (exactly once, no quantifier needed). When `lazy` is `true`, a `?` is appended to the suffix (e.g., `*?`, `+?`, `{2,5}?`), except when the suffix is already `?` or empty.
368
390
  - `rgxGroupWrap` (`false`): Returns `false` as a constant, since the quantifier suffix binds tightly to the preceding group and does not need additional wrapping.
369
391
 
370
392
  #### Methods
@@ -424,6 +446,28 @@ A function `rgxLookbehind` is provided with the same parameters as this class' c
424
446
  - `reverse() => RGXLookaheadToken`: Returns a new `RGXLookaheadToken` with the same tokens and positivity.
425
447
  - `toRgx() => RegExp`: Resolves the lookbehind to a `RegExp`. Positive lookbehinds produce `(?<=...)` and negative lookbehinds produce `(?<!...)`.
426
448
 
449
+ ### RGXSubpatternToken extends RGXClassToken
450
+ A class representing a backreference to a previously captured group, either by name or by group number. Named backreferences produce `\k<name>` and numbered backreferences produce `\N` (where N is the group number). This is useful for matching the same text that was captured by a previous group.
451
+
452
+ A function `rgxSubpattern` is provided with the same parameters as this class' constructor, for easier instantiation without needing to use the `new` keyword.
453
+
454
+ #### Static Properties
455
+ - `check(value: unknown): value is RGXSubpatternToken`: A type guard that checks if the given value is an instance of `RGXSubpatternToken`.
456
+ - `assert(value: unknown): asserts value is RGXSubpatternToken`: An assertion that checks if the given value is an instance of `RGXSubpatternToken`. If the assertion fails, an `RGXInvalidTokenError` will be thrown.
457
+
458
+ #### Constructor
459
+ ```typescript
460
+ constructor(pattern: string | number)
461
+ ```
462
+ - `pattern` (`string | number`): The backreference pattern. If a string, it must be a valid identifier (validated via `assertValidIdentifier`) and produces a named backreference (`\k<name>`). If a number, it must be a positive integer (>= 1, as groups are 1-indexed) and produces a numbered backreference (`\N`). Non-integer numbers are floored.
463
+
464
+ #### Properties
465
+ - `pattern` (`string | number`): The backreference pattern. Setting this validates the value: strings must be valid identifiers, numbers must be positive integers (>= 1). Non-integer numbers are floored.
466
+
467
+ #### Methods
468
+ - `toRgx() => RegExp`: Resolves the backreference to a `RegExp`. Named patterns produce `/\k<name>/` and numbered patterns produce `/\N/`.
469
+ - `clone(depth: CloneDepth = "max") => RGXSubpatternToken`: Creates a clone of this token. When `depth` is `0`, returns `this`; otherwise, returns a new `RGXSubpatternToken` with the same pattern.
470
+
427
471
  ### RGXClassWrapperToken extends RGXClassToken
428
472
  A class that wraps any `RGXToken` as an `RGXClassToken`, giving you access to the extended API class tokens provide. It delegates `rgxIsGroup` and `rgxIsRepeatable` to the wrapped token where possible.
429
473
 
@@ -442,7 +486,7 @@ constructor(token: RGXToken)
442
486
  #### Properties
443
487
  - `token` (`RGXToken`): The wrapped token.
444
488
  - `rgxIsGroup` (`boolean`): Delegates to the wrapped token's group status via `isRGXGroupedToken`. Returns `true` if the wrapped token is a grouped token, otherwise `false`.
445
- - `rgxIsRepeatable` (`boolean`): If the wrapped token is an `RGXClassToken`, delegates to its `rgxIsRepeatable` property. Otherwise, returns `true`.
489
+ - `rgxIsRepeatable` (`boolean`): If the wrapped token is an `RGXConvertibleToken`, delegates to its `rgxIsRepeatable` property (defaulting to `true` if not present). Otherwise, returns `true`.
446
490
 
447
491
  #### Methods
448
492
  - `unwrap() => RGXToken`: Returns the original wrapped token.
@@ -552,11 +596,11 @@ Asserts that the given value is a native token (string, number, boolean, or no-o
552
596
  function isRGXConvertibleToken(value: unknown, returnCheck?: boolean): value is RGXConvertibleToken
553
597
  ```
554
598
 
555
- Checks if the given value is a convertible token (an object with a `toRgx` method). If the `rgxGroupWrap`, `rgxIsRepeatable`, or `rgxIsGroup` properties are present, they must be booleans; otherwise, the check fails. When `returnCheck` is `true` (the default), also validates that `toRgx` is callable and returns a valid `RGXToken` (which can be any RGX token type, including other convertible tokens, allowing for recursive structures).
599
+ Checks if the given value is a convertible token (an object with a `toRgx` method). If the `rgxGroupWrap`, `rgxIsRepeatable`, or `rgxIsGroup` properties are present, they must be booleans; otherwise, the check fails. If the `rgxAcceptInsertion` property is present, it must be a callable that returns a `string` or `boolean`; otherwise, the check fails. Validates that `toRgx` is callable and returns a valid `RGXToken` (which can be any RGX token type, including other convertible tokens, allowing for recursive structures). When `returnCheck` is `false`, only checks for the presence and callability of function properties instead of also checking their return values.
556
600
 
557
601
  #### Parameters
558
602
  - `value` (`unknown`): The value to check.
559
- - `returnCheck` (`boolean`, optional): Whether to validate the return value of the `toRgx` method. Defaults to `true`. When `false`, only checks that `toRgx` exists and is callable. **Note**: Setting this to `false` makes the type guard assertion strictly unsafe, as it doesn't verify that the `toRgx` method actually returns a valid `RGXToken`. However, depending on the type of the value you're checking, you might not need that safety (e.g., when checking values that you know are valid based on other context).
603
+ - `returnCheck` (`boolean`, optional): Whether to validate the return value of the `toRgx` method. Defaults to `true`. When `false`, only checks that `toRgx` exists and is callable. **Note**: Setting this to `false` makes the type guard assertion strictly unsafe, as it doesn't verify that the methods actually return valid values. However, depending on the type of the value you're checking, you might not need that safety (e.g., when checking values that you know are valid based on other context).
560
604
 
561
605
  #### Returns
562
606
  - `boolean`: `true` if the value is a convertible token, otherwise `false`.
@@ -565,7 +609,7 @@ Checks if the given value is a convertible token (an object with a `toRgx` metho
565
609
  ```typescript
566
610
  function assertRGXConvertibleToken(value: unknown, returnCheck?: boolean): asserts value is RGXConvertibleToken
567
611
  ```
568
- Asserts that the given value is a convertible token (an object with a `toRgx` method). If the `rgxGroupWrap`, `rgxIsRepeatable`, or `rgxIsGroup` properties are present, they must be booleans; otherwise, the assertion fails. When `returnCheck` is `true` (the default), also validates that `toRgx` is callable and returns a valid `RGXToken` (which can be any RGX token type, including other convertible tokens, allowing for recursive structures). If the assertion fails, an `RGXInvalidTokenError` will be thrown.
612
+ Asserts that the given value is a convertible token (an object with a `toRgx` method). If the `rgxGroupWrap`, `rgxIsRepeatable`, or `rgxIsGroup` properties are present, they must be booleans; otherwise, the assertion fails. If the `rgxAcceptInsertion` property is present, it must be a callable that returns a `string` or `boolean`; otherwise, the assertion fails. When `returnCheck` is `true` (the default), also validates that `toRgx` is callable and returns a valid `RGXToken` (which can be any RGX token type, including other convertible tokens, allowing for recursive structures). If the assertion fails, an `RGXInvalidTokenError` will be thrown.
569
613
 
570
614
  #### Parameters
571
615
  - `value` (`unknown`): The value to assert.
@@ -868,7 +912,7 @@ For convertible tokens, if the token has an `rgxGroupWrap` property, that value
868
912
  function rgxConcat(tokens: RGXToken[], groupWrap?: boolean, currentFlags?: string): ValidRegexString
869
913
  ```
870
914
 
871
- A helper function that resolves an array of RGX tokens and concatenates their resolved string representations together. This is useful for cases where you want to concatenate multiple tokens without creating a union between them.
915
+ A helper function that resolves an array of RGX tokens and concatenates their resolved string representations together. This is useful for cases where you want to concatenate multiple tokens without creating a union between them. Before returning, any convertible token in the array that defines `rgxAcceptInsertion` is checked; if it returns `false` or a string, an `RGXInsertionRejectedError` is thrown.
872
916
 
873
917
  #### Parameters
874
918
  - `tokens` (`RGXToken[]`): The array of RGX tokens to resolve and concatenate.
@@ -883,7 +927,7 @@ A helper function that resolves an array of RGX tokens and concatenates their re
883
927
  function rgx(flags?: string): (strings: TemplateStringsArray, ...tokens: RGXToken[]) => ExtRegExp
884
928
  ```
885
929
 
886
- Creates and returns a template tag function that constructs an `ExtRegExp` object from the provided template literal with the provided flags. The template literal can contain RGX tokens, which will be resolved and concatenated with the literal parts to form the final regex pattern.
930
+ Creates and returns a template tag function that constructs an `ExtRegExp` object from the provided template literal with the provided flags. The template literal can contain RGX tokens, which will be resolved and concatenated with the literal parts to form the final regex pattern. Before constructing the pattern, any convertible token that defines `rgxAcceptInsertion` is checked; if it returns `false` or a string, an `RGXInsertionRejectedError` is thrown.
887
931
 
888
932
  The provided `flags` are passed as `currentFlags` to the resolver, enabling inline modifier groups for any `RegExp` literal tokens whose localizable flags (`i`, `m`, `s`) differ from the parent flags. For example, embedding `/foo/i` in a no-flag context produces `(?i:foo)`, while embedding `/bar/` in an `i`-flag context produces `(?-i:bar)`.
889
933
 
@@ -918,7 +962,7 @@ const pattern4 = rgx()`${beginning}${caseInsensitiveWord} world${end}`; // /^(?i
918
962
  ```typescript
919
963
  function rgxa(tokens: RGXToken[], flags?: string): ExtRegExp
920
964
  ```
921
- As an alternative to using the `rgx` template tag, you can directly call `rgxa` with an array of RGX tokens and optional flags to get an `ExtRegExp` object. This is useful in cases where you don't want to use a template literal. Like `rgx`, the provided `flags` are passed as `currentFlags` to the resolver, enabling inline modifier groups for `RegExp` literal tokens whose localizable flags differ.
965
+ As an alternative to using the `rgx` template tag, you can directly call `rgxa` with an array of RGX tokens and optional flags to get an `ExtRegExp` object. This is useful in cases where you don't want to use a template literal. Like `rgx`, the provided `flags` are passed as `currentFlags` to the resolver, enabling inline modifier groups for `RegExp` literal tokens whose localizable flags differ. Before constructing the pattern, any convertible token in the array that defines `rgxAcceptInsertion` is checked; if it returns `false` or a string, an `RGXInsertionRejectedError` is thrown.
922
966
 
923
967
  #### Parameters
924
968
  - `tokens` (`RGXToken[]`): The RGX tokens to be resolved and concatenated to form the regex pattern.
@@ -1,4 +1,4 @@
1
- import { RGXConvertibleToken, RGXToken, ValidRegexString } from "../types";
1
+ import { RGXConvertibleToken, RGXToken, ValidRegexFlags, ValidRegexString } from "../types";
2
2
  import { RGXTokenCollectionInput } from "../collection";
3
3
  import { CloneDepth } from "@ptolemy2002/immutability-utils";
4
4
  import type { RGXClassUnionToken } from "./union";
@@ -9,6 +9,7 @@ import type { RGXLookbehindToken } from "./lookbehind";
9
9
  export declare abstract class RGXClassToken implements RGXConvertibleToken {
10
10
  abstract toRgx(): RGXToken;
11
11
  abstract clone(depth?: CloneDepth): ThisType<this>;
12
+ rgxAcceptInsertion(tokens: RGXToken[], flags: ValidRegexFlags): string | boolean;
12
13
  static check: (value: unknown) => value is RGXClassToken;
13
14
  static assert: (value: unknown) => asserts value is RGXClassToken;
14
15
  get rgxIsGroup(): boolean;
@@ -16,8 +17,8 @@ export declare abstract class RGXClassToken implements RGXConvertibleToken {
16
17
  get rgxGroupWrap(): boolean;
17
18
  or(...others: RGXTokenCollectionInput[]): RGXClassUnionToken;
18
19
  group(args?: RGXGroupTokenArgs): RGXGroupToken;
19
- repeat(min?: number, max?: number | null): RGXRepeatToken;
20
- optional(): RGXRepeatToken;
20
+ repeat(min?: number, max?: number | null, lazy?: boolean): RGXRepeatToken;
21
+ optional(lazy?: boolean): RGXRepeatToken;
21
22
  asLookahead(positive?: boolean): RGXLookaheadToken;
22
23
  asLookbehind(positive?: boolean): RGXLookbehindToken;
23
24
  resolve(): ValidRegexString;
@@ -4,6 +4,9 @@ exports.RGXClassToken = void 0;
4
4
  const errors_1 = require("../errors");
5
5
  const resolve_1 = require("../resolve");
6
6
  class RGXClassToken {
7
+ rgxAcceptInsertion(tokens, flags) {
8
+ return true;
9
+ }
7
10
  get rgxIsGroup() {
8
11
  return false;
9
12
  }
@@ -19,11 +22,11 @@ class RGXClassToken {
19
22
  group(args = {}) {
20
23
  throw new errors_1.RGXNotImplementedError('RGXClassToken.group(args)', 'call rgxClassInit() first.');
21
24
  }
22
- repeat(min = 1, max = min) {
23
- throw new errors_1.RGXNotImplementedError('RGXClassToken.repeat(min, max)', 'call rgxClassInit() first.');
25
+ repeat(min = 1, max = min, lazy = false) {
26
+ throw new errors_1.RGXNotImplementedError('RGXClassToken.repeat(min, max, lazy)', 'call rgxClassInit() first.');
24
27
  }
25
- optional() {
26
- return this.repeat(0, 1);
28
+ optional(lazy = false) {
29
+ return this.repeat(0, 1, lazy);
27
30
  }
28
31
  asLookahead(positive = true) {
29
32
  throw new errors_1.RGXNotImplementedError('RGXClassToken.asLookahead(positive)', 'call rgxClassInit() first.');
@@ -7,4 +7,5 @@ export * from "./lookaround";
7
7
  export * from "./lookahead";
8
8
  export * from "./lookbehind";
9
9
  export * from "./wrapper";
10
+ export * from "./subpattern";
10
11
  export * from "./toRGXClassToken";
@@ -23,4 +23,5 @@ __exportStar(require("./lookaround"), exports);
23
23
  __exportStar(require("./lookahead"), exports);
24
24
  __exportStar(require("./lookbehind"), exports);
25
25
  __exportStar(require("./wrapper"), exports);
26
+ __exportStar(require("./subpattern"), exports);
26
27
  __exportStar(require("./toRGXClassToken"), exports);
@@ -28,10 +28,10 @@ function rgxClassInit() {
28
28
  base_1.RGXClassToken.prototype.group = function (args = {}) {
29
29
  return new group_1.RGXGroupToken(args, [this]);
30
30
  };
31
- base_1.RGXClassToken.prototype.repeat = function (min = 1, max = min) {
31
+ base_1.RGXClassToken.prototype.repeat = function (min = 1, max = min, lazy = false) {
32
32
  if (lookaround_1.RGXLookaroundToken.check(this))
33
33
  throw new errors_1.RGXNotSupportedError("RGXLookaroundToken.repeat()", "Lookaround tokens cannot be repeated or made optional.");
34
- return new repeat_1.RGXRepeatToken(this, min, max);
34
+ return new repeat_1.RGXRepeatToken(this, min, max, lazy);
35
35
  };
36
36
  base_1.RGXClassToken.prototype.asLookahead = function (positive = true) {
37
37
  if (lookahead_1.RGXLookaheadToken.check(this))
@@ -5,6 +5,7 @@ export declare class RGXRepeatToken extends RGXClassToken {
5
5
  _token: RGXGroupedToken;
6
6
  _min: number;
7
7
  _max: number | null;
8
+ lazy: boolean;
8
9
  static check: (value: unknown) => value is RGXRepeatToken;
9
10
  static assert: (value: unknown) => asserts value is RGXRepeatToken;
10
11
  get min(): number;
@@ -14,9 +15,9 @@ export declare class RGXRepeatToken extends RGXClassToken {
14
15
  get token(): RGXGroupedToken;
15
16
  set token(value: RGXToken);
16
17
  get rgxGroupWrap(): false;
17
- constructor(token: RGXToken, min?: number, max?: number | null);
18
+ constructor(token: RGXToken, min?: number, max?: number | null, lazy?: boolean);
18
19
  get repeaterSuffix(): string;
19
20
  toRgx(): RGXToken;
20
21
  clone(depth?: CloneDepth): RGXRepeatToken;
21
22
  }
22
- export declare const rgxRepeat: (token: RGXToken, min?: number | undefined, max?: number | null | undefined) => RGXRepeatToken;
23
+ export declare const rgxRepeat: (token: RGXToken, min?: number | undefined, max?: number | null | undefined, lazy?: boolean | undefined) => RGXRepeatToken;
@@ -52,29 +52,37 @@ class RGXRepeatToken extends base_1.RGXClassToken {
52
52
  return false;
53
53
  }
54
54
  // By default, repeat a fixed number of times.
55
- constructor(token, min = 1, max = min) {
55
+ constructor(token, min = 1, max = min, lazy = false) {
56
56
  super();
57
57
  this._max = null;
58
+ this.lazy = false;
58
59
  this.token = token;
59
60
  this.min = min;
60
61
  this.max = max;
62
+ this.lazy = lazy;
61
63
  }
62
64
  get repeaterSuffix() {
63
- if (this.min === 0 && this.max === null)
64
- return '*';
65
- if (this.min === 1 && this.max === null)
66
- return '+';
67
- if (this.min === 0 && this.max === 1)
68
- return '?';
69
- if (this.max === null)
70
- return `{${this.min},}`;
71
- if (this.min === this.max) {
72
- // No need for a repeater suffix if we're repeating exactly once.
73
- if (this.min === 1)
74
- return '';
75
- return `{${this.min}}`;
76
- }
77
- return `{${this.min},${this.max}}`;
65
+ const result = (() => {
66
+ if (this.min === 0 && this.max === null)
67
+ return '*';
68
+ if (this.min === 1 && this.max === null)
69
+ return '+';
70
+ if (this.min === 0 && this.max === 1)
71
+ return '?';
72
+ if (this.max === null)
73
+ return `{${this.min},}`;
74
+ if (this.min === this.max) {
75
+ // No need for a repeater suffix if we're repeating exactly once.
76
+ if (this.min === 1)
77
+ return '';
78
+ return `{${this.min}}`;
79
+ }
80
+ return `{${this.min},${this.max}}`;
81
+ })();
82
+ if (this.lazy && result.length > 0 && result !== "?")
83
+ return result + '?';
84
+ else
85
+ return result;
78
86
  }
79
87
  toRgx() {
80
88
  // No-op if we're repeating zero times.
@@ -0,0 +1,13 @@
1
+ import { RGXClassToken } from "./base";
2
+ import { CloneDepth } from "@ptolemy2002/immutability-utils";
3
+ export declare class RGXSubpatternToken extends RGXClassToken {
4
+ _pattern: string | number;
5
+ get pattern(): string | number;
6
+ set pattern(value: string | number);
7
+ static check: (value: unknown) => value is RGXSubpatternToken;
8
+ static assert: (value: unknown) => asserts value is RGXSubpatternToken;
9
+ constructor(pattern: string | number);
10
+ toRgx(): RegExp;
11
+ clone(depth?: CloneDepth): RGXSubpatternToken;
12
+ }
13
+ export declare const rgxSubpattern: (pattern: string | number) => RGXSubpatternToken;
@@ -0,0 +1,43 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.rgxSubpattern = exports.RGXSubpatternToken = void 0;
4
+ const base_1 = require("./base");
5
+ const typeGuards_1 = require("../typeGuards");
6
+ const errors_1 = require("../errors");
7
+ const internal_1 = require("../internal");
8
+ class RGXSubpatternToken extends base_1.RGXClassToken {
9
+ get pattern() {
10
+ return this._pattern;
11
+ }
12
+ set pattern(value) {
13
+ if (typeof value === "string") {
14
+ (0, typeGuards_1.assertValidIdentifier)(value);
15
+ this._pattern = value;
16
+ }
17
+ else {
18
+ (0, errors_1.assertInRange)(value, { min: 1 }, "Subpattern group numbers must be positive integers (groups are 1-indexed).");
19
+ this._pattern = Math.floor(value);
20
+ }
21
+ }
22
+ constructor(pattern) {
23
+ super();
24
+ this.pattern = pattern;
25
+ }
26
+ toRgx() {
27
+ if (typeof this.pattern === "string") {
28
+ return new RegExp(`\\k<${this.pattern}>`);
29
+ }
30
+ else {
31
+ return new RegExp(`\\${this.pattern}`);
32
+ }
33
+ }
34
+ clone(depth = "max") {
35
+ if (depth === 0)
36
+ return this;
37
+ return new RGXSubpatternToken(this.pattern);
38
+ }
39
+ }
40
+ exports.RGXSubpatternToken = RGXSubpatternToken;
41
+ RGXSubpatternToken.check = (0, internal_1.createClassGuardFunction)(RGXSubpatternToken);
42
+ RGXSubpatternToken.assert = (0, internal_1.createAssertClassGuardFunction)(RGXSubpatternToken);
43
+ exports.rgxSubpattern = (0, internal_1.createConstructFunction)(RGXSubpatternToken);
@@ -15,8 +15,8 @@ class RGXClassWrapperToken extends base_1.RGXClassToken {
15
15
  return (0, typeGuards_1.isRGXGroupedToken)(this.token);
16
16
  }
17
17
  get rgxIsRepeatable() {
18
- if ((0, typeGuards_1.isRGXToken)(this.token, 'class'))
19
- return this.token.rgxIsRepeatable;
18
+ if ((0, typeGuards_1.isRGXToken)(this.token, 'convertible'))
19
+ return this.token.rgxIsRepeatable ?? true;
20
20
  // Assume any other token is repeatable, since we don't know its implementation.
21
21
  return true;
22
22
  }
package/dist/concat.js CHANGED
@@ -1,8 +1,11 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.rgxConcat = rgxConcat;
4
+ const internal_1 = require("./internal");
4
5
  const resolve_1 = require("./resolve");
5
6
  // Wrapper for letting an array of tokens be resolved as a concatenation instead of a union.
6
7
  function rgxConcat(tokens, groupWrap = true, currentFlags = '') {
7
- return tokens.map(t => (0, resolve_1.resolveRGXToken)(t, groupWrap, true, currentFlags)).join('');
8
+ const result = tokens.map(t => (0, resolve_1.resolveRGXToken)(t, groupWrap, true, currentFlags)).join('');
9
+ (0, internal_1.assureAcceptance)(tokens, currentFlags);
10
+ return result;
8
11
  }
@@ -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';
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' | 'INSERTION_REJECTED';
2
2
  export declare class RGXError extends Error {
3
3
  _message: string;
4
4
  code: RGXErrorCode;
@@ -9,3 +9,4 @@ export * from './outOfBounds';
9
9
  export * from './invalidFlagTransformerKey';
10
10
  export * from './flagTransformerConflict';
11
11
  export * from './notSupported';
12
+ export * from './insertionRejected';
@@ -25,3 +25,4 @@ __exportStar(require("./outOfBounds"), exports);
25
25
  __exportStar(require("./invalidFlagTransformerKey"), exports);
26
26
  __exportStar(require("./flagTransformerConflict"), exports);
27
27
  __exportStar(require("./notSupported"), exports);
28
+ __exportStar(require("./insertionRejected"), exports);
@@ -0,0 +1,6 @@
1
+ import { RGXError } from "./base";
2
+ export declare class RGXInsertionRejectedError extends RGXError {
3
+ reason: string | null;
4
+ constructor(reason?: string | null, message?: string | null);
5
+ calcMessage(message: string): string;
6
+ }
@@ -0,0 +1,21 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.RGXInsertionRejectedError = void 0;
4
+ const base_1 = require("./base");
5
+ class RGXInsertionRejectedError extends base_1.RGXError {
6
+ constructor(reason = null, message = null) {
7
+ super(message || "", "INSERTION_REJECTED");
8
+ this.reason = null;
9
+ this.reason = reason;
10
+ this.name = "RGXInsertionRejectedError";
11
+ }
12
+ calcMessage(message) {
13
+ let result = `Insertion rejected`;
14
+ if (this.reason)
15
+ result += `; Reason: ${this.reason}`;
16
+ if (message)
17
+ result += `; Additional info: ${message}`;
18
+ return result;
19
+ }
20
+ }
21
+ exports.RGXInsertionRejectedError = RGXInsertionRejectedError;
package/dist/index.js CHANGED
@@ -38,6 +38,7 @@ __exportStar(require("./clone"), exports);
38
38
  (0, flag_transformer_1.registerCustomFlagTransformers)();
39
39
  function rgxa(tokens, flags = '') {
40
40
  (0, ExtRegExp_1.assertValidRegexFlags)(flags);
41
+ (0, internal_1.assureAcceptance)(tokens, flags);
41
42
  const pattern = (0, concat_1.rgxConcat)(tokens, true, flags);
42
43
  return (0, ExtRegExp_1.extRegExp)(pattern, flags);
43
44
  }
@@ -0,0 +1,2 @@
1
+ import { RGXToken, ValidRegexFlags } from "../types";
2
+ export declare function assureAcceptance(tokens: RGXToken[], flags: ValidRegexFlags): void;
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.assureAcceptance = assureAcceptance;
4
+ const errors_1 = require("../errors");
5
+ const typeGuards_1 = require("../typeGuards");
6
+ function assureAcceptance(tokens, flags) {
7
+ for (const token of tokens) {
8
+ if ((0, typeGuards_1.isRGXConvertibleToken)(token) && token.rgxAcceptInsertion) {
9
+ const messageOrAccepted = token.rgxAcceptInsertion(tokens, flags);
10
+ if (messageOrAccepted === true)
11
+ continue;
12
+ if (messageOrAccepted === false)
13
+ throw new errors_1.RGXInsertionRejectedError();
14
+ throw new errors_1.RGXInsertionRejectedError(messageOrAccepted);
15
+ }
16
+ }
17
+ }
@@ -3,3 +3,4 @@ export * from "./createClassGuardFunction";
3
3
  export * from "./taggedTemplateToArray";
4
4
  export * from "./isConstructor";
5
5
  export * from "./getProxy";
6
+ export * from "./assureAcceptance";
@@ -19,3 +19,4 @@ __exportStar(require("./createClassGuardFunction"), exports);
19
19
  __exportStar(require("./taggedTemplateToArray"), exports);
20
20
  __exportStar(require("./isConstructor"), exports);
21
21
  __exportStar(require("./getProxy"), exports);
22
+ __exportStar(require("./assureAcceptance"), exports);
@@ -98,6 +98,16 @@ function isRGXConvertibleToken(value, returnCheck = true) {
98
98
  return false;
99
99
  if ('rgxIsGroup' in value && typeof value.rgxIsGroup !== 'boolean')
100
100
  return false;
101
+ // If the rgxAcceptInsertion property exists, it must be a function that returns a string or boolean.
102
+ if ('rgxAcceptInsertion' in value) {
103
+ if (!(0, is_callable_1.default)(value.rgxAcceptInsertion))
104
+ return false;
105
+ if (returnCheck) {
106
+ const acceptResult = value.rgxAcceptInsertion([], '');
107
+ if (typeof acceptResult !== 'string' && typeof acceptResult !== 'boolean')
108
+ return false;
109
+ }
110
+ }
101
111
  if ((0, is_callable_1.default)(value.toRgx)) {
102
112
  if (!returnCheck)
103
113
  return true;
package/dist/types.d.ts CHANGED
@@ -7,6 +7,7 @@ export type RGXLiteralToken = RegExp;
7
7
  export type RGXNativeToken = string | number | boolean | RGXNoOpToken;
8
8
  export type RGXConvertibleToken = {
9
9
  toRgx: () => RGXToken;
10
+ rgxAcceptInsertion?: (tokens: RGXToken[], flags: ValidRegexFlags) => string | boolean;
10
11
  readonly rgxGroupWrap?: boolean;
11
12
  readonly rgxIsGroup?: boolean;
12
13
  readonly rgxIsRepeatable?: boolean;
@@ -16,7 +17,7 @@ export type RGXClassTokenConstructor = new (...args: unknown[]) => RGXClassToken
16
17
  export type RGXGroupedToken = RGXToken[] | RGXLiteralToken | RGXGroupedConvertibleToken;
17
18
  export type RGXGroupedConvertibleToken = (RGXConvertibleToken & {
18
19
  readonly rgxIsGroup: true;
19
- }) | (Omit<RGXConvertibleToken, "toRGX"> & {
20
+ }) | (Omit<RGXConvertibleToken, "toRgx"> & {
20
21
  toRgx: () => RGXGroupedToken;
21
22
  readonly rgxGroupWrap: true;
22
23
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ptolemy2002/rgx",
3
- "version": "5.0.0",
3
+ "version": "5.2.0",
4
4
  "private": false,
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",