99// stack. A node is an int32 index, never a heap pointer. Backtracking truncates the three
1010// slices to saved lengths; the slices keep their capacity across parses (reset to len 0), so a
1111// warmed parser allocates ~nothing per parse.
12- 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' ;
13- import { portableIR , buildLexDispatchPlan , lexTokFirstBytes , punctFirstBytes , buildLexIdPlan , buildArenaIdPlan , lidOf , kidOf , ttIdOf , ruleIdOf , TT_SKIP_PUNCT , rangesHaveNonAscii } from './emit-portable.ts' ;
12+ import { 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' ;
13+ import { portableIR , buildLexDispatchPlan , lexTokFirstBytes , punctFirstBytes , buildLexIdPlan , buildArenaIdPlan , lidOf , kidOf , ttIdOf , ruleIdOf , TT_SKIP_PUNCT , rangesHaveNonAscii , isFirstGuardable , groupByPreserveOrder } from './emit-portable.ts' ;
1414import type { Target } from './emit.ts' ;
1515import type { TokenPattern , CstGrammar } from './types.ts' ;
1616
@@ -37,8 +37,8 @@ const firstCond = (f: FirstSig, t: string, ids: LexIdPlan) => f
3737 ? `(${ f . lits . map ( ( l ) => `${ t } .Lid == ${ lidOf ( ids , l ) } ` ) . join ( ' || ' ) || 'false' } || ${ f . toks . map ( ( k ) => `${ t } .Kid == ${ kidOf ( ids , k ) } ` ) . join ( ' || ' ) || 'false' } )`
3838 : 'false' ;
3939/** Non-null FirstSig small enough to pre-filter before a backtracking attempt. */
40- const isGuardable = ( f : FirstSig ) : f is NonNullable < FirstSig > =>
41- f !== null && f . lits . length + f . toks . length > 0 && f . lits . length + f . toks . length <= FIRST_GUARD_K ;
40+ const isGuardable = ( f : FirstSig , nAlts ?: number ) : f is NonNullable < FirstSig > =>
41+ isFirstGuardable ( f , nAlts ) ;
4242
4343/** Emit kid/lid lookup tables into generated lexer source. */
4444function renderIdTablesGo ( ids : LexIdPlan ) : string {
@@ -504,12 +504,13 @@ function stepCond(s: Step, ids: LexIdPlan, ar: ArenaIdPlan): string {
504504 case 'alt' : {
505505 if ( s . predictive ) return `func() bool { ${ predAltBody ( s . branches , ids , s . firsts , ar ) } }()` ;
506506 const firsts = s . firsts ?? [ ] ;
507- const needPeek = s . branches . some ( ( _ , i ) => isGuardable ( firsts [ i ] ?? null ) ) ;
507+ const nAlts = s . branches . length ;
508+ const needPeek = s . branches . some ( ( _ , i ) => isGuardable ( firsts [ i ] ?? null , nAlts ) ) ;
508509 const peekInit = needPeek ? `_ft := peek(); ` : '' ;
509510 const tries = s . branches . map ( ( br , i ) => {
510511 const body = `{ save := pos; sb := len(scratch); nb := len(nodes); kb := len(kids); if ${ br . length ? br . map ( ( x ) => stepCond ( x , ids , ar ) ) . join ( ' && ' ) : 'true' } { return true }; pos = save; scratch = scratch[:sb]; nodes = nodes[:nb]; kids = kids[:kb] }` ;
511512 const f = firsts [ i ] ?? null ;
512- if ( ! isGuardable ( f ) ) return body ;
513+ if ( ! isGuardable ( f , nAlts ) ) return body ;
513514 return `if _ft != nil && ${ firstCond ( f , '_ft' , ids ) } ${ body } ` ;
514515 } ) . join ( '; ' ) ;
515516 return `func() bool { ${ peekInit } ${ tries } ; return false }()` ;
@@ -583,7 +584,7 @@ ${r.alts.map(arm).join(' ')}
583584 const alt = ( steps : Step [ ] , i : number ) => {
584585 const cond = steps . map ( ( x ) => stepCond ( x , ids , ar ) ) . join ( ' && ' ) ;
585586 const restore = `pos = save; scratch = scratch[:sb]; nodes = nodes[:nb]; kids = kids[:kb]` ;
586- if ( ! isGuardable ( r . altFirst [ i ] ) ) {
587+ if ( ! isGuardable ( r . altFirst [ i ] , r . alts . length ) ) {
587588 return `\tif ${ cond } { return finish(${ ruleIdOf ( ar , r . cstName ) } , sb, offAt(save), save) }
588589\t${ restore } ` ;
589590 }
@@ -592,7 +593,7 @@ ${r.alts.map(arm).join(' ')}
592593\t\t${ restore }
593594\t}` ;
594595 } ;
595- const needPeek = r . alts . some ( ( _ , i ) => isGuardable ( r . altFirst [ i ] ) ) ;
596+ const needPeek = r . alts . some ( ( _ , i ) => isGuardable ( r . altFirst [ i ] , r . alts . length ) ) ;
596597 return `func parse${ r . name } () int32 {
597598\tsave := pos; sb := len(scratch); nb := len(nodes); kb := len(kids)
598599${ needPeek ? '\t_ft := peek()\n' : '' } ${ r . alts . map ( alt ) . join ( '\n' ) }
@@ -700,28 +701,63 @@ function prattRule(r: PrattRule, tpl: TplCfg | null, ids: LexIdPlan, ar: ArenaId
700701 const bin = r . binary . map ( ( b ) => `${ lidOf ( ids , b . op ) } : {${ b . lbp } , ${ b . rbp } }` ) . join ( ', ' ) ;
701702 const pre = r . prefix . map ( ( p ) => `${ lidOf ( ids , p . op ) } : ${ p . rbp } ` ) . join ( ', ' ) ;
702703 const atoms = r . nudToks . map ( ( k ) => `${ kidOf ( ids , k ) } : true` ) . join ( ', ' ) ;
703- const bracketNud = ( b : Bracket ) => `\tif t.Lid == ${ lidOf ( ids , b . first ) } {
704+ const bracketNudBody = ( b : Bracket ) => `{
704705\t\tsave := pos; sb := len(scratch); nb := len(nodes); kb := len(kids)
705706\t\tif ${ b . steps . map ( ( x ) => stepCond ( x , ids , ar ) ) . join ( ' && ' ) } { return finish(${ ruleIdOf ( ar , r . cstName ) } , sb, t.Off, save) }
706707\t\tpos = save; scratch = scratch[:sb]; nodes = nodes[:nb]; kids = kids[:kb]
707708\t}` ;
708- const ledArm = ( b : Bracket , accessTail : boolean , lbp : number | null , sameLine : boolean , nll : string [ ] | null ) => `\t\tif ${ accessTail ? '!tailClosed && ' : '' } ${ lbp !== null ? `${ lbp } > minBp && ` : '' } ${ sameLine ? '!t.Nl && ' : '' } ${ nll ? `!_inW([]string{${ nll . map ( J ) . join ( ', ' ) } }, headLeafText(left)) && ` : '' } !_suppressCur[${ lidOf ( ids , b . first ) } ] && t.Lid == ${ lidOf ( ids , b . first ) } {
709+ const bracketNudSwitch = ( ( ) => {
710+ if ( r . nudBrackets . length === 0 ) return '' ;
711+ const groups = groupByPreserveOrder ( r . nudBrackets , ( b ) => lidOf ( ids , b . first ) ) ;
712+ return `\tswitch t.Lid {
713+ ${ groups . map ( ( g ) => `\tcase ${ g . key } :
714+ ${ g . members . map ( ( { item : b } ) => `\t\t${ bracketNudBody ( b ) } ` ) . join ( '\n' ) } `) . join ( '\n' ) }
715+ \t}` ;
716+ } ) ( ) ;
717+ const ledGuard = ( accessTail : boolean , lbp : number | null , sameLine : boolean , nll : string [ ] | null , lid : number ) => {
718+ const parts : string [ ] = [ ] ;
719+ if ( accessTail ) parts . push ( '!tailClosed' ) ;
720+ if ( lbp !== null ) parts . push ( `${ lbp } > minBp` ) ;
721+ if ( sameLine ) parts . push ( '!t.Nl' ) ;
722+ if ( nll ) parts . push ( `!_inW([]string{${ nll . map ( J ) . join ( ', ' ) } }, headLeafText(left))` ) ;
723+ parts . push ( `!_suppressCur[${ lid } ]` ) ;
724+ return parts . join ( ' && ' ) ;
725+ } ;
726+ const ledBody = ( b : Bracket ) => `{
709727\t\t\tledSave := pos; sb := len(scratch); nb := len(nodes); kb := len(kids)
710728\t\t\tscratch = append(scratch, left)
711- \t\t\tif ${ b . steps . map ( ( x ) => stepCond ( x , ids , ar ) ) . join ( ' && ' ) } { left = finish(${ ruleIdOf ( ar , r . cstName ) } , sb, int(nodes[left].Offset), int(nodes[left].TokStart)); continue }
712- \t\t\tpos = ledSave; scratch = scratch[:sb]; nodes = nodes[:nb]; kids = kids[:kb]; break
729+ \t\t\tif ${ b . steps . map ( ( x ) => stepCond ( x , ids , ar ) ) . join ( ' && ' ) } { left = finish(${ ruleIdOf ( ar , r . cstName ) } , sb, int(nodes[left].Offset), int(nodes[left].TokStart)); continue LedLoop }
730+ \t\t\tpos = ledSave; scratch = scratch[:sb]; nodes = nodes[:nb]; kids = kids[:kb]; break LedLoop
731+ \t\t}` ;
732+ const ledSwitch = ( ( ) => {
733+ if ( r . leds . length === 0 ) return '' ;
734+ const groups = groupByPreserveOrder ( r . leds , ( b ) => lidOf ( ids , b . first ) ) ;
735+ return `\t\tswitch t.Lid {
736+ ${ groups . map ( ( g ) => {
737+ const lid = g . key as number ;
738+ const arms = g . members . map ( ( { item : b , index : i } ) =>
739+ `\t\t\tif ${ ledGuard ( r . ledAccessTail [ i ] ! , r . ledLbp [ i ] ! , r . ledSameLine [ i ] ! , r . ledNotLeftLeaf [ i ] ! , lid ) } ${ ledBody ( b ) } ` ) ;
740+ return `\t\tcase ${ lid } :\n${ arms . join ( '\n' ) } ` ;
741+ } ) . join ( '\n' ) }
713742\t\t}` ;
714- const postfixArm = ( tok : string ) => {
715- const tplPart = tpl && tok === tpl . token ? `
743+ } ) ( ) ;
744+ const postfixTokSwitch = ( ( ) => {
745+ if ( r . postfixToks . length === 0 ) return '' ;
746+ const groups = groupByPreserveOrder ( r . postfixToks , ( tok ) => kidOf ( ids , tok ) ) ;
747+ const hasTpl = ! ! ( tpl && r . postfixToks . includes ( tpl . token ) ) ;
748+ const tplPart = hasTpl ? `
716749\t\tif !tailClosed && t.Kind == "$templateHead" {
717750\t\t\tnode := matchTemplate()
718- \t\t\tif node >= 0 { sb := len(scratch); scratch = append(scratch, left, node); left = finish(${ ruleIdOf ( ar , r . cstName ) } , sb, int(nodes[left].Offset), int(nodes[left].TokStart)); continue }
751+ \t\t\tif node >= 0 { sb := len(scratch); scratch = append(scratch, left, node); left = finish(${ ruleIdOf ( ar , r . cstName ) } , sb, int(nodes[left].Offset), int(nodes[left].TokStart)); continue LedLoop }
719752\t\t}` : '' ;
720- return `\t\tif !tailClosed && t.Kid == ${ kidOf ( ids , tok ) } {
721- \t\t\tsb := len(scratch); scratch = append(scratch, left); pushLeaf(uint8(t.Kid), uint32(pos)); pos++
722- \t\t\tleft = finish(${ ruleIdOf ( ar , r . cstName ) } , sb, int(nodes[left].Offset), int(nodes[left].TokStart)); continue
753+ return `\t\tswitch t.Kid {
754+ ${ groups . map ( ( g ) => `\t\tcase ${ g . key } :
755+ \t\t\tif !tailClosed {
756+ \t\t\t\tsb := len(scratch); scratch = append(scratch, left); pushLeaf(uint8(t.Kid), uint32(pos)); pos++
757+ \t\t\t\tleft = finish(${ ruleIdOf ( ar , r . cstName ) } , sb, int(nodes[left].Offset), int(nodes[left].TokStart)); continue LedLoop
758+ \t\t\t}` ) . join ( '\n' ) }
723759\t\t}${ tplPart } ` ;
724- } ;
760+ } ) ( ) ;
725761 const post = r . postfix . map ( ( p ) => `${ lidOf ( ids , p . op ) } : ${ p . lbp } ` ) . join ( ', ' ) ;
726762 return `var ${ r . name } BIN = map[uint16]bp{${ bin } }
727763var ${ r . name } PRE = map[uint16]int{${ pre } }
@@ -740,11 +776,11 @@ func ${r.name}bp(minBp int) int32 {
740776\tif left < 0 { return -1 }
741777\tif _capped { return left }
742778\ttailClosed := false
743- \tfor {
779+ ${ ( r . leds . length > 0 || r . postfixToks . length > 0 ) ? 'LedLoop:\n' : '' } \tfor {
744780\t\tt := peek()
745781\t\tif t == nil { break }
746- ${ r . leds . map ( ( b , i ) => ledArm ( b , r . ledAccessTail [ i ] , r . ledLbp [ i ] , r . ledSameLine [ i ] , r . ledNotLeftLeaf [ i ] ) ) . join ( '\n' ) }
747- ${ r . postfixToks . map ( postfixArm ) . join ( '\n' ) }
782+ ${ ledSwitch }
783+ ${ postfixTokSwitch }
748784\t\tif post, ok := ${ r . name } POST[t.Lid]; ok && !tailClosed && post > minBp {
749785\t\t\tsb := len(scratch); scratch = append(scratch, left); pushLeaf(${ ttIdOf ( ar , '$operator' ) } , uint32(pos)); pos++; tailClosed = true
750786\t\t\tleft = finish(${ ruleIdOf ( ar , r . cstName ) } , sb, int(nodes[left].Offset), int(nodes[left].TokStart)); continue
@@ -771,7 +807,7 @@ ${tplNud}\tif ${r.name}ATOM[t.Kid] {
771807\t\tsb := len(scratch); ts := pos; pushLeaf(uint8(t.Kid), uint32(pos)); pos++
772808\t\treturn finish(${ ruleIdOf ( ar , r . cstName ) } , sb, t.Off, ts)
773809\t}
774- ${ r . nudBrackets . map ( bracketNud ) . join ( '\n' ) }
810+ ${ bracketNudSwitch }
775811\tif pbp, ok := ${ r . name } PRE[t.Lid]; ok {
776812\t\tsave := pos; sb := len(scratch); nb := len(nodes); kb := len(kids)
777813\t\tpushLeaf(${ ttIdOf ( ar , '$operator' ) } , uint32(pos)); pos++
0 commit comments