re2js 2.8.6 → 2.8.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,4 +1,5 @@
1
1
  # RE2JS is the JavaScript port of RE2, a regular expression engine that provides linear time matching
2
+
2
3
  [![Test/Build/Publish](https://github.com/le0pard/re2js/actions/workflows/ci.yml/badge.svg)](https://github.com/le0pard/re2js/actions/workflows/ci.yml)
3
4
 
4
5
  ## [Playground](https://re2js.leopard.in.ua/)
@@ -51,9 +52,9 @@ You can compile a regex pattern using the `compile()` function:
51
52
  ```js
52
53
  import { RE2JS } from 're2js'
53
54
 
54
- const p = RE2JS.compile('abc');
55
- console.log(p.pattern()); // Outputs: 'abc'
56
- console.log(p.flags()); // Outputs: 0
55
+ const p = RE2JS.compile('abc')
56
+ console.log(p.pattern()) // Outputs: 'abc'
57
+ console.log(p.flags()) // Outputs: 0
57
58
  ```
58
59
 
59
60
  The `compile()` function also supports flags:
@@ -61,9 +62,9 @@ The `compile()` function also supports flags:
61
62
  ```js
62
63
  import { RE2JS } from 're2js'
63
64
 
64
- const p = RE2JS.compile('abc', RE2JS.CASE_INSENSITIVE | RE2JS.MULTILINE);
65
- console.log(p.pattern()); // Outputs: 'abc'
66
- console.log(p.flags()); // Outputs: 5
65
+ const p = RE2JS.compile('abc', RE2JS.CASE_INSENSITIVE | RE2JS.MULTILINE)
66
+ console.log(p.pattern()) // Outputs: 'abc'
67
+ console.log(p.flags()) // Outputs: 5
67
68
  ```
68
69
 
69
70
  #### Tagged Template Literals (No Double-Escaping)
@@ -71,15 +72,15 @@ console.log(p.flags()); // Outputs: 5
71
72
  To avoid the tedious "double-backslash" escaping problem common with the `RE2JS.compile("\\d+")` syntax, the library provides a handy `re` tagged template literal. This allows you to write patterns exactly as they appear in standard JavaScript regex literals:
72
73
 
73
74
  ```js
74
- import { re, RE2JS } from 're2js';
75
+ import { re, RE2JS } from 're2js'
75
76
 
76
77
  // Instead of RE2JS.compile('\\b\\w+\\b')
77
- const p1 = re`\b\w+\b`;
78
- console.log(p1.pattern()); // Outputs: '\b\w+\b'
78
+ const p1 = re`\b\w+\b`
79
+ console.log(p1.pattern()) // Outputs: '\b\w+\b'
79
80
 
80
81
  // You can also pass flags by calling `re` as a function first:
81
- const p2 = re(RE2JS.CASE_INSENSITIVE | RE2JS.MULTILINE)`^foo\d+`;
82
- console.log(p2.test('FOO42')); // true
82
+ const p2 = re(RE2JS.CASE_INSENSITIVE | RE2JS.MULTILINE)`^foo\d+`
83
+ console.log(p2.test('FOO42')) // true
83
84
  ```
84
85
 
85
86
  #### Supported flags:
@@ -118,7 +119,7 @@ RE2JS.LOOKBEHINDS
118
119
 
119
120
  RE2JS allows you to check if a string matches a given regex pattern using the `matches()` function.
120
121
 
121
- ***Performance Note:** The `matches()` method is highly optimized. It performs a strict anchored check and runs directly on the high-speed DFA (Deterministic Finite Automaton) engine without tracking capture groups or instantiating a stateful `Matcher` object.*
122
+ _**Performance Note:** The `matches()` method is highly optimized. It performs a strict anchored check and runs directly on the high-speed DFA (Deterministic Finite Automaton) engine without tracking capture groups or instantiating a stateful `Matcher` object._
122
123
 
123
124
  ```js
124
125
  import { RE2JS } from 're2js'
@@ -130,10 +131,7 @@ RE2JS.compile('ab+c').matches('abbbc') // true
130
131
  RE2JS.compile('ab+c').matches('cbbba') // false
131
132
  // with flags
132
133
  RE2JS.compile('ab+c', RE2JS.CASE_INSENSITIVE).matches('AbBBc') // true
133
- RE2JS.compile(
134
- '^ab.*c$',
135
- RE2JS.DOTALL | RE2JS.MULTILINE | RE2JS.CASE_INSENSITIVE
136
- ).matches('AB\nc') // true
134
+ RE2JS.compile('^ab.*c$', RE2JS.DOTALL | RE2JS.MULTILINE | RE2JS.CASE_INSENSITIVE).matches('AB\nc') // true
137
135
  ```
138
136
 
139
137
  ### High-Performance Boolean Testing
@@ -145,14 +143,14 @@ If you only need to know **whether** a string matches a pattern (without extract
145
143
  Tests if the regular expression matches **any part** of the provided input (unanchored). This method mirrors the standard JavaScript `RegExp.prototype.test()` API
146
144
 
147
145
  ```js
148
- import { RE2JS } from 're2js';
146
+ import { RE2JS } from 're2js'
149
147
 
150
148
  // Compile once, reuse often
151
- const re = RE2JS.compile('error|warning|critical');
149
+ const re = RE2JS.compile('error|warning|critical')
152
150
 
153
151
  // Extremely fast, unanchored DFA search
154
152
  if (re.test('The system encountered a critical failure')) {
155
- console.log('Log needs attention!');
153
+ console.log('Log needs attention!')
156
154
  }
157
155
  ```
158
156
 
@@ -160,16 +158,16 @@ if (re.test('The system encountered a critical failure')) {
160
158
 
161
159
  Tests if the regular expression matches the entire input string (anchored to both start and end).
162
160
 
163
- *Note: `RE2JS.matches()` delegates to this method, so they provide the exact same performance and behavior.*
161
+ _Note: `RE2JS.matches()` delegates to this method, so they provide the exact same performance and behavior._
164
162
 
165
163
  ```js
166
- import { RE2JS } from 're2js';
164
+ import { RE2JS } from 're2js'
167
165
 
168
- const isHex = RE2JS.compile('[0-9A-Fa-f]+');
166
+ const isHex = RE2JS.compile('[0-9A-Fa-f]+')
169
167
 
170
168
  // Fast, anchored DFA validation
171
- console.log(isHex.testExact('1A4F')); // true
172
- console.log(isHex.testExact('1A4F-xyz')); // false
169
+ console.log(isHex.testExact('1A4F')) // true
170
+ console.log(isHex.testExact('1A4F-xyz')) // false
173
171
  ```
174
172
 
175
173
  ### Checking Initial Match
@@ -237,27 +235,26 @@ Instead of dealing with a stateful `Matcher` object, `exec()` performs a single
237
235
  ```js
238
236
  import { RE2JS } from 're2js'
239
237
 
240
- const re = RE2JS.compile('(?P<first>\\w+) (?:(?P<middle>\\w+) )?(?P<last>\\w+)');
241
- const result = re.exec('John Doe');
238
+ const re = RE2JS.compile('(?P<first>\\w+) (?:(?P<middle>\\w+) )?(?P<last>\\w+)')
239
+ const result = re.exec('John Doe')
242
240
 
243
241
  if (result !== null) {
244
- console.log(result[0]); // "John Doe" (Full match)
245
- console.log(result[1]); // "John" (Group 1)
246
- console.log(result[2]); // undefined (Group 2 didn't match)
247
- console.log(result[3]); // "Doe" (Group 3)
242
+ console.log(result[0]) // "John Doe" (Full match)
243
+ console.log(result[1]) // "John" (Group 1)
244
+ console.log(result[2]) // undefined (Group 2 didn't match)
245
+ console.log(result[3]) // "Doe" (Group 3)
248
246
 
249
- console.log(result.index); // 0
250
- console.log(result.input); // "John Doe"
247
+ console.log(result.index) // 0
248
+ console.log(result.input) // "John Doe"
251
249
 
252
250
  // Named groups dictionary
253
- console.log(result.groups.first); // "John"
254
- console.log(result.groups.middle); // undefined
255
- console.log(result.groups.last); // "Doe"
251
+ console.log(result.groups.first) // "John"
252
+ console.log(result.groups.middle) // undefined
253
+ console.log(result.groups.last) // "Doe"
256
254
  }
257
-
258
255
  ```
259
256
 
260
- ***Performance Note:** If you are running `exec()` inside a `while` loop to manually extract multiple matches globally, it is highly recommended to use `.matchAll()` instead, as it is cleaner, strictly stateless, and avoids infinite loop pitfalls.*
257
+ _**Performance Note:** If you are running `exec()` inside a `while` loop to manually extract multiple matches globally, it is highly recommended to use `.matchAll()` instead, as it is cleaner, strictly stateless, and avoids infinite loop pitfalls._
261
258
 
262
259
  ### Iterating Over Matches (`matchAll`)
263
260
 
@@ -275,15 +272,15 @@ const input = 'Dates: 2024-05 and 2025-11.'
275
272
 
276
273
  // Native ES6 Iteration
277
274
  for (const match of re2.matchAll(input)) {
278
- console.log(match[0]); // "2024-05", then "2025-11"
279
- console.log(match.index); // 7, then 19
280
- console.log(match.groups); // { year: '2024', month: '05' } then { year: '2025', month: '11' }
281
- console.log(match.groups.year); // "2024", then "2025"
275
+ console.log(match[0]) // "2024-05", then "2025-11"
276
+ console.log(match.index) // 7, then 19
277
+ console.log(match.groups) // { year: '2024', month: '05' } then { year: '2025', month: '11' }
278
+ console.log(match.groups.year) // "2024", then "2025"
282
279
  }
283
280
 
284
281
  // Or easily collect all matches into an array
285
- const allMatches = [...re2.matchAll(input)];
286
- console.log(allMatches.length); // 2
282
+ const allMatches = [...re2.matchAll(input)]
283
+ console.log(allMatches.length) // 2
287
284
  ```
288
285
 
289
286
  ### Multi-Pattern Matching (RE2Set)
@@ -301,8 +298,8 @@ const set = new RE2Set()
301
298
 
302
299
  // Add patterns to the set.
303
300
  // The add() method returns the integer ID of the pattern.
304
- set.add('error') // ID: 0
305
- set.add('warning') // ID: 1
301
+ set.add('error') // ID: 0
302
+ set.add('warning') // ID: 1
306
303
  set.add('critical') // ID: 2
307
304
 
308
305
  // You must compile the set before matching!
@@ -331,20 +328,20 @@ import { RE2Set, RE2JS } from 're2js'
331
328
 
332
329
  // Anchor the set to match the entire string, make it case-insensitive,
333
330
  // and limit the DFA memory usage to ~4MB (default is 8MB).
334
- const maxMem = 4 * 1024 * 1024;
331
+ const maxMem = 4 * 1024 * 1024
335
332
  const set = new RE2Set(RE2Set.ANCHOR_BOTH, RE2JS.CASE_INSENSITIVE, maxMem)
336
333
 
337
334
  set.add('foo') // ID: 0
338
335
  set.add('bar') // ID: 1
339
- set.add('.*') // ID: 2
336
+ set.add('.*') // ID: 2
340
337
 
341
338
  set.compile()
342
339
 
343
- console.log(set.match('FOO')) // [0, 2] (Matches 'foo' and '.*' because of CASE_INSENSITIVE)
340
+ console.log(set.match('FOO')) // [0, 2] (Matches 'foo' and '.*' because of CASE_INSENSITIVE)
344
341
  console.log(set.match('foobar')) // [2] (Only '.*' matches the entire string because of ANCHOR_BOTH)
345
342
  ```
346
343
 
347
- ***Performance Note:** `RE2Set` heavily utilizes the high-speed DFA engine to process multi-pattern matches simultaneously. However, if your patterns contain boundaries (e.g., `\b`) or trigger a massive state explosion, it will seamlessly and safely fall back to the bounded NFA engine.*
344
+ _**Performance Note:** `RE2Set` heavily utilizes the high-speed DFA engine to process multi-pattern matches simultaneously. However, if your patterns contain boundaries (e.g., `\b`) or trigger a massive state explosion, it will seamlessly and safely fall back to the bounded NFA engine._
348
345
 
349
346
  #### Example: Fast JS Routing with RE2Set
350
347
 
@@ -483,9 +480,7 @@ The `group()` method retrieves the content matched by a specific name of capturi
483
480
  import { RE2JS } from 're2js'
484
481
 
485
482
  // example with `(?P<name>expr)`
486
- const p = RE2JS.compile(
487
- '(?P<baz>f(?P<foo>b*a(?P<another>r+)){0,10})(?P<bag>bag)?(?P<nomatch>zzz)?'
488
- )
483
+ const p = RE2JS.compile('(?P<baz>f(?P<foo>b*a(?P<another>r+)){0,10})(?P<bag>bag)?(?P<nomatch>zzz)?')
489
484
  const matchString = p.matcher('fbbarrrrrbag')
490
485
  if (matchString.matches()) {
491
486
  matchString.group('baz') // 'fbbarrrrr'
@@ -496,9 +491,7 @@ if (matchString.matches()) {
496
491
  }
497
492
 
498
493
  // example with `(?<name>expr)`
499
- const m = RE2JS.compile(
500
- '(?<baz>f(?<foo>b*a))'
501
- )
494
+ const m = RE2JS.compile('(?<baz>f(?<foo>b*a))')
502
495
  const mString = m.matcher('fbba')
503
496
  if (mString.matches()) {
504
497
  mString.group('baz') // 'fbba'
@@ -538,9 +531,7 @@ The `replaceAll()` method replaces all occurrences of a pattern match in a strin
538
531
  ```js
539
532
  import { RE2JS } from 're2js'
540
533
 
541
- RE2JS.compile('Frog')
542
- .matcher("What the Frog's Eye Tells the Frog's Brain")
543
- .replaceAll('Lizard') // "What the Lizard's Eye Tells the Lizard's Brain"
534
+ RE2JS.compile('Frog').matcher("What the Frog's Eye Tells the Frog's Brain").replaceAll('Lizard') // "What the Lizard's Eye Tells the Lizard's Brain"
544
535
  RE2JS.compile('(.)(.)(.)(.)(.)(.)(.)(.)(.)(.)(.)(.)(.)')
545
536
  .matcher('abcdefghijklmnopqrstuvwxyz123')
546
537
  .replaceAll('$10$20') // 'jb0wo0123'
@@ -549,6 +540,7 @@ RE2JS.compile('(.)(.)(.)(.)(.)(.)(.)(.)(.)(.)(.)(.)(.)')
549
540
  Note that the replacement string can include references to capturing groups from the pattern
550
541
 
551
542
  Parameters:
543
+
552
544
  - `replacement (String | Function)`: The string that replaces the substrings found, or a function invoked to create the new substring. When passing a string, capture groups and special characters have special behavior. For example:
553
545
  - `$&` refers to the entire matched substring
554
546
  - `$1, $2, ...` refer to the corresponding capture groups in the pattern
@@ -569,12 +561,8 @@ Examples:
569
561
  ```js
570
562
  import { RE2JS } from 're2js'
571
563
 
572
- RE2JS.compile('(\\w+) (\\w+)')
573
- .matcher('Hello World')
574
- .replaceAll('$& - $&') // 'Hello World - Hello World'
575
- RE2JS.compile('(\\w+) (\\w+)')
576
- .matcher('Hello World')
577
- .replaceAll('$0 - $0', true) // 'Hello World - Hello World'
564
+ RE2JS.compile('(\\w+) (\\w+)').matcher('Hello World').replaceAll('$& - $&') // 'Hello World - Hello World'
565
+ RE2JS.compile('(\\w+) (\\w+)').matcher('Hello World').replaceAll('$0 - $0', true) // 'Hello World - Hello World'
578
566
  ```
579
567
 
580
568
  #### Replacing the First Occurrence
@@ -584,9 +572,7 @@ The `replaceFirst()` method replaces the first occurrence of a pattern match in
584
572
  ```js
585
573
  import { RE2JS } from 're2js'
586
574
 
587
- RE2JS.compile('Frog')
588
- .matcher("What the Frog's Eye Tells the Frog's Brain")
589
- .replaceFirst('Lizard') // "What the Lizard's Eye Tells the Frog's Brain"
575
+ RE2JS.compile('Frog').matcher("What the Frog's Eye Tells the Frog's Brain").replaceFirst('Lizard') // "What the Lizard's Eye Tells the Frog's Brain"
590
576
  RE2JS.compile('(.)(.)(.)(.)(.)(.)(.)(.)(.)(.)(.)(.)(.)')
591
577
  .matcher('abcdefghijklmnopqrstuvwxyz123')
592
578
  .replaceFirst('$10$20') // 'jb0nopqrstuvwxyz123'
@@ -610,23 +596,21 @@ The replacer function is invoked for each match, and its return value is used as
610
596
  import { RE2JS } from 're2js'
611
597
 
612
598
  // Example 1: Dynamic replacements
613
- const re1 = RE2JS.compile('\\d+');
614
- const m1 = re1.matcher('Numbers: 1, 2, 3');
599
+ const re1 = RE2JS.compile('\\d+')
600
+ const m1 = re1.matcher('Numbers: 1, 2, 3')
615
601
 
616
- m1.replaceAll((match) => String(Number(match) * 10));
602
+ m1.replaceAll((match) => String(Number(match) * 10))
617
603
  // 'Numbers: 10, 20, 30'
618
604
 
619
-
620
605
  // Example 2: Using named capture groups and function signature
621
- const re2 = RE2JS.compile('(?P<first>\\w+) (?:(?P<middle>\\w+) )?(?P<last>\\w+)');
622
- const m2 = re2.matcher('Hello World');
606
+ const re2 = RE2JS.compile('(?P<first>\\w+) (?:(?P<middle>\\w+) )?(?P<last>\\w+)')
607
+ const m2 = re2.matcher('Hello World')
623
608
 
624
609
  m2.replaceFirst((match, p1, p2, p3, offset, string, groups) => {
625
610
  // 'middle' didn't match, so p2 and groups.middle will be undefined
626
- return `${groups.last}, ${groups.first}`;
627
- });
611
+ return `${groups.last}, ${groups.first}`
612
+ })
628
613
  // 'World, Hello'
629
-
630
614
  ```
631
615
 
632
616
  ### Safe Replacements
@@ -671,9 +655,9 @@ The program size represents a very approximate measure of a regexp's "cost". Lar
671
655
  ```js
672
656
  import { RE2JS } from 're2js'
673
657
 
674
- console.log(RE2JS.compile('^').programSize()); // Outputs: 3
675
- console.log(RE2JS.compile('a+b').programSize()); // Outputs: 5
676
- console.log(RE2JS.compile('(a+b?)').programSize()); // Outputs: 8
658
+ console.log(RE2JS.compile('^').programSize()) // Outputs: 3
659
+ console.log(RE2JS.compile('a+b').programSize()) // Outputs: 5
660
+ console.log(RE2JS.compile('(a+b?)').programSize()) // Outputs: 8
677
661
  ```
678
662
 
679
663
  ### Translating Regular Expressions
@@ -710,20 +694,20 @@ Originally, the C++ implementation of the RE2 engine included both NFA (Nondeter
710
694
 
711
695
  `re2js` achieves full architectural parity with the highly optimized Go `regexp` package and incorporates advanced performance features from the original C++ engine. To maximize execution speed on everyday queries without ever sacrificing memory safety, `re2js` intelligently and dynamically routes execution through a highly advanced multi-tiered architecture:
712
696
 
713
- * **The Prefilter Engine:** Analyzes the Abstract Syntax Tree (AST) before execution to extract mandatory string literals (e.g., extracting `"error"` and `"critical"` from `/error.*critical/`). It uses blistering-fast native JavaScript `indexOf` to instantly reject mismatches, completely bypassing the regex state-machines.
714
- * **Aggressive AST Simplification:** Trims impossible match branches and collapses redundant logic prior to compilation, mathematically pruning dead execution paths to dramatically reduce the size of the generated state machine.
715
- * **Multi-Pattern Sets (`RE2Set`):** Combines hundreds or thousands of regular expressions into a single combined DFA, allowing you to search a string for all patterns simultaneously in strict $O(N)$ linear time.
716
- * **OnePass DFA:** Provides high-speed capture group extraction for mathematically 1-unambiguous patterns, bypassing thread queues entirely.
717
- * **Lazy Powerset DFA:** Executes high-speed boolean matches (e.g., `.test()`) by fusing active states dynamically on the fly.
718
- * **BitState Backtracker:** Avoids heavy object array allocations by using bitwise operations to extract captures on short-to-medium length strings.
719
- * **Pike VM (NFA):** Acts as the robust, bounded-memory fallback engine for complex, ambiguous patterns that exceed fast-path limits.
697
+ - **The Prefilter Engine:** Analyzes the Abstract Syntax Tree (AST) before execution to extract mandatory string literals (e.g., extracting `"error"` and `"critical"` from `/error.*critical/`). It uses blistering-fast native JavaScript `indexOf` to instantly reject mismatches, completely bypassing the regex state-machines.
698
+ - **Aggressive AST Simplification:** Trims impossible match branches and collapses redundant logic prior to compilation, mathematically pruning dead execution paths to dramatically reduce the size of the generated state machine.
699
+ - **Multi-Pattern Sets (`RE2Set`):** Combines hundreds or thousands of regular expressions into a single combined DFA, allowing you to search a string for all patterns simultaneously in strict $O(N)$ linear time.
700
+ - **OnePass DFA:** Provides high-speed capture group extraction for mathematically 1-unambiguous patterns, bypassing thread queues entirely.
701
+ - **Lazy Powerset DFA:** Executes high-speed boolean matches (e.g., `.test()`) by fusing active states dynamically on the fly.
702
+ - **BitState Backtracker:** Avoids heavy object array allocations by using bitwise operations to extract captures on short-to-medium length strings.
703
+ - **Pike VM (NFA):** Acts as the robust, bounded-memory fallback engine for complex, ambiguous patterns that exceed fast-path limits.
720
704
 
721
705
  Thanks to these dynamic fast-paths, `re2js` delivers performance comparable to native engines for simple queries, while remaining completely immune to catastrophic backtracking and stack overflow crashes.
722
706
 
723
707
  Should you require maximum absolute performance on the server side when using RE2, it would be beneficial to consider the following packages for JS:
724
708
 
725
- - [Node-RE2](https://github.com/uhop/node-re2/): A powerful RE2 C++ binding for Node.js
726
- - [RE2-WASM](https://github.com/google/re2-wasm/): This package is a WASM wrapper for RE2. Please note, as of now, it does not work in browsers
709
+ - [Node-RE2](https://github.com/uhop/node-re2/): A powerful RE2 C++ binding for Node.js
710
+ - [RE2-WASM](https://github.com/google/re2-wasm/): This package is a WASM wrapper for RE2. Please note, as of now, it does not work in browsers
727
711
 
728
712
  ### RE2JS vs RE2-Node (C++ Bindings)
729
713
 
@@ -732,7 +716,7 @@ Because RE2JS's Lazy DFA, Prefilter, and OnePass engines operate efficiently wit
732
716
  Here is a [benchmark running 30,000 items](src/__tests__/re2.bench.js) through both engines using their respective `.test()` fast-paths (averages of multiple runs):
733
717
 
734
718
  | Benchmark Scenario | Pattern Example | RE2JS (Pure JS) | RE2-Node (C++) | Result |
735
- |:--------------------------|:---------------------------|:----------------|:---------------|:----------------------------|
719
+ | :------------------------ | :------------------------- | :-------------- | :------------- | :-------------------------- |
736
720
  | **ReDoS Attempt** | `/(a+)+!/` | **2.16 ms** | 15.94 ms | `re2js` is **7.38x** faster |
737
721
  | **Simple Literal** | `/damage/` | **2.58 ms** | 12.39 ms | `re2js` is **4.80x** faster |
738
722
  | **Deep State Machine** | `/([0-9]+(/[0-9]+)+)/` | **11.20 ms** | 15.76 ms | `re2js` is **1.41x** faster |
@@ -744,10 +728,10 @@ Here is a [benchmark running 30,000 items](src/__tests__/re2.bench.js) through b
744
728
  | **Word Boundaries (NFA)** | `/\b(Flying\|First...)\b/` | 107.12 ms | **15.41 ms** | `re2-node` is 6.95x faster |
745
729
 
746
730
  **Takeaways:**
747
- * **Pure JS Strengths:** For complex state tracking (nested groups, wildcards) and literal string scanning, `re2js` actually beats the native C++ bindings. V8's Turbofan JIT compiler is able to heavily optimize the Pure JS DFA loop, bypassing the C++ boundary entirely.
748
- * **C++ Strengths:** For character class evaluations (Case Insensitivity, Bounded Repetitions), `re2-node` has a slight edge thanks to highly optimized, hardware-level memory tables.
749
- * **The NFA Fallback:** Pure DFA engines mathematically cannot track look-behind context like Word Boundaries (`\b`). When RE2JS encounters these, it safely bails out to its NFA engine. As shown in the benchmarks, the pure JS NFA is significantly slower than the C++ NFA. **For maximum performance in RE2JS, avoid `\b` when doing bulk boolean `.test()` matching.**
750
731
 
732
+ - **Pure JS Strengths:** For complex state tracking (nested groups, wildcards) and literal string scanning, `re2js` actually beats the native C++ bindings. V8's Turbofan JIT compiler is able to heavily optimize the Pure JS DFA loop, bypassing the C++ boundary entirely.
733
+ - **C++ Strengths:** For character class evaluations (Case Insensitivity, Bounded Repetitions), `re2-node` has a slight edge thanks to highly optimized, hardware-level memory tables.
734
+ - **The NFA Fallback:** Pure DFA engines mathematically cannot track look-behind context like Word Boundaries (`\b`). When RE2JS encounters these, it safely bails out to its NFA engine. As shown in the benchmarks, the pure JS NFA is significantly slower than the C++ NFA. **For maximum performance in RE2JS, avoid `\b` when doing bulk boolean `.test()` matching.**
751
735
 
752
736
  ### RE2JS vs JavaScript's native RegExp
753
737
 
@@ -759,7 +743,7 @@ const string = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa!'
759
743
 
760
744
  // Running 30,000 iterations
761
745
  RE2JS.compile(regex).test(string) // Total time: ~9.87 ms
762
- new RegExp(regex).test(string) // Total time: ~11.43 ms
746
+ new RegExp(regex).test(string) // Total time: ~11.43 ms
763
747
  ```
764
748
 
765
749
  For safe, simple patterns, the RE2JS DFA fast-path is heavily optimized and performs at parity with—or even slightly faster than—V8's native RegExp engine.
@@ -771,7 +755,7 @@ const string = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa!'
771
755
  // Running 30,000 iterations
772
756
  RE2JS.compile(regex).test(string) // Total time: ~454.17 ms
773
757
  // Running EXACTLY 1 iteration
774
- new RegExp(regex).test(string) // Total time: ~105802.02 ms (over 105 seconds)
758
+ new RegExp(regex).test(string) // Total time: ~105802.02 ms (over 105 seconds)
775
759
  ```
776
760
 
777
761
  In the second example, a ReDoS scenario is depicted. The regular expression `([a-z]+)+$` is a potentially problematic one because it contains a nested quantifier. In standard NFA engines (like JavaScript's native `RegExp`), nested quantifiers can cause catastrophic backtracking. If a malicious user inputs a carefully crafted string, it results in exponentially high processing times, leading to a Denial of Service (DoS) attack.
@@ -787,25 +771,25 @@ However, `re2js` implements a breakthrough algorithmic approach ([developed by r
787
771
  You can enable it by passing the `RE2JS.LOOKBEHINDS` flag during compilation:
788
772
 
789
773
  ```js
790
- import { RE2JS } from 're2js';
774
+ import { RE2JS } from 're2js'
791
775
 
792
776
  // Positive Lookbehind: Match 'bar' only if preceded by 'foo'
793
- const positive = RE2JS.compile('(?<=foo)bar', RE2JS.LOOKBEHINDS);
794
- positive.test('foobar'); // true
795
- positive.test('bazbar'); // false
777
+ const positive = RE2JS.compile('(?<=foo)bar', RE2JS.LOOKBEHINDS)
778
+ positive.test('foobar') // true
779
+ positive.test('bazbar') // false
796
780
 
797
781
  // Negative Lookbehind: Match 'bar' only if NOT preceded by 'foo'
798
- const negative = RE2JS.compile('(?<!foo)bar', RE2JS.LOOKBEHINDS);
799
- negative.test('bazbar'); // true
800
- negative.test('foobar'); // false
782
+ const negative = RE2JS.compile('(?<!foo)bar', RE2JS.LOOKBEHINDS)
783
+ negative.test('bazbar') // true
784
+ negative.test('foobar') // false
801
785
  ```
802
786
 
803
787
  ### Important Limitations and Warnings
804
788
 
805
789
  1. **Performance Overhead:** If a regex contains a lookbehind, the engine is forced to safely bypass the ultra-fast Lazy DFA and OnePass engines. It evaluates the lookbehinds using parallel automata running on the NFA (Pike VM). While execution remains mathematically safe and linear $O(n)$, the NFA engine is generally slower than the DFA fast-paths. Use lookbehinds only when necessary.
806
- 2. **Prefix Acceleration is Disabled:** To ensure the parallel tracking automata initialize correctly, high-speed string prefix skipping (e.g., using `indexOf` to jump to a starting literal) is disabled when lookbehinds are present.
807
- 3. **Captureless Guarantee:** To prevent state-explosion vulnerabilities and maintain strict safety invariants, lookbehinds are strictly evaluated as *captureless*. If you attempt to include a capturing group inside a lookbehind (e.g., `(?<=(foo))bar`), the engine will proactively throw a `SyntaxError` at compile time. Use non-capturing groups `(?:...)` instead.
808
-
790
+ 2. **Prefix Acceleration is Disabled:** The lookbehind automata must observe every character of the input, so high-speed string prefix skipping (e.g., using `indexOf` to jump to a starting literal) is disabled when lookbehinds are present.
791
+ 3. **Captureless Guarantee:** To prevent state-explosion vulnerabilities and maintain strict safety invariants, lookbehinds are strictly evaluated as _captureless_. If you attempt to include a capturing group inside a lookbehind (e.g., `(?<=(foo))bar`), the engine will proactively throw a `SyntaxError` at compile time. Use non-capturing groups `(?:...)` instead.
792
+ 4. **Repeated Matching:** The lookbehind automata run from the beginning of the input, because a lookbehind may depend on text arbitrarily far behind the match (e.g. `(?<=x.*)b`). A `Matcher` therefore carries their state from one match to the next: `find()` loops, `matchAll()`, `split()` and `replaceAll()` scan the input once and stay linear in its length. Only a `find(start)` that jumps _backwards_ (to a position before the previous search started) re-evaluates the lookbehinds from the beginning of the input.
809
793
 
810
794
  ## Development
811
795
 
@@ -818,4 +802,10 @@ yarn node ./tools/scripts/genUnicodeTable.js > src/UnicodeTables.js
818
802
 
819
803
  To run `make_perl_groups.pl`, you need to have Perl installed (the required version is specified inside `.tool-versions`).
820
804
 
805
+ The conformance tests in `src/__tests__/Exec.test.js` run the RE2 and AT&T POSIX test data through every matching engine, on both UTF-16 and UTF-8 input. By default they check a sample of the 5.8 million-line exhaustive RE2 data; to check all of it (this takes several minutes):
806
+
807
+ ```bash
808
+ RE2JS_EXHAUSTIVE=1 yarn vitest run --project=node src/__tests__/Exec.test.js
809
+ ```
810
+
821
811
  [Playground website](https://re2js.leopard.in.ua/) maintained in `www` branch