@@ -23,8 +23,7 @@ export type RegExpAST =
23
23
| { type : "optional" , inner : RegExpAST }
24
24
| { type : "repeat" , inner : RegExpAST , bounds : RepeatBounds }
25
25
| { type : "capture-group" , name ?: string , inner : RegExpAST }
26
- | { type : "positive-lookahead" , inner : RegExpAST , right : RegExpAST }
27
- | { type : "negative-lookahead" , inner : RegExpAST , right : RegExpAST }
26
+ | { type : "lookahead" , isPositive : boolean , inner : RegExpAST , right : RegExpAST }
28
27
| { type : "start-anchor" , left : RegExpAST , right : RegExpAST }
29
28
| { type : "end-anchor" , left : RegExpAST , right : RegExpAST }
30
29
@@ -56,8 +55,7 @@ function isNullable(ast: RegExpAST): boolean {
56
55
}
57
56
}
58
57
case "capture-group" : return isNullable ( ast . inner )
59
- case "positive-lookahead" : return isNullable ( ast . inner ) && isNullable ( ast . right )
60
- case "negative-lookahead" : return ! isNullable ( ast . inner ) && isNullable ( ast . right )
58
+ case "lookahead" : return isNullable ( ast . inner ) && isNullable ( ast . right )
61
59
case "start-anchor" : return isNullable ( ast . left ) && isNullable ( ast . right )
62
60
case "end-anchor" : return isNullable ( ast . left ) && isNullable ( ast . right )
63
61
}
@@ -80,8 +78,7 @@ function desugar(ast: RegExpAST): RegExpAST {
80
78
case 'star' : return star ( desugar ( ast . inner ) )
81
79
case 'start-anchor' : return startAnchor ( desugar ( ast . left ) , desugar ( ast . right ) )
82
80
case 'end-anchor' : return endAnchor ( desugar ( ast . left ) , desugar ( ast . right ) )
83
- case 'positive-lookahead' : return positiveLookahead ( desugar ( ast . inner ) , desugar ( ast . right ) )
84
- case 'negative-lookahead' : return negativeLookahead ( desugar ( ast . inner ) , desugar ( ast . right ) )
81
+ case 'lookahead' : return lookahead ( ast . isPositive , desugar ( ast . inner ) , desugar ( ast . right ) )
85
82
// sugar nodes:
86
83
case 'capture-group' : return desugar ( ast . inner )
87
84
case 'plus' : {
@@ -243,26 +240,15 @@ function pullUpStartAnchor(ast: RegExpAST): RegExpAST {
243
240
return endAnchor ( left , undefined )
244
241
}
245
242
}
246
- case "positive- lookahead" : {
243
+ case "lookahead" : {
247
244
const inner = pullUpStartAnchor ( ast . inner )
248
245
const right = pullUpStartAnchor ( ast . right )
249
246
if ( inner . type === 'start-anchor' ) {
250
- throw new UnsupportedSyntaxError ( 'start anchors (^) inside lookaheads are not supported' )
247
+ throw new UnsupportedSyntaxError ( 'start anchors inside lookaheads like (?=^a) are not supported' )
251
248
} else if ( right . type === 'start-anchor' ) {
252
- return startAnchor ( undefined , positiveLookahead ( ast . inner , right . right ) )
249
+ return startAnchor ( undefined , lookahead ( ast . isPositive , ast . inner , right . right ) )
253
250
} else {
254
- return positiveLookahead ( ast . inner , right )
255
- }
256
- }
257
- case "negative-lookahead" : {
258
- const inner = pullUpStartAnchor ( ast . inner )
259
- const right = pullUpStartAnchor ( ast . right )
260
- if ( inner . type === 'start-anchor' ) {
261
- throw new UnsupportedSyntaxError ( 'start anchors (^) inside lookaheads are not supported' )
262
- } else if ( right . type === 'start-anchor' ) {
263
- return startAnchor ( undefined , negativeLookahead ( ast . inner , right . right ) )
264
- } else {
265
- return negativeLookahead ( ast . inner , right )
251
+ return lookahead ( ast . isPositive , ast . inner , right )
266
252
}
267
253
}
268
254
}
@@ -392,26 +378,15 @@ function pullUpEndAnchor(ast: RegExpAST): RegExpAST {
392
378
return endAnchor ( left , undefined ) // i.e. `l$`
393
379
}
394
380
}
395
- case "positive- lookahead" : {
381
+ case "lookahead" : {
396
382
const inner = pullUpEndAnchor ( ast . inner )
397
383
const right = pullUpEndAnchor ( ast . right )
398
384
if ( inner . type === 'end-anchor' ) {
399
- throw new UnsupportedSyntaxError ( 'end anchors ($) inside lookaheads are not supported' )
385
+ throw new UnsupportedSyntaxError ( 'end anchors inside lookaheads like (?=a$) are not supported' )
400
386
} else if ( right . type === 'end-anchor' ) {
401
- return endAnchor ( positiveLookahead ( ast . inner , right . left ) , undefined )
387
+ return endAnchor ( lookahead ( ast . isPositive , ast . inner , right . left ) , undefined )
402
388
} else {
403
- return positiveLookahead ( ast . inner , right )
404
- }
405
- }
406
- case "negative-lookahead" : {
407
- const inner = pullUpEndAnchor ( ast . inner )
408
- const right = pullUpEndAnchor ( ast . right )
409
- if ( inner . type === 'end-anchor' ) {
410
- throw new UnsupportedSyntaxError ( 'end anchors ($) inside lookaheads are not supported' )
411
- } else if ( right . type === 'end-anchor' ) {
412
- return endAnchor ( negativeLookahead ( ast . inner , right . right ) , undefined )
413
- } else {
414
- return negativeLookahead ( ast . inner , right )
389
+ return lookahead ( ast . isPositive , ast . inner , right )
415
390
}
416
391
}
417
392
}
@@ -458,15 +433,13 @@ function toExtRegexAux(ast: RegExpAST): RE.ExtRegex {
458
433
case 'concat' : return RE . concat ( toExtRegexAux ( ast . left ) , toExtRegexAux ( ast . right ) )
459
434
case 'union' : return RE . union ( toExtRegexAux ( ast . left ) , toExtRegexAux ( ast . right ) )
460
435
case 'star' : return RE . star ( toExtRegexAux ( ast . inner ) )
461
- case 'positive-lookahead' : {
462
- const inner = toExtRegexAux ( ast . inner )
463
- const right = toExtRegexAux ( ast . right )
464
- return RE . intersection ( inner , right )
465
- }
466
- case 'negative-lookahead' : {
436
+ case 'lookahead' : {
467
437
const inner = toExtRegexAux ( ast . inner )
468
438
const right = toExtRegexAux ( ast . right )
469
- return RE . intersection ( RE . complement ( inner ) , right )
439
+ if ( ast . isPositive )
440
+ return RE . intersection ( inner , right )
441
+ else
442
+ return RE . intersection ( RE . complement ( inner ) , right )
470
443
}
471
444
}
472
445
checkedAllCases ( ast . type )
@@ -525,18 +498,12 @@ export function captureGroup(inner: RegExpAST, name?: string): RegExpAST {
525
498
return { type : 'capture-group' , inner, name }
526
499
}
527
500
528
- export function positiveLookahead (
529
- inner : RegExpAST ,
530
- right : RegExpAST ,
531
- ) : RegExpAST {
532
- return { type : 'positive-lookahead' , inner, right }
533
- }
534
-
535
- export function negativeLookahead (
501
+ export function lookahead (
502
+ isPositive : boolean ,
536
503
inner : RegExpAST ,
537
504
right : RegExpAST ,
538
505
) : RegExpAST {
539
- return { type : 'negative- lookahead' , inner, right }
506
+ return { type : 'lookahead' , isPositive , inner, right }
540
507
}
541
508
542
509
//////////////////////////////////////////////
@@ -587,10 +554,8 @@ function debugShow_(ast: RegExpAST): unknown {
587
554
return { type : 'repeat' , inner : debugShow_ ( ast . inner ) , bounds : ast . bounds }
588
555
case 'capture-group' :
589
556
return { type : 'capture-group' , name : ast . name , inner : debugShow_ ( ast . inner ) }
590
- case 'positive-lookahead' :
591
- return { type : 'positive-lookahead' , inner : debugShow_ ( ast . inner ) }
592
- case 'negative-lookahead' :
593
- return { type : 'negative-lookahead' , inner : debugShow_ ( ast . inner ) }
557
+ case 'lookahead' :
558
+ return { type : 'lookahead' , isPositive : ast . isPositive , inner : debugShow_ ( ast . inner ) }
594
559
}
595
560
checkedAllCases ( ast )
596
561
}
@@ -644,10 +609,14 @@ export function toString(ast: RegExpAST, options: RenderOptions): string {
644
609
645
610
case 'capture-group' :
646
611
return captureGroupToString ( ast . name , ast . inner , options )
647
- case 'positive-lookahead' :
648
- return '(?=' + toString ( ast . inner , options ) + ')' + maybeWithParens ( ast . right , ast , options )
649
- case 'negative-lookahead' :
650
- return '(?!' + toString ( ast . inner , options ) + ')' + maybeWithParens ( ast . right , ast , options )
612
+ case 'lookahead' : {
613
+ const inner = toString ( ast . inner , options )
614
+ const right = maybeWithParens ( ast . right , ast , options )
615
+ if ( ast . isPositive )
616
+ return '(?=' + inner + ')' + right
617
+ else
618
+ return '(?!' + inner + ')' + right
619
+ }
651
620
}
652
621
checkedAllCases ( ast )
653
622
}
@@ -666,8 +635,7 @@ function precLevel(nodeType: RegExpAST['type']) {
666
635
667
636
case 'concat' : return 4
668
637
669
- case 'positive-lookahead' : return 3
670
- case 'negative-lookahead' : return 3
638
+ case 'lookahead' : return 3
671
639
672
640
case 'start-anchor' : return 2
673
641
case 'end-anchor' : return 2
0 commit comments