regex-partial-match 1.1.0 → 1.1.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 +2 -2
- package/lib/compilePartial.js +5 -5
- package/lib/partialMatchRegExp.js +45 -37
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -71,7 +71,7 @@ Such combinations have not been tested.
|
|
|
71
71
|
|
|
72
72
|
### Patterns with backreferences
|
|
73
73
|
|
|
74
|
-
Backreferences cannot be handled by the `|$(?![\s\S])` transform alone because they are atomic — `\1` must match the entire captured string or fail, and its length is only known at runtime. `PartialMatchRegExp` first tries a full match natively,
|
|
74
|
+
Backreferences cannot be handled by the `|$(?![\s\S])` transform alone because they are atomic — `\1` must match the entire captured string or fail, and its length is only known at runtime. `PartialMatchRegExp` first tries a full match natively, but that native result only wins outright if nothing earlier in the input could still be a viable partial — a cheap bound check settles that without needing to resolve the backreference's actual value, so the common case (no earlier partial exists) stays fast. Otherwise it runs a "capture scan": a variant of the pattern with each backreference swapped for a lazy `(?:[\s\S]*?)` wildcard, so the group it depends on can still capture against a partial input — matching anything, or nothing at all, without needing to already know the backreference's value.
|
|
75
75
|
|
|
76
76
|
Whatever that scan captures (or leaves `undefined`, if the group hasn't been reached yet) is then used to build a fresh partial-matching regex for this specific input, expanding the backreference character-by-character from the captured value with the same per-atom transform as the rest of the pattern. See [docs/backreferences.md](./docs/backreferences.md) for the full algorithm, including the prefer-longer post-processing that preserves correct captures for groups inside quantifiers.
|
|
77
77
|
|
|
@@ -418,7 +418,7 @@ partial.features.has("backreference"); // true
|
|
|
418
418
|
|
|
419
419
|
Two things worth knowing about how these tags line up with the grammar:
|
|
420
420
|
|
|
421
|
-
- **One ECMA-262 production can map to several tags.** `Assertion` alone covers `^`, `$`, `\b`, `\B`, and all four lookarounds — `features` splits it by whichever discriminant is easiest to read off during the walk (`^` vs `$`, `=` vs `!` after `(?<`, etc.), since that information is free at the point each construct is
|
|
421
|
+
- **One ECMA-262 production can map to several tags.** `Assertion` alone covers `^`, `$`, `\b`, `\B`, and all four lookarounds — `features` splits it by whichever discriminant is easiest to read off during the walk (`^` vs `$`, `=` vs `!` after `(?<`, etc.), since that information is free at the point each construct is recognised.
|
|
422
422
|
- **A named capturing group always carries both `namedGroup` and `capturingGroup`.** The grammar treats a capturing group with a name and one without as the same production (`( GroupSpecifier? Disjunction )`), not two, so both tags are added together.
|
|
423
423
|
|
|
424
424
|
## 📜 License
|
package/lib/compilePartial.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import escapeAtom from "./escapeAtom.js";
|
|
2
2
|
const OCCURRENCES_REGEX = /\{\d+,?\d*\}/y;
|
|
3
3
|
const NOT_NUMBERS_REGEX = /\D/g;
|
|
4
|
+
const MAYBE_HAS_BACKREFERENCE_REGEX = /\\[1-9]|\\k</;
|
|
4
5
|
const DISJUNCTION_TO_END_OF_INPUT = "|$(?![\\s\\S]))";
|
|
5
|
-
const MAYBE_HAS_BACKREFERENCE = /\\[1-9]|\\k</;
|
|
6
6
|
const isBackreference = (part) => typeof part !== "string";
|
|
7
7
|
const isNumericBackreference = (part) => isBackreference(part) && typeof part.ref === "number";
|
|
8
8
|
function walk(regex) {
|
|
@@ -295,7 +295,7 @@ function expandCaptured(value, isUnicode) {
|
|
|
295
295
|
}
|
|
296
296
|
export const compilePartial = (regex) => {
|
|
297
297
|
const { parts, groupCount, features } = walk(regex);
|
|
298
|
-
if (!
|
|
298
|
+
if (!MAYBE_HAS_BACKREFERENCE_REGEX.test(regex.source)) {
|
|
299
299
|
return {
|
|
300
300
|
kind: "static",
|
|
301
301
|
regex: new RegExp(render(parts, backrefToken), regex.flags),
|
|
@@ -303,14 +303,14 @@ export const compilePartial = (regex) => {
|
|
|
303
303
|
};
|
|
304
304
|
}
|
|
305
305
|
const isUnicode = regex.unicode || regex.unicodeSets;
|
|
306
|
-
const
|
|
306
|
+
const sanitisedParts = isUnicode
|
|
307
307
|
? parts
|
|
308
308
|
: reclassifyOctalEscapes(parts, regex.source, groupCount);
|
|
309
|
-
const backreferences =
|
|
309
|
+
const backreferences = sanitisedParts.filter(isBackreference);
|
|
310
310
|
if (backreferences.length === 0) {
|
|
311
311
|
return {
|
|
312
312
|
kind: "static",
|
|
313
|
-
regex: new RegExp(render(
|
|
313
|
+
regex: new RegExp(render(sanitisedParts, backrefToken), regex.flags),
|
|
314
314
|
features
|
|
315
315
|
};
|
|
316
316
|
}
|
|
@@ -9,7 +9,7 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (
|
|
|
9
9
|
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
|
|
10
10
|
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
|
11
11
|
};
|
|
12
|
-
var
|
|
12
|
+
var _PartialMatchRegExp_instances, _PartialMatchRegExp_compiledPartial, _PartialMatchRegExp_execDynamic;
|
|
13
13
|
import { compilePartial } from "./compilePartial.js";
|
|
14
14
|
/**
|
|
15
15
|
* A `RegExp` subclass that supports partial (prefix) matching.
|
|
@@ -39,48 +39,56 @@ import { compilePartial } from "./compilePartial.js";
|
|
|
39
39
|
*/
|
|
40
40
|
class PartialMatchRegExp extends RegExp {
|
|
41
41
|
constructor(pattern, flags) {
|
|
42
|
-
var _a, _b;
|
|
43
42
|
super(pattern, flags);
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
? [compiled.dynamic, null]
|
|
49
|
-
: [null, compiled.regex];
|
|
50
|
-
this.features = compiled.features;
|
|
43
|
+
_PartialMatchRegExp_instances.add(this);
|
|
44
|
+
_PartialMatchRegExp_compiledPartial.set(this, void 0);
|
|
45
|
+
__classPrivateFieldSet(this, _PartialMatchRegExp_compiledPartial, compilePartial(this), "f");
|
|
46
|
+
this.features = __classPrivateFieldGet(this, _PartialMatchRegExp_compiledPartial, "f").features;
|
|
51
47
|
}
|
|
52
48
|
exec(input) {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
}
|
|
60
|
-
const start = this.lastIndex;
|
|
61
|
-
const originalMatch = super.exec(input);
|
|
62
|
-
if (originalMatch)
|
|
63
|
-
return originalMatch;
|
|
64
|
-
const { originalCaptureScan, preScan, expand } = __classPrivateFieldGet(this, _PartialMatchRegExp_dynamic, "f");
|
|
65
|
-
originalCaptureScan.lastIndex = start;
|
|
66
|
-
let capture = originalCaptureScan.exec(input);
|
|
67
|
-
if (capture === null) {
|
|
68
|
-
preScan.lastIndex = start;
|
|
69
|
-
capture = preScan.exec(input);
|
|
70
|
-
}
|
|
71
|
-
if (capture === null)
|
|
72
|
-
return null;
|
|
73
|
-
const expanded = new RegExp(expand(capture), this.flags);
|
|
74
|
-
expanded.lastIndex = start;
|
|
75
|
-
const match = expanded.exec(input);
|
|
76
|
-
if (match === null)
|
|
77
|
-
return null;
|
|
78
|
-
preferLongerCaptures(match, capture);
|
|
79
|
-
this.lastIndex = expanded.lastIndex;
|
|
49
|
+
const compiled = __classPrivateFieldGet(this, _PartialMatchRegExp_compiledPartial, "f");
|
|
50
|
+
if (compiled.kind === "dynamic")
|
|
51
|
+
return __classPrivateFieldGet(this, _PartialMatchRegExp_instances, "m", _PartialMatchRegExp_execDynamic).call(this, compiled.dynamic, input);
|
|
52
|
+
const { regex } = compiled;
|
|
53
|
+
const match = execFrom(regex, input, this.lastIndex);
|
|
54
|
+
this.lastIndex = regex.lastIndex;
|
|
80
55
|
return match;
|
|
81
56
|
}
|
|
82
57
|
}
|
|
83
|
-
|
|
58
|
+
_PartialMatchRegExp_compiledPartial = new WeakMap(), _PartialMatchRegExp_instances = new WeakSet(), _PartialMatchRegExp_execDynamic = function _PartialMatchRegExp_execDynamic(dynamic, input) {
|
|
59
|
+
var _a, _b;
|
|
60
|
+
const { originalCaptureScan, preScan, expand } = dynamic;
|
|
61
|
+
const honoursLastIndex = this.global || this.sticky;
|
|
62
|
+
const start = honoursLastIndex ? this.lastIndex : 0;
|
|
63
|
+
const originalMatch = super.exec(input);
|
|
64
|
+
if (isAtOrBefore(originalMatch, start))
|
|
65
|
+
return originalMatch;
|
|
66
|
+
let preScanMatch = null;
|
|
67
|
+
if (originalMatch) {
|
|
68
|
+
preScanMatch = execFrom(preScan, input, start);
|
|
69
|
+
const noEarlierPartialPossible = preScanMatch === null || preScanMatch.index >= originalMatch.index;
|
|
70
|
+
if (noEarlierPartialPossible)
|
|
71
|
+
return originalMatch;
|
|
72
|
+
}
|
|
73
|
+
const capture = (_b = (_a = execFrom(originalCaptureScan, input, start)) !== null && _a !== void 0 ? _a : preScanMatch) !== null && _b !== void 0 ? _b : execFrom(preScan, input, start);
|
|
74
|
+
if (capture === null)
|
|
75
|
+
return originalMatch;
|
|
76
|
+
const expanded = new RegExp(expand(capture), this.flags);
|
|
77
|
+
const match = execFrom(expanded, input, start);
|
|
78
|
+
if (match === null || isAtOrBefore(originalMatch, match.index))
|
|
79
|
+
return originalMatch;
|
|
80
|
+
preferLongerCaptures(match, capture);
|
|
81
|
+
if (honoursLastIndex)
|
|
82
|
+
this.lastIndex = expanded.lastIndex;
|
|
83
|
+
return match;
|
|
84
|
+
};
|
|
85
|
+
function execFrom(regex, input, start) {
|
|
86
|
+
regex.lastIndex = start;
|
|
87
|
+
return regex.exec(input);
|
|
88
|
+
}
|
|
89
|
+
function isAtOrBefore(match, index) {
|
|
90
|
+
return match !== null && match.index <= index;
|
|
91
|
+
}
|
|
84
92
|
function pickLonger(scanned, matched) {
|
|
85
93
|
return scanned !== undefined &&
|
|
86
94
|
matched !== undefined &&
|