@@ -1127,8 +1127,8 @@ class Emitter {
11271127 // with the value's baked int (so the runtime does int compares, not string work).
11281128 matchLiteralCall ( value : string ) : string {
11291129 const d = this . a . symtab . classifyKey ( value ) ;
1130- if ( d . kind === 'kw' ) return `matchKwLit(${ J ( value ) } , ${ d . t } )` ;
1131- if ( d . kind === 'punct' ) return value === '>' ? `matchPuLitGT(${ d . t } )` : `matchPuLit(${ J ( value ) } , ${ d . t } )` ;
1130+ if ( d . kind === 'kw' ) return `matchKwLit(${ d . t } )` ;
1131+ if ( d . kind === 'punct' ) return value === '>' ? `matchPuLitGT(${ d . t } )` : `matchPuLit(${ d . t } )` ;
11321132 // A literal key that classifies as a token-name (a token name used as a literal):
11331133 // unreachable for real grammars, but stay safe via the generic matchLiteral.
11341134 return `matchLiteral(${ J ( value ) } )` ;
@@ -1288,8 +1288,6 @@ function resolveLexerImport(): string { return pathResolve(__dir, 'gen-lexer.ts'
12881288// ONLY change: where the interpreter called matchExpr(alt)/matchSeq(items) per arm,
12891289// these call the GENERATED per-arm matcher functions (installed via the rule fns).
12901290function emitRuntime ( e : Emitter ) {
1291- // Per-site text materialization (soa: slice the source span; fallback: text column).
1292- const TXT_OE = e . soa ? 'src.slice(off, end)' : 'tkText[pos]' ;
12931291 // Column element type: Uint8 when the kind/literal id spaces fit a byte.
12941292 const st = e . a . symtab ;
12951293 let tMax = 1 ;
@@ -1346,34 +1344,34 @@ function childEnd(c) { return c.end; }
13461344// && tok.text === value. With interned kinds that is tok.k >= K_NAMED_MIN (a declared
13471345// token name; '' is PUNCT, templates are below NAMED_MIN) && tok.t === KW(value).
13481346// Returns the SAME $keyword leaf as before. value/kw are baked by the caller.
1349- function matchKwLit(value, kw) {
1347+ function matchKwLit(kw) {
13501348 // A kw-range t can only come from a named token (template spans never intern to a
13511349 // keyword), so the old k >= K_NAMED_MIN guard was redundant — one int compare.
13521350 if (pos >= cap || tkT[pos] !== kw) return null;
13531351 const off = tkOff[pos];
13541352 const end = tkEnd[pos];
13551353 if (++pos > maxPos) maxPos = pos;
1356- return { kind: 'leaf', tokenType: '$keyword', text: value, offset: off, end };
1354+ return { kind: 'leaf', tokenType: '$keyword', offset: off, end };
13571355}
13581356// Punct literal: tok.type === '' && tok.text === value, with the gt-splice fallback.
13591357// tok.t === PU(value) is the exact-text fast path; the splice handles a longer
13601358// gt-led token matching the gt key. value/pu are baked by the caller.
1361- function matchPuLit(value, pu) {
1359+ function matchPuLit(pu) {
13621360 // A pu-range t can only come from a punct token, so the old k === K_PUNCT guard was
13631361 // redundant — one int compare. The '>'-split lives only in matchPuLitGT ('>' sites).
13641362 if (pos >= cap || tkT[pos] !== pu) return null;
13651363 const off = tkOff[pos];
13661364 const end = tkEnd[pos];
13671365 if (++pos > maxPos) maxPos = pos;
1368- return { kind: 'leaf', tokenType: '$punct', text: value, offset: off, end };
1366+ return { kind: 'leaf', tokenType: '$punct', offset: off, end };
13691367}
13701368function matchPuLitGT(pu) {
13711369 if (pos >= cap) return null;
13721370 const off = tkOff[pos];
13731371 if (tkT[pos] === pu) {
13741372 const end = tkEnd[pos];
13751373 if (++pos > maxPos) maxPos = pos;
1376- return { kind: 'leaf', tokenType: '$punct', text: '>', offset: off, end };
1374+ return { kind: 'leaf', tokenType: '$punct', offset: off, end };
13771375 }
13781376 // Split multi-'>' tokens: '>>', '>>>', '>>=', '>>>=' can yield a single '>': shift the
13791377 // columns up one slot and write the '>' + rest pair in place (both born flag-less,
@@ -1397,17 +1395,17 @@ function matchPuLitGT(pu) {
13971395 memoNode.fill(undefined);
13981396 memoEnd.fill(undefined);
13991397 if (++pos > maxPos) maxPos = pos;
1400- return { kind: 'leaf', tokenType: '$punct', text: '>', offset: off, end: off + 1 };
1398+ return { kind: 'leaf', tokenType: '$punct', offset: off, end: off + 1 };
14011399 }
14021400 return null;
14031401}
14041402// Generic matchLiteral kept for any unspecialized site: classify value via the baked
14051403// tables (no per-call isKeywordLiteral / string compares) and delegate.
14061404function matchLiteral(value) {
14071405 const kw = LIT_KW.get(value);
1408- if (kw !== undefined) return matchKwLit(value, kw);
1406+ if (kw !== undefined) return matchKwLit(kw);
14091407 if (value === '>') return matchPuLitGT(LIT_PU.get(value) ?? 0);
1410- return matchPuLit(value, LIT_PU.get(value) ?? 0);
1408+ return matchPuLit(LIT_PU.get(value) ?? 0);
14111409}
14121410
14131411// Match a token ref by its baked TYPE kind: tok.type === name ⟺ tok.k === nameKind.
@@ -1416,9 +1414,8 @@ function matchTokK(name, nameKind) {
14161414 if (pos >= cap || tkK[pos] !== nameKind) return null;
14171415 const off = tkOff[pos];
14181416 const end = tkEnd[pos];
1419- const text = ${ TXT_OE } ;
14201417 if (++pos > maxPos) maxPos = pos;
1421- return { kind: 'leaf', tokenType: name, text, offset: off, end };
1418+ return { kind: 'leaf', tokenType: name, offset: off, end };
14221419}
14231420
14241421// (First-token / FIRST-set gating is baked at emit time: per-set _qN byte-table fns
@@ -1428,31 +1425,31 @@ function parseTemplateExpr() {
14281425 if (pos >= cap) return null;
14291426 const k = tkK[pos];
14301427 if (k === K_TPL_TOKEN) {
1431- const off = tkOff[pos]; const end = tkEnd[pos]; const text = ${ TXT_OE } ;
1428+ const off = tkOff[pos]; const end = tkEnd[pos];
14321429 if (++pos > maxPos) maxPos = pos;
1433- return { kind: 'leaf', tokenType: templateTokenName, text, offset: off, end };
1430+ return { kind: 'leaf', tokenType: templateTokenName, offset: off, end };
14341431 }
14351432 if (k === K_TEMPLATE_HEAD) {
14361433 const children = [];
1437- { const off = tkOff[pos]; const end = tkEnd[pos]; const text = ${ TXT_OE } ;
1434+ { const off = tkOff[pos]; const end = tkEnd[pos];
14381435 if (++pos > maxPos) maxPos = pos;
1439- children.push({ kind: 'leaf', tokenType: '$templateHead', text, offset: off, end }); }
1436+ children.push({ kind: 'leaf', tokenType: '$templateHead', offset: off, end }); }
14401437 const interpRule = currentPrattContext ?? EXPR_RULE;
14411438 while (true) {
14421439 const exprNode = RULES[interpRule]();
14431440 if (exprNode) children.push(exprNode);
14441441 if (pos >= cap) break;
14451442 const nk = tkK[pos];
14461443 if (nk === K_TEMPLATE_MIDDLE) {
1447- const off = tkOff[pos]; const end = tkEnd[pos]; const text = ${ TXT_OE } ;
1444+ const off = tkOff[pos]; const end = tkEnd[pos];
14481445 if (++pos > maxPos) maxPos = pos;
1449- children.push({ kind: 'leaf', tokenType: '$templateMiddle', text, offset: off, end });
1446+ children.push({ kind: 'leaf', tokenType: '$templateMiddle', offset: off, end });
14501447 continue;
14511448 }
14521449 if (nk === K_TEMPLATE_TAIL) {
1453- const off = tkOff[pos]; const end = tkEnd[pos]; const text = ${ TXT_OE } ;
1450+ const off = tkOff[pos]; const end = tkEnd[pos];
14541451 if (++pos > maxPos) maxPos = pos;
1455- children.push({ kind: 'leaf', tokenType: '$templateTail', text, offset: off, end });
1452+ children.push({ kind: 'leaf', tokenType: '$templateTail', offset: off, end });
14561453 break;
14571454 }
14581455 break;
@@ -1589,9 +1586,9 @@ function emitPrattRule(e: Emitter, a: ReturnType<typeof analyze>, rule: RuleDecl
15891586 e . emit ( ` if (pos < cap) {` ) ;
15901587 e . emit ( ` const info = PREFIX_BY_T[tkT[pos]];` ) ;
15911588 e . emit ( ` if (info) {` ) ;
1592- e . emit ( ` const _o = tkOff[pos]; const _e = tkEnd[pos]; const _tx = ${ e . textAt ( 'pos' ) } ; ` ) ;
1589+ e . emit ( ` const _o = tkOff[pos]; const _e = tkEnd[pos];` ) ;
15931590 e . emit ( ` if (++pos > maxPos) maxPos = pos;` ) ;
1594- e . emit ( ` const opLeaf = { kind: 'leaf', tokenType: '$operator', text: _tx, offset: _o, end: _e };` ) ;
1591+ e . emit ( ` const opLeaf = { kind: 'leaf', tokenType: '$operator', offset: _o, end: _e };` ) ;
15951592 e . emit ( ` const rhs = ${ ruleFn } _pratt(info.rbp);` ) ;
15961593 e . emit ( ` if (rhs && pos > bestNudPos) { lhs = { kind: 'node', rule: ${ J ( rule . name ) } , children: [opLeaf, rhs], offset: opLeaf.offset, end: rhs.end }; bestNudPos = pos; }` ) ;
15971594 e . emit ( ` }` ) ;
@@ -1656,20 +1653,20 @@ function emitPrattRule(e: Emitter, a: ReturnType<typeof analyze>, rule: RuleDecl
16561653 e . emit ( ` if (info && info.lbp > minBp) {` ) ;
16571654 e . emit ( ` if (info.position === 'postfix') {` ) ;
16581655 e . emit ( ` if (!tailClosed) {` ) ;
1659- e . emit ( ` const _o = tkOff[pos]; const _e = tkEnd[pos]; const _tx = ${ e . textAt ( 'pos' ) } ; ` ) ;
1656+ e . emit ( ` const _o = tkOff[pos]; const _e = tkEnd[pos];` ) ;
16601657 e . emit ( ` if (++pos > maxPos) maxPos = pos;` ) ;
1661- e . emit ( ` const opLeaf = { kind: 'leaf', tokenType: '$operator', text: _tx, offset: _o, end: _e };` ) ;
1658+ e . emit ( ` const opLeaf = { kind: 'leaf', tokenType: '$operator', offset: _o, end: _e };` ) ;
16621659 e . emit ( ` lhs = { kind: 'node', rule: ${ J ( rule . name ) } , children: [lhs, opLeaf], offset: lhs.offset, end: opLeaf.end };` ) ;
16631660 e . emit ( ` tailClosed = true; matched = true;` ) ;
16641661 e . emit ( ` }` ) ;
16651662 e . emit ( ` } else {` ) ;
16661663 e . emit ( ` if (NOUNARY_T[tkT[pos]] !== 0 && lhs.kind === 'node') {` ) ;
16671664 e . emit ( ` const head = lhs.children[0];` ) ;
1668- e . emit ( ` if (head && head.kind === 'leaf' && head.tokenType === '$operator' && prefixOps.has(head.text) && !postfixOpValues.has(head.text )) { return null; }` ) ;
1665+ e . emit ( ` if (head && head.kind === 'leaf' && head.tokenType === '$operator' && prefixOps.has(src.slice( head.offset, head.end)) && !postfixOpValues.has(src.slice( head.offset, head.end) )) { return null; }` ) ;
16691666 e . emit ( ` }` ) ;
1670- e . emit ( ` const _o = tkOff[pos]; const _e = tkEnd[pos]; const _tx = ${ e . textAt ( 'pos' ) } ; ` ) ;
1667+ e . emit ( ` const _o = tkOff[pos]; const _e = tkEnd[pos];` ) ;
16711668 e . emit ( ` if (++pos > maxPos) maxPos = pos;` ) ;
1672- e . emit ( ` const opLeaf = { kind: 'leaf', tokenType: '$operator', text: _tx, offset: _o, end: _e };` ) ;
1669+ e . emit ( ` const opLeaf = { kind: 'leaf', tokenType: '$operator', offset: _o, end: _e };` ) ;
16731670 e . emit ( ` const rhs = ${ ruleFn } _pratt(info.rbp);` ) ;
16741671 e . emit ( ` if (rhs) { lhs = { kind: 'node', rule: ${ J ( rule . name ) } , children: [lhs, opLeaf, rhs], offset: lhs.offset, end: rhs.end }; matched = true; }` ) ;
16751672 e . emit ( ` else { pos = ledSaved; }` ) ;
@@ -1829,6 +1826,11 @@ export function tokenAt(i) {
18291826 };
18301827}
18311828
1829+ // The CST is span-only: a node's text is derived from the source it was parsed from.
1830+ export function getText(node, source) {
1831+ return source.slice(node.offset, node.end);
1832+ }
1833+
18321834export function parse(source, entryRule) {
18331835${ e . soa ? ` tokenize(source);` : String . raw ` src = source;
18341836 const _toks = tokenize(source);
0 commit comments