@@ -522,6 +522,105 @@ function buildPratt(
522522 return { kind : 'pratt' , name, cstName, nudToks, nudBrackets, nudSeqs, nudSeqFirst : [ ] , nudSeqPredictive : false , nudCapped, nudCappedFirst : [ ] , prefix, binary, leds, ledAccessTail, ledLbp, ledSameLine, ledNotLeftLeaf, postfixToks, postfix } ;
523523}
524524
525+ // ── Lex integer ids (kid = kind index, lid = text/literal index) ──
526+ //
527+ // Parser matching hot paths compare u16/uint16/number instead of strings. kid indexes
528+ // `kids` (token kind names); lid indexes `lids` (literal texts). lid is a pure function of
529+ // text and is independent of kind — matching the existing `match_lit` (text-only) contract.
530+
531+ export type LexIdPlan = {
532+ kids : string [ ] ; // kids[0]='' reserved for punct (kind=""); then tokens in decl order + engine-emitted (NEWLINE, $template*)
533+ lids : string [ ] ; // lids[0]='' = "not any literal"; then puncts (longest-first), then remaining texts lexicographically
534+ } ;
535+
536+ /** Collect every literal text that appears in the Step IR (match_lit / altlit / sep / suppress / FIRST / Pratt). */
537+ function collectStepLitTexts ( steps : Step [ ] , out : Set < string > ) : void {
538+ for ( const s of steps ) {
539+ switch ( s . t ) {
540+ case 'lit' : out . add ( s . value ) ; break ;
541+ case 'altlit' : for ( const o of s . opts ) out . add ( o . value ) ; break ;
542+ case 'sep' : out . add ( s . delim ) ; collectStepLitTexts ( [ s . elem ] , out ) ; break ;
543+ case 'suppress' : for ( const c of s . connectors ) out . add ( c ) ; collectStepLitTexts ( s . steps , out ) ; break ;
544+ case 'star' : collectStepLitTexts ( [ s . step ] , out ) ; break ;
545+ case 'opt' : case 'not' : case 'seq' : collectStepLitTexts ( s . steps , out ) ; break ;
546+ case 'alt' : for ( const b of s . branches ) collectStepLitTexts ( b , out ) ; break ;
547+ default : break ;
548+ }
549+ }
550+ }
551+
552+ /**
553+ * Build stable kid/lid tables for a ParserIR. Semantics:
554+ * - kid(kind) = kids.indexOf(kind); punct kind "" → 0.
555+ * - lid(text) = lids.indexOf(text), or 0 if absent. Independent of kind.
556+ */
557+ export function buildLexIdPlan ( ir : ParserIR ) : LexIdPlan {
558+ // kids: '' + lexer token names (declaration order) + engine-emitted kinds not already present
559+ const kids : string [ ] = [ '' ] ;
560+ const seenKid = new Set < string > ( [ '' ] ) ;
561+ const addKid = ( k : string ) => { if ( ! seenKid . has ( k ) ) { seenKid . add ( k ) ; kids . push ( k ) ; } } ;
562+ for ( const t of ir . tokens ) addKid ( t . name ) ;
563+ if ( ir . newlineCfg ) addKid ( ir . newlineCfg . token ) ;
564+ if ( ir . tpl ) {
565+ addKid ( '$templateHead' ) ;
566+ addKid ( '$templateMiddle' ) ;
567+ addKid ( '$templateTail' ) ;
568+ }
569+
570+ // lids universe: puncts + keyword/other lit texts from IR + regexCtx text sets + tpl delimiters
571+ const litTexts = new Set < string > ( ) ;
572+ for ( const p of ir . puncts ) litTexts . add ( p ) ;
573+ for ( const r of ir . rules ) {
574+ if ( r . kind === 'rd' ) {
575+ for ( const alt of r . alts ) collectStepLitTexts ( alt , litTexts ) ;
576+ } else {
577+ for ( const b of r . nudBrackets ) { litTexts . add ( b . first ) ; collectStepLitTexts ( b . steps , litTexts ) ; }
578+ for ( const seq of r . nudSeqs ) collectStepLitTexts ( seq , litTexts ) ;
579+ for ( const c of r . nudCapped ) collectStepLitTexts ( c . steps , litTexts ) ;
580+ for ( const p of r . prefix ) litTexts . add ( p . op ) ;
581+ for ( const b of r . binary ) litTexts . add ( b . op ) ;
582+ for ( const b of r . leds ) { litTexts . add ( b . first ) ; collectStepLitTexts ( b . steps , litTexts ) ; }
583+ for ( const p of r . postfix ) litTexts . add ( p . op ) ;
584+ for ( const nll of r . ledNotLeftLeaf ) if ( nll ) for ( const x of nll ) litTexts . add ( x ) ;
585+ }
586+ }
587+ if ( ir . regexCtx ) {
588+ for ( const x of ir . regexCtx . divisionTexts ) litTexts . add ( x ) ;
589+ for ( const x of ir . regexCtx . regexTexts ) litTexts . add ( x ) ;
590+ for ( const x of ir . regexCtx . parenHeadKw ) litTexts . add ( x ) ;
591+ for ( const x of ir . regexCtx . memberAccess ) litTexts . add ( x ) ;
592+ for ( const x of ir . regexCtx . postfixAfterValue ) litTexts . add ( x ) ;
593+ }
594+ if ( ir . tpl ) {
595+ litTexts . add ( ir . tpl . open ) ;
596+ litTexts . add ( ir . tpl . interpOpen ) ;
597+ litTexts . add ( ir . tpl . interpClose ) ;
598+ litTexts . add ( ir . tpl . braceOpen ) ;
599+ }
600+ if ( ir . newlineCfg ) {
601+ for ( const x of ir . newlineCfg . flowOpen ) litTexts . add ( x ) ;
602+ for ( const x of ir . newlineCfg . flowClose ) litTexts . add ( x ) ;
603+ }
604+
605+ // Stable order: puncts longest-first (ir.puncts), then remaining texts lexicographically
606+ const punctSet = new Set ( ir . puncts ) ;
607+ const rest = [ ...litTexts ] . filter ( ( t ) => ! punctSet . has ( t ) ) . sort ( ( a , b ) => ( a < b ? - 1 : a > b ? 1 : 0 ) ) ;
608+ const lids : string [ ] = [ '' , ...ir . puncts , ...rest ] ;
609+ return { kids, lids } ;
610+ }
611+
612+ /** Resolve lid for a known text; 0 if not in the plan (matches runtime lid_of). */
613+ export function lidOf ( plan : LexIdPlan , text : string ) : number {
614+ const i = plan . lids . indexOf ( text ) ;
615+ return i >= 0 ? i : 0 ;
616+ }
617+
618+ /** Resolve kid for a known kind; 0 if not in the plan. */
619+ export function kidOf ( plan : LexIdPlan , kind : string ) : number {
620+ const i = plan . kids . indexOf ( kind ) ;
621+ return i >= 0 ? i : 0 ;
622+ }
623+
525624// ── Lexer first-byte dispatch (IR analysis; targets render buckets) ──
526625
527626export type LexFirstBytes = { bytes : number [ ] ; nonAscii : boolean } ;
0 commit comments