@ptolemy2002/rgx 12.6.0 → 12.7.1

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
@@ -16,6 +16,7 @@ Because there is so much to document, it has been broken up into multiple files.
16
16
  - [clone](./docs/util/clone.md) - The `cloneRGXToken` function, which creates a clone of a given RGX token to a specified depth.
17
17
  - [escapeRegex](./docs/util/escapeRegex.md) - The `escapeRegex` function, which escapes special regex characters in a given string and assures you that the result is valid Regex.
18
18
  - [regexMatchAtPosition](./docs/util/regexMatchAtPosition.md) - The `regexMatchAtPosition` function and related functions, which attempt to match a given regular expression at a specific position in a string.
19
+ - [regexMatchAfterPosition](./docs/util/regexMatchAfterPosition.md) - The `regexMatchAfterPosition` function and related functions, which search for a regular expression match at or after a specific position in a string.
19
20
  - [regexWithFlags](./docs/util/regexWithFlags.md) - The `regexWithFlags` function, which creates a new regular expression with the same source as a given regular expression but with different flags.
20
21
  - [createRegex](./docs/util/createRegex.md) - The `createRegex` function, which safely constructs a `RegExp` or `ExtRegExp` from a pattern string, converting `SyntaxError` into `RGXInvalidRegexStringError`.
21
22
  - [createRGXClassGuardFunction](./docs/util/createRGXClassGuardFunction.md) - The `createRGXClassGuardFunction` and `createAssertRGXClassGuardFunction` utilities for creating type guard and assertion functions for class instances.
@@ -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' | 'PART_VALIDATION_FAILED' | 'INVALID_LEXER_MODE' | 'LEXEME_NOT_MATCHED_AT_POSITION' | 'INVALID_RGX_LEXER' | 'INVALID_RGX_WALKER' | 'INVALID_RGX_PART' | 'NOT_DIRECT_REGEXP';
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' | 'REGEX_NOT_MATCHED_AFTER_POSITION' | 'PART_VALIDATION_FAILED' | 'INVALID_LEXER_MODE' | 'LEXEME_NOT_MATCHED_AT_POSITION' | 'INVALID_RGX_LEXER' | 'INVALID_RGX_WALKER' | 'INVALID_RGX_PART' | 'NOT_DIRECT_REGEXP';
2
2
  export declare class RGXError extends Error {
3
3
  _message: string;
4
4
  code: RGXErrorCode;
@@ -13,6 +13,7 @@ export * from './insertionRejected';
13
13
  export * from './constantConflict';
14
14
  export * from './invalidConstantKey';
15
15
  export * from './regexNotMatchedAtPosition';
16
+ export * from './regexNotMatchedAfterPosition';
16
17
  export * from './partValidationFailed';
17
18
  export * from './invalidLexerMode';
18
19
  export * from './lexemeNotMatchedAtPosition';
@@ -29,6 +29,7 @@ __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("./regexNotMatchedAfterPosition"), exports);
32
33
  __exportStar(require("./partValidationFailed"), exports);
33
34
  __exportStar(require("./invalidLexerMode"), exports);
34
35
  __exportStar(require("./lexemeNotMatchedAtPosition"), exports);
@@ -1,5 +1,5 @@
1
- import { RGXError, RGXPartValidationFailedError, RGXRegexNotMatchedAtPositionError } from "./";
2
- export type LexemeNotMatchedCauseError = RGXRegexNotMatchedAtPositionError | RGXPartValidationFailedError;
1
+ import { RGXError, RGXPartValidationFailedError, RGXRegexNotMatchedAfterPositionError, RGXRegexNotMatchedAtPositionError } from "./";
2
+ export type LexemeNotMatchedCauseError = RGXRegexNotMatchedAtPositionError | RGXPartValidationFailedError | RGXRegexNotMatchedAfterPositionError;
3
3
  export type LexemeNotMatchedCause = {
4
4
  id: string;
5
5
  error: LexemeNotMatchedCauseError;
@@ -4,7 +4,7 @@ exports.RGXLexemeNotMatchedAtPositionError = void 0;
4
4
  exports.isLexemeNotMatchedCauseError = isLexemeNotMatchedCauseError;
5
5
  const errors_1 = require("./");
6
6
  function isLexemeNotMatchedCauseError(error) {
7
- return error instanceof errors_1.RGXRegexNotMatchedAtPositionError || error instanceof errors_1.RGXPartValidationFailedError;
7
+ return error instanceof errors_1.RGXRegexNotMatchedAtPositionError || error instanceof errors_1.RGXPartValidationFailedError || error instanceof errors_1.RGXRegexNotMatchedAfterPositionError;
8
8
  }
9
9
  class RGXLexemeNotMatchedAtPositionError extends errors_1.RGXError {
10
10
  get position() {
@@ -0,0 +1,15 @@
1
+ import { RGXError } from "./base";
2
+ export declare class RGXRegexNotMatchedAfterPositionError 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.RGXRegexNotMatchedAfterPositionError = void 0;
4
+ const base_1 = require("./base");
5
+ const outOfBounds_1 = require("./outOfBounds");
6
+ class RGXRegexNotMatchedAfterPositionError extends base_1.RGXError {
7
+ set position(value) {
8
+ (0, outOfBounds_1.assertInRange)(value, { min: 0, max: this.source.length }, "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_AFTER_POSITION');
16
+ this.name = 'RGXRegexNotMatchedAfterPositionError';
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.RGXRegexNotMatchedAfterPositionError = RGXRegexNotMatchedAfterPositionError;
@@ -1,4 +1,5 @@
1
1
  export * from "./regexMatchAtPosition";
2
+ export * from "./regexMatchAfterPosition";
2
3
  export * from "./regexWithFlags";
3
4
  export * from "./normalizeRegexFlags";
4
5
  export * from "./createRGXClassGuardFunction";
@@ -15,6 +15,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
17
  __exportStar(require("./regexMatchAtPosition"), exports);
18
+ __exportStar(require("./regexMatchAfterPosition"), exports);
18
19
  __exportStar(require("./regexWithFlags"), exports);
19
20
  __exportStar(require("./normalizeRegexFlags"), exports);
20
21
  __exportStar(require("./createRGXClassGuardFunction"), exports);
@@ -0,0 +1,6 @@
1
+ export declare function regexMatchAfterPosition(regex: RegExp, str: string, position: number, includeMatch: true): [number, RegExpExecArray] | null;
2
+ export declare function regexMatchAfterPosition(regex: RegExp, str: string, position: number, includeMatch?: false): [number, string] | null;
3
+ export declare function doesRegexMatchAfterPosition(regex: RegExp, str: string, position: number, includeMatch: true): [number, RegExpExecArray] | false;
4
+ export declare function doesRegexMatchAfterPosition(regex: RegExp, str: string, position: number, includeMatch?: false): boolean;
5
+ export declare function assertRegexMatchesAfterPosition(regex: RegExp, str: string, position: number, contextSize?: number | null, includeMatch?: false): [number, string];
6
+ export declare function assertRegexMatchesAfterPosition(regex: RegExp, str: string, position: number, contextSize: number | null | undefined, includeMatch: true): [number, RegExpExecArray];
@@ -0,0 +1,34 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.regexMatchAfterPosition = regexMatchAfterPosition;
4
+ exports.doesRegexMatchAfterPosition = doesRegexMatchAfterPosition;
5
+ exports.assertRegexMatchesAfterPosition = assertRegexMatchesAfterPosition;
6
+ const outOfBounds_1 = require("../errors/outOfBounds");
7
+ const regexWithFlags_1 = require("./regexWithFlags");
8
+ const errors_1 = require("../errors");
9
+ function regexMatchAfterPosition(regex, str, position, includeMatch = false) {
10
+ /*
11
+ The g flag means global mode, which searches forward from lastIndex rather
12
+ than requiring a match exactly there (as the y/sticky flag does). By setting
13
+ lastIndex to the position we want to search from, exec will find the next
14
+ match at or after that position.
15
+ */
16
+ (0, outOfBounds_1.assertInRange)(position, { min: 0, max: str.length, inclusiveRight: false }, 'String index out of bounds');
17
+ const globalRegex = (0, regexWithFlags_1.regexWithFlags)(regex, "g");
18
+ globalRegex.lastIndex = position;
19
+ const match = globalRegex.exec(str);
20
+ return match ? (includeMatch ? [match.index, match] : [match.index, match[0]]) : null;
21
+ }
22
+ function doesRegexMatchAfterPosition(regex, str, position, includeMatch = false) {
23
+ const match = regexMatchAfterPosition(regex, str, position, true);
24
+ if (includeMatch)
25
+ return match ?? false;
26
+ return match !== null;
27
+ }
28
+ function assertRegexMatchesAfterPosition(regex, str, position, contextSize = 10, includeMatch = false) {
29
+ const result = regexMatchAfterPosition(regex, str, position, true);
30
+ if (result === null) {
31
+ throw new errors_1.RGXRegexNotMatchedAfterPositionError("Regex not matched after index", regex, str, position, contextSize);
32
+ }
33
+ return includeMatch ? result : [result[0], result[1][0]];
34
+ }
@@ -7,6 +7,7 @@ export type RGXWalkerOptions<R, S = unknown> = {
7
7
  share?: S;
8
8
  infinite?: boolean;
9
9
  looping?: boolean;
10
+ contiguous?: boolean;
10
11
  };
11
12
  export type RGXTokenOrPart<R, S = unknown, T = unknown> = RGXToken | RGXPart<R, S, T>;
12
13
  export type RGXWalkerStepDirective = "stop" | "skip" | "silent";
@@ -21,7 +22,9 @@ export declare class RGXWalker<R, S = unknown> {
21
22
  namedCaptures: Record<string, RGXCapture[]>;
22
23
  infinite: boolean;
23
24
  looping: boolean;
25
+ contiguous: boolean;
24
26
  private _stopped;
27
+ private _didReachEnd;
25
28
  static check: (value: unknown) => value is RGXWalker<unknown, unknown>;
26
29
  static assert: (value: unknown) => asserts value is RGXWalker<unknown, unknown>;
27
30
  get sourcePosition(): number;
@@ -26,7 +26,9 @@ function createBranchGroups(tokens) {
26
26
  }
27
27
  }
28
28
  function isMatchError(e) {
29
- return e instanceof errors_1.RGXRegexNotMatchedAtPositionError || e instanceof errors_1.RGXPartValidationFailedError;
29
+ return (e instanceof errors_1.RGXRegexNotMatchedAtPositionError ||
30
+ e instanceof errors_1.RGXRegexNotMatchedAfterPositionError ||
31
+ e instanceof errors_1.RGXPartValidationFailedError);
30
32
  }
31
33
  class RGXWalker {
32
34
  get sourcePosition() {
@@ -50,6 +52,8 @@ class RGXWalker {
50
52
  this.captures = [];
51
53
  this.namedCaptures = {};
52
54
  this._stopped = false;
55
+ // Only relevant in infinite mode, tracking whether we've reached the end yet.
56
+ this._didReachEnd = false;
53
57
  this.source = source;
54
58
  this.sourcePosition = options.startingSourcePosition ?? 0;
55
59
  this.tokens = tokens;
@@ -58,6 +62,7 @@ class RGXWalker {
58
62
  this.share = options.share ?? null;
59
63
  this.infinite = options.infinite ?? false;
60
64
  this.looping = options.looping ?? false;
65
+ this.contiguous = options.contiguous ?? true;
61
66
  }
62
67
  stop() {
63
68
  this._stopped = true;
@@ -92,11 +97,24 @@ class RGXWalker {
92
97
  }
93
98
  capture(token, includeMatch = false) {
94
99
  const regex = (0, utils_1.createRegex)((0, resolve_1.resolveRGXToken)(part_1.RGXPart.check(token) ? token.token : token));
95
- const match = (0, utils_1.assertRegexMatchesAtPosition)(regex, this.source, this.sourcePosition, 10, true);
96
- this.sourcePosition += match[0].length;
100
+ const args = [regex, this.source, this.sourcePosition, 10, true];
101
+ let match;
102
+ let endPosition;
103
+ if (this.contiguous) {
104
+ match = (0, utils_1.assertRegexMatchesAtPosition)(...args);
105
+ endPosition = this.sourcePosition + match[0].length;
106
+ }
107
+ else {
108
+ const [startPosition, _match] = (0, utils_1.assertRegexMatchesAfterPosition)(...args);
109
+ match = _match;
110
+ endPosition = startPosition + match[0].length;
111
+ }
112
+ this.sourcePosition = endPosition;
97
113
  return includeMatch ? match : match[0];
98
114
  }
99
115
  advanceToken() {
116
+ if (this.tokenPosition === this.tokens.length - 1)
117
+ this._didReachEnd = true;
100
118
  if (!this.infinite || this.tokenPosition < this.tokens.length - 1) {
101
119
  this.tokenPosition++;
102
120
  if (this.looping && this.atTokenEnd()) {
@@ -144,7 +162,7 @@ class RGXWalker {
144
162
  return this.capture(branchedToken, true);
145
163
  }
146
164
  catch (e) {
147
- if (part !== null && e instanceof errors_1.RGXRegexNotMatchedAtPositionError) {
165
+ if (part !== null && (e instanceof errors_1.RGXRegexNotMatchedAtPositionError || e instanceof errors_1.RGXRegexNotMatchedAfterPositionError)) {
148
166
  const control = part.afterFailure?.(e, { part, walker: this });
149
167
  // If this happens, afterFailure itself stopped the walker, so we just need to respect that.
150
168
  if (this.stopped)
@@ -182,12 +200,12 @@ class RGXWalker {
182
200
  }
183
201
  handleAfterCapture(token, captureResult, silent, start) {
184
202
  const control = token.afterCapture?.(captureResult, { part: token, walker: this });
185
- // If this happens, afterCapture itself stopped the walker, so we just need to respect that.
186
- if (this.stopped)
187
- return "stop";
188
203
  if (!silent && (control === "skip" || control === "silent" || control === "stop-silent")) {
189
204
  this.unregisterLastCapture(token);
190
205
  }
206
+ // If this happens, afterCapture itself stopped the walker, so we just need to respect that.
207
+ if (this.stopped)
208
+ return "stop";
191
209
  if (control === "skip") {
192
210
  this.sourcePosition = start;
193
211
  return "skip";
@@ -221,17 +239,34 @@ class RGXWalker {
221
239
  }
222
240
  silent = dir === "silent";
223
241
  }
224
- const start = this.sourcePosition;
225
242
  const branchedToken = isPart ? createBranchGroups(token.token) : createBranchGroups(token);
226
- const captureAttempt = this.attemptCapture(branchedToken, isPart ? token : null);
227
- if (captureAttempt === "stop") {
228
- this._stopped = true;
229
- return null;
243
+ let captureAttempt;
244
+ try {
245
+ captureAttempt = this.attemptCapture(branchedToken, isPart ? token : null);
246
+ if (captureAttempt === "stop") {
247
+ this._stopped = true;
248
+ return null;
249
+ }
250
+ if (captureAttempt === "skip") {
251
+ this.advanceToken();
252
+ return null;
253
+ }
230
254
  }
231
- if (captureAttempt === "skip") {
232
- this.advanceToken();
233
- return null;
255
+ catch (e) {
256
+ if (e instanceof errors_1.RGXRegexNotMatchedAfterPositionError) {
257
+ // If we're in infinite mode, we've reached the end before, and we're at the end now,
258
+ // this is recoverable. Just stop the walker instead of throwing an error.
259
+ if (this.infinite && this._didReachEnd && this.tokenPosition === this.tokens.length - 1) {
260
+ this._stopped = true;
261
+ return null;
262
+ }
263
+ }
264
+ throw e;
234
265
  }
266
+ // The reason we no longer track start as the position before attempting capture
267
+ // is the possibility of non-contiguous matches. In that case, there may be a gap between
268
+ // the previous position and the actual start of the capture.
269
+ const start = this.sourcePosition - captureAttempt[0].length;
235
270
  const raw = isPart ? token.rawTransform(captureAttempt[0]) : captureAttempt[0];
236
271
  const end = this.sourcePosition;
237
272
  const value = isPart ? token.transform(raw) : raw;
@@ -1,7 +1,7 @@
1
1
  import { RGXToken } from "../types";
2
2
  import type { RGXWalker } from "./base";
3
3
  import { CloneDepth } from "@ptolemy2002/immutability-utils";
4
- import { RGXPartValidationFailedError, RGXRegexNotMatchedAtPositionError } from "../errors";
4
+ import { RGXPartValidationFailedError, RGXRegexNotMatchedAfterPositionError, RGXRegexNotMatchedAtPositionError } from "../errors";
5
5
  export type RGXPartControl = "skip" | "stop" | "silent" | "stop-silent" | void;
6
6
  export type RGXCapture<T = unknown> = {
7
7
  raw: string;
@@ -23,7 +23,7 @@ export type RGXPartOptions<R, S = unknown, T = string> = {
23
23
  validate: (captured: RGXCapture<T>, context: RGXPartContext<R, S, T>) => boolean | string;
24
24
  beforeCapture: ((context: RGXPartContext<R, S, T>) => RGXPartControl) | null;
25
25
  afterCapture: ((capture: RGXCapture<T>, context: RGXPartContext<R, S, T>) => RGXPartControl) | null;
26
- afterFailure: ((e: RGXRegexNotMatchedAtPositionError, context: RGXPartContext<R, S, T>) => RGXPartControl) | null;
26
+ afterFailure: ((e: RGXRegexNotMatchedAtPositionError | RGXRegexNotMatchedAfterPositionError, context: RGXPartContext<R, S, T>) => RGXPartControl) | null;
27
27
  afterValidationFailure: ((e: RGXPartValidationFailedError, context: RGXPartContext<R, S, T>) => RGXPartControl) | null;
28
28
  };
29
29
  export declare class RGXPart<R, S = unknown, T = string> {
@@ -38,6 +38,8 @@ class RGXPart {
38
38
  transform: this.transform,
39
39
  beforeCapture: this.beforeCapture,
40
40
  afterCapture: this.afterCapture,
41
+ afterFailure: this.afterFailure,
42
+ afterValidationFailure: this.afterValidationFailure,
41
43
  });
42
44
  }
43
45
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ptolemy2002/rgx",
3
- "version": "12.6.0",
3
+ "version": "12.7.1",
4
4
  "private": false,
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",