|
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 | 14 | import { type ParserIR, type RdRule, type PrattRule, type Step, type Bracket, type CharRange, type LexTok, type TplCfg, type NewlineCfg, type FirstSig, type LexFirstBytes, type LexIdPlan, type ArenaIdPlan } from './emit-portable.ts'; |
15 | | -import { portableIR, buildLexDispatchPlan, lexTokFirstBytes, punctFirstBytes, buildLexIdPlan, buildArenaIdPlan, lidOf, kidOf, lidFlagTable, kidFlagTable, ttIdOf, ruleIdOf, TT_SKIP_PUNCT, rangesHaveNonAscii, isFirstGuardable, groupByPreserveOrder } from './emit-portable.ts'; |
| 15 | +import { portableIR, buildLexDispatchPlan, lexTokFirstBytes, punctFirstBytes, buildLexIdPlan, buildLidPrefilter, buildArenaIdPlan, lidOf, kidOf, lidFlagTable, kidFlagTable, ttIdOf, ruleIdOf, TT_SKIP_PUNCT, rangesHaveNonAscii, isFirstGuardable, groupByPreserveOrder } from './emit-portable.ts'; |
| 16 | +import { isKeywordLiteral } from './grammar-utils.ts'; |
16 | 17 | import type { Target } from './emit.ts'; |
17 | 18 | import type { TokenPattern, CstGrammar } from './types.ts'; |
18 | 19 |
|
@@ -48,28 +49,59 @@ function renderIdTablesRust(ids: LexIdPlan): string { |
48 | 49 | const kidsLit = ids.kids.map(J).join(', '); |
49 | 50 | const lidsLit = ids.lids.map(J).join(', '); |
50 | 51 | const kidArms = ids.kids.map((k, i) => `${J(k)} => ${i}`).join(', '); |
51 | | - // Group lids[1..] by UTF-8 byte length for a two-level match (len → text). Rust `str::len` is bytes. |
52 | | - const byLen = new Map<number, { text: string; id: number }[]>(); |
| 52 | + // Group lids[1..] by UTF-8 byte length; split keyword-shaped vs punct for separate match tables. |
| 53 | + const byLenKw = new Map<number, { text: string; id: number }[]>(); |
| 54 | + const byLenPu = new Map<number, { text: string; id: number }[]>(); |
53 | 55 | for (let i = 1; i < ids.lids.length; i++) { |
54 | | - const text = ids.lids[i]; |
| 56 | + const text = ids.lids[i]!; |
55 | 57 | const blen = Buffer.byteLength(text); |
56 | | - const arr = byLen.get(blen) ?? []; |
57 | | - arr.push({ text, id: i }); |
58 | | - byLen.set(blen, arr); |
| 58 | + const ent = { text, id: i }; |
| 59 | + const map = isKeywordLiteral(text) ? byLenKw : byLenPu; |
| 60 | + const arr = map.get(blen) ?? []; |
| 61 | + arr.push(ent); |
| 62 | + map.set(blen, arr); |
59 | 63 | } |
60 | | - const lenArms = [...byLen.entries()].sort((a, b) => a[0] - b[0]).map(([len, ents]) => { |
61 | | - const arms = ents.map((e) => `${J(e.text)} => ${e.id}`).join(', '); |
62 | | - return ` ${len} => match text { ${arms}, _ => 0 },`; |
63 | | - }).join('\n'); |
| 64 | + const lenArmsOf = (byLen: Map<number, { text: string; id: number }[]>) => |
| 65 | + [...byLen.entries()].sort((a, b) => a[0] - b[0]).map(([len, ents]) => { |
| 66 | + const arms = ents.map((e) => `${J(e.text)} => ${e.id}`).join(', '); |
| 67 | + return ` ${len} => match text { ${arms}, _ => 0 },`; |
| 68 | + }).join('\n'); |
| 69 | + const kwArms = lenArmsOf(byLenKw); |
| 70 | + const puArms = lenArmsOf(byLenPu); |
| 71 | + const pf = buildLidPrefilter(ids); |
| 72 | + const bitsLit = [...pf.firstByLenBits].join(', '); |
64 | 73 | return `const KIND_STR: &[&str] = &[${kidsLit}]; |
65 | 74 | const _LIDS: &[&str] = &[${lidsLit}]; |
| 75 | +const _LID_MAX_LEN: usize = ${pf.maxByteLen}; |
| 76 | +const _LID_FIRST_BITS: &[u8] = &[${bitsLit}]; |
66 | 77 | #[inline(always)] fn tok_kind(t: &Tok) -> &'static str { KIND_STR[t.kid as usize] } |
67 | 78 | #[inline(always)] fn tok_text<'a>(src: &'a str, t: &Tok) -> &'a str { &src[t.off as usize..t.end as usize] } |
68 | 79 | #[inline(always)] fn mk_tok(off: usize, end: usize, nl: bool, kid: u16, lid: u16) -> Tok { Tok { off: off as u32, end: end as u32, kid, lid, nl } } |
69 | 80 | fn kid_of(kind: &str) -> u16 { match kind { ${kidArms}, _ => 0 } } |
| 81 | +/// Ident/@-keyword: O(1) length×first-byte prefilter, then keyword-only match (no punct arms). |
| 82 | +#[inline(always)] |
70 | 83 | fn lid_of(text: &str) -> u16 { |
71 | | - match text.len() { |
72 | | -${lenArms || ' // no non-empty lids'} |
| 84 | + let n = text.len(); |
| 85 | + if n == 0 || n > _LID_MAX_LEN { return 0; } |
| 86 | + let b0 = text.as_bytes()[0]; |
| 87 | + if matches!(b0, b'A'..=b'Z' | b'a'..=b'z' | b'_' | b'$' | b'@') { |
| 88 | + let b0u = b0 as usize; |
| 89 | + if (_LID_FIRST_BITS[n * 32 + (b0u >> 3)] & (1u8 << (b0u & 7))) == 0 { return 0; } |
| 90 | + return lid_of_kw(text, n); |
| 91 | + } |
| 92 | + lid_of_punct(text, n) |
| 93 | +} |
| 94 | +#[inline(never)] |
| 95 | +fn lid_of_kw(text: &str, n: usize) -> u16 { |
| 96 | + match n { |
| 97 | +${kwArms || ' // no keyword lids'} |
| 98 | + _ => 0, |
| 99 | + } |
| 100 | +} |
| 101 | +#[inline(never)] |
| 102 | +fn lid_of_punct(text: &str, n: usize) -> u16 { |
| 103 | + match n { |
| 104 | +${puArms || ' // no punct lids'} |
73 | 105 | _ => 0, |
74 | 106 | } |
75 | 107 | } |
|
0 commit comments