11// Generate per-rule, per-ARM destructurers for a grammar's CST — the VALUE-level
22// sibling of gen-ast-types.ts. For every rule it emits
33//
4- // export type <Rule>Match = { arm: 'if', expr: ExprNode, … } | { arm: 'block', … } | …
5- // export function match<Rule>(n: <Rule>Node, src: string): <Rule>Match
4+ // export type <Rule>Match = { arm: 'if', expr: NodeEntry<'Expr'>, … } | …
5+ // export function match<Rule>(t: TreeAccess, n: NodeEntry<'Rule'>, src: string): <Rule>Match
6+ //
7+ // ARENA-NATIVE: matchers read the emitted parser's arena through the TreeAccess
8+ // interface (the emitted module's `tree` export satisfies it) — a node is an id, a
9+ // leaf is a token-encoded entry; nothing is materialized to match.
610//
711// The matcher re-derives WHICH grammar alternative a node matched and binds its
812// children to named fields — the discrimination the parser performed and the CST does
@@ -73,7 +77,15 @@ export function generateCstMatch(grammar: CstGrammar, importFrom: string): strin
7377 const tokenNames = new Set ( grammar . tokens . map ( t => t . name ) ) ;
7478 const templateTokenNames = new Set ( grammar . tokens . filter ( t => t . template ) . map ( t => t . name ) ) ;
7579 const ruleNames = new Set ( grammar . rules . map ( r => r . name ) ) ;
76- const usedIfaces = new Set < string > ( [ 'CstChild' , 'CstLeaf' ] ) ;
80+ // Int worlds, replicated from emit-parser's symtab (same deterministic derivation
81+ // from the grammar; any drift is caught instantly by cst-match-totality):
82+ // token TYPE kinds — '' punct = 1, $template spans = 2/3/4, named tokens from 5
83+ // rule ids — declaration order; the synthetic '$template' node = rules.length
84+ const typeKind = new Map < string , number > ( [ [ '' , 1 ] , [ '$templateHead' , 2 ] , [ '$templateMiddle' , 3 ] , [ '$templateTail' , 4 ] ] ) ;
85+ { let next = 5 ; for ( const t of grammar . tokens ) if ( ! typeKind . has ( t . name ) ) typeKind . set ( t . name , next ++ ) ; }
86+ const ruleId = new Map < string , number > ( grammar . rules . map ( ( r , i ) => [ r . name , i ] ) ) ;
87+ ruleId . set ( '$template' , grammar . rules . length ) ;
88+
7789
7890 // Pratt / leftRec classification (mirrors the engines' classifyAlts/classifyLeftRec:
7991 // a rule is op-bearing if any alt contains op/prefix/postfix markers; an alt whose
@@ -131,7 +143,7 @@ export function generateCstMatch(grammar: CstGrammar, importFrom: string): strin
131143 return { kind : 'node' , rule : self , cap : { name : nm , field : nm , tsType : nodeType ( self ) , card : 'one' } } ;
132144 }
133145 function opLeafCap ( nm : string ) : Step {
134- return { kind : 'tok' , name : '$operator' , template : false , cap : { name : nm , field : nm , tsType : 'CstLeaf ' , card : 'one' } } ;
146+ return { kind : 'tok' , name : '$operator' , template : false , cap : { name : nm , field : nm , tsType : 'LeafEntry ' , card : 'one' } } ;
135147 }
136148 function opPlan ( name : string , steps : Step [ ] , _self : string ) : ArmPlan {
137149 const captures = steps . map ( s => ( s as { cap : Capture } ) . cap ) ;
@@ -149,9 +161,7 @@ export function generateCstMatch(grammar: CstGrammar, importFrom: string): strin
149161 }
150162
151163 function nodeType ( rule : string ) : string {
152- const t = `${ rule } Node` ;
153- usedIfaces . add ( t ) ;
154- return t ;
164+ return `NodeEntry<${ J ( rule ) } >` ;
155165 }
156166
157167 // Name an arm from its first significant item; `sig` is already self-stripped for leds.
@@ -193,9 +203,8 @@ export function generateCstMatch(grammar: CstGrammar, importFrom: string): strin
193203 if ( tokenNames . has ( it . name ) ) {
194204 steps . push ( {
195205 kind : 'tok' , name : it . name , template : templateTokenNames . has ( it . name ) ,
196- cap : addCap ( captures , used , lowerFirst ( it . name ) , templateTokenNames . has ( it . name ) ? `CstLeaf | $templateNode ` : 'CstLeaf ' , card ) ,
206+ cap : addCap ( captures , used , lowerFirst ( it . name ) , templateTokenNames . has ( it . name ) ? `LeafEntry | NodeEntry<'$template'> ` : 'LeafEntry ' , card ) ,
197207 } ) ;
198- if ( templateTokenNames . has ( it . name ) ) usedIfaces . add ( '$templateNode' ) ;
199208 } else {
200209 steps . push ( { kind : 'node' , rule : it . name , cap : addCap ( captures , used , lowerFirst ( it . name ) , nodeType ( it . name ) , card ) } ) ;
201210 }
@@ -213,7 +222,7 @@ export function generateCstMatch(grammar: CstGrammar, importFrom: string): strin
213222 // capture it as <kw>Tok.
214223 const lead = inner [ 0 ] ;
215224 if ( it . kind === '?' && lead !== undefined && lead . kind === 'lit' && lead . tt === '$keyword' && lead . cap === undefined ) {
216- lead . cap = addCap ( captures , used , lead . text + 'Tok' , 'CstLeaf ' , innerCard ) ;
225+ lead . cap = addCap ( captures , used , lead . text + 'Tok' , 'LeafEntry ' , innerCard ) ;
217226 }
218227 if ( it . kind === '?' ) steps . push ( { kind : 'opt' , min1 : false , body : inner } ) ;
219228 else if ( it . kind === '*' ) steps . push ( { kind : 'many' , body : inner } ) ;
@@ -306,13 +315,13 @@ export function generateCstMatch(grammar: CstGrammar, importFrom: string): strin
306315 }
307316 w ( ` let i = 0;` ) ;
308317 renderSteps ( plan . steps , w , ' ' , ( ) => `return null;` ) ;
309- w ( ` if (i !== c.length ) return null;` ) ;
318+ w ( ` if (i !== cc ) return null;` ) ;
310319 const fields = plan . captures . map ( c => {
311320 if ( c . card === 'one' ) return `${ c . field } : ${ c . name } !` ;
312321 return c . field === c . name ? c . name : `${ c . field } : ${ c . name } ` ;
313322 } ) ;
314323 w ( ` return { arm: ${ J ( plan . name ) } ${ fields . length ? ', ' + fields . join ( ', ' ) : '' } };` ) ;
315- emit ( `function ${ fn } (c: readonly CstChild[] , src: string): ${ matchTypeName ( rule . name ) } | null {` ) ;
324+ emit ( `function ${ fn } (t: TreeAccess, n: number, cc: number , src: string): ${ matchTypeName ( rule . name ) } | null {` ) ;
316325 for ( const line of body ) emit ( line ) ;
317326 emit ( `}` ) ;
318327 return fn ;
@@ -324,35 +333,37 @@ export function generateCstMatch(grammar: CstGrammar, importFrom: string): strin
324333 }
325334
326335 function litCond ( text : string , tt : string ) : string {
327- return `__lit(c, i, src, ${ J ( text ) } , ${ J ( tt ) } )` ;
336+ return `__lit(t, cc, i, src, ${ J ( text ) } , ${ tt === '$keyword' ? 1 : 0 } )` ;
328337 }
329338
330339 function renderStep ( st : Step , w : ( s : string ) => void , ind : string , fail : ( ) => string ) : void {
331340 switch ( st . kind ) {
332341 case 'lit' :
333342 w ( `${ ind } if (!${ litCond ( st . text , st . tt ) } ) ${ fail ( ) } ` ) ;
334- if ( st . cap ) assign ( st . cap , `c [i] as CstLeaf ` , w , ind ) ;
343+ if ( st . cap ) assign ( st . cap , `__SC [i] as LeafEntry ` , w , ind ) ;
335344 w ( `${ ind } i++;` ) ;
336345 return ;
337346 case 'litAlt' : {
338347 const conds = st . texts . map ( ( t , k ) => litCond ( t , st . tt [ k ] ) ) ;
339348 w ( `${ ind } if (!(${ conds . join ( ' || ' ) } )) ${ fail ( ) } ` ) ;
340- if ( st . cap ) assign ( st . cap , `src.slice(c [i].offset, c [i].end ) as ${ st . cap . tsType } ` , w , ind ) ;
349+ if ( st . cap ) assign ( st . cap , `src.slice(t.offsetOf(__SC [i]), t.endOf(__SC [i]) ) as ${ st . cap . tsType } ` , w , ind ) ;
341350 w ( `${ ind } i++;` ) ;
342351 return ;
343352 }
344353 case 'tok' : {
345- const cond = st . template
346- ? `__tok(c, i, ${ J ( st . name ) } ) || __nodeOf(c, i, '$template')`
347- : `__tok(c, i, ${ J ( st . name ) } )` ;
354+ const cond = st . name === '$operator'
355+ ? `__opTok(t, cc, i)`
356+ : st . template
357+ ? `__tok(t, cc, i, ${ typeKind . get ( st . name ) } ) || __nodeOf(t, cc, i, ${ ruleId . get ( '$template' ) } )`
358+ : `__tok(t, cc, i, ${ typeKind . get ( st . name ) } )` ;
348359 w ( `${ ind } if (!(${ cond } )) ${ fail ( ) } ` ) ;
349- if ( st . cap ) assign ( st . cap , `c [i] as ${ st . cap . tsType } ` , w , ind ) ;
360+ if ( st . cap ) assign ( st . cap , `__SC [i] as ${ st . cap . tsType } ` , w , ind ) ;
350361 w ( `${ ind } i++;` ) ;
351362 return ;
352363 }
353364 case 'node' :
354- w ( `${ ind } if (!__nodeOf(c, i, ${ J ( st . rule ) } )) ${ fail ( ) } ` ) ;
355- if ( st . cap ) assign ( st . cap , `c [i] as ${ st . cap . tsType } ` , w , ind ) ;
365+ w ( `${ ind } if (!__nodeOf(t, cc, i, ${ ruleId . get ( st . rule ) } )) ${ fail ( ) } ` ) ;
366+ if ( st . cap ) assign ( st . cap , `__SC [i] as ${ st . cap . tsType } ` , w , ind ) ;
356367 w ( `${ ind } i++;` ) ;
357368 return ;
358369 case 'opt' : {
@@ -553,7 +564,7 @@ export function generateCstMatch(grammar: CstGrammar, importFrom: string): strin
553564 // ("always") arms appear in every bucket at their declaration position; the buckets
554565 // are superset filters (each arm fn re-checks exactly).
555566 const admits = plans . map ( p => firstAdmit ( p . steps ) ) ;
556- const tryLine = ( k : number ) => ` { const m = ${ fns [ k ] } (c , src); if (m !== null) return m; }` ;
567+ const tryLine = ( k : number ) => ` { const m = ${ fns [ k ] } (t, n, cc , src); if (m !== null) return m; }` ;
557568 const bucketLines = ( pred : ( keys : Set < string > ) => boolean ) : string [ ] =>
558569 plans . map ( ( _ , k ) => ( admits [ k ] . keys . size === 0 || pred ( admits [ k ] . keys ) ? tryLine ( k ) : '' ) )
559570 . filter ( Boolean ) ;
@@ -592,13 +603,12 @@ export function generateCstMatch(grammar: CstGrammar, importFrom: string): strin
592603 else cset . add ( Number ( key . slice ( 2 ) ) ) ;
593604 }
594605 }
595- lines . push ( `${ pad } const k1 = c[1] as (CstChild & { tokenType?: string; rule?: string }) | undefined;` ) ;
596- lines . push ( `${ pad } if (k1 === undefined) {` ) ;
606+ lines . push ( `${ pad } if (cc < 2) {` ) ;
597607 lines . push ( ...memberIdx . map ( ( k , i ) => ( restAdmit [ i ] === null || restAdmit [ i ] ! . canEmpty ? pad + ' ' + tryLine ( k ) . trim ( ) : '' ) ) . filter ( Boolean ) ) ;
598- lines . push ( `${ pad } } else if (k1.tokenType === undefined ) {` ) ;
599- lines . push ( `${ pad } switch (k1.rule ) {` ) ;
608+ lines . push ( `${ pad } } else if ((e1 = __SC[1]) >= 0 ) {` ) ;
609+ lines . push ( `${ pad } switch (t.ruleIdOf(e1) ) {` ) ;
600610 for ( const r of [ ...nset ] . sort ( ) ) {
601- lines . push ( `${ pad } case ${ J ( r ) } : {` ) ;
611+ lines . push ( `${ pad } case ${ ruleId . get ( r ) } : { // ${ r } ` ) ;
602612 lines . push ( ...subTry ( i => restAdmit [ i ] ! . keys . has ( 'n:' + r ) ) . map ( l => ' ' + l ) ) ;
603613 lines . push ( `${ pad } break;` ) ;
604614 lines . push ( `${ pad } }` ) ;
@@ -608,8 +618,8 @@ export function generateCstMatch(grammar: CstGrammar, importFrom: string): strin
608618 lines . push ( `${ pad } break;` ) ;
609619 lines . push ( `${ pad } }` ) ;
610620 lines . push ( `${ pad } }` ) ;
611- lines . push ( `${ pad } } else if (k1.tokenType === '$keyword' || k1.tokenType === '$punct' ) {` ) ;
612- lines . push ( `${ pad } switch (src.charCodeAt(k1.offset )) {` ) ;
621+ lines . push ( `${ pad } } else if ((_k1 = t.leafKindOf(e1)) === 1 || (_k1 === 0 && t.leafTokKindOf(e1) === 1) ) {` ) ;
622+ lines . push ( `${ pad } switch (src.charCodeAt(t.offsetOf(e1) )) {` ) ;
613623 for ( const cc of [ ...cset ] . sort ( ( a , b ) => a - b ) ) {
614624 lines . push ( `${ pad } case ${ cc } : {` ) ;
615625 lines . push ( ...subTry ( i => restAdmit [ i ] ! . keys . has ( 'c:' + cc ) ) . map ( l => ' ' + l ) ) ;
@@ -621,10 +631,13 @@ export function generateCstMatch(grammar: CstGrammar, importFrom: string): strin
621631 lines . push ( `${ pad } break;` ) ;
622632 lines . push ( `${ pad } }` ) ;
623633 lines . push ( `${ pad } }` ) ;
634+ lines . push ( `${ pad } } else if (_k1 === 2) {` ) ;
635+ lines . push ( ...subTry ( i => restAdmit [ i ] ! . keys . has ( 't:$operator' ) ) ) ;
624636 lines . push ( `${ pad } } else {` ) ;
625- lines . push ( `${ pad } switch (k1.tokenType ) {` ) ;
637+ lines . push ( `${ pad } switch (t.leafTokKindOf(e1) ) {` ) ;
626638 for ( const t of [ ...tset ] . sort ( ) ) {
627- lines . push ( `${ pad } case ${ J ( t ) } : {` ) ;
639+ if ( t === '$operator' ) continue ; // handled by the kind-2 branch above
640+ lines . push ( `${ pad } case ${ typeKind . get ( t ) } : { // ${ t } ` ) ;
628641 lines . push ( ...subTry ( i => restAdmit [ i ] ! . keys . has ( 't:' + t ) ) . map ( l => ' ' + l ) ) ;
629642 lines . push ( `${ pad } break;` ) ;
630643 lines . push ( `${ pad } }` ) ;
@@ -639,15 +652,16 @@ export function generateCstMatch(grammar: CstGrammar, importFrom: string): strin
639652 } ;
640653
641654 const disp : string [ ] = [ ] ;
642- disp . push ( `export function match${ sanitizeIdent ( rule . name ) } (n: ${ nName } , src: string): ${ tName } {` ) ;
643- disp . push ( ` const c = n.children ;` ) ;
644- disp . push ( ` const k0 = c[0] as (CstChild & { tokenType?: string; rule?: string }) | undefined ;` ) ;
645- disp . push ( ` if (k0 === undefined ) {` ) ;
655+ disp . push ( `export function match${ sanitizeIdent ( rule . name ) } (t: TreeAccess, n: NodeEntry< ${ J ( rule . name ) } > , src: string): ${ tName } {` ) ;
656+ disp . push ( ` const cc = __load(t, n) ;` ) ;
657+ disp . push ( ` let e1 = 0; let _k1 = 0 ;` ) ;
658+ disp . push ( ` if (cc === 0 ) {` ) ;
646659 for ( let k = 0 ; k < plans . length ; k ++ ) if ( admits [ k ] . canEmpty || admits [ k ] . keys . size === 0 ) disp . push ( tryLine ( k ) ) ;
647- disp . push ( ` } else if (k0.tokenType === undefined) {` ) ;
648- disp . push ( ` switch (k0.rule) {` ) ;
660+ disp . push ( ` } else { const e0 = __SC[0];` ) ;
661+ disp . push ( ` if (e0 >= 0) {` ) ;
662+ disp . push ( ` switch (t.ruleIdOf(e0)) {` ) ;
649663 for ( const r of [ ...nodeRules ] . sort ( ) ) {
650- disp . push ( ` case ${ J ( r ) } : {` ) ;
664+ disp . push ( ` case ${ ruleId . get ( r ) } : { // ${ r } ` ) ;
651665 const members = plans . map ( ( _ , k ) => k ) . filter ( k => admits [ k ] . keys . size === 0 || admits [ k ] . keys . has ( 'n:' + r ) ) ;
652666 const concrete = members . filter ( k => admits [ k ] . keys . size !== 0 ) ;
653667 const oneStep = concrete . every ( k => plans [ k ] . steps [ 0 ] ?. kind === 'node' ) ;
@@ -666,8 +680,9 @@ export function generateCstMatch(grammar: CstGrammar, importFrom: string): strin
666680 disp . push ( ` }` ) ;
667681 }
668682 disp . push ( ` }` ) ;
669- disp . push ( ` } else if (k0.tokenType === '$keyword' || k0.tokenType === '$punct') {` ) ;
670- disp . push ( ` switch (src.charCodeAt(k0.offset)) {` ) ;
683+ disp . push ( ` } else { const _k0 = t.leafKindOf(e0);` ) ;
684+ disp . push ( ` if (_k0 === 1 || (_k0 === 0 && t.leafTokKindOf(e0) === 1)) {` ) ;
685+ disp . push ( ` switch (src.charCodeAt(t.offsetOf(e0))) {` ) ;
671686 for ( const cc of [ ...charCodes ] . sort ( ( a , b ) => a - b ) ) {
672687 disp . push ( ` case ${ cc } : {` ) ;
673688 for ( const l of bucketLines ( keys => keys . has ( 'c:' + cc ) ) ) disp . push ( ' ' + l ) ;
@@ -681,10 +696,13 @@ export function generateCstMatch(grammar: CstGrammar, importFrom: string): strin
681696 disp . push ( ` }` ) ;
682697 }
683698 disp . push ( ` }` ) ;
699+ disp . push ( ` } else if (_k0 === 2) {` ) ;
700+ for ( const l of bucketLines ( keys => keys . has ( 't:$operator' ) ) ) disp . push ( l ) ;
684701 disp . push ( ` } else {` ) ;
685- disp . push ( ` switch (k0.tokenType ) {` ) ;
702+ disp . push ( ` switch (t.leafTokKindOf(e0) ) {` ) ;
686703 for ( const t of [ ...tokNames ] . sort ( ) ) {
687- disp . push ( ` case ${ J ( t ) } : {` ) ;
704+ if ( t === '$operator' ) continue ; // handled by the kind-2 branch above
705+ disp . push ( ` case ${ typeKind . get ( t ) } : { // ${ t } ` ) ;
688706 for ( const l of bucketLines ( keys => keys . has ( 't:' + t ) ) ) disp . push ( ' ' + l ) ;
689707 disp . push ( ` break;` ) ;
690708 disp . push ( ` }` ) ;
@@ -696,37 +714,73 @@ export function generateCstMatch(grammar: CstGrammar, importFrom: string): strin
696714 disp . push ( ` }` ) ;
697715 }
698716 disp . push ( ` }` ) ;
699- disp . push ( ` }` ) ;
700- disp . push ( ` throw new Error(${ J ( `match${ sanitizeIdent ( rule . name ) } : no arm matches` ) } + ' @' + n.offset );` ) ;
717+ disp . push ( ` } } } ` ) ;
718+ disp . push ( ` throw new Error(${ J ( `match${ sanitizeIdent ( rule . name ) } : no arm matches` ) } + ' @' + t.offsetOf(n) );` ) ;
701719 disp . push ( `}` ) ;
702720 bodyParts . push ( disp . join ( '\n' ) ) ;
703721 matcherMapEntries . push ( ` ${ J ( rule . name ) } : match${ sanitizeIdent ( rule . name ) } ,` ) ;
704722 }
705723
706724 header . push ( `// GENERATED by src/gen-cst-match.ts — do not edit. Per-arm CST destructurers for ${ J ( grammar . name ?? '' ) } .` ) ;
707725 header . push ( `/* eslint-disable */` ) ;
708- header . push ( `import type { ${ [ ...usedIfaces ] . sort ( ) . join ( ', ' ) } } from ${ J ( importFrom ) } ;` ) ;
709726 header . push ( `` ) ;
710- header . push ( `const __lit = (c: readonly CstChild[], i: number, src: string, text: string, tt: string): boolean => {` ) ;
711- header . push ( ` const k = c[i] as CstLeaf | undefined;` ) ;
712- header . push ( ` return k !== undefined && k.tokenType === tt && k.end - k.offset === text.length && src.startsWith(text, k.offset);` ) ;
727+ header . push ( `// The arena accessor surface the matchers read through (the emitted parser's` ) ;
728+ header . push ( `// \`tree\` export satisfies it). An ENTRY is a node id (>= 0) or a leaf (< 0).` ) ;
729+ header . push ( `export interface TreeAccess {` ) ;
730+ header . push ( ` ruleNameOf(id: number): string;` ) ;
731+ header . push ( ` ruleIdOf(id: number): number;` ) ;
732+ header . push ( ` childCount(id: number): number;` ) ;
733+ header . push ( ` childAt(id: number, i: number): number;` ) ;
734+ header . push ( ` childrenInto(id: number, out: number[]): number;` ) ;
735+ header . push ( ` leafTokenType(entry: number): string;` ) ;
736+ header . push ( ` leafKindOf(entry: number): number;` ) ;
737+ header . push ( ` leafTokKindOf(entry: number): number;` ) ;
738+ header . push ( ` offsetOf(entry: number): number;` ) ;
739+ header . push ( ` endOf(entry: number): number;` ) ;
740+ header . push ( `}` ) ;
741+ header . push ( `// Branded entry aliases — compile-time discrimination over plain numbers.` ) ;
742+ header . push ( `export type NodeEntry<R extends string = string> = number & { readonly __node?: R };` ) ;
743+ header . push ( `export type LeafEntry = number & { readonly __leaf?: true };` ) ;
744+ header . push ( `` ) ;
745+ header . push ( `// Per-call child-entry scratch: matchers are non-reentrant (one node at a time),` ) ;
746+ header . push ( `// so one bulk load replaces a childAt round-trip per probe.` ) ;
747+ header . push ( `const __SC: number[] = [];` ) ;
748+ header . push ( `const __load = (t: TreeAccess, n: number): number => t.childrenInto(n, __SC);` ) ;
749+ header . push ( `// kind: 1 = '$keyword' (leaf kind bit), 0 = '$punct' (type-derived + tok-kind 1).` ) ;
750+ header . push ( `const __lit = (t: TreeAccess, cc: number, i: number, src: string, text: string, kind: number): boolean => {` ) ;
751+ header . push ( ` if (i >= cc) return false;` ) ;
752+ header . push ( ` const e = __SC[i];` ) ;
753+ header . push ( ` if (e >= 0 || t.leafKindOf(e) !== kind || (kind === 0 && t.leafTokKindOf(e) !== 1)) return false;` ) ;
754+ header . push ( ` const off = t.offsetOf(e);` ) ;
755+ header . push ( ` return t.endOf(e) - off === text.length && src.startsWith(text, off);` ) ;
756+ header . push ( `};` ) ;
757+ header . push ( `const __tok = (t: TreeAccess, cc: number, i: number, k: number): boolean => {` ) ;
758+ header . push ( ` if (i >= cc) return false;` ) ;
759+ header . push ( ` const e = __SC[i];` ) ;
760+ header . push ( ` return e < 0 && t.leafKindOf(e) === 0 && t.leafTokKindOf(e) === k;` ) ;
713761 header . push ( `};` ) ;
714- header . push ( `const __tok = (c: readonly CstChild[], i: number, name: string): boolean => {` ) ;
715- header . push ( ` const k = c[i] as CstLeaf | undefined;` ) ;
716- header . push ( ` return k !== undefined && k.tokenType === name;` ) ;
762+ header . push ( `const __opTok = (t: TreeAccess, cc: number, i: number): boolean => {` ) ;
763+ header . push ( ` if (i >= cc) return false;` ) ;
764+ header . push ( ` const e = __SC[i];` ) ;
765+ header . push ( ` return e < 0 && t.leafKindOf(e) === 2;` ) ;
717766 header . push ( `};` ) ;
718- header . push ( `const __nodeOf = (c: readonly CstChild[], i: number, rule: string): boolean => {` ) ;
719- header . push ( ` const k = c[i] as { rule?: string } | undefined;` ) ;
720- header . push ( ` return k !== undefined && k.rule === rule;` ) ;
767+ header . push ( `const __nodeOf = (t: TreeAccess, cc: number, i: number, rid: number): boolean => {` ) ;
768+ header . push ( ` if (i >= cc) return false;` ) ;
769+ header . push ( ` const e = __SC[i];` ) ;
770+ header . push ( ` return e >= 0 && t.ruleIdOf(e) === rid;` ) ;
721771 header . push ( `};` ) ;
722772 header . push ( `` ) ;
723773
724774 const footer = [
725775 `` ,
726776 `/** rule name → its matcher (generic walking; the totality gate uses this). */` ,
727- `export const MATCHERS: Record<string, (n: never, src: string) => { arm: string }> = {` ,
777+ `export const MATCHERS: Record<string, (t: TreeAccess, n: never, src: string) => { arm: string }> = {` ,
728778 ...matcherMapEntries ,
729779 `};` ,
780+ `/** rule ID → matcher (the emitted parser's rowRule ids — declaration order). */` ,
781+ `export const MATCHERS_BY_ID: ((t: TreeAccess, n: never, src: string) => { arm: string })[] = [` ,
782+ ...grammar . rules . map ( r => ` match${ sanitizeIdent ( r . name ) } ,` ) ,
783+ `];` ,
730784 ] ;
731785
732786 return [ ...header , ...bodyParts . join ( '\n\n' ) . split ( '\n' ) , ...footer ] . join ( '\n' ) + '\n' ;
0 commit comments