Skip to content

Commit 0b6d7fd

Browse files
committed
emit-portable: the real javascript.ts grammar emits to ts/go/rust (issue #6)
The target-agnostic emitter now handles a full language end-to-end. javascript.ts — 89 rules after the [Await]/[Yield] fork — emits, compiles and runs in all three targets, byte-identical to createParser, and is gate-maintained (28/28 accept, 6/6 reject ×3, ASCII corpus). What it took: - Left recursion: a left-recursive non-Pratt rule (NewTarget, TS Type) now routes through buildPratt (atom-then-continuation), fixing the infinite recursion a plain rd rule hit. - The [Await]/[Yield] context fork: emitPortableParser applies `withAwaitYield` exactly as createParser does, so `await`/`yield` are keywords in async/generator bodies and identifiers elsewhere — name-forked into $A/$Y/$AY families. - A forked rule labels its CST node with the CANON base name (cstName), not the $-suffixed family name; and the $ in family names (a valid TS but not Go/Rust identifier) is sanitized to `_` for the emitted parse-fn names. - Full JS whitespace (`\s`: NBSP/LS/PS/…), not just ASCII. - A leaked `_capped` flag: it is a global, but gen-parser's `capped` is local, so a grouping `(arrow)` leaked the cap to the outer expression and dropped a trailing call (`(() => {})()`). Non-capped NUD arms now force it false. - Two more `sep` shapes (empty list `f()`, both surfaced by the real grammar). ts/go/rust all 28/28 on the ASCII corpus (destructuring, generators, classes, optional chaining, async/await, labels). Byte-based go/rust use UTF-8 offsets — identical to the JS oracle for ASCII; non-ASCII offset units differ inherently. Full suite 42/42.
1 parent ba158c0 commit 0b6d7fd

5 files changed

Lines changed: 115 additions & 48 deletions

File tree

src/emit-portable.ts

Lines changed: 40 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
// operators. buildIR THROWS on a construct outside this set rather than emit a wrong
2222
// parser. This is enough to derive a real JavaScript-subset parser (examples/minijs.ts).
2323
import type { CstGrammar, RuleExpr, TokenDecl, TokenPattern } from './types.ts';
24+
import { withAwaitYield } from './await-yield-fork.ts';
2425
import { analyzeGrammar, findEntryRule } from './grammar-analysis.ts';
2526
import { collectLiterals, isKeywordLiteral } from './grammar-utils.ts';
2627
import {
@@ -58,11 +59,12 @@ export type Step =
5859
| { t: 'suppress'; connectors: string[]; steps: Step[] }; // parse the body with these LED connectors disabled (no-`in` context)
5960
export type Alt = Step[];
6061

61-
export type RdRule = { kind: 'rd'; name: string; alts: Alt[] };
62+
export type RdRule = { kind: 'rd'; name: string; cstName: string; alts: Alt[] };
6263
export type Bracket = { first: string; steps: Step[] }; // a literal-led sequence (grouping/array; LED call/index)
6364
export type PrattRule = {
6465
kind: 'pratt';
65-
name: string;
66+
name: string; // the (possibly $A/$Y-forked) rule name — used for the parse fn names
67+
cstName: string; // the CANON name — the CST node label (a fork collapses to its base)
6668
nudToks: string[]; // NUD: a bare token wrapped in a node
6769
nudBrackets: Bracket[]; // NUD: '(' … ')' / '[' … ']'
6870
nudSeqs: Step[][]; // NUD: a general sequence (guarded ident, class expr), tried with backtracking
@@ -123,7 +125,10 @@ export interface Target {
123125
}
124126

125127
export function emitPortableParser(grammar: CstGrammar, target: Target): string {
126-
return target.render(buildIR(grammar));
128+
// Apply the [Await]/[Yield] context fork exactly as createParser does, so `await`/`yield`
129+
// are keywords inside async/generator bodies and identifiers outside — name-forked into
130+
// $A/$Y/$AY rule families. Every other consumer (and the portable parser) sees plain rules.
131+
return target.render(buildIR(withAwaitYield(grammar)));
127132
}
128133

129134
// ── buildIR: grammar + analysis → the target-agnostic parse plan ──
@@ -174,8 +179,12 @@ function buildIR(grammar: CstGrammar): ParserIR {
174179
}
175180

176181
const rules: RuleIR[] = grammar.rules.map((r) => {
177-
if (a.prattRules.has(r.name)) return buildPratt(r.name, r.body, a, stepOf, altSteps, litTtype);
178-
return { kind: 'rd', name: r.name, alts: r.body.type === 'alt' ? r.body.items.map(altSteps) : [altSteps(r.body)] };
182+
const cstName = (r as { canon?: string }).canon ?? r.name; // a forked $A/$Y rule labels its CST node with the base name
183+
// Pratt rules AND left-recursive non-Pratt rules (e.g. NewTarget, TS Type) both parse as
184+
// atom-then-continuation: buildPratt detects `startsSelf` and splits accordingly, so routing
185+
// left-recursive rules through it avoids the infinite left-recursion a plain rd rule would hit.
186+
if (a.prattRules.has(r.name) || a.leftRecSet.has(r.name)) return buildPratt(r.name, cstName, r.body, a, stepOf, altSteps, litTtype);
187+
return { kind: 'rd', name: r.name, cstName, alts: r.body.type === 'alt' ? r.body.items.map(altSteps) : [altSteps(r.body)] };
179188
});
180189

181190
// Regex-vs-division context (only if the grammar declares a regex token + config).
@@ -212,7 +221,30 @@ function buildIR(grammar: CstGrammar): ParserIR {
212221
};
213222
}
214223

215-
return { grammarName: grammar.name ?? 'grammar', entry: findEntryRule(grammar), tokens, puncts, rules, regexCtx, tpl };
224+
// The [Await]/[Yield] fork names rules `Expr$A`/`Expr$Y` — `$` is a valid TS identifier but
225+
// NOT a Go/Rust one. Sanitize every rule-IDENTIFIER use (`$`→`_`) for the emitted parse-fn
226+
// names; the CST node label (cstName) keeps the canon base name, so the tree is unchanged.
227+
const san = (n: string) => n.replace(/\$/g, '_');
228+
const sanStep = (s: Step): void => {
229+
if (s.t === 'rule' || s.t === 'ruleBp') s.name = san(s.name);
230+
else if (s.t === 'star') sanStep(s.step);
231+
else if (s.t === 'opt' || s.t === 'not' || s.t === 'seq' || s.t === 'suppress') s.steps.forEach(sanStep);
232+
else if (s.t === 'sep') sanStep(s.elem);
233+
else if (s.t === 'alt') s.branches.forEach((b) => b.forEach(sanStep));
234+
};
235+
for (const r of rules) {
236+
r.name = san(r.name);
237+
if (r.kind === 'rd') r.alts.forEach((alt) => alt.forEach(sanStep));
238+
else {
239+
r.nudBrackets.forEach((b) => b.steps.forEach(sanStep));
240+
r.nudSeqs.forEach((seq) => seq.forEach(sanStep));
241+
r.nudCapped.forEach((c) => c.steps.forEach(sanStep));
242+
r.leds.forEach((b) => b.steps.forEach(sanStep));
243+
}
244+
}
245+
if (tpl) tpl.interpRule = san(tpl.interpRule);
246+
247+
return { grammarName: grammar.name ?? 'grammar', entry: san(findEntryRule(grammar)), tokens, puncts, rules, regexCtx, tpl };
216248
}
217249

218250
// Classify a token: a fast-path shape (run/string/line/block) when one cleanly matches,
@@ -259,7 +291,7 @@ function codesToRanges(codes: number[]): CharRange[] {
259291
// A Pratt rule's alternatives → NUD atoms/brackets/prefix + binary + mixfix LEDs.
260292
// Binding powers come from the analysis (opTable/prefixOps), single-sourced with the interpreter.
261293
function buildPratt(
262-
name: string, body: RuleExpr, a: ReturnType<typeof analyzeGrammar>,
294+
name: string, cstName: string, body: RuleExpr, a: ReturnType<typeof analyzeGrammar>,
263295
stepOf: (e: RuleExpr) => Step, altSteps: (e: RuleExpr) => Step[],
264296
litTtype: (v: string) => '$keyword' | '$punct',
265297
): PrattRule {
@@ -344,5 +376,5 @@ function buildPratt(
344376
const postfix = sawPostfix
345377
? [...a.opTable.entries()].filter(([, info]) => info.position === 'postfix').map(([op, info]) => ({ op, lbp: info.lbp }))
346378
: [];
347-
return { kind: 'pratt', name, nudToks, nudBrackets, nudSeqs, nudCapped, prefix, binary, leds, ledAccessTail, ledLbp, postfixToks, postfix };
379+
return { kind: 'pratt', name, cstName, nudToks, nudBrackets, nudSeqs, nudCapped, prefix, binary, leds, ledAccessTail, ledLbp, postfixToks, postfix };
348380
}

src/target-go.ts

Lines changed: 18 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -154,8 +154,8 @@ ${emitHooks}
154154
\t_ = pendingNl
155155
${rxState}${tplState}${emitFn}${pushTokFn}${defs.length ? '\t_s = src\n' : ''}\tfor pos < n {
156156
\t\tc := int(src[pos])
157-
\t\tif c == 32 || c == 9 { pos++; continue }
158-
\t\tif c == 10 || c == 13 { pendingNl = true; pos++; continue }
157+
\t\tif c == 10 || c == 13 || c == 8232 || c == 8233 { pendingNl = true; pos++; continue }
158+
\t\tif c == 32 || c == 9 || c == 11 || c == 12 || c == 160 || c == 5760 || (c >= 8192 && c <= 8202) || c == 8239 || c == 8287 || c == 12288 || c == 65279 { pos++; continue }
159159
${tplDispatch}${toks}
160160
${puncts}
161161
\t\tpanic(fmt.Sprintf("lex error at %d", pos))
@@ -184,7 +184,7 @@ function stepCond(s: Step): string {
184184

185185
function rdRule(r: RdRule): string {
186186
const alt = (steps: Step[]) =>
187-
`\tif ${steps.map(stepCond).join(' && ')} { return finish(${J(r.name)}, sb, offAt(save)) }
187+
`\tif ${steps.map(stepCond).join(' && ')} { return finish(${J(r.cstName)}, sb, offAt(save)) }
188188
\tpos = save; scratch = scratch[:sb]; nodes = nodes[:nb]; kids = kids[:kb]`;
189189
return `func parse${r.name}() int32 {
190190
\tsave := pos; sb := len(scratch); nb := len(nodes); kb := len(kids)
@@ -199,32 +199,32 @@ function prattRule(r: PrattRule, tpl: TplCfg | null): string {
199199
\t\tnode := matchTemplate()
200200
\t\tif node < 0 { return -1 }
201201
\t\tsb := len(scratch); scratch = append(scratch, node)
202-
\t\treturn finish(${J(r.name)}, sb, nodes[node].Offset)
202+
\t\treturn finish(${J(r.cstName)}, sb, nodes[node].Offset)
203203
\t}\n`
204204
: '';
205205
const bin = r.binary.map((b) => `${J(b.op)}: {${b.lbp}, ${b.rbp}}`).join(', ');
206206
const pre = r.prefix.map((p) => `${J(p.op)}: ${p.rbp}`).join(', ');
207207
const atoms = r.nudToks.map((k) => `${J(k)}: true`).join(', ');
208208
const bracketNud = (b: Bracket) => `\tif t.Text == ${J(b.first)} {
209209
\t\tsave := pos; sb := len(scratch); nb := len(nodes); kb := len(kids)
210-
\t\tif ${b.steps.map(stepCond).join(' && ')} { return finish(${J(r.name)}, sb, t.Off) }
210+
\t\tif ${b.steps.map(stepCond).join(' && ')} { return finish(${J(r.cstName)}, sb, t.Off) }
211211
\t\tpos = save; scratch = scratch[:sb]; nodes = nodes[:nb]; kids = kids[:kb]
212212
\t}`;
213213
const ledArm = (b: Bracket, accessTail: boolean, lbp: number | null) => `\t\tif ${accessTail ? '!tailClosed && ' : ''}${lbp !== null ? `${lbp} > minBp && ` : ''}!_mySup[${J(b.first)}] && t.Text == ${J(b.first)} {
214214
\t\t\tledSave := pos; sb := len(scratch); nb := len(nodes); kb := len(kids)
215215
\t\t\tscratch = append(scratch, left)
216-
\t\t\tif ${b.steps.map(stepCond).join(' && ')} { left = finish(${J(r.name)}, sb, nodes[left].Offset); continue }
216+
\t\t\tif ${b.steps.map(stepCond).join(' && ')} { left = finish(${J(r.cstName)}, sb, nodes[left].Offset); continue }
217217
\t\t\tpos = ledSave; scratch = scratch[:sb]; nodes = nodes[:nb]; kids = kids[:kb]; break
218218
\t\t}`;
219219
const postfixArm = (tok: string) => {
220220
const tplPart = tpl && tok === tpl.token ? `
221221
\t\tif !tailClosed && t.Kind == "$templateHead" {
222222
\t\t\tnode := matchTemplate()
223-
\t\t\tif node >= 0 { sb := len(scratch); scratch = append(scratch, left, node); left = finish(${J(r.name)}, sb, nodes[left].Offset); continue }
223+
\t\t\tif node >= 0 { sb := len(scratch); scratch = append(scratch, left, node); left = finish(${J(r.cstName)}, sb, nodes[left].Offset); continue }
224224
\t\t}` : '';
225225
return `\t\tif !tailClosed && t.Kind == ${J(tok)} {
226226
\t\t\tsb := len(scratch); scratch = append(scratch, left, mkLeaf(t.Kind, t.Off, t.End)); pos++
227-
\t\t\tleft = finish(${J(r.name)}, sb, nodes[left].Offset); continue
227+
\t\t\tleft = finish(${J(r.cstName)}, sb, nodes[left].Offset); continue
228228
\t\t}${tplPart}`;
229229
};
230230
const post = r.postfix.map((p) => `${J(p.op)}: ${p.lbp}`).join(', ');
@@ -246,7 +246,7 @@ ${r.leds.map((b, i) => ledArm(b, r.ledAccessTail[i], r.ledLbp[i])).join('\n')}
246246
${r.postfixToks.map(postfixArm).join('\n')}
247247
\t\tif post, ok := ${r.name}POST[t.Text]; ok && !tailClosed && post > minBp {
248248
\t\t\tsb := len(scratch); scratch = append(scratch, left, mkLeaf("$operator", t.Off, t.End)); pos++; tailClosed = true
249-
\t\t\tleft = finish(${J(r.name)}, sb, nodes[left].Offset); continue
249+
\t\t\tleft = finish(${J(r.cstName)}, sb, nodes[left].Offset); continue
250250
\t\t}
251251
\t\tinfo, ok := ${r.name}BIN[t.Text]
252252
\t\tif !ok || info.lbp <= minBp { break }
@@ -256,18 +256,19 @@ ${r.postfixToks.map(postfixArm).join('\n')}
256256
\t\trhs := ${r.name}bp(info.rbp)
257257
\t\tif rhs < 0 { pos = ledSave; scratch = scratch[:sb]; break }
258258
\t\tscratch = append(scratch, rhs)
259-
\t\tleft = finish(${J(r.name)}, sb, nodes[left].Offset)
259+
\t\tleft = finish(${J(r.cstName)}, sb, nodes[left].Offset)
260260
\t}
261261
\treturn left
262262
}
263263
func ${r.name}nud(minBp int) int32 {
264264
\t_capped = false
265265
\tt := peek()
266266
\tif t == nil { return -1 }
267-
${r.nudCapped.map((c) => `\tif minBp < ${c.capBp} { save := pos; sb := len(scratch); nb := len(nodes); kb := len(kids); if ${c.steps.length ? c.steps.map(stepCond).join(' && ') : 'true'} { _capped = true; return finish(${J(r.name)}, sb, offAt(save)) }; pos = save; scratch = scratch[:sb]; nodes = nodes[:nb]; kids = kids[:kb] }`).join('\n')}
267+
${r.nudCapped.map((c) => `\tif minBp < ${c.capBp} { save := pos; sb := len(scratch); nb := len(nodes); kb := len(kids); if ${c.steps.length ? c.steps.map(stepCond).join(' && ') : 'true'} { _capped = true; return finish(${J(r.cstName)}, sb, offAt(save)) }; pos = save; scratch = scratch[:sb]; nodes = nodes[:nb]; kids = kids[:kb] }`).join('\n')}
268+
\t_r := func() int32 { // non-capped: a sub-parse may leave _capped set; force it false after
268269
${tplNud}\tif ${r.name}ATOM[t.Kind] {
269270
\t\tsb := len(scratch); scratch = append(scratch, mkLeaf(t.Kind, t.Off, t.End)); pos++
270-
\t\treturn finish(${J(r.name)}, sb, t.Off)
271+
\t\treturn finish(${J(r.cstName)}, sb, t.Off)
271272
\t}
272273
${r.nudBrackets.map(bracketNud).join('\n')}
273274
\tif pbp, ok := ${r.name}PRE[t.Text]; ok {
@@ -276,10 +277,13 @@ ${r.nudBrackets.map(bracketNud).join('\n')}
276277
\t\toperand := ${r.name}bp(pbp)
277278
\t\tif operand < 0 { pos = save; scratch = scratch[:sb]; nodes = nodes[:nb]; kids = kids[:kb]; return -1 }
278279
\t\tscratch = append(scratch, operand)
279-
\t\treturn finish(${J(r.name)}, sb, t.Off)
280+
\t\treturn finish(${J(r.cstName)}, sb, t.Off)
280281
\t}
281-
${r.nudSeqs.map((seq) => `\t{ save := pos; sb := len(scratch); nb := len(nodes); kb := len(kids); if ${seq.length ? seq.map(stepCond).join(' && ') : 'true'} { return finish(${J(r.name)}, sb, offAt(save)) }; pos = save; scratch = scratch[:sb]; nodes = nodes[:nb]; kids = kids[:kb] }`).join('\n')}
282+
${r.nudSeqs.map((seq) => `\t{ save := pos; sb := len(scratch); nb := len(nodes); kb := len(kids); if ${seq.length ? seq.map(stepCond).join(' && ') : 'true'} { return finish(${J(r.cstName)}, sb, offAt(save)) }; pos = save; scratch = scratch[:sb]; nodes = nodes[:nb]; kids = kids[:kb] }`).join('\n')}
282283
\treturn -1
284+
\t}()
285+
\t_capped = false
286+
\treturn _r
283287
}`;
284288
}
285289

src/target-rust.ts

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -219,7 +219,7 @@ function stepCondP(s: Step): string {
219219

220220
function rdRule(r: RdRule): string {
221221
const alt = (steps: Step[]) =>
222-
` { let mut kids: Vec<Cst> = Vec::new(); if ${steps.map(stepCond).join(' && ')} { return Some(self.branch(${J(r.name)}, kids, save)); } self.pos = save; }`;
222+
` { let mut kids: Vec<Cst> = Vec::new(); if ${steps.map(stepCond).join(' && ')} { return Some(self.branch(${J(r.cstName)}, kids, save)); } self.pos = save; }`;
223223
return ` fn parse_${r.name}(&mut self) -> Option<Cst> {
224224
let save = self.pos;
225225
${r.alts.map(alt).join('\n')}
@@ -230,29 +230,29 @@ ${r.alts.map(alt).join('\n')}
230230
function prattRule(r: PrattRule, tpl: TplCfg | null): string {
231231
const tplNud = tpl && r.nudToks.includes(tpl.token)
232232
? ` if t.kind == "$templateHead" {
233-
return self.match_template().map(|n| { let (o, e) = (n.offset, n.end); Cst::node(${J(r.name)}, vec![n], o, e) });
233+
return self.match_template().map(|n| { let (o, e) = (n.offset, n.end); Cst::node(${J(r.cstName)}, vec![n], o, e) });
234234
}\n`
235235
: '';
236236
const binArms = r.binary.map((b) => `${J(b.op)} => Some((${b.lbp}, ${b.rbp}))`).join(', ');
237237
const preArms = r.prefix.map((p) => `${J(p.op)} => Some(${p.rbp})`).join(', ');
238238
const atomArm = r.nudToks.map(J).join(' | ');
239239
const bracketNud = (b: Bracket) => ` if t.text == ${J(b.first)} {
240240
let save = self.pos; let mut kids: Vec<Cst> = Vec::new();
241-
if ${b.steps.map(stepCond).join(' && ')} { return Some(node(${J(r.name)}, kids)); }
241+
if ${b.steps.map(stepCond).join(' && ')} { return Some(node(${J(r.cstName)}, kids)); }
242242
self.pos = save; // fall through to the next NUD alternative
243243
}`;
244244
const ledArm = (b: Bracket, accessTail: boolean, lbp: number | null) => ` if ${accessTail ? '!tail_closed && ' : ''}${lbp !== null ? `${lbp} > min_bp && ` : ''}!my_sup.iter().any(|c| *c == ${J(b.first)}) && t.text == ${J(b.first)} {
245245
let led_save = self.pos; let mut kids: Vec<Cst> = Vec::new();
246246
if ${b.steps.map(stepCond).join(' && ')} {
247247
let mut full = vec![left]; full.append(&mut kids);
248-
left = node(${J(r.name)}, full); continue;
248+
left = node(${J(r.cstName)}, full); continue;
249249
}
250250
self.pos = led_save; break;
251251
}`;
252252
const postfixArm = (tok: string) => {
253253
const tplPart = tpl && tok === tpl.token ? `
254-
if !tail_closed && t.kind == "$templateHead" { if let Some(n) = self.match_template() { left = node(${J(r.name)}, vec![left, n]); continue; } }` : '';
255-
return ` if !tail_closed && t.kind == ${J(tok)} { self.pos += 1; let leaf = Cst::leaf(t.kind, t.off, t.end); left = node(${J(r.name)}, vec![left, leaf]); continue; }${tplPart}`;
254+
if !tail_closed && t.kind == "$templateHead" { if let Some(n) = self.match_template() { left = node(${J(r.cstName)}, vec![left, n]); continue; } }` : '';
255+
return ` if !tail_closed && t.kind == ${J(tok)} { self.pos += 1; let leaf = Cst::leaf(t.kind, t.off, t.end); left = node(${J(r.cstName)}, vec![left, leaf]); continue; }${tplPart}`;
256256
};
257257
const postArms = r.postfix.map((p) => `${J(p.op)} => Some(${p.lbp})`).join(', ');
258258
return ` fn parse_${r.name}(&mut self) -> Option<Cst> { self.${r.name}_bp(0) }
@@ -270,35 +270,41 @@ function prattRule(r: PrattRule, tpl: TplCfg | null): string {
270270
let t = match self.peek() { Some(t) => t, None => break };
271271
${r.leds.map((b, i) => ledArm(b, r.ledAccessTail[i], r.ledLbp[i])).join('\n')}
272272
${r.postfixToks.map(postfixArm).join('\n')}
273-
if let Some(plbp) = Parser::${r.name}_post(t.text) { if !tail_closed && plbp > min_bp { self.pos += 1; let op_leaf = Cst::leaf("$operator", t.off, t.end); left = node(${J(r.name)}, vec![left, op_leaf]); tail_closed = true; continue; } }
273+
if let Some(plbp) = Parser::${r.name}_post(t.text) { if !tail_closed && plbp > min_bp { self.pos += 1; let op_leaf = Cst::leaf("$operator", t.off, t.end); left = node(${J(r.cstName)}, vec![left, op_leaf]); tail_closed = true; continue; } }
274274
let (lbp, rbp) = match Parser::${r.name}_bin(t.text) { Some(x) => x, None => break };
275275
if lbp <= min_bp { break; }
276276
let led_save = self.pos;
277277
self.pos += 1;
278278
let op_leaf = Cst::leaf("$operator", t.off, t.end);
279279
let rhs = match self.${r.name}_bp(rbp) { Some(r) => r, None => { self.pos = led_save; break; } };
280-
left = node(${J(r.name)}, vec![left, op_leaf, rhs]);
280+
left = node(${J(r.cstName)}, vec![left, op_leaf, rhs]);
281281
}
282282
Some(left)
283283
}
284284
fn ${r.name}_nud(&mut self, min_bp: i64) -> Option<Cst> {
285285
self.capped = false;
286286
let t = self.peek()?;
287-
${r.nudCapped.map((c) => ` if min_bp < ${c.capBp} { let save = self.pos; let mut kids: Vec<Cst> = Vec::new(); if ${c.steps.length ? c.steps.map(stepCond).join(' && ') : 'true'} { self.capped = true; return Some(self.branch(${J(r.name)}, kids, save)); } self.pos = save; }`).join('\n')}
287+
${r.nudCapped.map((c) => ` if min_bp < ${c.capBp} { let save = self.pos; let mut kids: Vec<Cst> = Vec::new(); if ${c.steps.length ? c.steps.map(stepCond).join(' && ') : 'true'} { self.capped = true; return Some(self.branch(${J(r.cstName)}, kids, save)); } self.pos = save; }`).join('\n')}
288+
// non-capped: a sub-parse may leave capped set (grouping a capped arrow); force it false after
289+
let r = self.${r.name}_nud_rest(t);
290+
self.capped = false;
291+
r
292+
}
293+
fn ${r.name}_nud_rest(&mut self, t: Tok<'a>) -> Option<Cst> {
288294
${tplNud} if Parser::${r.name}_atom(t.kind) {
289295
self.pos += 1;
290-
return Some(Cst::node(${J(r.name)}, vec![Cst::leaf(t.kind, t.off, t.end)], t.off, t.end));
296+
return Some(Cst::node(${J(r.cstName)}, vec![Cst::leaf(t.kind, t.off, t.end)], t.off, t.end));
291297
}
292298
${r.nudBrackets.map(bracketNud).join('\n')}
293299
if let Some(pbp) = Parser::${r.name}_pre(t.text) {
294300
let save = self.pos; self.pos += 1;
295301
let op_leaf = Cst::leaf("$operator", t.off, t.end);
296302
match self.${r.name}_bp(pbp) {
297-
Some(operand) => { let (o, e) = (t.off, operand.end); return Some(Cst::node(${J(r.name)}, vec![op_leaf, operand], o, e)); }
303+
Some(operand) => { let (o, e) = (t.off, operand.end); return Some(Cst::node(${J(r.cstName)}, vec![op_leaf, operand], o, e)); }
298304
None => { self.pos = save; return None; }
299305
}
300306
}
301-
${r.nudSeqs.map((seq) => ` { let save = self.pos; let mut kids: Vec<Cst> = Vec::new(); if ${seq.length ? seq.map(stepCond).join(' && ') : 'true'} { return Some(self.branch(${J(r.name)}, kids, save)); } self.pos = save; }`).join('\n')}
307+
${r.nudSeqs.map((seq) => ` { let save = self.pos; let mut kids: Vec<Cst> = Vec::new(); if ${seq.length ? seq.map(stepCond).join(' && ') : 'true'} { return Some(self.branch(${J(r.cstName)}, kids, save)); } self.pos = save; }`).join('\n')}
302308
None
303309
}`;
304310
}

0 commit comments

Comments
 (0)