@@ -6,12 +6,18 @@ const recursiveToken = r`\(\?R=(?<rDepth>[^\)]+)\)|${gRToken}`;
6
6
const namedCapturingDelim = r `\(\?<(?![=!])(?<captureName>[^>]+)>` ;
7
7
const token = new RegExp ( r `${ namedCapturingDelim } |${ recursiveToken } |\(\?|\\?.` , 'gsu' ) ;
8
8
const overlappingRecursionMsg = 'Cannot use multiple overlapping recursions' ;
9
+ // See <github.com/slevithan/regex/blob/main/src/subclass.js>
10
+ const emulationGroupMarker = '$E$' ;
9
11
10
12
/**
11
13
@param {string } expression
14
+ @param {{
15
+ flags?: string;
16
+ useEmulationGroups?: boolean;
17
+ }} [data]
12
18
@returns {string }
13
19
*/
14
- export function recursion ( expression ) {
20
+ export function recursion ( expression , data ) {
15
21
// Keep the initial fail-check (which avoids unneeded processing) as fast as possible by testing
16
22
// without the accuracy improvement of using `hasUnescaped` with default `Context`
17
23
if ( ! ( new RegExp ( recursiveToken , 'su' ) . test ( expression ) ) ) {
@@ -20,6 +26,7 @@ export function recursion(expression) {
20
26
if ( hasUnescaped ( expression , r `\(\?\(DEFINE\)` , Context . DEFAULT ) ) {
21
27
throw new Error ( 'DEFINE groups cannot be used with recursion' ) ;
22
28
}
29
+ const useEmulationGroups = ! ! data ?. useEmulationGroups ;
23
30
const hasNumberedBackref = hasUnescaped ( expression , r `\\[1-9]` , Context . DEFAULT ) ;
24
31
const groupContentsStartPos = new Map ( ) ;
25
32
const openGroups = [ ] ;
@@ -57,7 +64,7 @@ export function recursion(expression) {
57
64
throw new Error ( overlappingRecursionMsg ) ;
58
65
}
59
66
// No need to parse further
60
- return makeRecursive ( pre , post , + rDepth , false ) ;
67
+ return makeRecursive ( pre , post , + rDepth , false , useEmulationGroups ) ;
61
68
// `\g<name&R=N>`, `\g<number&R=N>`
62
69
} else if ( gRNameOrNum ) {
63
70
assertMaxInBounds ( gRDepth ) ;
@@ -84,7 +91,7 @@ export function recursion(expression) {
84
91
}
85
92
const groupContentsPre = expression . slice ( startPos , match . index ) ;
86
93
const groupContentsPost = groupContents . slice ( groupContentsPre . length + m . length ) ;
87
- const expansion = makeRecursive ( groupContentsPre , groupContentsPost , + gRDepth , true ) ;
94
+ const expansion = makeRecursive ( groupContentsPre , groupContentsPost , + gRDepth , true , useEmulationGroups ) ;
88
95
const pre = expression . slice ( 0 , startPos ) ;
89
96
const post = expression . slice ( startPos + groupContents . length ) ;
90
97
// Modify the string we're looping over
@@ -139,9 +146,10 @@ function assertMaxInBounds(max) {
139
146
@param {string } post
140
147
@param {number } maxDepth
141
148
@param {boolean } isSubpattern
149
+ @param {boolean } useEmulationGroups
142
150
@returns {string }
143
151
*/
144
- function makeRecursive ( pre , post , maxDepth , isSubpattern ) {
152
+ function makeRecursive ( pre , post , maxDepth , isSubpattern , useEmulationGroups ) {
145
153
const namesInRecursed = new Set ( ) ;
146
154
// Avoid this work if not needed
147
155
if ( isSubpattern ) {
@@ -153,35 +161,41 @@ function makeRecursive(pre, post, maxDepth, isSubpattern) {
153
161
// Depth 2: 'pre(?:pre(?:)post)post'
154
162
// Depth 3: 'pre(?:pre(?:pre(?:)post)post)post'
155
163
return `${ pre } ${
156
- repeatWithDepth ( `(?:${ pre } ` , reps , ( isSubpattern ? namesInRecursed : null ) )
164
+ repeatWithDepth ( `(?:${ pre } ` , reps , ( isSubpattern ? namesInRecursed : null ) , 'forward' , useEmulationGroups )
157
165
} (?:)${
158
- repeatWithDepth ( `${ post } )` , reps , ( isSubpattern ? namesInRecursed : null ) , 'backward' )
166
+ repeatWithDepth ( `${ post } )` , reps , ( isSubpattern ? namesInRecursed : null ) , 'backward' , useEmulationGroups )
159
167
} ${ post } `;
160
168
}
161
169
162
170
/**
163
171
@param {string } expression
164
172
@param {number } reps
165
173
@param {Set<string> | null } namesInRecursed
166
- @param {'forward' | 'backward' } [direction]
174
+ @param {'forward' | 'backward' } direction
175
+ @param {boolean } useEmulationGroups
167
176
@returns {string }
168
177
*/
169
- function repeatWithDepth ( expression , reps , namesInRecursed , direction = 'forward' ) {
178
+ function repeatWithDepth ( expression , reps , namesInRecursed , direction , useEmulationGroups ) {
170
179
const startNum = 2 ;
171
180
const depthNum = i => direction === 'backward' ? reps - i + startNum - 1 : i + startNum ;
172
181
let result = '' ;
173
182
for ( let i = 0 ; i < reps ; i ++ ) {
174
183
const captureNum = depthNum ( i ) ;
175
184
result += replaceUnescaped (
176
185
expression ,
177
- r `${ namedCapturingDelim } |\\k<(?<backref>[^>]+)>` ,
186
+ r `${ namedCapturingDelim } |\\k<(?<backref>[^>]+)>${ useEmulationGroups ? r `|\((?!\?)` : '' } ` ,
178
187
( { 0 : m , groups : { captureName, backref} } ) => {
179
188
if ( backref && namesInRecursed && ! namesInRecursed . has ( backref ) ) {
180
189
// Don't alter backrefs to groups outside the recursed subpattern
181
190
return m ;
182
191
}
192
+ if ( m === '(' ) {
193
+ return `(${ emulationGroupMarker } ` ;
194
+ }
183
195
const suffix = `_$${ captureNum } ` ;
184
- return captureName ? `(?<${ captureName } ${ suffix } >` : r `\k<${ backref } ${ suffix } >` ;
196
+ return captureName ?
197
+ `(?<${ captureName } ${ suffix } >${ useEmulationGroups ? emulationGroupMarker : '' } ` :
198
+ r `\k<${ backref } ${ suffix } >` ;
185
199
} ,
186
200
Context . DEFAULT
187
201
) ;
0 commit comments