1111// parser allocates ~nothing per parse. Rule fns return `i32` (-1 = fail); sub-sequence
1212// combinators take non-capturing `fn(&mut Parser) -> bool` pointers (the kids vec is now on
1313// the Parser as `scratch`, so the second param the old owned-tree version threaded is gone).
14- import type { ParserIR , RdRule , PrattRule , Step , Bracket , CharRange , LexTok , TplCfg , NewlineCfg , FirstSig , LexFirstBytes , LexIdPlan , ArenaIdPlan } from './emit-portable.ts' ;
14+ import { FIRST_GUARD_K , type ParserIR , type RdRule , type PrattRule , type Step , type Bracket , type CharRange , type LexTok , type TplCfg , type NewlineCfg , type FirstSig , type LexFirstBytes , type LexIdPlan , type ArenaIdPlan } from './emit-portable.ts' ;
1515import { portableIR , buildLexDispatchPlan , lexTokFirstBytes , punctFirstBytes , buildLexIdPlan , buildArenaIdPlan , lidOf , kidOf , ttIdOf , ruleIdOf , TT_SKIP_PUNCT , rangesHaveNonAscii } from './emit-portable.ts' ;
1616import type { Target } from './emit.ts' ;
1717import type { TokenPattern , CstGrammar } from './types.ts' ;
@@ -39,6 +39,9 @@ function emitAsciiBoolTableRs(name: string, rs: CharRange[]): string {
3939const firstCond = ( f : FirstSig , t : string , ids : LexIdPlan ) => f
4040 ? `(${ f . lits . map ( ( l ) => `${ t } .lid == ${ lidOf ( ids , l ) } ` ) . join ( ' || ' ) || 'false' } || ${ f . toks . map ( ( k ) => `${ t } .kid == ${ kidOf ( ids , k ) } ` ) . join ( ' || ' ) || 'false' } )`
4141 : 'false' ;
42+ /** Non-null FirstSig small enough to pre-filter before a backtracking attempt. */
43+ const isGuardable = ( f : FirstSig ) : f is NonNullable < FirstSig > =>
44+ f !== null && f . lits . length + f . toks . length > 0 && f . lits . length + f . toks . length <= FIRST_GUARD_K ;
4245
4346/** Emit kid/lid lookup tables into generated lexer source (length-bucketed lid_of match). */
4447function renderIdTablesRust ( ids : LexIdPlan ) : string {
@@ -575,7 +578,7 @@ function stepCond(s: Step, ids: LexIdPlan, ar: ArenaIdPlan): string {
575578 case 'opt' : return `self.opt(|p| ${ s . steps . map ( ( x ) => stepCondP ( x , ids , ar ) ) . join ( ' && ' ) } )` ;
576579 case 'sep' : return `self.sep_by(|p| ${ stepCondP ( s . elem , ids , ar ) } , ${ lidOf ( ids , s . delim ) } )` ;
577580 case 'altlit' : return `self.alt_lit(&[${ s . opts . map ( ( o ) => `(${ lidOf ( ids , o . value ) } , ${ ttIdOf ( ar , o . ttype ) } )` ) . join ( ', ' ) } ])` ;
578- case 'alt' : return s . predictive ? `(|p: &mut Parser<'a>| -> bool { ${ predAltBody ( s . branches , ids , ar , s . firsts ) } })(self)` : `(|p: &mut Parser<'a>| -> bool { ${ altBody ( s . branches , ids , ar ) } })(self)` ;
581+ case 'alt' : return s . predictive ? `(|p: &mut Parser<'a>| -> bool { ${ predAltBody ( s . branches , ids , ar , s . firsts ) } })(self)` : `(|p: &mut Parser<'a>| -> bool { ${ altBody ( s . branches , ids , ar , s . firsts ) } })(self)` ;
579582 case 'not' : return `(|p: &mut Parser<'a>| -> bool { ${ notBody ( s . steps , ids , ar ) } })(self)` ;
580583 case 'seq' : return `(${ s . steps . length ? s . steps . map ( ( x ) => stepCond ( x , ids , ar ) ) . join ( ' && ' ) : 'true' } )` ;
581584 case 'sameLine' : return `matches!(self.peek(), Some(t) if !t.nl)` ;
@@ -584,8 +587,18 @@ function stepCond(s: Step, ids: LexIdPlan, ar: ArenaIdPlan): string {
584587}
585588// A backtracking inline alternation rendered as an immediately-applied closure over p,
586589// so it composes identically whether it sits at top level or already inside a closure.
587- function altBody ( branches : Step [ ] [ ] , ids : LexIdPlan , ar : ArenaIdPlan ) : string {
588- return `${ branches . map ( ( br ) => `{ let sp = p.pos; let sb = p.scratch.len(); let nb = p.nodes.len(); let kb = p.kids.len(); if ${ br . length ? br . map ( ( x ) => stepCondP ( x , ids , ar ) ) . join ( ' && ' ) : 'true' } { return true; } p.pos = sp; p.scratch.truncate(sb); p.nodes.truncate(nb); p.kids.truncate(kb); }` ) . join ( ' ' ) } false` ;
590+ // Non-null FirstSig branches get a FIRST pre-filter (skip without save/restore) when |sig|≤K.
591+ function altBody ( branches : Step [ ] [ ] , ids : LexIdPlan , ar : ArenaIdPlan , firsts ?: FirstSig [ ] ) : string {
592+ const fs = firsts ?? [ ] ;
593+ const needPeek = branches . some ( ( _ , i ) => isGuardable ( fs [ i ] ?? null ) ) ;
594+ const peekInit = needPeek ? `let _ft = p.peek(); ` : '' ;
595+ const tries = branches . map ( ( br , i ) => {
596+ const body = `{ let sp = p.pos; let sb = p.scratch.len(); let nb = p.nodes.len(); let kb = p.kids.len(); if ${ br . length ? br . map ( ( x ) => stepCondP ( x , ids , ar ) ) . join ( ' && ' ) : 'true' } { return true; } p.pos = sp; p.scratch.truncate(sb); p.nodes.truncate(nb); p.kids.truncate(kb); }` ;
597+ const f = fs [ i ] ?? null ;
598+ if ( ! isGuardable ( f ) ) return body ;
599+ return `if let Some(t) = _ft { if ${ firstCond ( f , 't' , ids ) } ${ body } }` ;
600+ } ) . join ( ' ' ) ;
601+ return `${ peekInit } ${ tries } false` ;
589602}
590603// Zero-width negative lookahead: try the steps, restore, succeed iff they did NOT all match.
591604function notBody ( steps : Step [ ] , ids : LexIdPlan , ar : ArenaIdPlan ) : string {
@@ -602,7 +615,7 @@ function stepCondP(s: Step, ids: LexIdPlan, ar: ArenaIdPlan): string {
602615 case 'opt' : return `p.opt(|p| ${ s . steps . map ( ( x ) => stepCondP ( x , ids , ar ) ) . join ( ' && ' ) } )` ;
603616 case 'sep' : return `p.sep_by(|p| ${ stepCondP ( s . elem , ids , ar ) } , ${ lidOf ( ids , s . delim ) } )` ;
604617 case 'altlit' : return `p.alt_lit(&[${ s . opts . map ( ( o ) => `(${ lidOf ( ids , o . value ) } , ${ ttIdOf ( ar , o . ttype ) } )` ) . join ( ', ' ) } ])` ;
605- case 'alt' : return s . predictive ? `(|p: &mut Parser<'a>| -> bool { ${ predAltBody ( s . branches , ids , ar , s . firsts ) } })(p)` : `(|p: &mut Parser<'a>| -> bool { ${ altBody ( s . branches , ids , ar ) } })(p)` ;
618+ case 'alt' : return s . predictive ? `(|p: &mut Parser<'a>| -> bool { ${ predAltBody ( s . branches , ids , ar , s . firsts ) } })(p)` : `(|p: &mut Parser<'a>| -> bool { ${ altBody ( s . branches , ids , ar , s . firsts ) } })(p)` ;
606619 case 'not' : return `(|p: &mut Parser<'a>| -> bool { ${ notBody ( s . steps , ids , ar ) } })(p)` ;
607620 case 'seq' : return `(${ s . steps . length ? s . steps . map ( ( x ) => stepCondP ( x , ids , ar ) ) . join ( ' && ' ) : 'true' } )` ;
608621 case 'sameLine' : return `matches!(p.peek(), Some(t) if !t.nl)` ;
@@ -750,12 +763,22 @@ ${r.alts.map(arm).join('\n')}
750763 None
751764 }` ;
752765 }
753- const alt = ( steps : Step [ ] ) =>
754- ` if ${ steps . map ( ( x ) => stepCond ( x , ids , ar ) ) . join ( ' && ' ) } { return Some(self.finish(${ ruleIdOf ( ar , r . cstName ) } , sb, self.off_at(save), save)); }
755- self.pos = save; self.scratch.truncate(sb); self.nodes.truncate(nb); self.kids.truncate(kb);` ;
766+ const alt = ( steps : Step [ ] , i : number ) => {
767+ const cond = steps . map ( ( x ) => stepCond ( x , ids , ar ) ) . join ( ' && ' ) ;
768+ const restore = `self.pos = save; self.scratch.truncate(sb); self.nodes.truncate(nb); self.kids.truncate(kb);` ;
769+ if ( ! isGuardable ( r . altFirst [ i ] ) ) {
770+ return ` if ${ cond } { return Some(self.finish(${ ruleIdOf ( ar , r . cstName ) } , sb, self.off_at(save), save)); }
771+ ${ restore } ` ;
772+ }
773+ return ` if let Some(t) = _ft { if ${ firstCond ( r . altFirst [ i ] , 't' , ids ) } {
774+ if ${ cond } { return Some(self.finish(${ ruleIdOf ( ar , r . cstName ) } , sb, self.off_at(save), save)); }
775+ ${ restore }
776+ } }` ;
777+ } ;
778+ const needPeek = r . alts . some ( ( _ , i ) => isGuardable ( r . altFirst [ i ] ) ) ;
756779 return ` fn parse_${ r . name } (&mut self) -> Option<i32> {
757780 let save = self.pos; let sb = self.scratch.len(); let nb = self.nodes.len(); let kb = self.kids.len();
758- ${ r . alts . map ( alt ) . join ( '\n' ) }
781+ ${ needPeek ? ' let _ft = self.peek();\n' : '' } ${ r . alts . map ( alt ) . join ( '\n' ) }
759782 None
760783 }` ;
761784}
0 commit comments