|
11 | 11 | // parser allocates ~nothing per parse. Rule fns return `i32` (-1 = fail); sub-sequence |
12 | 12 | // combinators take non-capturing `fn(&mut Parser) -> bool` pointers (the kids vec is now on |
13 | 13 | // the Parser as `scratch`, so the second param the old owned-tree version threaded is gone). |
14 | | -import type { ParserIR, RdRule, PrattRule, Step, Bracket, CharRange, LexTok, TplCfg, NewlineCfg, FirstSig } from './emit-portable.ts'; |
15 | | -import { portableIR } from './emit-portable.ts'; |
| 14 | +import type { ParserIR, RdRule, PrattRule, Step, Bracket, CharRange, LexTok, TplCfg, NewlineCfg, FirstSig, LexFirstBytes } from './emit-portable.ts'; |
| 15 | +import { portableIR, buildLexDispatchPlan, lexTokFirstBytes, punctFirstBytes } from './emit-portable.ts'; |
16 | 16 | import type { Target } from './emit.ts'; |
17 | 17 | import type { TokenPattern, CstGrammar } from './types.ts'; |
18 | 18 |
|
@@ -86,6 +86,67 @@ function scanTok(t: LexTok, defs: string[], stateful: boolean, rxTok?: string, t |
86 | 86 | return ` if ${gate}true { let e = ${m}(src, pos as i64); if e > pos as i64 { let e = e as usize; ${push('e')}pos = e; continue; } }`; |
87 | 87 | } |
88 | 88 |
|
| 89 | +function rustByteLit(b: number): string { |
| 90 | + if ((b >= 97 && b <= 122) || (b >= 65 && b <= 90) || (b >= 48 && b <= 57)) return `b'${String.fromCharCode(b)}'`; |
| 91 | + if ([33, 35, 36, 37, 38, 42, 43, 44, 45, 46, 47, 58, 59, 61, 63, 64, 94, 95].includes(b)) return `b'${String.fromCharCode(b)}'`; |
| 92 | + return String(b); |
| 93 | +} |
| 94 | + |
| 95 | +function rustMatchLabels(bytes: number[]): string { |
| 96 | + const sorted = [...bytes].sort((a, b) => a - b); |
| 97 | + const parts: string[] = []; |
| 98 | + for (let i = 0; i < sorted.length; i++) { |
| 99 | + const lo = sorted[i]; |
| 100 | + let hi = lo; |
| 101 | + while (i + 1 < sorted.length && sorted[i + 1] === hi + 1) hi = sorted[++i]; |
| 102 | + if (lo === hi) { |
| 103 | + parts.push(rustByteLit(lo)); |
| 104 | + } else { |
| 105 | + const ls = rustByteLit(lo), hs = rustByteLit(hi); |
| 106 | + parts.push(ls.startsWith('b') && hs.startsWith('b') ? `${ls}..=${hs}` : `${lo}..=${hi}`); |
| 107 | + } |
| 108 | + } |
| 109 | + return parts.join(' | '); |
| 110 | +} |
| 111 | + |
| 112 | +function buildLexCandidates( |
| 113 | + ir: ParserIR, defs: string[], stateful: boolean, rxTok: string | undefined, tplTok: string | undefined, |
| 114 | + punctLine: (p: string) => string, |
| 115 | +): { codes: string[]; firsts: (LexFirstBytes | null)[] } { |
| 116 | + const codes: string[] = []; |
| 117 | + const firsts: (LexFirstBytes | null)[] = []; |
| 118 | + for (const t of ir.tokens) { |
| 119 | + const code = scanTok(t, defs, stateful, rxTok, tplTok); |
| 120 | + if (!code) continue; |
| 121 | + codes.push(code); |
| 122 | + firsts.push(lexTokFirstBytes(t)); |
| 123 | + } |
| 124 | + for (const p of ir.puncts) { |
| 125 | + codes.push(punctLine(p)); |
| 126 | + firsts.push(punctFirstBytes(p)); |
| 127 | + } |
| 128 | + return { codes, firsts }; |
| 129 | +} |
| 130 | + |
| 131 | +/** Shared first-byte dispatch for all lexFrom variants in this target. */ |
| 132 | +function renderLexByteDispatchRust(codes: string[], firsts: (LexFirstBytes | null)[], indent: string): string { |
| 133 | + const { arms, fallbackIndices } = buildLexDispatchPlan(firsts); |
| 134 | + const fallback = fallbackIndices.map((i) => codes[i]).join('\n'); |
| 135 | + let matchArms = ''; |
| 136 | + for (const arm of arms) { |
| 137 | + matchArms += `${indent} ${rustMatchLabels(arm.bytes)} => {\n`; |
| 138 | + matchArms += arm.indices.map((i) => codes[i]).join('\n') + '\n'; |
| 139 | + matchArms += `${indent} }\n`; |
| 140 | + } |
| 141 | + return `${indent} if c >= 128 { |
| 142 | +${fallback} |
| 143 | +${indent} } else { |
| 144 | +${indent} match b[pos] { |
| 145 | +${matchArms}${indent} _ => {} |
| 146 | +${indent} } |
| 147 | +${indent} }`; |
| 148 | +} |
| 149 | + |
89 | 150 | function newlinePartsRs(nl: NewlineCfg): { consts: string; fields: string; init: string; boundary: string; ws: string; hooks: string; boundaryFrom: string; wsFrom: string; hooksFrom: string } { |
90 | 151 | const commentSkip = nl.comment |
91 | 152 | ? ` if src[p..].starts_with(${J(nl.comment)}) { let mut e = p; while e < n && b[e] != 10 { e += 1; } pos = e; continue; }\n` |
@@ -183,9 +244,10 @@ function lexer(ir: ParserIR): string { |
183 | 244 | const rxOrTpl = !!(rx || tpl) && !rxOnly && !tplOnly && !rxTpl; |
184 | 245 | const stateful = !!(rx || tpl); |
185 | 246 | const newlineOnly = !!(nl && !rx && !tpl); |
186 | | - const toks = ir.tokens.map((t) => scanTok(t, defs, stateful, rx?.regexToken, tpl?.token)).join('\n'); |
187 | | - const puncts = ir.puncts.map((p) => |
188 | | - ` if src[pos..].starts_with(${J(p)}) { ${stateful ? `st.emit("", &src[pos..pos + ${p.length}], pos, pos + ${p.length});` : `toks.push(Tok { kind: "", text: &src[pos..pos + ${p.length}], off: pos, end: pos + ${p.length}, nl: pending_nl }); pending_nl = false;`} pos += ${p.length}; continue; }`).join('\n'); |
| 247 | + const punctLine = (p: string) => |
| 248 | + ` if src[pos..].starts_with(${J(p)}) { ${stateful ? `st.emit("", &src[pos..pos + ${p.length}], pos, pos + ${p.length});` : `toks.push(Tok { kind: "", text: &src[pos..pos + ${p.length}], off: pos, end: pos + ${p.length}, nl: pending_nl }); pending_nl = false;`} pos += ${p.length}; continue; }`; |
| 249 | + const { codes: lexCodes, firsts: lexFirsts } = buildLexCandidates(ir, defs, stateful, rx?.regexToken, tpl?.token, punctLine); |
| 250 | + const cascade = renderLexByteDispatchRust(lexCodes, lexFirsts, ' '); |
189 | 251 | const rsArr = (a: string[]) => `&[${a.map(J).join(', ')}]`; |
190 | 252 | // Struct fields / emit hooks / init are assembled per-feature so a grammar can have regex, |
191 | 253 | // templates, or both share one LexState. |
@@ -308,13 +370,11 @@ impl<'a, 'b> RxTplScan<'a, 'b> { |
308 | 370 | if c == 10 || c == 13 { ${nlVar} = true; pos += 1; continue; } // LF/CR |
309 | 371 | `; |
310 | 372 | const loopBody = `${nlBoundary} let c = b[pos] as u32; |
311 | | -${nlWs}${tplDispatch}${toks} |
312 | | -${puncts} |
| 373 | +${nlWs}${tplDispatch}${cascade} |
313 | 374 | panic!("lex error at {}", pos);`; |
314 | 375 | if (rxOnly) { |
315 | 376 | const rxLoopBody = `${nlBoundary} let c = b[pos] as u32; |
316 | | -${nlWs}${toks} |
317 | | -${puncts} |
| 377 | +${nlWs}${cascade} |
318 | 378 | panic!("lex error at {}", pos);`; |
319 | 379 | return `${defs.length ? defs.join('\n') + '\n' : ''}${rxConsts}${tplFn}${rxScanImpl}fn lex_from<'a>(src: &'a str, mut pos: usize, mut pending_nl: bool, mut prev_text: &'a str, mut prev_kind: &'static str, mut bp_text: &'a str, mut has_prev: bool, mut has_prev2: bool, mut paren_head: Vec<bool>, mut last_close: bool, mut last_bang: bool, acc: &mut Vec<Tok<'a>>, limit: usize) -> (usize, bool, &'a str, &'static str, &'a str, bool, bool, Vec<bool>, bool, bool) { |
320 | 380 | let b = src.as_bytes(); |
@@ -383,8 +443,7 @@ ${loopBody} |
383 | 443 | .replace(/toks\.push\(Tok \{ kind: ([^,]+), text: ([^,]+), off: pos, end: ([^,]+), nl: pending_nl \}\); pending_nl = false; ?/g, 'st.push_tok($1, $2, pos, $3); ') |
384 | 444 | .replace(/pending_nl/g, 'st.pending_nl'); |
385 | 445 | const nlLoopBody = `${nlRs!.boundaryFrom} let c = b[pos] as u32; |
386 | | -${nlRs!.wsFrom}${rustNlScan(toks)} |
387 | | -${rustNlScan(puncts)} |
| 446 | +${nlRs!.wsFrom}${rustNlScan(cascade)} |
388 | 447 | panic!("lex error at {}", pos);`; |
389 | 448 | return `${defs.length ? defs.join('\n') + '\n' : ''}${rxConsts}${tplFn}struct NlScan<'a, 'b> { acc: &'a mut Vec<Tok<'b>>, pending_nl: bool, line_start: bool, emitted_content: bool, flow_depth: i64 } |
390 | 449 | impl<'a, 'b> NlScan<'a, 'b> { |
|
0 commit comments