re2js 1.3.0 → 1.3.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 +34 -0
- package/build/index.cjs.cjs +270 -213
- package/build/index.cjs.cjs.map +1 -1
- package/build/index.esm.d.ts +28 -13
- package/build/index.esm.d.ts.map +1 -1
- package/build/index.esm.js +271 -213
- package/build/index.esm.js.map +1 -1
- package/build/index.umd.js +270 -213
- package/build/index.umd.js.map +1 -1
- package/package.json +12 -12
package/README.md
CHANGED
|
@@ -161,6 +161,40 @@ matchString.group() // 'e'
|
|
|
161
161
|
matchString.find(7) // false
|
|
162
162
|
```
|
|
163
163
|
|
|
164
|
+
### High-Performance Boolean Testing
|
|
165
|
+
|
|
166
|
+
If you only need to know **whether** a string matches a pattern (without extracting capture groups), you should use the `test()` and `testExact()` methods. Unlike `matches()` or `matcher()`, these methods do not instantiate stateful `Matcher` objects and request exactly `0` capture groups. This guarantees that execution is securely routed to the DFA (Deterministic Finite Automaton) engine whenever possible
|
|
167
|
+
|
|
168
|
+
#### `test(input)`
|
|
169
|
+
|
|
170
|
+
Tests if the regular expression matches **any part** of the provided input (unanchored). This method mirrors the standard JavaScript `RegExp.prototype.test()` API
|
|
171
|
+
|
|
172
|
+
```js
|
|
173
|
+
import { RE2JS } from 're2js';
|
|
174
|
+
|
|
175
|
+
// Compile once, reuse often
|
|
176
|
+
const re = RE2JS.compile('error|warning|critical');
|
|
177
|
+
|
|
178
|
+
// Extremely fast, unanchored DFA search
|
|
179
|
+
if (re.test('The system encountered a critical failure')) {
|
|
180
|
+
console.log('Log needs attention!');
|
|
181
|
+
}
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
#### `testExact(input)`
|
|
185
|
+
|
|
186
|
+
Tests if the regular expression matches the entire input string (anchored to both start and end)
|
|
187
|
+
|
|
188
|
+
```js
|
|
189
|
+
import { RE2JS } from 're2js';
|
|
190
|
+
|
|
191
|
+
const isHex = RE2JS.compile('[0-9A-Fa-f]+');
|
|
192
|
+
|
|
193
|
+
// Fast, anchored DFA validation
|
|
194
|
+
console.log(isHex.testExact('1A4F')); // true
|
|
195
|
+
console.log(isHex.testExact('1A4F-xyz')); // false
|
|
196
|
+
```
|
|
197
|
+
|
|
164
198
|
### Checking Initial Match
|
|
165
199
|
|
|
166
200
|
The `lookingAt()` method determines whether the start of the given string matches the pattern
|
package/build/index.cjs.cjs
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* re2js
|
|
3
3
|
* RE2JS is the JavaScript port of RE2, a regular expression engine that provides linear time matching
|
|
4
4
|
*
|
|
5
|
-
* @version v1.3.
|
|
5
|
+
* @version v1.3.1
|
|
6
6
|
* @author Alexey Vasiliev
|
|
7
7
|
* @homepage https://github.com/le0pard/re2js#readme
|
|
8
8
|
* @repository github:le0pard/re2js
|
|
@@ -843,6 +843,181 @@ class MatcherInput {
|
|
|
843
843
|
}
|
|
844
844
|
}
|
|
845
845
|
|
|
846
|
+
/**
|
|
847
|
+
* MachineInput abstracts different representations of the input text supplied to the Machine. It
|
|
848
|
+
* provides one-character lookahead.
|
|
849
|
+
*/
|
|
850
|
+
class MachineInputBase {
|
|
851
|
+
static EOF() {
|
|
852
|
+
return -1 << 3;
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
// can we look ahead without losing info?
|
|
856
|
+
canCheckPrefix() {
|
|
857
|
+
return true;
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
// Returns the end position in the same units as step().
|
|
861
|
+
endPos() {
|
|
862
|
+
return this.end;
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
// An implementation of MachineInput for UTF-8 byte arrays.
|
|
867
|
+
// |pos| and |width| are byte indices.
|
|
868
|
+
class MachineUTF8Input extends MachineInputBase {
|
|
869
|
+
constructor(bytes, start = 0, end = bytes.length) {
|
|
870
|
+
super();
|
|
871
|
+
this.bytes = bytes;
|
|
872
|
+
this.start = start;
|
|
873
|
+
this.end = end;
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
// Returns the rune at the specified index; the units are
|
|
877
|
+
// unspecified, but could be UTF-8 byte, UTF-16 char, or rune
|
|
878
|
+
// indices. Returns the width (in the same units) of the rune in
|
|
879
|
+
// the lower 3 bits, and the rune (Unicode code point) in the high
|
|
880
|
+
// bits. Never negative, except for EOF which is represented as -1
|
|
881
|
+
// << 3 | 0.
|
|
882
|
+
step(i) {
|
|
883
|
+
i += this.start;
|
|
884
|
+
if (i >= this.end) {
|
|
885
|
+
return MachineInputBase.EOF();
|
|
886
|
+
}
|
|
887
|
+
let x = this.bytes[i++] & 255;
|
|
888
|
+
if ((x & 128) === 0) {
|
|
889
|
+
return x << 3 | 1;
|
|
890
|
+
} else if ((x & 224) === 192) {
|
|
891
|
+
x = x & 31;
|
|
892
|
+
if (i >= this.end) {
|
|
893
|
+
return MachineInputBase.EOF();
|
|
894
|
+
}
|
|
895
|
+
x = x << 6 | this.bytes[i++] & 63;
|
|
896
|
+
return x << 3 | 2;
|
|
897
|
+
} else if ((x & 240) === 224) {
|
|
898
|
+
x = x & 15;
|
|
899
|
+
if (i + 1 >= this.end) {
|
|
900
|
+
return MachineInputBase.EOF();
|
|
901
|
+
}
|
|
902
|
+
x = x << 6 | this.bytes[i++] & 63;
|
|
903
|
+
x = x << 6 | this.bytes[i++] & 63;
|
|
904
|
+
return x << 3 | 3;
|
|
905
|
+
} else {
|
|
906
|
+
x = x & 7;
|
|
907
|
+
if (i + 2 >= this.end) {
|
|
908
|
+
return MachineInputBase.EOF();
|
|
909
|
+
}
|
|
910
|
+
x = x << 6 | this.bytes[i++] & 63;
|
|
911
|
+
x = x << 6 | this.bytes[i++] & 63;
|
|
912
|
+
x = x << 6 | this.bytes[i++] & 63;
|
|
913
|
+
return x << 3 | 4;
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
// Returns the index relative to |pos| at which |re2.prefix| is found
|
|
918
|
+
// in this input stream, or a negative value if not found.
|
|
919
|
+
index(re2, pos) {
|
|
920
|
+
pos += this.start;
|
|
921
|
+
const i = this.indexOf(this.bytes, re2.prefixUTF8, pos);
|
|
922
|
+
return i < 0 ? i : i - pos;
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
// Returns a bitmask of EMPTY_* flags.
|
|
926
|
+
context(pos) {
|
|
927
|
+
pos += this.start;
|
|
928
|
+
let r1 = -1;
|
|
929
|
+
if (pos > this.start && pos <= this.end) {
|
|
930
|
+
let start = pos - 1;
|
|
931
|
+
r1 = this.bytes[start--];
|
|
932
|
+
if (r1 >= 128) {
|
|
933
|
+
let lim = pos - 4;
|
|
934
|
+
if (lim < this.start) {
|
|
935
|
+
lim = this.start;
|
|
936
|
+
}
|
|
937
|
+
while (start >= lim && (this.bytes[start] & 192) === 128) {
|
|
938
|
+
start--;
|
|
939
|
+
}
|
|
940
|
+
if (start < this.start) {
|
|
941
|
+
start = this.start;
|
|
942
|
+
}
|
|
943
|
+
r1 = this.step(start) >> 3;
|
|
944
|
+
}
|
|
945
|
+
}
|
|
946
|
+
const r2 = pos < this.end ? this.step(pos) >> 3 : -1;
|
|
947
|
+
return Utils.emptyOpContext(r1, r2);
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
// Returns the index of the first occurrence of array |target| within
|
|
951
|
+
// array |source| after |fromIndex|, or -1 if not found.
|
|
952
|
+
indexOf(source, target, fromIndex = 0) {
|
|
953
|
+
let targetLength = target.length;
|
|
954
|
+
if (targetLength === 0) {
|
|
955
|
+
return -1;
|
|
956
|
+
}
|
|
957
|
+
let sourceLength = source.length;
|
|
958
|
+
for (let i = fromIndex; i <= sourceLength - targetLength; i++) {
|
|
959
|
+
for (let j = 0; j < targetLength; j++) {
|
|
960
|
+
if (source[i + j] !== target[j]) {
|
|
961
|
+
break;
|
|
962
|
+
} else if (j === targetLength - 1) {
|
|
963
|
+
return i;
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
return -1;
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
|
|
971
|
+
// |pos| and |width| are in JS "char" units.
|
|
972
|
+
class MachineUTF16Input extends MachineInputBase {
|
|
973
|
+
constructor(charSequence, start = 0, end = charSequence.length) {
|
|
974
|
+
super();
|
|
975
|
+
this.charSequence = charSequence;
|
|
976
|
+
this.start = start;
|
|
977
|
+
this.end = end;
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
// Returns the rune at the specified index; the units are
|
|
981
|
+
// unspecified, but could be UTF-8 byte, UTF-16 char, or rune
|
|
982
|
+
// indices. Returns the width (in the same units) of the rune in
|
|
983
|
+
// the lower 3 bits, and the rune (Unicode code point) in the high
|
|
984
|
+
// bits. Never negative, except for EOF which is represented as -1
|
|
985
|
+
// << 3 | 0.
|
|
986
|
+
step(pos) {
|
|
987
|
+
pos += this.start;
|
|
988
|
+
if (pos < this.end) {
|
|
989
|
+
const rune = this.charSequence.codePointAt(pos);
|
|
990
|
+
return rune << 3 | Utils.charCount(rune);
|
|
991
|
+
} else {
|
|
992
|
+
return MachineInputBase.EOF();
|
|
993
|
+
}
|
|
994
|
+
}
|
|
995
|
+
|
|
996
|
+
// Returns the index relative to |pos| at which |re2.prefix| is found
|
|
997
|
+
// in this input stream, or a negative value if not found.
|
|
998
|
+
index(re2, pos) {
|
|
999
|
+
pos += this.start;
|
|
1000
|
+
const i = this.charSequence.indexOf(re2.prefix, pos);
|
|
1001
|
+
return i < 0 ? i : i - pos;
|
|
1002
|
+
}
|
|
1003
|
+
|
|
1004
|
+
// Returns a bitmask of EMPTY_* flags.
|
|
1005
|
+
context(pos) {
|
|
1006
|
+
pos += this.start;
|
|
1007
|
+
const r1 = pos > 0 && pos <= this.charSequence.length ? this.charSequence.codePointAt(pos - 1) : -1;
|
|
1008
|
+
const r2 = pos < this.charSequence.length ? this.charSequence.codePointAt(pos) : -1;
|
|
1009
|
+
return Utils.emptyOpContext(r1, r2);
|
|
1010
|
+
}
|
|
1011
|
+
}
|
|
1012
|
+
class MachineInput {
|
|
1013
|
+
static fromUTF8(bytes, start = 0, end = bytes.length) {
|
|
1014
|
+
return new MachineUTF8Input(bytes, start, end);
|
|
1015
|
+
}
|
|
1016
|
+
static fromUTF16(charSequence, start = 0, end = charSequence.length) {
|
|
1017
|
+
return new MachineUTF16Input(charSequence, start, end);
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1020
|
+
|
|
846
1021
|
class RE2JSException extends Error {
|
|
847
1022
|
/** @param {string} message */
|
|
848
1023
|
constructor(message) {
|
|
@@ -923,17 +1098,6 @@ class RE2JSFlagsException extends RE2JSException {
|
|
|
923
1098
|
}
|
|
924
1099
|
}
|
|
925
1100
|
|
|
926
|
-
/**
|
|
927
|
-
* An exception thrown by DFA
|
|
928
|
-
*/
|
|
929
|
-
class RE2JSDfaMemoryException extends RE2JSException {
|
|
930
|
-
/** @param {string} message */
|
|
931
|
-
constructor(message) {
|
|
932
|
-
super(message);
|
|
933
|
-
this.name = 'RE2JSDfaMemoryException';
|
|
934
|
-
}
|
|
935
|
-
}
|
|
936
|
-
|
|
937
1101
|
/**
|
|
938
1102
|
* A stateful iterator that interprets a regex {@code RE2JS} on a specific input.
|
|
939
1103
|
*
|
|
@@ -1490,181 +1654,6 @@ class Matcher {
|
|
|
1490
1654
|
}
|
|
1491
1655
|
}
|
|
1492
1656
|
|
|
1493
|
-
/**
|
|
1494
|
-
* MachineInput abstracts different representations of the input text supplied to the Machine. It
|
|
1495
|
-
* provides one-character lookahead.
|
|
1496
|
-
*/
|
|
1497
|
-
class MachineInputBase {
|
|
1498
|
-
static EOF() {
|
|
1499
|
-
return -1 << 3;
|
|
1500
|
-
}
|
|
1501
|
-
|
|
1502
|
-
// can we look ahead without losing info?
|
|
1503
|
-
canCheckPrefix() {
|
|
1504
|
-
return true;
|
|
1505
|
-
}
|
|
1506
|
-
|
|
1507
|
-
// Returns the end position in the same units as step().
|
|
1508
|
-
endPos() {
|
|
1509
|
-
return this.end;
|
|
1510
|
-
}
|
|
1511
|
-
}
|
|
1512
|
-
|
|
1513
|
-
// An implementation of MachineInput for UTF-8 byte arrays.
|
|
1514
|
-
// |pos| and |width| are byte indices.
|
|
1515
|
-
class MachineUTF8Input extends MachineInputBase {
|
|
1516
|
-
constructor(bytes, start = 0, end = bytes.length) {
|
|
1517
|
-
super();
|
|
1518
|
-
this.bytes = bytes;
|
|
1519
|
-
this.start = start;
|
|
1520
|
-
this.end = end;
|
|
1521
|
-
}
|
|
1522
|
-
|
|
1523
|
-
// Returns the rune at the specified index; the units are
|
|
1524
|
-
// unspecified, but could be UTF-8 byte, UTF-16 char, or rune
|
|
1525
|
-
// indices. Returns the width (in the same units) of the rune in
|
|
1526
|
-
// the lower 3 bits, and the rune (Unicode code point) in the high
|
|
1527
|
-
// bits. Never negative, except for EOF which is represented as -1
|
|
1528
|
-
// << 3 | 0.
|
|
1529
|
-
step(i) {
|
|
1530
|
-
i += this.start;
|
|
1531
|
-
if (i >= this.end) {
|
|
1532
|
-
return MachineInputBase.EOF();
|
|
1533
|
-
}
|
|
1534
|
-
let x = this.bytes[i++] & 255;
|
|
1535
|
-
if ((x & 128) === 0) {
|
|
1536
|
-
return x << 3 | 1;
|
|
1537
|
-
} else if ((x & 224) === 192) {
|
|
1538
|
-
x = x & 31;
|
|
1539
|
-
if (i >= this.end) {
|
|
1540
|
-
return MachineInputBase.EOF();
|
|
1541
|
-
}
|
|
1542
|
-
x = x << 6 | this.bytes[i++] & 63;
|
|
1543
|
-
return x << 3 | 2;
|
|
1544
|
-
} else if ((x & 240) === 224) {
|
|
1545
|
-
x = x & 15;
|
|
1546
|
-
if (i + 1 >= this.end) {
|
|
1547
|
-
return MachineInputBase.EOF();
|
|
1548
|
-
}
|
|
1549
|
-
x = x << 6 | this.bytes[i++] & 63;
|
|
1550
|
-
x = x << 6 | this.bytes[i++] & 63;
|
|
1551
|
-
return x << 3 | 3;
|
|
1552
|
-
} else {
|
|
1553
|
-
x = x & 7;
|
|
1554
|
-
if (i + 2 >= this.end) {
|
|
1555
|
-
return MachineInputBase.EOF();
|
|
1556
|
-
}
|
|
1557
|
-
x = x << 6 | this.bytes[i++] & 63;
|
|
1558
|
-
x = x << 6 | this.bytes[i++] & 63;
|
|
1559
|
-
x = x << 6 | this.bytes[i++] & 63;
|
|
1560
|
-
return x << 3 | 4;
|
|
1561
|
-
}
|
|
1562
|
-
}
|
|
1563
|
-
|
|
1564
|
-
// Returns the index relative to |pos| at which |re2.prefix| is found
|
|
1565
|
-
// in this input stream, or a negative value if not found.
|
|
1566
|
-
index(re2, pos) {
|
|
1567
|
-
pos += this.start;
|
|
1568
|
-
const i = this.indexOf(this.bytes, re2.prefixUTF8, pos);
|
|
1569
|
-
return i < 0 ? i : i - pos;
|
|
1570
|
-
}
|
|
1571
|
-
|
|
1572
|
-
// Returns a bitmask of EMPTY_* flags.
|
|
1573
|
-
context(pos) {
|
|
1574
|
-
pos += this.start;
|
|
1575
|
-
let r1 = -1;
|
|
1576
|
-
if (pos > this.start && pos <= this.end) {
|
|
1577
|
-
let start = pos - 1;
|
|
1578
|
-
r1 = this.bytes[start--];
|
|
1579
|
-
if (r1 >= 128) {
|
|
1580
|
-
let lim = pos - 4;
|
|
1581
|
-
if (lim < this.start) {
|
|
1582
|
-
lim = this.start;
|
|
1583
|
-
}
|
|
1584
|
-
while (start >= lim && (this.bytes[start] & 192) === 128) {
|
|
1585
|
-
start--;
|
|
1586
|
-
}
|
|
1587
|
-
if (start < this.start) {
|
|
1588
|
-
start = this.start;
|
|
1589
|
-
}
|
|
1590
|
-
r1 = this.step(start) >> 3;
|
|
1591
|
-
}
|
|
1592
|
-
}
|
|
1593
|
-
const r2 = pos < this.end ? this.step(pos) >> 3 : -1;
|
|
1594
|
-
return Utils.emptyOpContext(r1, r2);
|
|
1595
|
-
}
|
|
1596
|
-
|
|
1597
|
-
// Returns the index of the first occurrence of array |target| within
|
|
1598
|
-
// array |source| after |fromIndex|, or -1 if not found.
|
|
1599
|
-
indexOf(source, target, fromIndex = 0) {
|
|
1600
|
-
let targetLength = target.length;
|
|
1601
|
-
if (targetLength === 0) {
|
|
1602
|
-
return -1;
|
|
1603
|
-
}
|
|
1604
|
-
let sourceLength = source.length;
|
|
1605
|
-
for (let i = fromIndex; i <= sourceLength - targetLength; i++) {
|
|
1606
|
-
for (let j = 0; j < targetLength; j++) {
|
|
1607
|
-
if (source[i + j] !== target[j]) {
|
|
1608
|
-
break;
|
|
1609
|
-
} else if (j === targetLength - 1) {
|
|
1610
|
-
return i;
|
|
1611
|
-
}
|
|
1612
|
-
}
|
|
1613
|
-
}
|
|
1614
|
-
return -1;
|
|
1615
|
-
}
|
|
1616
|
-
}
|
|
1617
|
-
|
|
1618
|
-
// |pos| and |width| are in JS "char" units.
|
|
1619
|
-
class MachineUTF16Input extends MachineInputBase {
|
|
1620
|
-
constructor(charSequence, start = 0, end = charSequence.length) {
|
|
1621
|
-
super();
|
|
1622
|
-
this.charSequence = charSequence;
|
|
1623
|
-
this.start = start;
|
|
1624
|
-
this.end = end;
|
|
1625
|
-
}
|
|
1626
|
-
|
|
1627
|
-
// Returns the rune at the specified index; the units are
|
|
1628
|
-
// unspecified, but could be UTF-8 byte, UTF-16 char, or rune
|
|
1629
|
-
// indices. Returns the width (in the same units) of the rune in
|
|
1630
|
-
// the lower 3 bits, and the rune (Unicode code point) in the high
|
|
1631
|
-
// bits. Never negative, except for EOF which is represented as -1
|
|
1632
|
-
// << 3 | 0.
|
|
1633
|
-
step(pos) {
|
|
1634
|
-
pos += this.start;
|
|
1635
|
-
if (pos < this.end) {
|
|
1636
|
-
const rune = this.charSequence.codePointAt(pos);
|
|
1637
|
-
return rune << 3 | Utils.charCount(rune);
|
|
1638
|
-
} else {
|
|
1639
|
-
return MachineInputBase.EOF();
|
|
1640
|
-
}
|
|
1641
|
-
}
|
|
1642
|
-
|
|
1643
|
-
// Returns the index relative to |pos| at which |re2.prefix| is found
|
|
1644
|
-
// in this input stream, or a negative value if not found.
|
|
1645
|
-
index(re2, pos) {
|
|
1646
|
-
pos += this.start;
|
|
1647
|
-
const i = this.charSequence.indexOf(re2.prefix, pos);
|
|
1648
|
-
return i < 0 ? i : i - pos;
|
|
1649
|
-
}
|
|
1650
|
-
|
|
1651
|
-
// Returns a bitmask of EMPTY_* flags.
|
|
1652
|
-
context(pos) {
|
|
1653
|
-
pos += this.start;
|
|
1654
|
-
const r1 = pos > 0 && pos <= this.charSequence.length ? this.charSequence.codePointAt(pos - 1) : -1;
|
|
1655
|
-
const r2 = pos < this.charSequence.length ? this.charSequence.codePointAt(pos) : -1;
|
|
1656
|
-
return Utils.emptyOpContext(r1, r2);
|
|
1657
|
-
}
|
|
1658
|
-
}
|
|
1659
|
-
class MachineInput {
|
|
1660
|
-
static fromUTF8(bytes, start = 0, end = bytes.length) {
|
|
1661
|
-
return new MachineUTF8Input(bytes, start, end);
|
|
1662
|
-
}
|
|
1663
|
-
static fromUTF16(charSequence, start = 0, end = charSequence.length) {
|
|
1664
|
-
return new MachineUTF16Input(charSequence, start, end);
|
|
1665
|
-
}
|
|
1666
|
-
}
|
|
1667
|
-
|
|
1668
1657
|
/**
|
|
1669
1658
|
* A single instruction in the regular expression virtual machine.
|
|
1670
1659
|
*
|
|
@@ -2123,21 +2112,40 @@ class Machine {
|
|
|
2123
2112
|
}
|
|
2124
2113
|
}
|
|
2125
2114
|
|
|
2115
|
+
// FNV-1a 32-bit hash for an array of integers.
|
|
2116
|
+
// Extremely fast, allocates no memory, and produces good distribution.
|
|
2117
|
+
const hashPCs = pcs => {
|
|
2118
|
+
let h = -2128831035; // 0x811c9dc5 (32-bit signed offset basis)
|
|
2119
|
+
for (let i = 0; i < pcs.length; i++) {
|
|
2120
|
+
h ^= pcs[i];
|
|
2121
|
+
h = Math.imul(h, 16777619); // 0x01000193 (FNV prime)
|
|
2122
|
+
}
|
|
2123
|
+
return h;
|
|
2124
|
+
};
|
|
2125
|
+
|
|
2126
|
+
// Zero-allocation array comparison for hash collision resolution
|
|
2127
|
+
const arraysEqual = (a, b) => {
|
|
2128
|
+
if (a.length !== b.length) return false;
|
|
2129
|
+
for (let i = 0; i < a.length; i++) {
|
|
2130
|
+
if (a[i] !== b[i]) return false;
|
|
2131
|
+
}
|
|
2132
|
+
return true;
|
|
2133
|
+
};
|
|
2126
2134
|
class DFAState {
|
|
2127
|
-
constructor(
|
|
2128
|
-
this.
|
|
2129
|
-
this.nfaStates = nfaStates; // Array of Instruction PCs
|
|
2135
|
+
constructor(nfaStates, isMatch) {
|
|
2136
|
+
this.nfaStates = nfaStates; // Int32Array of Instruction PCs
|
|
2130
2137
|
this.isMatch = isMatch; // Boolean
|
|
2131
|
-
this.nextAscii = new Array(Unicode.MAX_ASCII + 1).fill(null); // Flat array for blisteringly fast ASCII lookups
|
|
2138
|
+
this.nextAscii = new Array(Unicode.MAX_ASCII + 1).fill(null); // Flat array for blisteringly fast ASCII lookups
|
|
2132
2139
|
this.nextMap = new Map(); // Cache of Char -> DFAState
|
|
2133
2140
|
}
|
|
2134
2141
|
}
|
|
2135
2142
|
class DFA {
|
|
2136
2143
|
constructor(prog) {
|
|
2137
2144
|
this.prog = prog;
|
|
2138
|
-
this.stateCache = new Map(); //
|
|
2145
|
+
this.stateCache = new Map(); // hash(number) -> DFAState[]
|
|
2146
|
+
this.stateCount = 0; // Tracks total states for memory limits
|
|
2139
2147
|
this.startState = null;
|
|
2140
|
-
this.stateLimit = 10000; // Prevent memory explosion (ReDoS protection)
|
|
2148
|
+
this.stateLimit = 10000; // Prevent memory explosion (ReDoS protection)
|
|
2141
2149
|
}
|
|
2142
2150
|
|
|
2143
2151
|
// Follows epsilon (empty) transitions to find all reachable states without consuming a char
|
|
@@ -2181,17 +2189,37 @@ class DFA {
|
|
|
2181
2189
|
const closureResult = this.computeClosure(pcs);
|
|
2182
2190
|
if (!closureResult) return null; // Bailout to NFA required
|
|
2183
2191
|
|
|
2184
|
-
const
|
|
2185
|
-
|
|
2186
|
-
|
|
2192
|
+
const sortedPCs = closureResult.pcs;
|
|
2193
|
+
const hash = hashPCs(sortedPCs);
|
|
2194
|
+
|
|
2195
|
+
// Lookup hash bucket
|
|
2196
|
+
let bucket = this.stateCache.get(hash);
|
|
2197
|
+
if (bucket) {
|
|
2198
|
+
// Resolve potential hash collisions
|
|
2199
|
+
for (let i = 0; i < bucket.length; i++) {
|
|
2200
|
+
const state = bucket[i];
|
|
2201
|
+
if (arraysEqual(state.nfaStates, sortedPCs)) {
|
|
2202
|
+
return state;
|
|
2203
|
+
}
|
|
2204
|
+
}
|
|
2205
|
+
} else {
|
|
2206
|
+
bucket = [];
|
|
2207
|
+
this.stateCache.set(hash, bucket);
|
|
2187
2208
|
}
|
|
2188
2209
|
|
|
2189
2210
|
// Safety: prevent memory exhaustion from state explosion
|
|
2190
|
-
|
|
2191
|
-
|
|
2211
|
+
// We flush the cache and return null, which seamlessly routes execution to the NFA
|
|
2212
|
+
if (this.stateCount >= this.stateLimit) {
|
|
2213
|
+
this.stateCache.clear();
|
|
2214
|
+
this.stateCount = 0;
|
|
2215
|
+
this.startState = null;
|
|
2216
|
+
return null;
|
|
2192
2217
|
}
|
|
2193
|
-
|
|
2194
|
-
|
|
2218
|
+
|
|
2219
|
+
// State not found, create it and add to bucket
|
|
2220
|
+
const state = new DFAState(sortedPCs, closureResult.isMatch);
|
|
2221
|
+
bucket.push(state);
|
|
2222
|
+
this.stateCount++;
|
|
2195
2223
|
return state;
|
|
2196
2224
|
}
|
|
2197
2225
|
|
|
@@ -2255,6 +2283,11 @@ class DFA {
|
|
|
2255
2283
|
const r = input.step(i);
|
|
2256
2284
|
const rune = r >> 3;
|
|
2257
2285
|
const width = r & 7;
|
|
2286
|
+
|
|
2287
|
+
// prevent infinite loop on EOF
|
|
2288
|
+
if (width === 0) {
|
|
2289
|
+
break;
|
|
2290
|
+
}
|
|
2258
2291
|
currentState = this.step(currentState, rune, anchor);
|
|
2259
2292
|
|
|
2260
2293
|
// If we hit an unrecoverable DFA error or bailout, signal fallback
|
|
@@ -5417,18 +5450,10 @@ class RE2 {
|
|
|
5417
5450
|
if (ncap > 0) {
|
|
5418
5451
|
return this.doExecuteNFA(input, pos, anchor, ncap);
|
|
5419
5452
|
}
|
|
5420
|
-
|
|
5421
|
-
|
|
5422
|
-
|
|
5423
|
-
|
|
5424
|
-
return dfaResult ? [] : null; // Return empty array to signify "matched but no captures"
|
|
5425
|
-
}
|
|
5426
|
-
} catch (e) {
|
|
5427
|
-
if (e instanceof RE2JSDfaMemoryException) {
|
|
5428
|
-
this.dfa = new DFA(this.prog); // flush cache
|
|
5429
|
-
} else {
|
|
5430
|
-
throw e;
|
|
5431
|
-
}
|
|
5453
|
+
const dfaResult = this.dfa.match(input, pos, anchor);
|
|
5454
|
+
if (dfaResult !== null) {
|
|
5455
|
+
// DFA succeeded (returned true or false)
|
|
5456
|
+
return dfaResult ? [] : null; // Return empty array to signify "matched but no captures"
|
|
5432
5457
|
}
|
|
5433
5458
|
|
|
5434
5459
|
// Fallback to NFA
|
|
@@ -6312,6 +6337,39 @@ class RE2JS {
|
|
|
6312
6337
|
return new Matcher(this, input);
|
|
6313
6338
|
}
|
|
6314
6339
|
|
|
6340
|
+
/**
|
|
6341
|
+
* Tests whether the regular expression matches any part of the input string.
|
|
6342
|
+
* Performance Note: This method is highly optimized. Because it only returns
|
|
6343
|
+
* a boolean and does not extract capture groups, it bypasses the `Matcher` overhead
|
|
6344
|
+
* and guarantees execution on the high-speed DFA engine whenever possible.
|
|
6345
|
+
*
|
|
6346
|
+
* @param {string|number[]} input - The input string or UTF-8 byte array to test against.
|
|
6347
|
+
* @returns {boolean} `true` if the pattern is found anywhere in the input, `false` otherwise.
|
|
6348
|
+
*/
|
|
6349
|
+
test(input) {
|
|
6350
|
+
if (Array.isArray(input)) {
|
|
6351
|
+
// Reuse the existing UTF-8 fast-path method
|
|
6352
|
+
return this.re2Input.matchUTF8(input);
|
|
6353
|
+
}
|
|
6354
|
+
|
|
6355
|
+
// Reuse the existing UTF-16 fast-path method
|
|
6356
|
+
return this.re2Input.match(input);
|
|
6357
|
+
}
|
|
6358
|
+
|
|
6359
|
+
/**
|
|
6360
|
+
* Tests whether the regular expression matches the ENTIRE input string.
|
|
6361
|
+
* * **Performance Note:** This operates identically to `.matches()`, but is significantly
|
|
6362
|
+
* faster because it does not request capture group data. By requesting 0 capture groups,
|
|
6363
|
+
* it securely routes execution through the DFA fast-path.
|
|
6364
|
+
*
|
|
6365
|
+
* @param {string|number[]} input - The input string or UTF-8 byte array to test against.
|
|
6366
|
+
* @returns {boolean} `true` if the exact input string fully matches the pattern, `false` otherwise.
|
|
6367
|
+
*/
|
|
6368
|
+
testExact(input) {
|
|
6369
|
+
const machineInput = Array.isArray(input) ? MachineInput.fromUTF8(input) : MachineInput.fromUTF16(input);
|
|
6370
|
+
return this.re2Input.executeEngine(machineInput, 0, RE2Flags.ANCHOR_BOTH, 0) !== null;
|
|
6371
|
+
}
|
|
6372
|
+
|
|
6315
6373
|
/**
|
|
6316
6374
|
* Splits input around instances of the regular expression. It returns an array giving the strings
|
|
6317
6375
|
* that occur before, between, and after instances of the regular expression.
|
|
@@ -6433,7 +6491,6 @@ class RE2JS {
|
|
|
6433
6491
|
exports.Matcher = Matcher;
|
|
6434
6492
|
exports.RE2JS = RE2JS;
|
|
6435
6493
|
exports.RE2JSCompileException = RE2JSCompileException;
|
|
6436
|
-
exports.RE2JSDfaMemoryException = RE2JSDfaMemoryException;
|
|
6437
6494
|
exports.RE2JSException = RE2JSException;
|
|
6438
6495
|
exports.RE2JSFlagsException = RE2JSFlagsException;
|
|
6439
6496
|
exports.RE2JSGroupException = RE2JSGroupException;
|