parseman 0.58.3 → 0.59.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/analysis/diagnostics.js +159 -137
- package/dist/analysis/diagnostics.js.map +3 -3
- package/dist/cli/index.js +161 -138
- package/dist/cli/index.js.map +3 -3
- package/dist/combinators/choice.d.ts +0 -5
- package/dist/combinators/choice.d.ts.map +1 -1
- package/dist/combinators/dispatch.d.ts.map +1 -1
- package/dist/combinators/field-trivia.d.ts.map +1 -1
- package/dist/combinators/keywords.d.ts.map +1 -1
- package/dist/combinators/literal.d.ts.map +1 -1
- package/dist/combinators/node.d.ts.map +1 -1
- package/dist/combinators/regex.d.ts.map +1 -1
- package/dist/combinators/scanTo.d.ts +1 -16
- package/dist/combinators/scanTo.d.ts.map +1 -1
- package/dist/combinators/token.d.ts.map +1 -1
- package/dist/compiler/ir-serialize.d.ts.map +1 -1
- package/dist/compiler/linker.d.ts +0 -3
- package/dist/compiler/linker.d.ts.map +1 -1
- package/dist/compiler/token-alphabet.d.ts +5 -25
- package/dist/compiler/token-alphabet.d.ts.map +1 -1
- package/dist/compiler/token-capability.js.map +1 -1
- package/dist/compiler/token-dispatch.d.ts +8 -79
- package/dist/compiler/token-dispatch.d.ts.map +1 -1
- package/dist/cst/capture-buffer.d.ts.map +1 -1
- package/dist/cst/plain.js +69 -0
- package/dist/cst/plain.js.map +6 -0
- package/dist/cst/root-trivia-scope.d.ts +0 -3
- package/dist/cst/root-trivia-scope.d.ts.map +1 -1
- package/dist/cst/trivia-entries.d.ts +0 -2
- package/dist/cst/trivia-entries.d.ts.map +1 -1
- package/dist/cst/trivia-kinds.d.ts +0 -5
- package/dist/cst/trivia-kinds.d.ts.map +1 -1
- package/dist/index.js +163 -143
- package/dist/index.js.map +3 -3
- package/dist/language-service/index.js +19 -2
- package/dist/language-service/index.js.map +2 -2
- package/dist/line-index.d.ts +13 -0
- package/dist/line-index.d.ts.map +1 -1
- package/dist/plugin/entry-supercompiler.d.ts.map +1 -1
- package/dist/plugin/index.js +165 -145
- package/dist/plugin/index.js.map +3 -3
- package/dist/recovery/scan.d.ts.map +1 -1
- package/dist/run/index.js +4 -1
- package/dist/run/index.js.map +2 -2
- package/dist/spec/index.js +18 -1
- package/dist/spec/index.js.map +2 -2
- package/dist/spec/model.d.ts +0 -8
- package/dist/spec/model.d.ts.map +1 -1
- package/dist/table/assemble.d.ts.map +1 -1
- package/dist/table/emit-assembly.d.ts +1 -1
- package/dist/table/emit-assembly.d.ts.map +1 -1
- package/dist/table/emit.d.ts +6 -0
- package/dist/table/emit.d.ts.map +1 -1
- package/dist/table/encode.d.ts.map +1 -1
- package/dist/table/index.js +142 -133
- package/dist/table/index.js.map +3 -3
- package/dist/table/program.d.ts +0 -1
- package/dist/table/program.d.ts.map +1 -1
- package/dist/table/run-support.d.ts.map +1 -1
- package/dist/table/scalar-terminal.d.ts +0 -7
- package/dist/table/scalar-terminal.d.ts.map +1 -1
- package/dist/table/scan-shapes.d.ts +0 -8
- package/dist/table/scan-shapes.d.ts.map +1 -1
- package/dist/table/site-labels.d.ts +0 -2
- package/dist/table/site-labels.d.ts.map +1 -1
- package/dist/version.d.ts +1 -1
- package/package.json +2 -1
- package/src/combinators/choice.ts +1 -1
- package/src/combinators/dispatch.ts +2 -1
- package/src/combinators/field-trivia.ts +6 -5
- package/src/combinators/keywords.ts +3 -2
- package/src/combinators/literal.ts +5 -4
- package/src/combinators/node.ts +3 -1
- package/src/combinators/regex.ts +5 -4
- package/src/combinators/scanTo.ts +3 -2
- package/src/combinators/token.ts +3 -6
- package/src/compiler/ir-serialize.ts +2 -1
- package/src/compiler/linker.ts +5 -3
- package/src/compiler/token-alphabet.ts +14 -10
- package/src/compiler/token-dispatch.ts +10 -313
- package/src/compiler/token-scanner.ts +2 -2
- package/src/cst/capture-buffer.ts +13 -5
- package/src/cst/plain.d.ts +35 -0
- package/src/cst/plain.js +74 -0
- package/src/cst/root-trivia-scope.ts +1 -1
- package/src/cst/trivia-entries.ts +1 -1
- package/src/cst/trivia-kinds.ts +1 -1
- package/src/line-index.ts +37 -0
- package/src/plugin/entry-supercompiler.ts +3 -2
- package/src/recovery/scan.ts +2 -1
- package/src/spec/model.ts +1 -1
- package/src/table/assemble.ts +33 -35
- package/src/table/emit-assembly.ts +27 -24
- package/src/table/emit.ts +9 -1
- package/src/table/encode.ts +16 -39
- package/src/table/program.ts +1 -1
- package/src/table/run-support.ts +4 -7
- package/src/table/scalar-terminal.ts +1 -1
- package/src/table/scan-shapes.ts +15 -46
- package/src/table/site-labels.ts +1 -1
- package/src/version.ts +1 -1
|
@@ -1,83 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Packing and case-fold helpers for the dormant token scanner (`token-scanner.ts`).
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* Measured on the built css artifact: 40,269 B of 3,336,650 B was exactly these
|
|
11
|
-
* chains, 10,499 B of it inside `_r_StylesheetAtRule` alone.
|
|
12
|
-
*
|
|
13
|
-
* Instead the selector's matched span is walked to a small integer case id, and
|
|
14
|
-
* the cases select on the integer. Every id strategy below is BUILT, not
|
|
15
|
-
* reasoned about — `PARSEMAN_DISPATCH=<id>:<sel>` picks one so they can be
|
|
16
|
-
* measured on the shipped artifact rather than in a microbenchmark, which
|
|
17
|
-
* already misled us once: a standalone "table" looked slow because that
|
|
18
|
-
* implementation sliced and lowercased and hashed, not because table dispatch
|
|
19
|
-
* is slow.
|
|
20
|
-
*
|
|
21
|
-
* Every strategy is "compute a candidate case cheaply, then VERIFY against the
|
|
22
|
-
* packed key text", except `trie`, whose walk is already exact.
|
|
23
|
-
*/
|
|
24
|
-
|
|
25
|
-
/** One dispatch case's keys, in emission order. Case ids are 1-based; 0 = no match. */
|
|
26
|
-
export type DispatchKeySet = { keys: readonly string[]; caseInsensitive: boolean }
|
|
27
|
-
|
|
28
|
-
export type IdStrategy = 'trie' | 'lenswitch' | 'firstchar' | 'phash'
|
|
29
|
-
export type ArmSelection = 'ifchain' | 'switch'
|
|
30
|
-
|
|
31
|
-
export type DispatchConfig = { id: IdStrategy | 'auto'; sel: ArmSelection }
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* `PARSEMAN_DISPATCH=<id>:<sel>`. The default is the MEASURED winner on the
|
|
35
|
-
* shipped css artifact, not a preference. All five configurations were built
|
|
36
|
-
* into this emitter and compared on the real artifact with the trees diffed for
|
|
37
|
-
* equality, interleaved in one process (medians of 31 rounds):
|
|
38
|
-
*
|
|
39
|
-
* config ms/parse rel raw B gzip B
|
|
40
|
-
* chain 6.092 1.000 3,336,650 426,247
|
|
41
|
-
* trie:ifchain 5.967 0.979 3,311,657 424,465 <- default
|
|
42
|
-
* trie:switch 5.945 0.976 3,333,421 424,895
|
|
43
|
-
* phash:switch 6.011 0.987 3,331,361 424,637
|
|
44
|
-
*
|
|
45
|
-
* `firstchar` and `lenswitch` BUILD but are inapplicable to css's at-keyword
|
|
46
|
-
* sets — every key starts with `@`, and the lengths collide — so they fall back
|
|
47
|
-
* and emit the chain. `phash` finds an injective hash (the search works) but
|
|
48
|
-
* costs more table bytes than the trie saves. `switch` arm selection is within
|
|
49
|
-
* noise of the chain of integer compares at 3 sites x 8 cases; its larger raw
|
|
50
|
-
* size is the downstream formatter indenting case bodies, which is why gzip is
|
|
51
|
-
* the metric that decides here.
|
|
52
|
-
*
|
|
53
|
-
* The speed spread across ALL configurations is 2.4% — dispatch keying is not
|
|
54
|
-
* where css parse time goes.
|
|
4
|
+
* This module once also emitted token-keyed `dispatch()` id strategies (trie,
|
|
5
|
+
* length switch, first char, perfect hash) for the source lowering deleted in
|
|
6
|
+
* 0.47.0. Those strategies, their `PARSEMAN_DISPATCH` selector and their
|
|
7
|
+
* measurements were removed with nothing left calling them; they last existed at
|
|
8
|
+
* `4a5d0341`. What remains is what `token-scanner.ts` imports.
|
|
55
9
|
*/
|
|
56
|
-
export function dispatchConfigFromEnv(env: Record<string, string | undefined>): DispatchConfig {
|
|
57
|
-
const raw = env.PARSEMAN_DISPATCH
|
|
58
|
-
if (raw === undefined || raw === '') return { id: 'trie', sel: 'ifchain' }
|
|
59
|
-
const [idPart, selPart] = raw.split(':')
|
|
60
|
-
const id = (['trie', 'lenswitch', 'firstchar', 'phash', 'auto'] as const).find(s => s === idPart) ?? 'auto'
|
|
61
|
-
const sel: ArmSelection = selPart === 'ifchain' ? 'ifchain' : 'switch'
|
|
62
|
-
return { id, sel }
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
/** Flattened (key, caseIndex) pairs, folded when the case is case-insensitive. */
|
|
66
|
-
type FlatKey = { text: string; caseIndex: number }
|
|
67
|
-
|
|
68
|
-
function flatten(cases: ReadonlyArray<DispatchKeySet>): FlatKey[] | null {
|
|
69
|
-
const out: FlatKey[] = []
|
|
70
|
-
for (const [i, c] of cases.entries()) {
|
|
71
|
-
for (const key of c.keys) {
|
|
72
|
-
if (key.length === 0) return null
|
|
73
|
-
// A case-SENSITIVE key containing an ASCII letter cannot share a folded
|
|
74
|
-
// comparison with the rest; refuse rather than accept the wrong case.
|
|
75
|
-
if (!c.caseInsensitive && /[a-zA-Z]/.test(key)) return null
|
|
76
|
-
out.push({ text: foldText(key), caseIndex: i + 1 })
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
return out.length === 0 ? null : out
|
|
80
|
-
}
|
|
81
10
|
|
|
82
11
|
/**
|
|
83
12
|
* ASCII letter-only case fold. Used IDENTICALLY when building the tables and
|
|
@@ -88,11 +17,6 @@ function flatten(cases: ReadonlyArray<DispatchKeySet>): FlatKey[] | null {
|
|
|
88
17
|
*/
|
|
89
18
|
export const foldExpr = (c: string): string => `(${c} >= 65 && ${c} <= 90 ? ${c} + 32 : ${c})`
|
|
90
19
|
export function foldCode(c: number): number { return c >= 65 && c <= 90 ? c + 32 : c }
|
|
91
|
-
function foldText(t: string): string {
|
|
92
|
-
let out = ''
|
|
93
|
-
for (let i = 0; i < t.length; i++) out += String.fromCharCode(foldCode(t.charCodeAt(i)))
|
|
94
|
-
return out
|
|
95
|
-
}
|
|
96
20
|
|
|
97
21
|
/**
|
|
98
22
|
* Pack a non-negative int array into a two-chars-per-value string literal.
|
|
@@ -103,7 +27,7 @@ function foldText(t: string): string {
|
|
|
103
27
|
* this is the ONLY implementation: a second copy in token-scanner.ts had the same
|
|
104
28
|
* encoding and the same missing check, which is how one defect became two.
|
|
105
29
|
*/
|
|
106
|
-
|
|
30
|
+
const PACK_MAX = 4095
|
|
107
31
|
export function packInts(values: readonly number[]): string {
|
|
108
32
|
let s = ''
|
|
109
33
|
for (const v of values) {
|
|
@@ -118,236 +42,9 @@ export function packInts(values: readonly number[]): string {
|
|
|
118
42
|
return JSON.stringify(s)
|
|
119
43
|
}
|
|
120
44
|
|
|
121
|
-
/* ------------------------------------------------------------------ *
|
|
122
|
-
* Shared runtime helpers, emitted ONCE per artifact however many sites
|
|
123
|
-
* use them. That is what lets tables shrink the artifact instead of
|
|
124
|
-
* growing it: only the small per-site data is duplicated.
|
|
125
|
-
* ------------------------------------------------------------------ */
|
|
126
|
-
|
|
127
|
-
export type SharedHelper = 'unpack' | 'verify' | 'trie' | 'lenswitch' | 'firstchar' | 'phash'
|
|
128
|
-
|
|
129
|
-
export function sharedHelperDecl(kind: SharedHelper, n: (h: SharedHelper) => string): string {
|
|
130
|
-
switch (kind) {
|
|
131
|
-
case 'unpack':
|
|
132
|
-
return `const ${n('unpack')} = s => { const o = new Int32Array(s.length >> 1); for (let i = 0; i < o.length; i++) o[i] = (s.charCodeAt(i * 2) - 35) | ((s.charCodeAt(i * 2 + 1) - 35) << 6); return o }`
|
|
133
|
-
case 'verify':
|
|
134
|
-
return [
|
|
135
|
-
`function ${n('verify')}(input, from, to, keys, ko, kl) {`,
|
|
136
|
-
` if (to - from !== kl) return false`,
|
|
137
|
-
` for (let i = 0; i < kl; i++) { const c = input.charCodeAt(from + i); if (${foldExpr('c')} !== keys.charCodeAt(ko + i)) return false }`,
|
|
138
|
-
` return true`,
|
|
139
|
-
`}`,
|
|
140
|
-
].join('\n')
|
|
141
|
-
case 'trie':
|
|
142
|
-
return [
|
|
143
|
-
`function ${n('trie')}(input, from, to, ch, st, nx, ac) {`,
|
|
144
|
-
` let node = 0`,
|
|
145
|
-
` for (let i = from; i < to; i++) {`,
|
|
146
|
-
` const c0 = input.charCodeAt(i), c = ${foldExpr('c0')}`,
|
|
147
|
-
` let t = -1`,
|
|
148
|
-
` for (let e = st[node], z = st[node + 1]; e < z; e++) if (ch.charCodeAt(e) === c) { t = nx[e]; break }`,
|
|
149
|
-
` if (t < 0) return 0`,
|
|
150
|
-
` node = t`,
|
|
151
|
-
` }`,
|
|
152
|
-
` return ac[node]`,
|
|
153
|
-
`}`,
|
|
154
|
-
].join('\n')
|
|
155
|
-
case 'lenswitch':
|
|
156
|
-
return [
|
|
157
|
-
`function ${n('lenswitch')}(input, from, to, lens, cix, keys, kos, kls) {`,
|
|
158
|
-
` const len = to - from`,
|
|
159
|
-
` for (let i = 0; i < lens.length; i++) {`,
|
|
160
|
-
` if (lens[i] !== len) continue`,
|
|
161
|
-
` if (${n('verify')}(input, from, to, keys, kos[i], kls[i])) return cix[i]`,
|
|
162
|
-
` }`,
|
|
163
|
-
` return 0`,
|
|
164
|
-
`}`,
|
|
165
|
-
].join('\n')
|
|
166
|
-
case 'firstchar':
|
|
167
|
-
return [
|
|
168
|
-
`function ${n('firstchar')}(input, from, to, tab, cix, keys, kos, kls) {`,
|
|
169
|
-
` const c0 = input.charCodeAt(from), c = ${foldExpr('c0')}`,
|
|
170
|
-
` if (c > 127) return 0`,
|
|
171
|
-
` let i = tab[c]`,
|
|
172
|
-
` if (i === 0) return 0`,
|
|
173
|
-
` i--`,
|
|
174
|
-
` return ${n('verify')}(input, from, to, keys, kos[i], kls[i]) ? cix[i] : 0`,
|
|
175
|
-
`}`,
|
|
176
|
-
].join('\n')
|
|
177
|
-
case 'phash':
|
|
178
|
-
return [
|
|
179
|
-
`function ${n('phash')}(input, from, to, p1, p2, mul, mod, tab, cix, keys, kos, kls) {`,
|
|
180
|
-
` const len = to - from`,
|
|
181
|
-
` let a = 0, b = 0`,
|
|
182
|
-
` if (p1 < len) { const c = input.charCodeAt(from + p1); a = ${foldExpr('c')} }`,
|
|
183
|
-
` if (p2 < len) { const c = input.charCodeAt(from + p2); b = ${foldExpr('c')} }`,
|
|
184
|
-
` let i = tab[(len * mul + a * 31 + b) % mod]`,
|
|
185
|
-
` if (i === 0) return 0`,
|
|
186
|
-
` i--`,
|
|
187
|
-
` return ${n('verify')}(input, from, to, keys, kos[i], kls[i]) ? cix[i] : 0`,
|
|
188
|
-
`}`,
|
|
189
|
-
].join('\n')
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
/** Per-site emission: declarations to hoist plus the call expression. */
|
|
194
|
-
export type SiteEmission = {
|
|
195
|
-
strategy: IdStrategy
|
|
196
|
-
decls: string[]
|
|
197
|
-
callExpr: string
|
|
198
|
-
helpers: SharedHelper[]
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
/** Packed key text plus offset/length/case tables, shared by every verify strategy. */
|
|
202
|
-
function packKeys(prefix: string, unpack: string, flat: FlatKey[]): {
|
|
203
|
-
decls: string[]; kos: string; kls: string; cix: string; keys: string
|
|
204
|
-
} {
|
|
205
|
-
let text = ''
|
|
206
|
-
const kos: number[] = []
|
|
207
|
-
const kls: number[] = []
|
|
208
|
-
const cix: number[] = []
|
|
209
|
-
for (const f of flat) {
|
|
210
|
-
kos.push(text.length)
|
|
211
|
-
kls.push(f.text.length)
|
|
212
|
-
cix.push(f.caseIndex)
|
|
213
|
-
text += f.text
|
|
214
|
-
}
|
|
215
|
-
return {
|
|
216
|
-
decls: [
|
|
217
|
-
`const ${prefix}k = ${JSON.stringify(text)}`,
|
|
218
|
-
`const ${prefix}o = ${unpack}(${packInts(kos)})`,
|
|
219
|
-
`const ${prefix}l = ${unpack}(${packInts(kls)})`,
|
|
220
|
-
`const ${prefix}x = ${unpack}(${packInts(cix)})`,
|
|
221
|
-
],
|
|
222
|
-
keys: `${prefix}k`, kos: `${prefix}o`, kls: `${prefix}l`, cix: `${prefix}x`,
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
function emitTrie(prefix: string, unpack: string, helper: string, flat: FlatKey[], pos: string, end: string): SiteEmission | null {
|
|
227
|
-
type Node = { edges: Map<number, number>; accept: number }
|
|
228
|
-
const nodes: Node[] = [{ edges: new Map(), accept: 0 }]
|
|
229
|
-
for (const f of flat) {
|
|
230
|
-
let n = 0
|
|
231
|
-
for (let i = 0; i < f.text.length; i++) {
|
|
232
|
-
const code = f.text.charCodeAt(i)
|
|
233
|
-
let t = nodes[n]!.edges.get(code)
|
|
234
|
-
if (t === undefined) { t = nodes.length; nodes.push({ edges: new Map(), accept: 0 }); nodes[n]!.edges.set(code, t) }
|
|
235
|
-
n = t
|
|
236
|
-
}
|
|
237
|
-
if (nodes[n]!.accept !== 0 && nodes[n]!.accept !== f.caseIndex) return null
|
|
238
|
-
nodes[n]!.accept = f.caseIndex
|
|
239
|
-
}
|
|
240
|
-
const start: number[] = [0]; const char: number[] = []; const next: number[] = []; const accept: number[] = []
|
|
241
|
-
for (const nd of nodes) {
|
|
242
|
-
for (const [c, t] of [...nd.edges.entries()].sort((a, b) => a[0] - b[0])) { char.push(c); next.push(t) }
|
|
243
|
-
start.push(char.length); accept.push(nd.accept)
|
|
244
|
-
}
|
|
245
|
-
return {
|
|
246
|
-
strategy: 'trie',
|
|
247
|
-
decls: [
|
|
248
|
-
`const ${prefix}c = ${JSON.stringify(char.map(c => String.fromCharCode(c)).join(''))}`,
|
|
249
|
-
`const ${prefix}s = ${unpack}(${packInts(start)})`,
|
|
250
|
-
`const ${prefix}n = ${unpack}(${packInts(next)})`,
|
|
251
|
-
`const ${prefix}a = ${unpack}(${packInts(accept)})`,
|
|
252
|
-
],
|
|
253
|
-
callExpr: `${helper}(input, ${pos}, ${end}, ${prefix}c, ${prefix}s, ${prefix}n, ${prefix}a)`,
|
|
254
|
-
helpers: ['unpack', 'trie'],
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
function emitLenSwitch(prefix: string, unpack: string, helper: string, flat: FlatKey[], pos: string, end: string): SiteEmission | null {
|
|
259
|
-
// Only worth it when length is close to decisive: at most one collision bucket.
|
|
260
|
-
const byLen = new Map<number, number>()
|
|
261
|
-
for (const f of flat) byLen.set(f.text.length, (byLen.get(f.text.length) ?? 0) + 1)
|
|
262
|
-
if ([...byLen.values()].filter(v => v > 1).length > 1) return null
|
|
263
|
-
const packed = packKeys(prefix, unpack, flat)
|
|
264
|
-
return {
|
|
265
|
-
strategy: 'lenswitch',
|
|
266
|
-
decls: [...packed.decls, `const ${prefix}n = ${unpack}(${packInts(flat.map(f => f.text.length))})`],
|
|
267
|
-
callExpr: `${helper}(input, ${pos}, ${end}, ${prefix}n, ${packed.cix}, ${packed.keys}, ${packed.kos}, ${packed.kls})`,
|
|
268
|
-
helpers: ['unpack', 'verify', 'lenswitch'],
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
function emitFirstChar(prefix: string, unpack: string, helper: string, flat: FlatKey[], pos: string, end: string): SiteEmission | null {
|
|
273
|
-
const tab = new Array<number>(128).fill(0)
|
|
274
|
-
for (const [i, f] of flat.entries()) {
|
|
275
|
-
const c = f.text.charCodeAt(0)
|
|
276
|
-
// Requires the key set to SEPARATE on char 0 — that is the whole premise.
|
|
277
|
-
if (c > 127 || tab[c] !== 0) return null
|
|
278
|
-
tab[c] = i + 1
|
|
279
|
-
}
|
|
280
|
-
const packed = packKeys(prefix, unpack, flat)
|
|
281
|
-
return {
|
|
282
|
-
strategy: 'firstchar',
|
|
283
|
-
decls: [...packed.decls, `const ${prefix}t = ${unpack}(${packInts(tab)})`],
|
|
284
|
-
callExpr: `${helper}(input, ${pos}, ${end}, ${prefix}t, ${packed.cix}, ${packed.keys}, ${packed.kos}, ${packed.kls})`,
|
|
285
|
-
helpers: ['unpack', 'verify', 'firstchar'],
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
/**
|
|
290
|
-
* gperf-style SEARCH for an injective hash over the ACTUAL key set. A FIXED
|
|
291
|
-
* discriminator provably fails — `(len,c1,c2)` collides on
|
|
292
|
-
* `@counter-style`/`@color-profile` and `@font-feature-values`/
|
|
293
|
-
* `@font-palette-values` — but that says nothing about a SEARCHED one, which is
|
|
294
|
-
* what this does: try every character-position pair against a set of
|
|
295
|
-
* multipliers and moduli and keep the first combination with no collisions.
|
|
296
|
-
*/
|
|
297
|
-
function emitPHash(prefix: string, unpack: string, helper: string, flat: FlatKey[], pos: string, end: string): SiteEmission | null {
|
|
298
|
-
const maxLen = Math.max(...flat.map(f => f.text.length))
|
|
299
|
-
const at = (f: FlatKey, p: number): number => (p < f.text.length ? f.text.charCodeAt(p) : 0)
|
|
300
|
-
for (let p1 = 0; p1 < maxLen; p1++) {
|
|
301
|
-
for (let p2 = p1; p2 < maxLen; p2++) {
|
|
302
|
-
for (const mul of [1, 3, 7, 11, 17, 31, 53]) {
|
|
303
|
-
for (let mod = flat.length; mod <= flat.length * 6 + 16; mod++) {
|
|
304
|
-
const slots = new Array<number>(mod).fill(0)
|
|
305
|
-
let ok = true
|
|
306
|
-
for (const [i, f] of flat.entries()) {
|
|
307
|
-
const h = (f.text.length * mul + at(f, p1) * 31 + at(f, p2)) % mod
|
|
308
|
-
if (slots[h] !== 0) { ok = false; break }
|
|
309
|
-
slots[h] = i + 1
|
|
310
|
-
}
|
|
311
|
-
if (!ok) continue
|
|
312
|
-
const packed = packKeys(prefix, unpack, flat)
|
|
313
|
-
return {
|
|
314
|
-
strategy: 'phash',
|
|
315
|
-
decls: [...packed.decls, `const ${prefix}t = ${unpack}(${packInts(slots)})`],
|
|
316
|
-
callExpr: `${helper}(input, ${pos}, ${end}, ${p1}, ${p2}, ${mul}, ${mod}, ${prefix}t, ${packed.cix}, ${packed.keys}, ${packed.kos}, ${packed.kls})`,
|
|
317
|
-
helpers: ['unpack', 'verify', 'phash'],
|
|
318
|
-
}
|
|
319
|
-
}
|
|
320
|
-
}
|
|
321
|
-
}
|
|
322
|
-
}
|
|
323
|
-
return null
|
|
324
|
-
}
|
|
325
|
-
|
|
326
45
|
/**
|
|
327
|
-
*
|
|
328
|
-
* at macro time rather than applying one strategy everywhere: separation on
|
|
329
|
-
* char 0 is the cheapest test there is, a searched perfect hash next, then
|
|
330
|
-
* length, with the trie as the always-applicable fallback.
|
|
46
|
+
* The decoder for `packInts`, emitted once per artifact under `name`.
|
|
331
47
|
*/
|
|
332
|
-
export function
|
|
333
|
-
|
|
334
|
-
cfg: DispatchConfig,
|
|
335
|
-
prefix: string,
|
|
336
|
-
helperName: (h: SharedHelper) => string,
|
|
337
|
-
pos: string,
|
|
338
|
-
end: string,
|
|
339
|
-
): SiteEmission | null {
|
|
340
|
-
const flat = flatten(cases)
|
|
341
|
-
if (flat === null) return null
|
|
342
|
-
const unpack = helperName('unpack')
|
|
343
|
-
const order: IdStrategy[] = cfg.id === 'auto' ? ['firstchar', 'phash', 'lenswitch', 'trie'] : [cfg.id]
|
|
344
|
-
for (const s of order) {
|
|
345
|
-
const built =
|
|
346
|
-
s === 'trie' ? emitTrie(prefix, unpack, helperName('trie'), flat, pos, end)
|
|
347
|
-
: s === 'lenswitch' ? emitLenSwitch(prefix, unpack, helperName('lenswitch'), flat, pos, end)
|
|
348
|
-
: s === 'firstchar' ? emitFirstChar(prefix, unpack, helperName('firstchar'), flat, pos, end)
|
|
349
|
-
: emitPHash(prefix, unpack, helperName('phash'), flat, pos, end)
|
|
350
|
-
if (built !== null) return built
|
|
351
|
-
}
|
|
352
|
-
return null
|
|
48
|
+
export function unpackHelperDecl(name: string): string {
|
|
49
|
+
return `const ${name} = s => { const o = new Int32Array(s.length >> 1); for (let i = 0; i < o.length; i++) o[i] = (s.charCodeAt(i * 2) - 35) | ((s.charCodeAt(i * 2 + 1) - 35) << 6); return o }`
|
|
353
50
|
}
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
import type { Alphabet } from './token-alphabet.ts'
|
|
36
36
|
import { TOK_EOF, TOK_UNKNOWN, TOK_WS } from './token-alphabet.ts'
|
|
37
37
|
import { firstSetFromRegex } from '../regex/first-set.ts'
|
|
38
|
-
import { packInts,
|
|
38
|
+
import { packInts, unpackHelperDecl } from './token-dispatch.ts'
|
|
39
39
|
import { foldCode, foldExpr } from './token-dispatch.ts'
|
|
40
40
|
|
|
41
41
|
/**
|
|
@@ -193,7 +193,7 @@ export function emitScanner(alphabet: Alphabet, ns: string): ScannerEmission {
|
|
|
193
193
|
// two halves of one encoding still lived in two files. They agree today — the
|
|
194
194
|
// point is that widening the encoding (which the `RangeError` tells you to do)
|
|
195
195
|
// must not be able to change one half and miss the other.
|
|
196
|
-
decls.push(
|
|
196
|
+
decls.push(unpackHelperDecl(p('_tkUnpack')))
|
|
197
197
|
decls.push(`const ${p('_tkNext_')} = ${p('_tkUnpack')}(${packInts(NEXT)})`)
|
|
198
198
|
decls.push(`const ${p('_tkStart')} = ${p('_tkUnpack')}(${packInts(START)})`)
|
|
199
199
|
decls.push(`const ${p('_tkAccept')} = ${p('_tkUnpack')}(${packInts(ACCEPT)})`)
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { ParseContext, Span } from '../types.ts'
|
|
2
|
-
import { annotateSpanFromLineContext } from '../line-index.ts'
|
|
2
|
+
import { annotateSpanFromLineContext, lineSpanFromContext } from '../line-index.ts'
|
|
3
|
+
import { PlainBuf, PlainLeaf } from './plain.js'
|
|
3
4
|
|
|
4
5
|
/** Lazy per-node capture state — arrays materialized on first push. */
|
|
5
6
|
export type CstCaptureBuf = {
|
|
@@ -42,7 +43,7 @@ export function beginCstNodeCapture(ctx: ParseContext): CstNodeCaptureSaved {
|
|
|
42
43
|
cap: ctx.captureTrivia,
|
|
43
44
|
buf: ctx._cstBuf,
|
|
44
45
|
}
|
|
45
|
-
ctx._cstBuf =
|
|
46
|
+
ctx._cstBuf = new PlainBuf(undefined, undefined, 0)
|
|
46
47
|
ctx._cstChildren = undefined
|
|
47
48
|
ctx._cstLeaves = undefined
|
|
48
49
|
ctx._cstRawChildren = undefined
|
|
@@ -79,14 +80,21 @@ export function endCstNodeCapture(
|
|
|
79
80
|
return materialized
|
|
80
81
|
}
|
|
81
82
|
|
|
83
|
+
/** Every caller passes a leaf `{ _tag, value, span }` (parseman's own terminals; this is not public API). */
|
|
84
|
+
function isLeaf(v: unknown): v is { _tag: 'leaf'; value: unknown; span: Span } {
|
|
85
|
+
return typeof v === 'object' && v !== null && (v as { _tag?: unknown })._tag === 'leaf'
|
|
86
|
+
}
|
|
87
|
+
|
|
82
88
|
export function pushCstLeaf(ctx: ParseContext, leaf: unknown): void {
|
|
83
89
|
if (!ctx.trackLines) {
|
|
84
90
|
pushCstChild(ctx, leaf, leaf)
|
|
85
91
|
return
|
|
86
92
|
}
|
|
87
|
-
const annotated =
|
|
88
|
-
?
|
|
89
|
-
: leaf
|
|
93
|
+
const annotated = isLeaf(leaf)
|
|
94
|
+
? new PlainLeaf(leaf.value, lineSpanFromContext(leaf.span, ctx))
|
|
95
|
+
: leaf && typeof leaf === 'object' && (leaf as { span?: unknown }).span
|
|
96
|
+
? { ...(leaf as Record<string, unknown>), span: annotateSpanFromLineContext((leaf as { span: Span }).span, ctx) }
|
|
97
|
+
: leaf
|
|
90
98
|
pushCstChild(ctx, annotated, annotated)
|
|
91
99
|
}
|
|
92
100
|
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The objects a parse keeps until it ends — spans, leaves, default nodes, field
|
|
3
|
+
* captures, capture buffers — built by `new` calls instead of object literals.
|
|
4
|
+
*
|
|
5
|
+
* WHY. V8 keeps an allocation site per object literal and decides, per site, whether to
|
|
6
|
+
* allocate straight into old space (pretenuring). The decision is taken during warm-up
|
|
7
|
+
* from where scavenges happen to fall, so identical processes split into a fast and a
|
|
8
|
+
* slow mode, and any change in bundle layout re-rolls it: on Node 26 a pretenured
|
|
9
|
+
* process ran 34–61 mark-compacts per run where a fast one ran 1. A `new` call has no
|
|
10
|
+
* allocation site, so these objects are never pretenured. Each constructor's
|
|
11
|
+
* `prototype` is `Object.prototype`, so what it builds is a plain object: same
|
|
12
|
+
* prototype, same `constructor`, same keys in the same order as the literal it replaces.
|
|
13
|
+
*
|
|
14
|
+
* ONE MODULE. Every constructor defines its own hidden class, so two copies of this
|
|
15
|
+
* module build two maps for the same shape, and every site that reads both turns
|
|
16
|
+
* polymorphic. The build therefore emits it once (`dist/cst/plain.js`) and every public
|
|
17
|
+
* entry imports that file (`scripts/build.mjs`). The emitted engine receives the same
|
|
18
|
+
* functions as factory parameters (`EMITTED_PARAMS`).
|
|
19
|
+
*/
|
|
20
|
+
import type { Span } from '../types.ts'
|
|
21
|
+
import type { CstCaptureBuf } from './capture-buffer.ts'
|
|
22
|
+
|
|
23
|
+
export type PlainLeafObject = { _tag: 'leaf'; value: unknown; span: Span }
|
|
24
|
+
export type PlainNodeObject = { _tag: 'node'; type: string; span: Span; state: unknown; children: unknown[] }
|
|
25
|
+
export type PlainFieldObject = { name: string; value: unknown; span: Span }
|
|
26
|
+
|
|
27
|
+
export declare const PlainSpan: new (start: number, end: number) => Span
|
|
28
|
+
export declare const PlainLeaf: new (value: unknown, span: Span) => PlainLeafObject
|
|
29
|
+
export type PlainLineSpanObject = { start: number; end: number; startLine: number; startColumn: number; endLine: number; endColumn: number }
|
|
30
|
+
export declare const PlainLineSpan: new (start: number, end: number, startLine: number, startColumn: number, endLine: number, endColumn: number) => PlainLineSpanObject
|
|
31
|
+
export declare const PlainTaggedNode: new (type: string, tags: readonly string[], span: Span, state: unknown, children: unknown[]) => { _tag: 'node'; type: string; tags: readonly string[]; span: Span; state: unknown; children: unknown[] }
|
|
32
|
+
export declare const PlainTriviaField: new (name: string, value: unknown, span: Span, leading: string, trailing: string) => { name: string; value: unknown; span: Span; leading: string; trailing: string }
|
|
33
|
+
export declare const PlainNode: new (type: string, span: Span, state: unknown, children: unknown[]) => PlainNodeObject
|
|
34
|
+
export declare const PlainField: new (name: string, value: unknown, span: Span) => PlainFieldObject
|
|
35
|
+
export declare const PlainBuf: new (rawOnly: true | undefined, noRaw: true | undefined, rawLen: number) => CstCaptureBuf
|
package/src/cst/plain.js
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
// Constructors for the objects a parse keeps until it ends. Plain JavaScript on
|
|
2
|
+
// purpose: a function constructor whose `prototype` is `Object.prototype` is the only
|
|
3
|
+
// spelling that builds a plain object with a `new` call, and TypeScript cannot declare
|
|
4
|
+
// one in a `.ts` file without a cast. `plain.d.ts` types them. Why constructors at all,
|
|
5
|
+
// and why this is ONE module shared by every bundle: see `plain.d.ts`.
|
|
6
|
+
|
|
7
|
+
export function PlainSpan(start, end) {
|
|
8
|
+
this.start = start
|
|
9
|
+
this.end = end
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function PlainLeaf(value, span) {
|
|
13
|
+
this._tag = 'leaf'
|
|
14
|
+
this.value = value
|
|
15
|
+
this.span = span
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function PlainLineSpan(start, end, startLine, startColumn, endLine, endColumn) {
|
|
19
|
+
this.start = start
|
|
20
|
+
this.end = end
|
|
21
|
+
this.startLine = startLine
|
|
22
|
+
this.startColumn = startColumn
|
|
23
|
+
this.endLine = endLine
|
|
24
|
+
this.endColumn = endColumn
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function PlainNode(type, span, state, children) {
|
|
28
|
+
this._tag = 'node'
|
|
29
|
+
this.type = type
|
|
30
|
+
this.span = span
|
|
31
|
+
this.state = state
|
|
32
|
+
this.children = children
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function PlainTaggedNode(type, tags, span, state, children) {
|
|
36
|
+
this._tag = 'node'
|
|
37
|
+
this.type = type
|
|
38
|
+
this.tags = tags
|
|
39
|
+
this.span = span
|
|
40
|
+
this.state = state
|
|
41
|
+
this.children = children
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function PlainField(name, value, span) {
|
|
45
|
+
this.name = name
|
|
46
|
+
this.value = value
|
|
47
|
+
this.span = span
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function PlainTriviaField(name, value, span, leading, trailing) {
|
|
51
|
+
this.name = name
|
|
52
|
+
this.value = value
|
|
53
|
+
this.span = span
|
|
54
|
+
this.leading = leading
|
|
55
|
+
this.trailing = trailing
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Every field assigned, so the three buffer kinds share one shape. `rawLen` is always a
|
|
59
|
+
// count: a field that held `undefined` in most buffers and a number in some would
|
|
60
|
+
// generalize and deoptimize the pushes that read it.
|
|
61
|
+
export function PlainBuf(rawOnly, noRaw, rawLen) {
|
|
62
|
+
this.rawOnly = rawOnly
|
|
63
|
+
this.noRaw = noRaw
|
|
64
|
+
this.rawLen = rawLen
|
|
65
|
+
this.single = undefined
|
|
66
|
+
this.ch = undefined
|
|
67
|
+
this.rawSingle = undefined
|
|
68
|
+
this.raw = undefined
|
|
69
|
+
this.tl = undefined
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
for (const f of [PlainSpan, PlainLineSpan, PlainLeaf, PlainNode, PlainTaggedNode, PlainField, PlainTriviaField, PlainBuf]) {
|
|
73
|
+
f.prototype = Object.prototype
|
|
74
|
+
}
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
|
|
15
15
|
/** The message `parser()` raises for an unclassified local scope. Exported so the
|
|
16
16
|
* three engines cannot drift a word apart. */
|
|
17
|
-
|
|
17
|
+
const ROOT_TRIVIA_SCOPE_REFUSAL
|
|
18
18
|
= 'parser(): selected root trivia requires classifiedTrivia() for every local trivia scope, or rootCapture: \'opaque\'.'
|
|
19
19
|
|
|
20
20
|
/**
|
|
@@ -61,7 +61,7 @@ export type RootTriviaIndex = {
|
|
|
61
61
|
const EMPTY_INDICES: readonly number[] = Object.freeze([])
|
|
62
62
|
const EMPTY_GAPS: readonly RootTriviaGap[] = Object.freeze([])
|
|
63
63
|
/** Number of packed fields in one selected-root trivia row. */
|
|
64
|
-
|
|
64
|
+
const SELECTED_ROOT_STRIDE = 5
|
|
65
65
|
|
|
66
66
|
type RootTriviaMaps = {
|
|
67
67
|
before: Map<number, number[]>
|
package/src/cst/trivia-kinds.ts
CHANGED
|
@@ -60,7 +60,7 @@ function unwrapTrivia(p: Combinator<unknown>): Combinator<unknown> {
|
|
|
60
60
|
}
|
|
61
61
|
|
|
62
62
|
/** Strip `label()` wrapper; returns inner parser for matching. */
|
|
63
|
-
|
|
63
|
+
function peelLabel(p: Combinator<unknown>): { label: string; parser: Combinator<unknown> } | null {
|
|
64
64
|
if (p._def.tag === 'label') return { label: p._def.label, parser: p._def.parser }
|
|
65
65
|
return null
|
|
66
66
|
}
|
package/src/line-index.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Span } from './types.ts'
|
|
2
|
+
import { PlainLineSpan, PlainSpan } from './cst/plain.js'
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* Precomputed index of newline positions for O(log n) offset->line/col lookup.
|
|
@@ -98,6 +99,42 @@ export function annotateSpan(span: Span, index: LineIndex): Span {
|
|
|
98
99
|
}
|
|
99
100
|
}
|
|
100
101
|
|
|
102
|
+
const SPAN_KEYS = ['start', 'end', 'startLine', 'startColumn', 'endLine', 'endColumn'] as const
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* A copy of a span, exactly as given: the same keys in the same order. The two shapes
|
|
106
|
+
* parseman builds itself, `{ start, end }` and the six-key line span, are rebuilt by their
|
|
107
|
+
* constructors (`src/cst/plain.d.ts`); any other shape a caller passes (an extra key, a
|
|
108
|
+
* different order, a partial line span) is spread, as before.
|
|
109
|
+
*/
|
|
110
|
+
export function copySpan(span: Span): Span {
|
|
111
|
+
// `for…in` walks the keys in order without allocating a key list.
|
|
112
|
+
let n = 0
|
|
113
|
+
for (const key in span) {
|
|
114
|
+
if (key !== SPAN_KEYS[n]) return { ...span }
|
|
115
|
+
n++
|
|
116
|
+
}
|
|
117
|
+
if (n === 2) return new PlainSpan(span.start, span.end)
|
|
118
|
+
if (n === 6 && span.startLine !== undefined && span.startColumn !== undefined && span.endLine !== undefined && span.endColumn !== undefined) {
|
|
119
|
+
return new PlainLineSpan(span.start, span.end, span.startLine, span.startColumn, span.endLine, span.endColumn)
|
|
120
|
+
}
|
|
121
|
+
return { ...span }
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* `annotateSpanFromLineContext` for a span parseman built itself (`{ start, end }` only),
|
|
126
|
+
* returning a constructor-built line span (`src/cst/plain.d.ts`). The public form keeps
|
|
127
|
+
* any extra keys a caller's span carries, so it spreads.
|
|
128
|
+
*/
|
|
129
|
+
export function lineSpanFromContext(span: Span, ctx: LineTrackingContext): Span {
|
|
130
|
+
if (!ctx.trackLines) return span
|
|
131
|
+
const index = ctx._lineIndex ?? (ctx._lineStarts ? { lineStarts: ctx._lineStarts } : undefined)
|
|
132
|
+
if (!index) return span
|
|
133
|
+
const s = offsetToLineCol(index, span.start)
|
|
134
|
+
const e = offsetToLineCol(index, span.end)
|
|
135
|
+
return new PlainLineSpan(span.start, span.end, s.line, s.col, e.line, e.col)
|
|
136
|
+
}
|
|
137
|
+
|
|
101
138
|
export function annotateSpanFromLineContext(
|
|
102
139
|
span: Span,
|
|
103
140
|
ctx: LineTrackingContext,
|
|
@@ -12,6 +12,7 @@ import { OP_ATTEMPT, OP_NODE, OP_NODE_TRACK, OP_REPV, OP_SCAN } from '../table/o
|
|
|
12
12
|
import { emitAssemblySource, type EmitResult } from '../table/emit-assembly.ts'
|
|
13
13
|
import { defaultAssemblyCfgs } from '../table/emit.ts'
|
|
14
14
|
import { assemblyExtraIps, resolveTable, type TableProgram } from '../table/program.ts'
|
|
15
|
+
import { FACTORY_ARITY_GUARD } from '../table/emit.ts'
|
|
15
16
|
|
|
16
17
|
type Ast = { type: string; start?: number; end?: number; [key: string]: unknown }
|
|
17
18
|
|
|
@@ -271,8 +272,8 @@ export function supercompileRuleMapReplacement(
|
|
|
271
272
|
if (typeof body.start !== 'number' || typeof body.end !== 'number') return replacement
|
|
272
273
|
const start = body.start - prefix.length + 1
|
|
273
274
|
const end = body.end - prefix.length - 1
|
|
274
|
-
if (replacement.slice(start, end) !== canonical.source + '\n') return replacement
|
|
275
|
-
const transformed = replacement.slice(0, start) + candidate.source + '\n' + replacement.slice(end)
|
|
275
|
+
if (replacement.slice(start, end) !== FACTORY_ARITY_GUARD + canonical.source + '\n') return replacement
|
|
276
|
+
const transformed = replacement.slice(0, start) + FACTORY_ARITY_GUARD + candidate.source + '\n' + replacement.slice(end)
|
|
276
277
|
const checked = parseSync('parseman-entry-supercompile-replacement-result.js', prefix + transformed)
|
|
277
278
|
return checked.errors.length === 0 ? transformed : replacement
|
|
278
279
|
}
|
package/src/recovery/scan.ts
CHANGED
|
@@ -2,6 +2,7 @@ import type { Combinator, FirstSet, ParseContext, ParseError, ParseResult, Recov
|
|
|
2
2
|
import { cstCaptureActive, pushCstChild } from '../cst/capture-buffer.ts'
|
|
3
3
|
import { rangesHaveLead } from '../lead-ranges.ts'
|
|
4
4
|
import { annotateSpanFromLineContext, recordLineRangeFromContext } from '../line-index.ts'
|
|
5
|
+
import { PlainSpan } from '../cst/plain.js'
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* Recovery MECHANISM — sync-source-agnostic. These primitives implement "scan
|
|
@@ -112,7 +113,7 @@ export function recoverScan(
|
|
|
112
113
|
): { error: ParseError; end: number } {
|
|
113
114
|
let scanPos = from
|
|
114
115
|
while (scanPos < input.length && !probeAt(sync, input, scanPos, ctx)) scanPos++
|
|
115
|
-
const span =
|
|
116
|
+
const span = new PlainSpan(from, scanPos)
|
|
116
117
|
if (ctx.trackLines) recordLineRangeFromContext(ctx, input, from, scanPos)
|
|
117
118
|
const error: ParseError = { _tag: 'parseError', span: ctx.trackLines ? annotateSpanFromLineContext(span, ctx) : span, expected }
|
|
118
119
|
ctx._errors?.push(error)
|
package/src/spec/model.ts
CHANGED
|
@@ -247,7 +247,7 @@ function fixedString(alt: string): string | undefined {
|
|
|
247
247
|
* boundary (`word()` / `keywords()` and every hand-written copy of them), and a
|
|
248
248
|
* `\b…\b`-anchored word.
|
|
249
249
|
*/
|
|
250
|
-
|
|
250
|
+
function fixedStringsOfRegex(source: string): string[] | undefined {
|
|
251
251
|
let src = source
|
|
252
252
|
// `\b` anchors assert a boundary, exactly like the `(?![…])` guard.
|
|
253
253
|
if (src.startsWith('\\b')) src = src.slice(2)
|