regex-partial-match 1.2.0 → 2.0.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 +169 -178
- package/lib/core/corePartialMatchRegExp.d.ts +24 -0
- package/lib/core/corePartialMatchRegExp.js +22 -0
- package/lib/core/index.d.ts +4 -0
- package/lib/core/index.js +2 -0
- package/lib/{extend.d.ts → extend/index.d.ts} +6 -7
- package/lib/{extend.js → extend/index.js} +1 -1
- package/lib/features/index.d.ts +20 -0
- package/lib/features/index.js +23 -0
- package/lib/hitEnd/index.d.ts +45 -0
- package/lib/hitEnd/index.js +57 -0
- package/lib/hitEnd/matchHitEnd.d.ts +4 -0
- package/lib/hitEnd/matchHitEnd.js +51 -0
- package/lib/hitEnd/partRole.d.ts +3 -0
- package/lib/hitEnd/partRole.js +17 -0
- package/lib/hitEnd/truncationProbe.d.ts +8 -0
- package/lib/hitEnd/truncationProbe.js +187 -0
- package/lib/hitEnd/truncationProbeCache.d.ts +14 -0
- package/lib/hitEnd/truncationProbeCache.js +1 -0
- package/lib/index.d.ts +4 -0
- package/lib/index.js +4 -0
- package/lib/modules/backreferences/asNativeAtom.d.ts +2 -0
- package/lib/modules/backreferences/asNativeAtom.js +10 -0
- package/lib/modules/backreferences/asPreScanPart.d.ts +2 -0
- package/lib/modules/backreferences/asPreScanPart.js +8 -0
- package/lib/modules/backreferences/backreferenceRecorder.d.ts +2 -0
- package/lib/modules/backreferences/backreferenceRecorder.js +38 -0
- package/lib/modules/backreferences/caseFoldFlags.d.ts +1 -0
- package/lib/modules/backreferences/caseFoldFlags.js +3 -0
- package/lib/modules/backreferences/compileDynamic.d.ts +4 -0
- package/lib/modules/backreferences/compileDynamic.js +53 -0
- package/lib/modules/backreferences/compiledBackreferences.d.ts +9 -0
- package/lib/modules/backreferences/compiledBackreferences.js +9 -0
- package/lib/modules/backreferences/constants.d.ts +2 -0
- package/lib/modules/backreferences/constants.js +2 -0
- package/lib/modules/backreferences/execDynamic.d.ts +2 -0
- package/lib/modules/backreferences/execDynamic.js +43 -0
- package/lib/modules/backreferences/flagsAtBackreference.d.ts +2 -0
- package/lib/modules/backreferences/flagsAtBackreference.js +4 -0
- package/lib/modules/backreferences/groupName.d.ts +2 -0
- package/lib/modules/backreferences/groupName.js +8 -0
- package/lib/modules/backreferences/index.d.ts +10 -0
- package/lib/modules/backreferences/index.js +20 -0
- package/lib/modules/backreferences/longestBakedPrefixEndingInput.d.ts +1 -0
- package/lib/modules/backreferences/longestBakedPrefixEndingInput.js +46 -0
- package/lib/modules/backreferences/renderParts.d.ts +2 -0
- package/lib/modules/backreferences/renderParts.js +9 -0
- package/lib/modules/backreferences/resolvedFromScan.d.ts +2 -0
- package/lib/modules/backreferences/resolvedFromScan.js +10 -0
- package/lib/modules/backreferences/startsWithUnderFlags.d.ts +1 -0
- package/lib/modules/backreferences/startsWithUnderFlags.js +7 -0
- package/lib/modules/carets/appendMultilineCaret.d.ts +7 -0
- package/lib/modules/carets/appendMultilineCaret.js +181 -0
- package/lib/modules/carets/caretRecorder.d.ts +2 -0
- package/lib/modules/carets/caretRecorder.js +164 -0
- package/lib/modules/carets/caretSyntax.d.ts +3 -0
- package/lib/modules/carets/caretSyntax.js +11 -0
- package/lib/modules/carets/index.d.ts +9 -0
- package/lib/modules/carets/index.js +18 -0
- package/lib/modules/carets/lineTerminator.d.ts +1 -0
- package/lib/modules/carets/lineTerminator.js +31 -0
- package/lib/modules/carets/needsCaretRules.d.ts +1 -0
- package/lib/modules/carets/needsCaretRules.js +15 -0
- package/lib/modules/carets/quantifierBounds.d.ts +3 -0
- package/lib/modules/carets/quantifierBounds.js +18 -0
- package/lib/modules/index.d.ts +3 -0
- package/lib/modules/index.js +2 -0
- package/lib/partialMatchRegExp/atomSyntax.d.ts +17 -0
- package/lib/partialMatchRegExp/atomSyntax.js +27 -0
- package/lib/partialMatchRegExp/backreferenceExpansion.d.ts +5 -0
- package/lib/partialMatchRegExp/backreferenceExpansion.js +1 -0
- package/lib/partialMatchRegExp/backreferenceHook.d.ts +11 -0
- package/lib/partialMatchRegExp/backreferenceHook.js +1 -0
- package/lib/partialMatchRegExp/caretHook.d.ts +17 -0
- package/lib/partialMatchRegExp/caretHook.js +5 -0
- package/lib/partialMatchRegExp/compilePartial/compileWith.d.ts +3 -0
- package/lib/partialMatchRegExp/compilePartial/compileWith.js +36 -0
- package/lib/partialMatchRegExp/compilePartial/compiled.d.ts +29 -0
- package/lib/partialMatchRegExp/compilePartial/compiled.js +14 -0
- package/lib/partialMatchRegExp/compilePartial/constants.d.ts +4 -0
- package/lib/partialMatchRegExp/compilePartial/constants.js +4 -0
- package/lib/partialMatchRegExp/compilePartial/groupShape.d.ts +4 -0
- package/lib/partialMatchRegExp/compilePartial/groupShape.js +9 -0
- package/lib/partialMatchRegExp/compilePartial/toStatic.d.ts +3 -0
- package/lib/partialMatchRegExp/compilePartial/toStatic.js +4 -0
- package/lib/partialMatchRegExp/constants.d.ts +5 -0
- package/lib/partialMatchRegExp/constants.js +5 -0
- package/lib/partialMatchRegExp/createPartialMatchRegExp.d.ts +3 -0
- package/lib/partialMatchRegExp/createPartialMatchRegExp.js +21 -0
- package/lib/partialMatchRegExp/execFrom.d.ts +1 -0
- package/lib/partialMatchRegExp/execFrom.js +4 -0
- package/lib/partialMatchRegExp/index.d.ts +49 -0
- package/lib/partialMatchRegExp/index.js +31 -0
- package/lib/partialMatchRegExp/legacyEscape.d.ts +2 -0
- package/lib/partialMatchRegExp/legacyEscape.js +18 -0
- package/lib/partialMatchRegExp/moduleHooks.d.ts +14 -0
- package/lib/partialMatchRegExp/moduleHooks.js +15 -0
- package/lib/partialMatchRegExp/opaqueModule.d.ts +9 -0
- package/lib/partialMatchRegExp/opaqueModule.js +1 -0
- package/lib/partialMatchRegExp/part.d.ts +15 -0
- package/lib/partialMatchRegExp/part.js +2 -0
- package/lib/partialMatchRegExp/partialMatchInternals.d.ts +3 -0
- package/lib/partialMatchRegExp/partialMatchInternals.js +4 -0
- package/lib/partialMatchRegExp/quantifier.d.ts +6 -0
- package/lib/partialMatchRegExp/quantifier.js +9 -0
- package/lib/partialMatchRegExp/rawLookaroundInfo.d.ts +11 -0
- package/lib/partialMatchRegExp/rawLookaroundInfo.js +1 -0
- package/lib/partialMatchRegExp/regexFeatures.d.ts +2 -0
- package/lib/partialMatchRegExp/regexFeatures.js +41 -0
- package/lib/partialMatchRegExp/scope.d.ts +12 -0
- package/lib/partialMatchRegExp/scope.js +45 -0
- package/lib/partialMatchRegExp/walk.d.ts +16 -0
- package/lib/partialMatchRegExp/walk.js +412 -0
- package/lib/partialMatchRegExp/withModules.d.ts +13 -0
- package/lib/partialMatchRegExp/withModules.js +27 -0
- package/package.json +46 -53
- package/lib/compilePartial.d.ts +0 -26
- package/lib/compilePartial.js +0 -397
- package/lib/partialMatchRegExp.d.ts +0 -52
- package/lib/partialMatchRegExp.js +0 -126
- /package/lib/{escapeAtom.d.ts → modules/backreferences/escapeAtom.d.ts} +0 -0
- /package/lib/{escapeAtom.js → modules/backreferences/escapeAtom.js} +0 -0
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type PartialMatchRegExp from "../partialMatchRegExp/index.ts";
|
|
2
|
+
/**
|
|
3
|
+
* Whether the engine reached the end of the input while producing `match` —
|
|
4
|
+
* a match `partial` produced — so that more input could change it.
|
|
5
|
+
*
|
|
6
|
+
* The contract is that of the JDK's `Matcher.hitEnd()`: when this returns
|
|
7
|
+
* `false`, no continuation of the input changes the match's index or text,
|
|
8
|
+
* and its captures are the ones the original pattern produces, except for
|
|
9
|
+
* the two limits noted under `@remarks` below, where `false` is reported
|
|
10
|
+
* despite a read of the end. `true` means the match read the end of the
|
|
11
|
+
* input — an atom ran out of input, a greedy quantifier stopped there with
|
|
12
|
+
* nothing left to read, or `$`, `\b` or `\B` held there — so more input
|
|
13
|
+
* could extend it, change which alternative wins, or invalidate it, and
|
|
14
|
+
* its captures are the closest to what a full match would report rather
|
|
15
|
+
* than final.
|
|
16
|
+
*
|
|
17
|
+
* `false` is not "is a match": `/hello \w+/` matches `"hello world"` in full
|
|
18
|
+
* and still reports `true`, since `\w+` read the end looking for more. An
|
|
19
|
+
* exact-length match that consumed the last character without reading past
|
|
20
|
+
* it reports `false`.
|
|
21
|
+
*
|
|
22
|
+
* @param partial - The `PartialMatchRegExp` instance that produced `match`
|
|
23
|
+
* @param match - A match returned by `partial.exec()`
|
|
24
|
+
* @returns `true` when the match read the end of the input, `false` when it
|
|
25
|
+
* settled short of it
|
|
26
|
+
*
|
|
27
|
+
* @remarks
|
|
28
|
+
* Conservative where the JDK is exact in one place: a bounded greedy
|
|
29
|
+
* quantifier (`?`, `{n,m}`) fully taken at the end of the input reports
|
|
30
|
+
* `true` — on a group, and the same way for an unequal-bound `{n,m}`
|
|
31
|
+
* directly on a single atom once it's saturated at its maximum. Cannot see
|
|
32
|
+
* a read of the end inside a raw lookaround, or inside a lookahead in an
|
|
33
|
+
* earlier iteration of a quantified group.
|
|
34
|
+
*
|
|
35
|
+
* @example
|
|
36
|
+
* ```typescript
|
|
37
|
+
* import PartialMatchRegExp, { hitEnd } from "regex-partial-match";
|
|
38
|
+
*
|
|
39
|
+
* const partial = new PartialMatchRegExp(/^\d{4}-\d{2}-\d{2}/);
|
|
40
|
+
* const match = partial.exec('2024-06');
|
|
41
|
+
*
|
|
42
|
+
* match !== null && hitEnd(partial, match); // true - keep typing
|
|
43
|
+
* ```
|
|
44
|
+
*/
|
|
45
|
+
export default function hitEnd(partial: PartialMatchRegExp, match: RegExpExecArray): boolean;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { compiledOf } from "../partialMatchRegExp/partialMatchInternals.js";
|
|
2
|
+
import matchHitEnd from "./matchHitEnd.js";
|
|
3
|
+
/**
|
|
4
|
+
* Whether the engine reached the end of the input while producing `match` —
|
|
5
|
+
* a match `partial` produced — so that more input could change it.
|
|
6
|
+
*
|
|
7
|
+
* The contract is that of the JDK's `Matcher.hitEnd()`: when this returns
|
|
8
|
+
* `false`, no continuation of the input changes the match's index or text,
|
|
9
|
+
* and its captures are the ones the original pattern produces, except for
|
|
10
|
+
* the two limits noted under `@remarks` below, where `false` is reported
|
|
11
|
+
* despite a read of the end. `true` means the match read the end of the
|
|
12
|
+
* input — an atom ran out of input, a greedy quantifier stopped there with
|
|
13
|
+
* nothing left to read, or `$`, `\b` or `\B` held there — so more input
|
|
14
|
+
* could extend it, change which alternative wins, or invalidate it, and
|
|
15
|
+
* its captures are the closest to what a full match would report rather
|
|
16
|
+
* than final.
|
|
17
|
+
*
|
|
18
|
+
* `false` is not "is a match": `/hello \w+/` matches `"hello world"` in full
|
|
19
|
+
* and still reports `true`, since `\w+` read the end looking for more. An
|
|
20
|
+
* exact-length match that consumed the last character without reading past
|
|
21
|
+
* it reports `false`.
|
|
22
|
+
*
|
|
23
|
+
* @param partial - The `PartialMatchRegExp` instance that produced `match`
|
|
24
|
+
* @param match - A match returned by `partial.exec()`
|
|
25
|
+
* @returns `true` when the match read the end of the input, `false` when it
|
|
26
|
+
* settled short of it
|
|
27
|
+
*
|
|
28
|
+
* @remarks
|
|
29
|
+
* Conservative where the JDK is exact in one place: a bounded greedy
|
|
30
|
+
* quantifier (`?`, `{n,m}`) fully taken at the end of the input reports
|
|
31
|
+
* `true` — on a group, and the same way for an unequal-bound `{n,m}`
|
|
32
|
+
* directly on a single atom once it's saturated at its maximum. Cannot see
|
|
33
|
+
* a read of the end inside a raw lookaround, or inside a lookahead in an
|
|
34
|
+
* earlier iteration of a quantified group.
|
|
35
|
+
*
|
|
36
|
+
* @example
|
|
37
|
+
* ```typescript
|
|
38
|
+
* import PartialMatchRegExp, { hitEnd } from "regex-partial-match";
|
|
39
|
+
*
|
|
40
|
+
* const partial = new PartialMatchRegExp(/^\d{4}-\d{2}-\d{2}/);
|
|
41
|
+
* const match = partial.exec('2024-06');
|
|
42
|
+
*
|
|
43
|
+
* match !== null && hitEnd(partial, match); // true - keep typing
|
|
44
|
+
* ```
|
|
45
|
+
*/
|
|
46
|
+
export default function hitEnd(partial, match) {
|
|
47
|
+
var _a, _b;
|
|
48
|
+
const compiled = compiledOf(partial);
|
|
49
|
+
return matchHitEnd(compiled, match, ((_a = compiled.probeCache) !== null && _a !== void 0 ? _a : (compiled.probeCache = {
|
|
50
|
+
rawLookarounds: (_b = compiled.rawLookarounds) !== null && _b !== void 0 ? _b : [],
|
|
51
|
+
flags: partial.flags,
|
|
52
|
+
probe: undefined,
|
|
53
|
+
stickyPreScan: undefined,
|
|
54
|
+
expansions: [],
|
|
55
|
+
oldestExpansion: 0
|
|
56
|
+
})));
|
|
57
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { CompiledPartial } from "../partialMatchRegExp/compilePartial/compiled.ts";
|
|
2
|
+
import type { TruncationProbeCache } from "./truncationProbeCache.ts";
|
|
3
|
+
export declare const EXPANDED_PROBES_KEPT = 12;
|
|
4
|
+
export default function matchHitEnd(compiled: CompiledPartial, match: RegExpExecArray, cache: TruncationProbeCache): boolean;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { buildTruncationProbe, tookTruncationBranch } from "./truncationProbe.js";
|
|
2
|
+
import { backreferenceExpansion } from "../partialMatchRegExp/backreferenceExpansion.js";
|
|
3
|
+
import { FLAGS_IRRELEVANT_TO_REBUILD } from "../partialMatchRegExp/constants.js";
|
|
4
|
+
export const EXPANDED_PROBES_KEPT = 12;
|
|
5
|
+
export default function matchHitEnd(compiled, match, cache) {
|
|
6
|
+
const probe = "dynamic" in compiled
|
|
7
|
+
? dynamicProbe(compiled, match, cache)
|
|
8
|
+
: unexpandedProbe(compiled, cache);
|
|
9
|
+
return tookTruncationBranch(probe, match.input, match.index);
|
|
10
|
+
}
|
|
11
|
+
function dynamicProbe(compiled, match, cache) {
|
|
12
|
+
var _a;
|
|
13
|
+
const parts = (_a = match[backreferenceExpansion]) !== null && _a !== void 0 ? _a : expandedPartsAt(compiled, match, cache);
|
|
14
|
+
if (parts === undefined)
|
|
15
|
+
return unexpandedProbe(compiled, cache);
|
|
16
|
+
for (const expansion of cache.expansions) {
|
|
17
|
+
if (sameParts(expansion.parts, parts))
|
|
18
|
+
return expansion.probe;
|
|
19
|
+
}
|
|
20
|
+
const probe = probeOf(parts, cache);
|
|
21
|
+
cache.expansions[cache.oldestExpansion] = { parts, probe };
|
|
22
|
+
cache.oldestExpansion = (cache.oldestExpansion + 1) % EXPANDED_PROBES_KEPT;
|
|
23
|
+
return probe;
|
|
24
|
+
}
|
|
25
|
+
function unexpandedProbe(compiled, cache) {
|
|
26
|
+
var _a;
|
|
27
|
+
return ((_a = cache.probe) !== null && _a !== void 0 ? _a : (cache.probe = probeOf(compiled.parts, cache)));
|
|
28
|
+
}
|
|
29
|
+
function probeOf(parts, { rawLookarounds, flags }) {
|
|
30
|
+
return buildTruncationProbe(parts, rawLookarounds, flags);
|
|
31
|
+
}
|
|
32
|
+
function expandedPartsAt(compiled, match, cache) {
|
|
33
|
+
var _a;
|
|
34
|
+
const { preScan, expand } = compiled.dynamic;
|
|
35
|
+
(_a = cache.stickyPreScan) !== null && _a !== void 0 ? _a : (cache.stickyPreScan = new RegExp(preScan.source, cache.flags.replace(FLAGS_IRRELEVANT_TO_REBUILD, "") + "y"));
|
|
36
|
+
cache.stickyPreScan.lastIndex = match.index;
|
|
37
|
+
const capture = cache.stickyPreScan.exec(match.input);
|
|
38
|
+
if (capture === null)
|
|
39
|
+
return undefined;
|
|
40
|
+
const parts = expand(capture);
|
|
41
|
+
return parts.length === compiled.parts.length ? undefined : parts;
|
|
42
|
+
}
|
|
43
|
+
function sameParts(cached, parts) {
|
|
44
|
+
if (cached.length !== parts.length)
|
|
45
|
+
return false;
|
|
46
|
+
for (let index = 0; index < parts.length; index++) {
|
|
47
|
+
if (cached[index] !== parts[index])
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
return true;
|
|
51
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { NAMED_GROUP_OPENING, endsAtTruncationBranch, isRawLookaround } from "../partialMatchRegExp/atomSyntax.js";
|
|
2
|
+
import { isBackreference } from "../partialMatchRegExp/part.js";
|
|
3
|
+
function isGroupOpen(part) {
|
|
4
|
+
return (part === "(" ||
|
|
5
|
+
(part.startsWith(NAMED_GROUP_OPENING) && !isRawLookaround(part)));
|
|
6
|
+
}
|
|
7
|
+
export function roleOf(part) {
|
|
8
|
+
if (isBackreference(part))
|
|
9
|
+
return "backreference";
|
|
10
|
+
if (isRawLookaround(part))
|
|
11
|
+
return "rawLookaround";
|
|
12
|
+
if (isGroupOpen(part))
|
|
13
|
+
return "groupOpen";
|
|
14
|
+
if (endsAtTruncationBranch(part))
|
|
15
|
+
return "truncationEnd";
|
|
16
|
+
return "plain";
|
|
17
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type Part } from "../partialMatchRegExp/part.ts";
|
|
2
|
+
import type { RawLookarounds } from "../partialMatchRegExp/rawLookaroundInfo.ts";
|
|
3
|
+
export interface TruncationProbe {
|
|
4
|
+
regex: RegExp;
|
|
5
|
+
markerGroups: readonly number[];
|
|
6
|
+
}
|
|
7
|
+
export declare const buildTruncationProbe: (parts: readonly Part[], rawLookarounds: RawLookarounds, flags: string) => TruncationProbe;
|
|
8
|
+
export declare const tookTruncationBranch: (probe: TruncationProbe, input: string, index: number) => boolean;
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import { legacyEscapeAsLiteral } from "../partialMatchRegExp/legacyEscape.js";
|
|
2
|
+
import { FLAGS_IRRELEVANT_TO_REBUILD } from "../partialMatchRegExp/constants.js";
|
|
3
|
+
import { DISJUNCTION_TO_END_OF_INPUT, END_ANCHOR, OPTIONAL_ATOM_OPENING, isOptionalAtom, isWordBoundaryAtom } from "../partialMatchRegExp/atomSyntax.js";
|
|
4
|
+
import { isQuantifier, quantifierEndingAt } from "../partialMatchRegExp/quantifier.js";
|
|
5
|
+
import { isBackreference, isNumericBackreference } from "../partialMatchRegExp/part.js";
|
|
6
|
+
import { roleOf } from "./partRole.js";
|
|
7
|
+
const END_OF_INPUT = DISJUNCTION_TO_END_OF_INPUT.slice(1, -1);
|
|
8
|
+
const OPTIONAL_QUANTIFIER = "?";
|
|
9
|
+
const EXACT_QUANTIFIER = /^\{0*([1-9]\d*|0)(?:,0*\1)?\}$/;
|
|
10
|
+
const MARKING = {
|
|
11
|
+
none: 0,
|
|
12
|
+
groupOpen: 1,
|
|
13
|
+
rawLookaround: 2,
|
|
14
|
+
truncationBranch: 3,
|
|
15
|
+
wordBoundary: 4,
|
|
16
|
+
endAnchor: 5,
|
|
17
|
+
readAtEnd: 6,
|
|
18
|
+
optionalAtEnd: 7
|
|
19
|
+
};
|
|
20
|
+
function isGreedyReadAtEnd(parts, index) {
|
|
21
|
+
const part = parts[index];
|
|
22
|
+
return (isQuantifier(part) &&
|
|
23
|
+
!EXACT_QUANTIFIER.test(part) &&
|
|
24
|
+
parts[index + 1] !== OPTIONAL_QUANTIFIER &&
|
|
25
|
+
quantifierEndingAt(parts, index) === index);
|
|
26
|
+
}
|
|
27
|
+
function markingOf(parts, index) {
|
|
28
|
+
const part = parts[index];
|
|
29
|
+
switch (roleOf(part)) {
|
|
30
|
+
case "backreference":
|
|
31
|
+
return MARKING.truncationBranch;
|
|
32
|
+
case "rawLookaround":
|
|
33
|
+
return MARKING.rawLookaround;
|
|
34
|
+
case "groupOpen":
|
|
35
|
+
return MARKING.groupOpen;
|
|
36
|
+
case "truncationEnd":
|
|
37
|
+
return isWordBoundaryAtom(part)
|
|
38
|
+
? MARKING.wordBoundary
|
|
39
|
+
: MARKING.truncationBranch;
|
|
40
|
+
case "plain": {
|
|
41
|
+
if (part === END_ANCHOR)
|
|
42
|
+
return MARKING.endAnchor;
|
|
43
|
+
if (!isGreedyReadAtEnd(parts, index))
|
|
44
|
+
return MARKING.none;
|
|
45
|
+
const previous = parts[index - 1];
|
|
46
|
+
return part === OPTIONAL_QUANTIFIER &&
|
|
47
|
+
(isBackreference(previous) || isOptionalAtom(previous))
|
|
48
|
+
? MARKING.optionalAtEnd
|
|
49
|
+
: MARKING.readAtEnd;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
const markingsOf = (parts) => parts.map((_, index) => markingOf(parts, index));
|
|
54
|
+
function markersAddedBy(marking) {
|
|
55
|
+
switch (marking) {
|
|
56
|
+
case MARKING.none:
|
|
57
|
+
case MARKING.groupOpen:
|
|
58
|
+
case MARKING.rawLookaround:
|
|
59
|
+
return 0;
|
|
60
|
+
case MARKING.wordBoundary:
|
|
61
|
+
return 2;
|
|
62
|
+
default:
|
|
63
|
+
return 1;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
function groupShiftTable(markings, rawLookarounds) {
|
|
67
|
+
var _a, _b;
|
|
68
|
+
const shiftForGroup = [0];
|
|
69
|
+
let markerCount = 0;
|
|
70
|
+
let rawLookaroundIndex = 0;
|
|
71
|
+
for (const marking of markings) {
|
|
72
|
+
if (marking === MARKING.groupOpen) {
|
|
73
|
+
shiftForGroup.push(markerCount);
|
|
74
|
+
}
|
|
75
|
+
else if (marking === MARKING.rawLookaround) {
|
|
76
|
+
const capturingGroupsOpened = (_b = (_a = rawLookarounds[rawLookaroundIndex++]) === null || _a === void 0 ? void 0 : _a.capturingGroupsOpened) !== null && _b !== void 0 ? _b : 0;
|
|
77
|
+
for (let opened = 0; opened < capturingGroupsOpened; opened++) {
|
|
78
|
+
shiftForGroup.push(markerCount);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
markerCount += markersAddedBy(marking);
|
|
82
|
+
}
|
|
83
|
+
return shiftForGroup;
|
|
84
|
+
}
|
|
85
|
+
function renumberedToken(backreference, shiftForGroup) {
|
|
86
|
+
return isNumericBackreference(backreference)
|
|
87
|
+
? "\\" + String(backreference.ref + shiftForGroup[backreference.ref])
|
|
88
|
+
: "\\k<" + backreference.ref + ">";
|
|
89
|
+
}
|
|
90
|
+
function renumberRawBackreferences(part, info, shiftForGroup) {
|
|
91
|
+
let renumbered = "";
|
|
92
|
+
let cursor = 0;
|
|
93
|
+
for (const reference of info.references) {
|
|
94
|
+
const relativeStart = reference.start - info.sourceStart;
|
|
95
|
+
const relativeEnd = reference.end - info.sourceStart;
|
|
96
|
+
renumbered +=
|
|
97
|
+
part.slice(cursor, relativeStart) +
|
|
98
|
+
rawReferenceReplacement(part.slice(relativeStart, relativeEnd), reference, shiftForGroup);
|
|
99
|
+
cursor = relativeEnd;
|
|
100
|
+
}
|
|
101
|
+
return renumbered + part.slice(cursor);
|
|
102
|
+
}
|
|
103
|
+
function rawReferenceReplacement(spelling, { ref }, shiftForGroup) {
|
|
104
|
+
return ref >= 1 && ref < shiftForGroup.length
|
|
105
|
+
? "\\" + String(ref + shiftForGroup[ref])
|
|
106
|
+
: legacyEscapeAsLiteral(spelling.slice(1));
|
|
107
|
+
}
|
|
108
|
+
export const buildTruncationProbe = (parts, rawLookarounds, flags) => {
|
|
109
|
+
var _a;
|
|
110
|
+
const markings = markingsOf(parts);
|
|
111
|
+
const shiftForGroup = groupShiftTable(markings, rawLookarounds);
|
|
112
|
+
const markerGroups = [];
|
|
113
|
+
let groupsOpened = 0;
|
|
114
|
+
let rawLookaroundIndex = 0;
|
|
115
|
+
const marker = () => {
|
|
116
|
+
markerGroups.push(groupsOpened + markerGroups.length + 1);
|
|
117
|
+
return "()";
|
|
118
|
+
};
|
|
119
|
+
const truncationBranch = () => "|" + marker() + END_OF_INPUT + ")";
|
|
120
|
+
const readAtEnd = () => "(?:" + marker() + END_OF_INPUT + "|)";
|
|
121
|
+
const probed = [];
|
|
122
|
+
for (let index = 0; index < parts.length; index++) {
|
|
123
|
+
const part = parts[index];
|
|
124
|
+
if (isBackreference(part)) {
|
|
125
|
+
probed.push(OPTIONAL_ATOM_OPENING +
|
|
126
|
+
renumberedToken(part, shiftForGroup) +
|
|
127
|
+
truncationBranch());
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
switch (markings[index]) {
|
|
131
|
+
case MARKING.rawLookaround: {
|
|
132
|
+
const rawLookaround = rawLookarounds[rawLookaroundIndex++];
|
|
133
|
+
groupsOpened += (_a = rawLookaround === null || rawLookaround === void 0 ? void 0 : rawLookaround.capturingGroupsOpened) !== null && _a !== void 0 ? _a : 0;
|
|
134
|
+
probed.push(rawLookaround
|
|
135
|
+
? renumberRawBackreferences(part, rawLookaround, shiftForGroup)
|
|
136
|
+
: part);
|
|
137
|
+
break;
|
|
138
|
+
}
|
|
139
|
+
case MARKING.groupOpen:
|
|
140
|
+
groupsOpened++;
|
|
141
|
+
probed.push(part);
|
|
142
|
+
break;
|
|
143
|
+
case MARKING.truncationBranch:
|
|
144
|
+
probed.push(part.slice(0, -DISJUNCTION_TO_END_OF_INPUT.length) + truncationBranch());
|
|
145
|
+
break;
|
|
146
|
+
case MARKING.wordBoundary:
|
|
147
|
+
probed.push(part.slice(0, -DISJUNCTION_TO_END_OF_INPUT.length) +
|
|
148
|
+
readAtEnd() +
|
|
149
|
+
truncationBranch());
|
|
150
|
+
break;
|
|
151
|
+
case MARKING.endAnchor:
|
|
152
|
+
probed.push(END_ANCHOR + "(?:" + marker() + "(?![\\s\\S])|)");
|
|
153
|
+
break;
|
|
154
|
+
case MARKING.readAtEnd:
|
|
155
|
+
probed.push(part + readAtEnd());
|
|
156
|
+
break;
|
|
157
|
+
case MARKING.optionalAtEnd:
|
|
158
|
+
probed[probed.length - 1] =
|
|
159
|
+
OPTIONAL_ATOM_OPENING +
|
|
160
|
+
probed[probed.length - 1] +
|
|
161
|
+
"|" +
|
|
162
|
+
marker() +
|
|
163
|
+
END_OF_INPUT +
|
|
164
|
+
"|)";
|
|
165
|
+
break;
|
|
166
|
+
default:
|
|
167
|
+
probed.push(part);
|
|
168
|
+
break;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
return {
|
|
172
|
+
regex: new RegExp(probed.join(""), flags.replace(FLAGS_IRRELEVANT_TO_REBUILD, "") + "y"),
|
|
173
|
+
markerGroups
|
|
174
|
+
};
|
|
175
|
+
};
|
|
176
|
+
export const tookTruncationBranch = (probe, input, index) => {
|
|
177
|
+
const { regex, markerGroups } = probe;
|
|
178
|
+
regex.lastIndex = index;
|
|
179
|
+
const probed = regex.exec(input);
|
|
180
|
+
if (probed === null)
|
|
181
|
+
return true;
|
|
182
|
+
for (const group of markerGroups) {
|
|
183
|
+
if (probed[group] !== undefined)
|
|
184
|
+
return true;
|
|
185
|
+
}
|
|
186
|
+
return false;
|
|
187
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { Part } from "../partialMatchRegExp/part.ts";
|
|
2
|
+
import type { RawLookarounds } from "../partialMatchRegExp/rawLookaroundInfo.ts";
|
|
3
|
+
import type { TruncationProbe } from "./truncationProbe.ts";
|
|
4
|
+
export interface TruncationProbeCache {
|
|
5
|
+
rawLookarounds: RawLookarounds;
|
|
6
|
+
flags: string;
|
|
7
|
+
probe: TruncationProbe | undefined;
|
|
8
|
+
stickyPreScan: RegExp | undefined;
|
|
9
|
+
expansions: {
|
|
10
|
+
parts: readonly Part[];
|
|
11
|
+
probe: TruncationProbe;
|
|
12
|
+
}[];
|
|
13
|
+
oldestExpansion: number;
|
|
14
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/lib/index.d.ts
ADDED
package/lib/index.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { isNumericBackreference } from "../../partialMatchRegExp/part.js";
|
|
2
|
+
import { asOptionalAtom } from "../../partialMatchRegExp/atomSyntax.js";
|
|
3
|
+
function backrefToken(backref) {
|
|
4
|
+
return isNumericBackreference(backref)
|
|
5
|
+
? "\\" + String(backref.ref)
|
|
6
|
+
: "\\k<" + backref.ref + ">";
|
|
7
|
+
}
|
|
8
|
+
export default function asNativeAtom(backref) {
|
|
9
|
+
return asOptionalAtom(backrefToken(backref));
|
|
10
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { ANY_CAPTURED_TEXT } from "./constants.js";
|
|
2
|
+
import { isBackreference } from "../../partialMatchRegExp/part.js";
|
|
3
|
+
import asNativeAtom from "./asNativeAtom.js";
|
|
4
|
+
export default function asPreScanPart(part) {
|
|
5
|
+
if (!isBackreference(part))
|
|
6
|
+
return part;
|
|
7
|
+
return part.forward ? asNativeAtom(part) : ANY_CAPTURED_TEXT;
|
|
8
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { decodeGroupName, groupNameOf } from "./groupName.js";
|
|
2
|
+
import { isNumericBackreference } from "../../partialMatchRegExp/part.js";
|
|
3
|
+
import { CASE_INSENSITIVE } from "../../partialMatchRegExp/scope.js";
|
|
4
|
+
const UNNAMED_GROUP_OPENING = "(";
|
|
5
|
+
export default function backreferenceRecorder() {
|
|
6
|
+
let closedGroupNumbers;
|
|
7
|
+
let referencesSinceLastClose;
|
|
8
|
+
return {
|
|
9
|
+
groupClosed(groupNumber, opening) {
|
|
10
|
+
(closedGroupNumbers !== null && closedGroupNumbers !== void 0 ? closedGroupNumbers : (closedGroupNumbers = new Set())).add(groupNumber);
|
|
11
|
+
if (opening === UNNAMED_GROUP_OPENING)
|
|
12
|
+
return;
|
|
13
|
+
const name = decodeGroupName(groupNameOf(opening));
|
|
14
|
+
const earlierReferences = referencesSinceLastClose === null || referencesSinceLastClose === void 0 ? void 0 : referencesSinceLastClose.get(name);
|
|
15
|
+
if (earlierReferences === undefined)
|
|
16
|
+
return;
|
|
17
|
+
for (const earlier of earlierReferences)
|
|
18
|
+
earlier.forward = true;
|
|
19
|
+
referencesSinceLastClose === null || referencesSinceLastClose === void 0 ? void 0 : referencesSinceLastClose.delete(name);
|
|
20
|
+
},
|
|
21
|
+
backreference(backreference, scope) {
|
|
22
|
+
backreference.caseInsensitive = (scope & CASE_INSENSITIVE) !== 0;
|
|
23
|
+
if (isNumericBackreference(backreference)) {
|
|
24
|
+
backreference.forward = !(closedGroupNumbers === null || closedGroupNumbers === void 0 ? void 0 : closedGroupNumbers.has(backreference.ref));
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
const name = decodeGroupName(backreference.ref);
|
|
28
|
+
backreference.forward = false;
|
|
29
|
+
referencesSinceLastClose !== null && referencesSinceLastClose !== void 0 ? referencesSinceLastClose : (referencesSinceLastClose = new Map());
|
|
30
|
+
const references = referencesSinceLastClose.get(name);
|
|
31
|
+
if (references === undefined) {
|
|
32
|
+
referencesSinceLastClose.set(name, [backreference]);
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
references.push(backreference);
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export default function caseFoldFlags(flags: string): string;
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { type Backreference, type Part } from "../../partialMatchRegExp/part.ts";
|
|
2
|
+
import type { Hooks } from "../../partialMatchRegExp/walk.ts";
|
|
3
|
+
import { CompiledBackreferences } from "./compiledBackreferences.ts";
|
|
4
|
+
export default function compileDynamic(parts: readonly Part[], backreferences: readonly Backreference[], flags: string, isUnicode: boolean, featureMask: number, hooks: Hooks): CompiledBackreferences;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import escapeAtom from "./escapeAtom.js";
|
|
2
|
+
import { ALTERNATION } from "./constants.js";
|
|
3
|
+
import { GROUP_CLOSING, ONLY_AT_END_OF_INPUT, OPTIONAL_ATOM_OPENING, asOptionalAtom } from "../../partialMatchRegExp/atomSyntax.js";
|
|
4
|
+
import { isBackreference } from "../../partialMatchRegExp/part.js";
|
|
5
|
+
import { CompiledBackreferences } from "./compiledBackreferences.js";
|
|
6
|
+
import asPreScanPart from "./asPreScanPart.js";
|
|
7
|
+
import resolvedFromScan from "./resolvedFromScan.js";
|
|
8
|
+
import startsWithUnderFlags from "./startsWithUnderFlags.js";
|
|
9
|
+
import longestBakedPrefixEndingInput from "./longestBakedPrefixEndingInput.js";
|
|
10
|
+
import flagsAtBackreference from "./flagsAtBackreference.js";
|
|
11
|
+
export default function compileDynamic(parts, backreferences, flags, isUnicode, featureMask, hooks) {
|
|
12
|
+
const dynamic = {
|
|
13
|
+
preScan: new RegExp(parts.map(asPreScanPart).join(""), flags),
|
|
14
|
+
expansionFitsCaptures: (expandedFrom, match, input) => {
|
|
15
|
+
var _a;
|
|
16
|
+
for (const backref of backreferences) {
|
|
17
|
+
const baked = resolvedFromScan(backref, expandedFrom);
|
|
18
|
+
if (baked === undefined)
|
|
19
|
+
continue;
|
|
20
|
+
const resolved = (_a = resolvedFromScan(backref, match)) !== null && _a !== void 0 ? _a : "";
|
|
21
|
+
if (resolved === baked)
|
|
22
|
+
continue;
|
|
23
|
+
const backrefFlags = flagsAtBackreference(backref, flags);
|
|
24
|
+
const consumed = longestBakedPrefixEndingInput(baked, input, backrefFlags);
|
|
25
|
+
if (!startsWithUnderFlags(resolved, consumed, backrefFlags))
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
return true;
|
|
29
|
+
},
|
|
30
|
+
expand: (capture) => {
|
|
31
|
+
const expanded = [];
|
|
32
|
+
for (const part of parts) {
|
|
33
|
+
if (!isBackreference(part)) {
|
|
34
|
+
expanded.push(part);
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
const captured = resolvedFromScan(part, capture);
|
|
38
|
+
if (captured === undefined || captured === "") {
|
|
39
|
+
expanded.push(part);
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
const atoms = isUnicode ? Array.from(captured) : captured.split("");
|
|
43
|
+
expanded.push(OPTIONAL_ATOM_OPENING, part, ALTERNATION);
|
|
44
|
+
for (const atom of atoms) {
|
|
45
|
+
expanded.push(asOptionalAtom(escapeAtom(atom)));
|
|
46
|
+
}
|
|
47
|
+
expanded.push(ONLY_AT_END_OF_INPUT, GROUP_CLOSING);
|
|
48
|
+
}
|
|
49
|
+
return expanded;
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
return new CompiledBackreferences(dynamic, parts, featureMask, hooks);
|
|
53
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { Compiled, type CompiledDynamic, type DynamicPath } from "../../partialMatchRegExp/compilePartial/compiled.ts";
|
|
2
|
+
import type { Part } from "../../partialMatchRegExp/part.ts";
|
|
3
|
+
import type { Hooks } from "../../partialMatchRegExp/walk.ts";
|
|
4
|
+
import execDynamic from "./execDynamic.ts";
|
|
5
|
+
export declare class CompiledBackreferences extends Compiled implements CompiledDynamic {
|
|
6
|
+
readonly dynamic: DynamicPath;
|
|
7
|
+
readonly execDynamic: typeof execDynamic;
|
|
8
|
+
constructor(dynamic: DynamicPath, parts: readonly Part[], featureMask: number, hooks: Hooks);
|
|
9
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { Compiled } from "../../partialMatchRegExp/compilePartial/compiled.js";
|
|
2
|
+
import execDynamic from "./execDynamic.js";
|
|
3
|
+
export class CompiledBackreferences extends Compiled {
|
|
4
|
+
constructor(dynamic, parts, featureMask, hooks) {
|
|
5
|
+
super(parts, featureMask, hooks);
|
|
6
|
+
this.dynamic = dynamic;
|
|
7
|
+
this.execDynamic = execDynamic;
|
|
8
|
+
}
|
|
9
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import renderParts from "./renderParts.js";
|
|
2
|
+
import { backreferenceExpansion } from "../../partialMatchRegExp/backreferenceExpansion.js";
|
|
3
|
+
import execFrom from "../../partialMatchRegExp/execFrom.js";
|
|
4
|
+
function isAtOrBefore(match, index) {
|
|
5
|
+
return match !== null && match.index <= index;
|
|
6
|
+
}
|
|
7
|
+
export default function execDynamic(dynamic, input) {
|
|
8
|
+
const { preScan, expand, expansionFitsCaptures } = dynamic;
|
|
9
|
+
const honoursLastIndex = this.global || this.sticky;
|
|
10
|
+
const start = honoursLastIndex ? this.lastIndex : 0;
|
|
11
|
+
const originalMatch = RegExp.prototype.exec.call(this, input);
|
|
12
|
+
if (isAtOrBefore(originalMatch, start))
|
|
13
|
+
return originalMatch;
|
|
14
|
+
let preScanMatch = null;
|
|
15
|
+
if (originalMatch) {
|
|
16
|
+
preScanMatch = execFrom(preScan, input, start);
|
|
17
|
+
const noEarlierPartialPossible = preScanMatch === null || preScanMatch.index >= originalMatch.index;
|
|
18
|
+
if (noEarlierPartialPossible)
|
|
19
|
+
return originalMatch;
|
|
20
|
+
}
|
|
21
|
+
const capture = preScanMatch !== null && preScanMatch !== void 0 ? preScanMatch : execFrom(preScan, input, start);
|
|
22
|
+
if (capture === null)
|
|
23
|
+
return originalMatch;
|
|
24
|
+
const scanningFlags = honoursLastIndex ? this.flags : this.flags + "g";
|
|
25
|
+
let expandedFrom = capture;
|
|
26
|
+
let expandedParts = expand(expandedFrom);
|
|
27
|
+
let expanded = new RegExp(renderParts(expandedParts), scanningFlags);
|
|
28
|
+
let match = execFrom(expanded, input, start);
|
|
29
|
+
if (match !== null && !expansionFitsCaptures(expandedFrom, match, input)) {
|
|
30
|
+
expandedFrom = match;
|
|
31
|
+
expandedParts = expand(expandedFrom);
|
|
32
|
+
expanded = new RegExp(renderParts(expandedParts), scanningFlags);
|
|
33
|
+
match = execFrom(expanded, input, expandedFrom.index);
|
|
34
|
+
if (match !== null && !expansionFitsCaptures(expandedFrom, match, input))
|
|
35
|
+
match = null;
|
|
36
|
+
}
|
|
37
|
+
if (match === null || isAtOrBefore(originalMatch, match.index))
|
|
38
|
+
return originalMatch;
|
|
39
|
+
if (honoursLastIndex)
|
|
40
|
+
this.lastIndex = expanded.lastIndex;
|
|
41
|
+
match[backreferenceExpansion] = expandedParts;
|
|
42
|
+
return match;
|
|
43
|
+
}
|