@@ -621,6 +621,93 @@ export function kidOf(plan: LexIdPlan, kind: string): number {
621621 return i >= 0 ? i : 0 ;
622622}
623623
624+ // ── Arena id tables (rule_id / tt_id for slim Node + inlined leaves) ──
625+ //
626+ // Arena stores only rule nodes; leaf kids are negative i32 encodings that pack
627+ // (tok_idx, tt_id). TT_NAMES is indexed by tt_id; RULE_NAMES by rule_id.
628+ // TT_NAMES starts with LexIdPlan.kids so token-kind leaves can use Tok.kid as tt_id.
629+
630+ export type ArenaIdPlan = {
631+ ttNames : string [ ] ; // TT_NAMES — leaf tokenType strings
632+ ruleNames : string [ ] ; // RULE_NAMES — CST rule labels
633+ } ;
634+
635+ /** Sentinel tt_id for $punct (trivia — never stored as a leaf). Not an index into ttNames. */
636+ export const TT_SKIP_PUNCT = 255 ;
637+
638+ /** Collect every ttype text that push_leaf / match_lit|match_tok may emit (excluding $punct). */
639+ function collectStepTtypes ( steps : Step [ ] , out : Set < string > ) : void {
640+ for ( const s of steps ) {
641+ switch ( s . t ) {
642+ case 'lit' : if ( s . ttype !== '$punct' ) out . add ( s . ttype ) ; break ;
643+ case 'tok' : out . add ( s . name ) ; break ;
644+ case 'altlit' : for ( const o of s . opts ) if ( o . ttype !== '$punct' ) out . add ( o . ttype ) ; break ;
645+ case 'sep' : collectStepTtypes ( [ s . elem ] , out ) ; break ;
646+ case 'suppress' : collectStepTtypes ( s . steps , out ) ; break ;
647+ case 'star' : collectStepTtypes ( [ s . step ] , out ) ; break ;
648+ case 'opt' : case 'not' : case 'seq' : collectStepTtypes ( s . steps , out ) ; break ;
649+ case 'alt' : for ( const b of s . branches ) collectStepTtypes ( b , out ) ; break ;
650+ default : break ;
651+ }
652+ }
653+ }
654+
655+ /**
656+ * Build TT_NAMES + RULE_NAMES for arena slim encoding.
657+ * TT_NAMES = kids 全体 ∪ {$keyword,$operator,$templateHead,$templateMiddle,$templateTail}
658+ * ∪ other push_leaf ttypes collected from the IR (exhaustive).
659+ * RULE_NAMES = every rule cstName ∪ {$template} when templates are enabled.
660+ */
661+ export function buildArenaIdPlan ( ir : ParserIR , lexIds : LexIdPlan ) : ArenaIdPlan {
662+ const ttNames = [ ...lexIds . kids ] ;
663+ const seenTt = new Set < string > ( ttNames ) ;
664+ const addTt = ( s : string ) => { if ( ! seenTt . has ( s ) ) { seenTt . add ( s ) ; ttNames . push ( s ) ; } } ;
665+ for ( const s of [ '$keyword' , '$operator' , '$templateHead' , '$templateMiddle' , '$templateTail' ] ) addTt ( s ) ;
666+
667+ const fromSteps = new Set < string > ( ) ;
668+ for ( const r of ir . rules ) {
669+ if ( r . kind === 'rd' ) {
670+ for ( const alt of r . alts ) collectStepTtypes ( alt , fromSteps ) ;
671+ } else {
672+ for ( const t of r . nudToks ) fromSteps . add ( t ) ;
673+ for ( const b of r . nudBrackets ) collectStepTtypes ( b . steps , fromSteps ) ;
674+ for ( const seq of r . nudSeqs ) collectStepTtypes ( seq , fromSteps ) ;
675+ for ( const c of r . nudCapped ) collectStepTtypes ( c . steps , fromSteps ) ;
676+ for ( const b of r . leds ) collectStepTtypes ( b . steps , fromSteps ) ;
677+ for ( const t of r . postfixToks ) fromSteps . add ( t ) ;
678+ }
679+ }
680+ if ( ir . newlineCfg ) fromSteps . add ( ir . newlineCfg . token ) ;
681+ for ( const s of fromSteps ) addTt ( s ) ;
682+
683+ if ( ttNames . length > 64 ) {
684+ throw new Error ( `TT_NAMES length ${ ttNames . length } exceeds tt_id limit 63` ) ;
685+ }
686+
687+ const ruleNames : string [ ] = [ ] ;
688+ const seenR = new Set < string > ( ) ;
689+ const addR = ( s : string ) => { if ( ! seenR . has ( s ) ) { seenR . add ( s ) ; ruleNames . push ( s ) ; } } ;
690+ for ( const r of ir . rules ) addR ( r . cstName ) ;
691+ if ( ir . tpl ) addR ( '$template' ) ;
692+
693+ return { ttNames, ruleNames } ;
694+ }
695+
696+ /** Resolve tt_id for a leaf tokenType; throws if missing (codegen bug). $punct → TT_SKIP_PUNCT. */
697+ export function ttIdOf ( plan : ArenaIdPlan , ttype : string ) : number {
698+ if ( ttype === '$punct' ) return TT_SKIP_PUNCT ;
699+ const i = plan . ttNames . indexOf ( ttype ) ;
700+ if ( i < 0 ) throw new Error ( `ttIdOf: unknown ttype ${ JSON . stringify ( ttype ) } ` ) ;
701+ return i ;
702+ }
703+
704+ /** Resolve rule_id for a CST rule label; throws if missing. */
705+ export function ruleIdOf ( plan : ArenaIdPlan , rule : string ) : number {
706+ const i = plan . ruleNames . indexOf ( rule ) ;
707+ if ( i < 0 ) throw new Error ( `ruleIdOf: unknown rule ${ JSON . stringify ( rule ) } ` ) ;
708+ return i ;
709+ }
710+
624711// ── Lexer first-byte dispatch (IR analysis; targets render buckets) ──
625712
626713export type LexFirstBytes = { bytes : number [ ] ; nonAscii : boolean } ;
0 commit comments