@ptolemy2002/rgx 12.5.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 +1 -0
- package/dist/errors/base.d.ts +1 -1
- package/dist/errors/index.d.ts +1 -0
- package/dist/errors/index.js +1 -0
- package/dist/errors/lexemeNotMatchedAtPosition.d.ts +2 -2
- package/dist/errors/lexemeNotMatchedAtPosition.js +1 -1
- package/dist/errors/regexNotMatchedAfterPosition.d.ts +15 -0
- package/dist/errors/regexNotMatchedAfterPosition.js +53 -0
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.js +1 -0
- package/dist/utils/regexMatchAfterPosition.d.ts +6 -0
- package/dist/utils/regexMatchAfterPosition.js +34 -0
- package/dist/walker/base.d.ts +12 -0
- package/dist/walker/base.js +168 -75
- package/dist/walker/part.d.ts +4 -4
- package/dist/walker/part.js +2 -0
- package/package.json +1 -1
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.
|
package/dist/errors/base.d.ts
CHANGED
|
@@ -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;
|
package/dist/errors/index.d.ts
CHANGED
|
@@ -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';
|
package/dist/errors/index.js
CHANGED
|
@@ -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;
|
package/dist/utils/index.d.ts
CHANGED
package/dist/utils/index.js
CHANGED
|
@@ -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
|
+
}
|
package/dist/walker/base.d.ts
CHANGED
|
@@ -7,8 +7,10 @@ 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>;
|
|
13
|
+
export type RGXWalkerStepDirective = "stop" | "skip" | "silent";
|
|
12
14
|
export declare class RGXWalker<R, S = unknown> {
|
|
13
15
|
readonly source: string;
|
|
14
16
|
_sourcePosition: number;
|
|
@@ -20,7 +22,9 @@ export declare class RGXWalker<R, S = unknown> {
|
|
|
20
22
|
namedCaptures: Record<string, RGXCapture[]>;
|
|
21
23
|
infinite: boolean;
|
|
22
24
|
looping: boolean;
|
|
25
|
+
contiguous: boolean;
|
|
23
26
|
private _stopped;
|
|
27
|
+
private _didReachEnd;
|
|
24
28
|
static check: (value: unknown) => value is RGXWalker<unknown, unknown>;
|
|
25
29
|
static assert: (value: unknown) => asserts value is RGXWalker<unknown, unknown>;
|
|
26
30
|
get sourcePosition(): number;
|
|
@@ -39,6 +43,14 @@ export declare class RGXWalker<R, S = unknown> {
|
|
|
39
43
|
remainingSource(): string | null;
|
|
40
44
|
capture(token: RGXTokenOrPart<R, S>, includeMatch: true): RegExpExecArray;
|
|
41
45
|
capture(token: RGXTokenOrPart<R, S>, includeMatch?: false): string;
|
|
46
|
+
private advanceToken;
|
|
47
|
+
private determineBranch;
|
|
48
|
+
private registerCapture;
|
|
49
|
+
private unregisterLastCapture;
|
|
50
|
+
private handleBeforeCapture;
|
|
51
|
+
private attemptCapture;
|
|
52
|
+
private validateCapture;
|
|
53
|
+
private handleAfterCapture;
|
|
42
54
|
step(): RGXCapture | null;
|
|
43
55
|
stepToToken(predicate: (token: RGXTokenOrPart<R>) => boolean): this;
|
|
44
56
|
stepToPart(predicate?: (part: RGXPart<R, S, unknown>) => boolean): this;
|
package/dist/walker/base.js
CHANGED
|
@@ -26,7 +26,9 @@ function createBranchGroups(tokens) {
|
|
|
26
26
|
}
|
|
27
27
|
}
|
|
28
28
|
function isMatchError(e) {
|
|
29
|
-
return e instanceof errors_1.RGXRegexNotMatchedAtPositionError ||
|
|
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,10 +97,123 @@ 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
|
|
96
|
-
|
|
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
|
}
|
|
115
|
+
advanceToken() {
|
|
116
|
+
if (this.tokenPosition === this.tokens.length - 1)
|
|
117
|
+
this._didReachEnd = true;
|
|
118
|
+
if (!this.infinite || this.tokenPosition < this.tokens.length - 1) {
|
|
119
|
+
this.tokenPosition++;
|
|
120
|
+
if (this.looping && this.atTokenEnd()) {
|
|
121
|
+
this.tokenPosition = 0;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
determineBranch(capture) {
|
|
126
|
+
for (let i = 0; i < capture.length - 1; i++) {
|
|
127
|
+
if (capture.groups?.[`rgx_branch_${i}`] !== undefined)
|
|
128
|
+
return i;
|
|
129
|
+
}
|
|
130
|
+
return 0;
|
|
131
|
+
}
|
|
132
|
+
registerCapture(captureResult, token) {
|
|
133
|
+
this.captures.push(captureResult);
|
|
134
|
+
if (token instanceof part_1.RGXPart && token.hasId()) {
|
|
135
|
+
if (!(token.id in this.namedCaptures))
|
|
136
|
+
this.namedCaptures[token.id] = [];
|
|
137
|
+
this.namedCaptures[token.id].push(captureResult);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
unregisterLastCapture(token) {
|
|
141
|
+
this.captures.pop();
|
|
142
|
+
if (token instanceof part_1.RGXPart && token.hasId()) {
|
|
143
|
+
this.namedCaptures[token.id].pop();
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
handleBeforeCapture(token) {
|
|
147
|
+
const control = token.beforeCapture?.({ part: token, walker: this });
|
|
148
|
+
// If this happens, beforeCapture itself stopped the walker, so we just need to respect that.
|
|
149
|
+
if (this.stopped)
|
|
150
|
+
return "stop";
|
|
151
|
+
if (control === "stop" || control === "stop-silent")
|
|
152
|
+
return "stop";
|
|
153
|
+
if (control === "skip")
|
|
154
|
+
return "skip";
|
|
155
|
+
if (control === "silent")
|
|
156
|
+
return "silent";
|
|
157
|
+
return null;
|
|
158
|
+
}
|
|
159
|
+
// Returns the capture on success, or a directive on handled failure. Unhandled errors are rethrown.
|
|
160
|
+
attemptCapture(branchedToken, part) {
|
|
161
|
+
try {
|
|
162
|
+
return this.capture(branchedToken, true);
|
|
163
|
+
}
|
|
164
|
+
catch (e) {
|
|
165
|
+
if (part !== null && (e instanceof errors_1.RGXRegexNotMatchedAtPositionError || e instanceof errors_1.RGXRegexNotMatchedAfterPositionError)) {
|
|
166
|
+
const control = part.afterFailure?.(e, { part, walker: this });
|
|
167
|
+
// If this happens, afterFailure itself stopped the walker, so we just need to respect that.
|
|
168
|
+
if (this.stopped)
|
|
169
|
+
return "stop";
|
|
170
|
+
if (control === "stop" || control === "stop-silent")
|
|
171
|
+
return "stop";
|
|
172
|
+
if (control === "skip")
|
|
173
|
+
return "skip";
|
|
174
|
+
// Handling silent is pointless here since it won't add a capture in either case, so we don't check for it.
|
|
175
|
+
}
|
|
176
|
+
throw e;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
// Returns a directive on handled validation failure, null on success. Unhandled errors are rethrown.
|
|
180
|
+
validateCapture(token, captureResult, start) {
|
|
181
|
+
try {
|
|
182
|
+
token.validate(captureResult, { part: token, walker: this });
|
|
183
|
+
return null;
|
|
184
|
+
}
|
|
185
|
+
catch (e) {
|
|
186
|
+
this.sourcePosition = start; // Reset source position on validation failure
|
|
187
|
+
if (e instanceof errors_1.RGXPartValidationFailedError) {
|
|
188
|
+
const control = token.afterValidationFailure?.(e, { part: token, walker: this });
|
|
189
|
+
// If this happens, afterValidationFailure itself stopped the walker, so we just need to respect that.
|
|
190
|
+
if (this.stopped)
|
|
191
|
+
return "stop";
|
|
192
|
+
if (control === "stop" || control === "stop-silent")
|
|
193
|
+
return "stop";
|
|
194
|
+
if (control === "skip")
|
|
195
|
+
return "skip";
|
|
196
|
+
// Handling silent is pointless here since it won't add a capture in either case, so we don't check for it.
|
|
197
|
+
}
|
|
198
|
+
throw e;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
handleAfterCapture(token, captureResult, silent, start) {
|
|
202
|
+
const control = token.afterCapture?.(captureResult, { part: token, walker: this });
|
|
203
|
+
if (!silent && (control === "skip" || control === "silent" || control === "stop-silent")) {
|
|
204
|
+
this.unregisterLastCapture(token);
|
|
205
|
+
}
|
|
206
|
+
// If this happens, afterCapture itself stopped the walker, so we just need to respect that.
|
|
207
|
+
if (this.stopped)
|
|
208
|
+
return "stop";
|
|
209
|
+
if (control === "skip") {
|
|
210
|
+
this.sourcePosition = start;
|
|
211
|
+
return "skip";
|
|
212
|
+
}
|
|
213
|
+
if (control === "stop" || control === "stop-silent")
|
|
214
|
+
return "stop";
|
|
215
|
+
return null;
|
|
216
|
+
}
|
|
99
217
|
step() {
|
|
100
218
|
if (!this.infinite && !this.looping && this.atTokenEnd()) {
|
|
101
219
|
this._stopped = true;
|
|
@@ -109,106 +227,81 @@ class RGXWalker {
|
|
|
109
227
|
const token = this.currentToken();
|
|
110
228
|
const isPart = token instanceof part_1.RGXPart;
|
|
111
229
|
let silent = false;
|
|
112
|
-
// Ask Part what to do — control flow via return values, not flags.
|
|
113
230
|
if (isPart) {
|
|
114
|
-
const
|
|
115
|
-
if (
|
|
231
|
+
const dir = this.handleBeforeCapture(token);
|
|
232
|
+
if (dir === "stop") {
|
|
116
233
|
this._stopped = true;
|
|
117
234
|
return null;
|
|
118
235
|
}
|
|
119
|
-
if (
|
|
120
|
-
this.
|
|
236
|
+
if (dir === "skip") {
|
|
237
|
+
this.advanceToken();
|
|
121
238
|
return null;
|
|
122
239
|
}
|
|
123
|
-
|
|
124
|
-
silent = true;
|
|
125
|
-
}
|
|
240
|
+
silent = dir === "silent";
|
|
126
241
|
}
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
let branchedToken;
|
|
130
|
-
if (isPart)
|
|
131
|
-
branchedToken = createBranchGroups(token.token);
|
|
132
|
-
else
|
|
133
|
-
branchedToken = createBranchGroups(token);
|
|
134
|
-
let capture;
|
|
242
|
+
const branchedToken = isPart ? createBranchGroups(token.token) : createBranchGroups(token);
|
|
243
|
+
let captureAttempt;
|
|
135
244
|
try {
|
|
136
|
-
|
|
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
|
+
}
|
|
137
254
|
}
|
|
138
255
|
catch (e) {
|
|
139
|
-
if (
|
|
140
|
-
|
|
141
|
-
|
|
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) {
|
|
142
260
|
this._stopped = true;
|
|
143
261
|
return null;
|
|
144
262
|
}
|
|
145
|
-
if (control === "skip") {
|
|
146
|
-
this.tokenPosition++;
|
|
147
|
-
return null;
|
|
148
|
-
}
|
|
149
|
-
// Handling silent is pointless here since it won't add a capture in either case, so we don't check for it.
|
|
150
263
|
}
|
|
151
264
|
throw e;
|
|
152
265
|
}
|
|
153
|
-
|
|
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;
|
|
270
|
+
const raw = isPart ? token.rawTransform(captureAttempt[0]) : captureAttempt[0];
|
|
154
271
|
const end = this.sourcePosition;
|
|
155
272
|
const value = isPart ? token.transform(raw) : raw;
|
|
156
|
-
let branch = 0;
|
|
157
|
-
// Determine branch index for captureResult by finding the first index
|
|
158
|
-
// with non-undefined match group.
|
|
159
|
-
for (let i = 0; i < capture.length - 1; i++) {
|
|
160
|
-
const branchKey = `rgx_branch_${i}`;
|
|
161
|
-
if (capture.groups && capture.groups[branchKey] !== undefined) {
|
|
162
|
-
branch = i;
|
|
163
|
-
break;
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
273
|
const captureResult = {
|
|
167
|
-
raw, value, start, end,
|
|
274
|
+
raw, value, start, end,
|
|
275
|
+
branch: this.determineBranch(captureAttempt),
|
|
168
276
|
ownerId: isPart && token.hasId() ? token.id : null,
|
|
169
|
-
groups:
|
|
277
|
+
groups: captureAttempt.groups ?? null
|
|
170
278
|
};
|
|
171
|
-
// Validate the part. If validation fails, it will throw an error, so nothing below will run.
|
|
172
279
|
if (isPart) {
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
this.sourcePosition = start; // Reset source position on validation failure
|
|
178
|
-
if (e instanceof errors_1.RGXPartValidationFailedError) {
|
|
179
|
-
const control = token.afterValidationFailure?.(e, { part: token, walker: this });
|
|
180
|
-
if (control === "stop") {
|
|
181
|
-
this._stopped = true;
|
|
182
|
-
return null;
|
|
183
|
-
}
|
|
184
|
-
if (control === "skip") {
|
|
185
|
-
this.tokenPosition++;
|
|
186
|
-
return null;
|
|
187
|
-
}
|
|
188
|
-
// Handling silent is pointless here since it won't add a capture in either case, so we don't check for it.
|
|
189
|
-
}
|
|
190
|
-
throw e;
|
|
280
|
+
const dir = this.validateCapture(token, captureResult, start);
|
|
281
|
+
if (dir === "stop") {
|
|
282
|
+
this._stopped = true;
|
|
283
|
+
return null;
|
|
191
284
|
}
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
this.captures.push(captureResult);
|
|
196
|
-
if (isPart && token.hasId()) {
|
|
197
|
-
if (!(token.id in this.namedCaptures))
|
|
198
|
-
this.namedCaptures[token.id] = [];
|
|
199
|
-
this.namedCaptures[token.id].push(captureResult);
|
|
285
|
+
if (dir === "skip") {
|
|
286
|
+
this.advanceToken();
|
|
287
|
+
return null;
|
|
200
288
|
}
|
|
201
289
|
}
|
|
202
|
-
|
|
290
|
+
if (!silent)
|
|
291
|
+
this.registerCapture(captureResult, token);
|
|
203
292
|
if (isPart) {
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
293
|
+
const dir = this.handleAfterCapture(token, captureResult, silent, start);
|
|
294
|
+
if (dir === "stop") {
|
|
295
|
+
this.advanceToken();
|
|
296
|
+
this._stopped = true;
|
|
297
|
+
return null;
|
|
298
|
+
}
|
|
299
|
+
if (dir === "skip") {
|
|
300
|
+
this.advanceToken();
|
|
301
|
+
return null;
|
|
210
302
|
}
|
|
211
303
|
}
|
|
304
|
+
this.advanceToken();
|
|
212
305
|
return captureResult;
|
|
213
306
|
}
|
|
214
307
|
stepToToken(predicate) {
|
package/dist/walker/part.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
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";
|
|
5
|
-
export type RGXPartControl = "skip" | "stop" | "silent" | void;
|
|
4
|
+
import { RGXPartValidationFailedError, RGXRegexNotMatchedAfterPositionError, RGXRegexNotMatchedAtPositionError } from "../errors";
|
|
5
|
+
export type RGXPartControl = "skip" | "stop" | "silent" | "stop-silent" | void;
|
|
6
6
|
export type RGXCapture<T = unknown> = {
|
|
7
7
|
raw: string;
|
|
8
8
|
value: T;
|
|
@@ -22,8 +22,8 @@ export type RGXPartOptions<R, S = unknown, T = string> = {
|
|
|
22
22
|
transform: (captured: string) => T;
|
|
23
23
|
validate: (captured: RGXCapture<T>, context: RGXPartContext<R, S, T>) => boolean | string;
|
|
24
24
|
beforeCapture: ((context: RGXPartContext<R, S, T>) => RGXPartControl) | null;
|
|
25
|
-
afterCapture: ((capture: RGXCapture<T>, context: RGXPartContext<R, S, T>) =>
|
|
26
|
-
afterFailure: ((e: RGXRegexNotMatchedAtPositionError, context: RGXPartContext<R, S, T>) => RGXPartControl) | null;
|
|
25
|
+
afterCapture: ((capture: RGXCapture<T>, 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> {
|
package/dist/walker/part.js
CHANGED