@@ -23,9 +23,10 @@ export type RegExpAST =
2323 | { type : "optional" , inner : RegExpAST }
2424 | { type : "repeat" , inner : RegExpAST , bounds : RepeatBounds }
2525 | { type : "capture-group" , name ?: string , inner : RegExpAST }
26- | { type : "lookahead" , isPositive : boolean , inner : RegExpAST , right : RegExpAST }
26+ | { type : "lookahead" , isPositive : boolean , inner : RegExpAST }
27+ | { type : "lookbehind" , isPositive : boolean , inner : RegExpAST }
2728 | { type : "start-anchor" , left : RegExpAST , right : RegExpAST }
28- | { type : "end-anchor" , left : RegExpAST , right : RegExpAST }
29+ | { type : "end-anchor" , left : RegExpAST , right : RegExpAST }
2930
3031export type RenderOptions = {
3132 useNonCapturingGroups : boolean
@@ -55,7 +56,8 @@ function isNullable(ast: RegExpAST): boolean {
5556 }
5657 }
5758 case "capture-group" : return isNullable ( ast . inner )
58- case "lookahead" : return isNullable ( ast . inner ) && isNullable ( ast . right )
59+ case "lookahead" : return isNullable ( ast . inner ) // TODO: is this correct?
60+ case "lookbehind" : return isNullable ( ast . inner ) // TODO: is this correct?
5961 case "start-anchor" : return isNullable ( ast . left ) && isNullable ( ast . right )
6062 case "end-anchor" : return isNullable ( ast . left ) && isNullable ( ast . right )
6163 }
@@ -78,7 +80,8 @@ function desugar(ast: RegExpAST): RegExpAST {
7880 case 'star' : return star ( desugar ( ast . inner ) )
7981 case 'start-anchor' : return startAnchor ( desugar ( ast . left ) , desugar ( ast . right ) )
8082 case 'end-anchor' : return endAnchor ( desugar ( ast . left ) , desugar ( ast . right ) )
81- case 'lookahead' : return lookahead ( ast . isPositive , desugar ( ast . inner ) , desugar ( ast . right ) )
83+ case 'lookahead' : return lookahead ( ast . isPositive , desugar ( ast . inner ) )
84+ case 'lookbehind' : return lookbehind ( ast . isPositive , desugar ( ast . inner ) )
8285 // sugar nodes:
8386 case 'capture-group' : return desugar ( ast . inner )
8487 case 'plus' : {
@@ -252,17 +255,20 @@ function pullUpStartAnchor(ast: RegExpAST, isLeftClosed: boolean): RegExpAST {
252255 return endAnchor ( left , undefined )
253256 }
254257 }
255- case "lookahead" : {
256- const inner = pullUpStartAnchor ( ast . inner , true )
257- const right = pullUpStartAnchor ( ast . right , isLeftClosed )
258- if ( inner . type === 'start-anchor' ) {
259- throw new UnsupportedSyntaxError ( 'start anchors inside lookaheads like (?=^a)' )
260- } else if ( right . type === 'start-anchor' ) {
261- return startAnchor ( undefined , lookahead ( ast . isPositive , ast . inner , right . right ) )
262- } else {
263- return lookahead ( ast . isPositive , inner , right )
264- }
265- }
258+ case "lookahead" :
259+ // FIXME:
260+ // const inner = pullUpStartAnchor(ast.inner, true)
261+ // const right = pullUpStartAnchor(ast.right, isLeftClosed)
262+ // if (inner.type === 'start-anchor') {
263+ // throw new UnsupportedSyntaxError('start anchors inside lookaheads like (?=^a)')
264+ // } else if (right.type === 'start-anchor') {
265+ // return startAnchor(undefined, lookahead(ast.isPositive, ast.inner, right.right))
266+ // } else {
267+ // return lookahead(ast.isPositive, inner, right)
268+ // }
269+ throw new UnsupportedSyntaxError ( 'lookahead assertion' )
270+ case 'lookbehind' :
271+ throw new UnsupportedSyntaxError ( 'lookbehind assertion' )
266272 }
267273 checkedAllCases ( ast . type )
268274}
@@ -384,17 +390,20 @@ function pullUpEndAnchor(ast: RegExpAST, isRightClosed: boolean): RegExpAST {
384390 return endAnchor ( left , undefined ) // i.e. `l$`
385391 }
386392 }
387- case "lookahead" : {
388- const inner = pullUpEndAnchor ( ast . inner , false )
389- const right = pullUpEndAnchor ( ast . right , isRightClosed )
390- if ( inner . type === 'end-anchor' ) {
391- throw new UnsupportedSyntaxError ( 'end anchors inside lookaheads like (?=a$)' )
392- } else if ( right . type === 'end-anchor' ) {
393- return endAnchor ( lookahead ( ast . isPositive , ast . inner , right . left ) , undefined )
394- } else {
395- return lookahead ( ast . isPositive , inner , right )
396- }
397- }
393+ case "lookahead" :
394+ // FIXME:
395+ // const inner = pullUpEndAnchor(ast.inner, false)
396+ // const right = pullUpEndAnchor(ast.right, isRightClosed)
397+ // if (inner.type === 'end-anchor') {
398+ // throw new UnsupportedSyntaxError('end anchors inside lookaheads like (?=a$)')
399+ // } else if (right.type === 'end-anchor') {
400+ // return endAnchor(lookahead(ast.isPositive, ast.inner, right.left), undefined)
401+ // } else {
402+ // return lookahead(ast.isPositive, inner, right)
403+ // }
404+ throw new UnsupportedSyntaxError ( 'lookahead assertion' )
405+ case "lookbehind" :
406+ throw new UnsupportedSyntaxError ( 'lookbehind assertion' )
398407 }
399408 checkedAllCases ( ast . type )
400409}
@@ -426,27 +435,30 @@ export function toExtRegex(ast: RegExpAST): RE.ExtRegex {
426435 // no end anchors anywhere and we have to append the implicit `.*`:
427436 ast = concat ( ast , dotStar )
428437 }
429-
438+
430439 return toExtRegexAux ( ast )
431440}
432441function toExtRegexAux ( ast : RegExpAST ) : RE . ExtRegex {
433442 assert ( ! isOneOf ( ast . type , sugarNodeTypes ) , `Got ${ ast . type } node. Expected desugared AST.` )
434- assert ( ast . type !== 'start-anchor' , `Unexpected start anchor. Should already be eliminated.` )
435- assert ( ast . type !== 'end-anchor' , `Unexpected end anchor. Should already be eliminated.` )
443+ assert ( ast . type !== 'start-anchor' , `Unexpected start anchor. Should already be eliminated.` )
444+ assert ( ast . type !== 'end-anchor' , `Unexpected end anchor. Should already be eliminated.` )
436445 switch ( ast . type ) {
437446 case 'epsilon' : return RE . epsilon
438447 case 'literal' : return RE . literal ( ast . charset )
439448 case 'concat' : return RE . concat ( toExtRegexAux ( ast . left ) , toExtRegexAux ( ast . right ) )
440449 case 'union' : return RE . union ( toExtRegexAux ( ast . left ) , toExtRegexAux ( ast . right ) )
441450 case 'star' : return RE . star ( toExtRegexAux ( ast . inner ) )
442- case 'lookahead' : {
443- const inner = toExtRegexAux ( ast . inner )
444- const right = toExtRegexAux ( ast . right )
445- if ( ast . isPositive )
446- return RE . intersection ( inner , right )
447- else
448- return RE . intersection ( RE . complement ( inner ) , right )
449- }
451+ case 'lookahead' :
452+ // FIXME:
453+ // const inner = toExtRegexAux(ast.inner)
454+ // const right = toExtRegexAux(ast.right)
455+ // if (ast.isPositive)
456+ // return RE.intersection(inner, right)
457+ // else
458+ // return RE.intersection(RE.complement(inner), right)
459+ throw new UnsupportedSyntaxError ( 'lookahead assertion' )
460+ case 'lookbehind' :
461+ throw new UnsupportedSyntaxError ( 'lookbehind assertion' )
450462 }
451463 checkedAllCases ( ast . type )
452464}
@@ -475,7 +487,7 @@ export function concat(left: RegExpAST, right: RegExpAST): RegExpAST {
475487 return { type : 'concat' , left, right }
476488}
477489
478- function seq ( asts : RegExpAST [ ] ) : RegExpAST {
490+ export function seq ( asts : RegExpAST [ ] ) : RegExpAST {
479491 if ( asts . length === 0 )
480492 return epsilon
481493 else
@@ -518,9 +530,15 @@ export function captureGroup(inner: RegExpAST, name?: string): RegExpAST {
518530export function lookahead (
519531 isPositive : boolean ,
520532 inner : RegExpAST ,
521- right : RegExpAST ,
522533) : RegExpAST {
523- return { type : 'lookahead' , isPositive, inner, right }
534+ return { type : 'lookahead' , isPositive, inner }
535+ }
536+
537+ export function lookbehind (
538+ isPositive : boolean ,
539+ inner : RegExpAST ,
540+ ) : RegExpAST {
541+ return { type : 'lookahead' , isPositive, inner }
524542}
525543
526544//////////////////////////////////////////////
@@ -573,6 +591,8 @@ function debugShow_(ast: RegExpAST): unknown {
573591 return { type : 'capture-group' , name : ast . name , inner : debugShow_ ( ast . inner ) }
574592 case 'lookahead' :
575593 return { type : 'lookahead' , isPositive : ast . isPositive , inner : debugShow_ ( ast . inner ) }
594+ case 'lookbehind' :
595+ return { type : 'lookbehind' , isPositive : ast . isPositive , inner : debugShow_ ( ast . inner ) }
576596 }
577597 checkedAllCases ( ast )
578598}
@@ -632,11 +652,17 @@ export function toString(ast: RegExpAST, options: RenderOptions): string {
632652 return captureGroupToString ( ast . name , ast . inner , options )
633653 case 'lookahead' : {
634654 const inner = toString ( ast . inner , options )
635- const right = maybeWithParens ( ast . right , ast , options )
636655 if ( ast . isPositive )
637- return '(?=' + inner + ')' + right
656+ return '(?=' + inner + ')'
657+ else
658+ return '(?!' + inner + ')'
659+ }
660+ case 'lookbehind' : {
661+ const inner = toString ( ast . inner , options )
662+ if ( ast . isPositive )
663+ return '(?<=' + inner + ')'
638664 else
639- return '(?!' + inner + ')' + right
665+ return '(?< !' + inner + ')'
640666 }
641667 }
642668 checkedAllCases ( ast )
@@ -648,6 +674,8 @@ function precLevel(nodeType: RegExpAST['type']) {
648674 case 'epsilon' : return 10
649675 case 'literal' : return 10
650676 case 'capture-group' : return 10
677+ case 'lookahead' : return 10
678+ case 'lookbehind' : return 10
651679
652680 case 'star' : return 5
653681 case 'plus' : return 5
@@ -656,8 +684,6 @@ function precLevel(nodeType: RegExpAST['type']) {
656684
657685 case 'concat' : return 4
658686
659- case 'lookahead' : return 3
660-
661687 case 'start-anchor' : return 2
662688 case 'end-anchor' : return 2
663689
0 commit comments