parseman 0.47.0 → 0.48.0
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/README.md +27 -21
- package/dist/analysis/diagnostics.js +2955 -357
- package/dist/analysis/diagnostics.js.map +3 -3
- package/dist/cli/index.js +2946 -348
- package/dist/cli/index.js.map +3 -3
- package/dist/combinators/expect.d.ts +1 -0
- package/dist/combinators/expect.d.ts.map +1 -1
- package/dist/combinators/expected.d.ts +8 -0
- package/dist/combinators/expected.d.ts.map +1 -0
- package/dist/combinators/first-set.d.ts +8 -0
- package/dist/combinators/first-set.d.ts.map +1 -1
- package/dist/combinators/grammar.d.ts.map +1 -1
- package/dist/combinators/keywords.d.ts +4 -0
- package/dist/combinators/keywords.d.ts.map +1 -1
- package/dist/combinators/literal.d.ts.map +1 -1
- package/dist/combinators/not.d.ts.map +1 -1
- package/dist/combinators/peek.d.ts.map +1 -1
- package/dist/combinators/regex.d.ts.map +1 -1
- package/dist/combinators/repeat.d.ts.map +1 -1
- package/dist/combinators/sequence.d.ts.map +1 -1
- package/dist/combinators/trivia-skip.d.ts +19 -1
- package/dist/combinators/trivia-skip.d.ts.map +1 -1
- package/dist/compiler/direct-projection.d.ts +12 -0
- package/dist/compiler/direct-projection.d.ts.map +1 -0
- package/dist/compiler/token-alphabet.d.ts +845 -5
- package/dist/compiler/token-alphabet.d.ts.map +1 -1
- package/dist/compiler/token-capability.d.ts +3 -0
- package/dist/compiler/token-capability.d.ts.map +1 -0
- package/dist/compiler/token-capability.js +3192 -0
- package/dist/compiler/token-capability.js.map +6 -0
- package/dist/cst/trivia-charscan.d.ts +3 -0
- package/dist/cst/trivia-charscan.d.ts.map +1 -1
- package/dist/cst/trivia-css-scanner.d.ts +5 -0
- package/dist/cst/trivia-css-scanner.d.ts.map +1 -0
- package/dist/cst/trivia-kinds.d.ts.map +1 -1
- package/dist/index.js +2959 -348
- package/dist/index.js.map +3 -3
- package/dist/language-service/index.js +7 -1
- package/dist/language-service/index.js.map +1 -1
- package/dist/plugin/index.js +2969 -358
- package/dist/plugin/index.js.map +3 -3
- package/dist/spec/index.js +366 -49
- package/dist/spec/index.js.map +2 -2
- package/dist/table/assemble.d.ts.map +1 -1
- package/dist/table/child-slots.d.ts.map +1 -1
- package/dist/table/compile-rule-map.d.ts +3 -1
- package/dist/table/compile-rule-map.d.ts.map +1 -1
- package/dist/table/emit-assembly.d.ts +13 -14
- package/dist/table/emit-assembly.d.ts.map +1 -1
- package/dist/table/emit.d.ts.map +1 -1
- package/dist/table/encode.d.ts.map +1 -1
- package/dist/table/exec-baseline.d.ts.map +1 -1
- package/dist/table/exec.d.ts.map +1 -1
- package/dist/table/index.js +4839 -2232
- package/dist/table/index.js.map +3 -3
- package/dist/table/lex-body.d.ts +9 -0
- package/dist/table/lex-body.d.ts.map +1 -0
- package/dist/table/lex-program.d.ts +25 -0
- package/dist/table/lex-program.d.ts.map +1 -0
- package/dist/table/ops.d.ts +47 -4
- package/dist/table/ops.d.ts.map +1 -1
- package/dist/table/program.d.ts +53 -2
- package/dist/table/program.d.ts.map +1 -1
- package/dist/table/scalar-terminal.d.ts +13 -0
- package/dist/table/scalar-terminal.d.ts.map +1 -0
- package/dist/table/site-labels.d.ts.map +1 -1
- package/dist/types.d.ts +2 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/version.d.ts +1 -1
- package/package.json +12 -11
- package/src/combinators/expect.ts +1 -0
- package/src/combinators/expected.ts +17 -0
- package/src/combinators/first-set.ts +82 -3
- package/src/combinators/grammar.ts +8 -3
- package/src/combinators/keywords.ts +22 -6
- package/src/combinators/literal.ts +2 -1
- package/src/combinators/not.ts +5 -1
- package/src/combinators/peek.ts +5 -1
- package/src/combinators/regex.ts +5 -3
- package/src/combinators/repeat.ts +4 -10
- package/src/combinators/sequence.ts +35 -16
- package/src/combinators/trivia-skip.ts +75 -11
- package/src/compiler/direct-projection.ts +43 -0
- package/src/compiler/token-alphabet.ts +4316 -12
- package/src/compiler/token-capability.ts +6 -0
- package/src/cst/trivia-charscan.ts +34 -0
- package/src/cst/trivia-css-scanner.ts +142 -0
- package/src/cst/trivia-kinds.ts +3 -1
- package/src/plugin/index.ts +4 -4
- package/src/table/assemble.ts +1224 -222
- package/src/table/child-slots.ts +4 -3
- package/src/table/compile-rule-map.ts +21 -3
- package/src/table/emit-assembly.ts +363 -109
- package/src/table/emit.ts +26 -1
- package/src/table/encode.ts +246 -54
- package/src/table/exec-baseline.ts +3 -7
- package/src/table/exec.ts +140 -18
- package/src/table/lex-body.ts +30 -0
- package/src/table/lex-program.ts +422 -0
- package/src/table/ops.ts +50 -4
- package/src/table/program.ts +67 -2
- package/src/table/scalar-terminal.ts +51 -0
- package/src/table/site-labels.ts +13 -3
- package/src/table/stamp.ts +3 -3
- package/src/types.ts +1 -1
- package/src/version.ts +1 -1
- package/dist/analysis/diagnostics.cjs +0 -13736
- package/dist/analysis/diagnostics.cjs.map +0 -6
- package/dist/cli/index.cjs +0 -14837
- package/dist/cli/index.cjs.map +0 -6
- package/dist/index.cjs +0 -16303
- package/dist/index.cjs.map +0 -6
- package/dist/language-service/index.cjs +0 -1302
- package/dist/language-service/index.cjs.map +0 -6
- package/dist/oracle/index.cjs +0 -254
- package/dist/oracle/index.cjs.map +0 -6
- package/dist/plugin/index.cjs +0 -16009
- package/dist/plugin/index.cjs.map +0 -6
- package/dist/run/index.cjs +0 -577
- package/dist/run/index.cjs.map +0 -6
- package/dist/spec/index.cjs +0 -6188
- package/dist/spec/index.cjs.map +0 -6
- package/dist/table/index.cjs +0 -11375
- package/dist/table/index.cjs.map +0 -6
|
@@ -0,0 +1,422 @@
|
|
|
1
|
+
import type { Combinator, ParseContext } from '../types.ts'
|
|
2
|
+
import { failAt } from '../combinators/probe.ts'
|
|
3
|
+
import type { LexBodyRecognizer } from './lex-body.ts'
|
|
4
|
+
import type { LexProgramSpec, ResolvedClass } from './program.ts'
|
|
5
|
+
|
|
6
|
+
/** A selected composite lexical body. Failure state is already published.
|
|
7
|
+
* `scan` is present only for the balanced shell and indexes the canonical
|
|
8
|
+
* driver-owned scan pool; it is not another recognizer implementation. */
|
|
9
|
+
export type LexProgramRunner = {
|
|
10
|
+
(input: string, pos: number, ctx: ParseContext, scan?: Combinator<unknown>): number
|
|
11
|
+
readonly scan?: number
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export const LEX_NODE_TERMINAL = 0
|
|
15
|
+
export const LEX_NODE_SEQUENCE2 = 1
|
|
16
|
+
export const LEX_NODE_SEQUENCE3 = 2
|
|
17
|
+
export const LEX_NODE_SEQUENCE4 = 3
|
|
18
|
+
export const LEX_NODE_SEQUENCE5 = 4
|
|
19
|
+
export const LEX_NODE_CHOICE2 = 5
|
|
20
|
+
export const LEX_NODE_CHOICE4 = 6
|
|
21
|
+
export const LEX_NODE_CHOICE8 = 7
|
|
22
|
+
export const LEX_NODE_NOT = 8
|
|
23
|
+
export const LEX_NODE_OPTIONAL = 9
|
|
24
|
+
|
|
25
|
+
export function lexProgramDigest(words: readonly number[]): number {
|
|
26
|
+
let hash = 0x811C9DC5
|
|
27
|
+
for (const word of words) {
|
|
28
|
+
hash ^= word | 0
|
|
29
|
+
hash = Math.imul(hash, 0x01000193)
|
|
30
|
+
}
|
|
31
|
+
return hash | 0
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function classHas(cls: ResolvedClass, code: number): boolean {
|
|
35
|
+
if (code < 0) return false
|
|
36
|
+
if (code < 128) return cls.ascii[code] === 1
|
|
37
|
+
for (let i = 0; i < cls.hi.length; i += 2) {
|
|
38
|
+
if (code >= cls.hi[i]! && code <= cls.hi[i + 1]!) return true
|
|
39
|
+
}
|
|
40
|
+
return false
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
type PureLexRunner = (input: string, pos: number) => number
|
|
44
|
+
type BuiltLexNode = { readonly match: PureLexRunner; readonly run: LexProgramRunner }
|
|
45
|
+
const committed = (ctx: ParseContext): boolean => ctx._fc === true
|
|
46
|
+
|
|
47
|
+
function trackLexLines(ctx: ParseContext, input: string, end: number): void {
|
|
48
|
+
let at = ctx._lineScannedTo ?? 0
|
|
49
|
+
const starts = ctx._lineStarts
|
|
50
|
+
if (starts === undefined || end <= at) return
|
|
51
|
+
while (at < end) {
|
|
52
|
+
if (input.charCodeAt(at) === 10) starts.push(at + 1)
|
|
53
|
+
at++
|
|
54
|
+
}
|
|
55
|
+
ctx._lineScannedTo = end
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function terminalNode(
|
|
59
|
+
matcher: LexBodyRecognizer,
|
|
60
|
+
failure: readonly string[],
|
|
61
|
+
tracksLines: boolean,
|
|
62
|
+
): BuiltLexNode {
|
|
63
|
+
const match: PureLexRunner = (input, pos) => {
|
|
64
|
+
const result = matcher(input, pos)
|
|
65
|
+
return result < 0 ? -1 : result / 2
|
|
66
|
+
}
|
|
67
|
+
return {
|
|
68
|
+
match,
|
|
69
|
+
run: (input, pos, ctx) => {
|
|
70
|
+
const end = match(input, pos)
|
|
71
|
+
if (end < 0) {
|
|
72
|
+
ctx._fe = pos
|
|
73
|
+
ctx._fx = failure as string[]
|
|
74
|
+
if (ctx._probe !== undefined) failAt(ctx, ctx._fx, pos)
|
|
75
|
+
return -1
|
|
76
|
+
}
|
|
77
|
+
if (tracksLines) trackLexLines(ctx, input, end)
|
|
78
|
+
return end
|
|
79
|
+
},
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function sequence2(a: BuiltLexNode, b: BuiltLexNode): BuiltLexNode {
|
|
84
|
+
return {
|
|
85
|
+
match: (input, pos) => {
|
|
86
|
+
const e0 = a.match(input, pos)
|
|
87
|
+
return e0 < 0 ? -1 : b.match(input, e0)
|
|
88
|
+
},
|
|
89
|
+
run: (input, pos, ctx) => {
|
|
90
|
+
const e0 = a.run(input, pos, ctx)
|
|
91
|
+
if (e0 < 0) return -1
|
|
92
|
+
ctx._fc = false
|
|
93
|
+
return b.run(input, e0, ctx)
|
|
94
|
+
},
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function sequence3(a: BuiltLexNode, b: BuiltLexNode, c: BuiltLexNode): BuiltLexNode {
|
|
99
|
+
return {
|
|
100
|
+
match: (input, pos) => {
|
|
101
|
+
const e0 = a.match(input, pos)
|
|
102
|
+
if (e0 < 0) return -1
|
|
103
|
+
const e1 = b.match(input, e0)
|
|
104
|
+
return e1 < 0 ? -1 : c.match(input, e1)
|
|
105
|
+
},
|
|
106
|
+
run: (input, pos, ctx) => {
|
|
107
|
+
const e0 = a.run(input, pos, ctx)
|
|
108
|
+
if (e0 < 0) return -1
|
|
109
|
+
ctx._fc = false
|
|
110
|
+
const e1 = b.run(input, e0, ctx)
|
|
111
|
+
if (e1 < 0) return -1
|
|
112
|
+
ctx._fc = false
|
|
113
|
+
return c.run(input, e1, ctx)
|
|
114
|
+
},
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function sequence4(a: BuiltLexNode, b: BuiltLexNode, c: BuiltLexNode, d: BuiltLexNode): BuiltLexNode {
|
|
119
|
+
const abc = sequence3(a, b, c)
|
|
120
|
+
return {
|
|
121
|
+
match: (input, pos) => {
|
|
122
|
+
const end = abc.match(input, pos)
|
|
123
|
+
return end < 0 ? -1 : d.match(input, end)
|
|
124
|
+
},
|
|
125
|
+
run: (input, pos, ctx) => {
|
|
126
|
+
const end = abc.run(input, pos, ctx)
|
|
127
|
+
if (end < 0) return -1
|
|
128
|
+
ctx._fc = false
|
|
129
|
+
return d.run(input, end, ctx)
|
|
130
|
+
},
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function sequence5(
|
|
135
|
+
a: BuiltLexNode, b: BuiltLexNode, c: BuiltLexNode, d: BuiltLexNode, e: BuiltLexNode,
|
|
136
|
+
): BuiltLexNode {
|
|
137
|
+
const abcd = sequence4(a, b, c, d)
|
|
138
|
+
return {
|
|
139
|
+
match: (input, pos) => {
|
|
140
|
+
const end = abcd.match(input, pos)
|
|
141
|
+
return end < 0 ? -1 : e.match(input, end)
|
|
142
|
+
},
|
|
143
|
+
run: (input, pos, ctx) => {
|
|
144
|
+
const end = abcd.run(input, pos, ctx)
|
|
145
|
+
if (end < 0) return -1
|
|
146
|
+
ctx._fc = false
|
|
147
|
+
return e.run(input, end, ctx)
|
|
148
|
+
},
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
type ChoiceTail = (
|
|
153
|
+
input: string, pos: number, ctx: ParseContext, lead: number, acc: string[] | undefined,
|
|
154
|
+
) => number
|
|
155
|
+
|
|
156
|
+
function appendExpected(acc: string[] | undefined, values: readonly string[] | undefined): string[] | undefined {
|
|
157
|
+
if (values === undefined || values.length === 0) return acc
|
|
158
|
+
if (acc === undefined) return values.slice()
|
|
159
|
+
acc.push(...values)
|
|
160
|
+
return acc
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function choiceNode(
|
|
164
|
+
arms: readonly BuiltLexNode[],
|
|
165
|
+
classes: readonly (ResolvedClass | undefined)[],
|
|
166
|
+
armExpected: readonly (readonly string[])[],
|
|
167
|
+
choiceExpected: readonly string[],
|
|
168
|
+
): BuiltLexNode {
|
|
169
|
+
let matchTail: PureLexRunner = () => -1
|
|
170
|
+
for (let i = arms.length - 1; i >= 0; i--) {
|
|
171
|
+
const arm = arms[i]!, next = matchTail
|
|
172
|
+
matchTail = (input, pos) => {
|
|
173
|
+
const end = arm.match(input, pos)
|
|
174
|
+
return end < 0 ? next(input, pos) : end
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
let tail: ChoiceTail = (_input, pos, ctx, _lead, acc) => {
|
|
178
|
+
ctx._fe = pos
|
|
179
|
+
ctx._fx = acc ?? choiceExpected as string[]
|
|
180
|
+
return -1
|
|
181
|
+
}
|
|
182
|
+
for (let i = arms.length - 1; i >= 0; i--) {
|
|
183
|
+
const arm = arms[i]!, cls = classes[i], staticExpected = armExpected[i]!, next = tail
|
|
184
|
+
tail = (input, pos, ctx, lead, acc) => {
|
|
185
|
+
if (cls !== undefined && !classHas(cls, lead)) {
|
|
186
|
+
return next(input, pos, ctx, lead, appendExpected(acc, staticExpected))
|
|
187
|
+
}
|
|
188
|
+
ctx._fc = false
|
|
189
|
+
const end = arm.run(input, pos, ctx)
|
|
190
|
+
if (end >= 0) return end
|
|
191
|
+
const nextAcc = appendExpected(acc, ctx._fx)
|
|
192
|
+
if (committed(ctx)) {
|
|
193
|
+
if (nextAcc !== undefined) ctx._fx = nextAcc
|
|
194
|
+
return -1
|
|
195
|
+
}
|
|
196
|
+
return next(input, pos, ctx, lead, nextAcc)
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
return {
|
|
200
|
+
match: matchTail,
|
|
201
|
+
run: (input, pos, ctx) => tail(input, pos, ctx, pos < input.length ? input.codePointAt(pos)! : -1, undefined),
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function notNode(body: BuiltLexNode, failure: readonly string[]): BuiltLexNode {
|
|
206
|
+
return {
|
|
207
|
+
match: (input, pos) => body.match(input, pos) < 0 ? pos : -1,
|
|
208
|
+
run: (input, pos, ctx) => {
|
|
209
|
+
if (body.match(input, pos) < 0) return pos
|
|
210
|
+
ctx._fe = pos
|
|
211
|
+
ctx._fx = failure as string[]
|
|
212
|
+
return -1
|
|
213
|
+
},
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function optionalNode(body: BuiltLexNode): BuiltLexNode {
|
|
218
|
+
return {
|
|
219
|
+
match: (input, pos) => {
|
|
220
|
+
const end = body.match(input, pos)
|
|
221
|
+
return end < 0 ? pos : end
|
|
222
|
+
},
|
|
223
|
+
run: (input, pos, ctx) => {
|
|
224
|
+
ctx._fc = false
|
|
225
|
+
const end = body.run(input, pos, ctx)
|
|
226
|
+
return end < 0 && !committed(ctx) ? pos : end
|
|
227
|
+
},
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function fixedTreeRunner(
|
|
232
|
+
words: readonly number[],
|
|
233
|
+
matchers: readonly LexBodyRecognizer[],
|
|
234
|
+
classes: readonly ResolvedClass[],
|
|
235
|
+
expected: readonly (readonly string[])[],
|
|
236
|
+
): LexProgramRunner {
|
|
237
|
+
const stack: BuiltLexNode[] = []
|
|
238
|
+
const pop = (): BuiltLexNode => {
|
|
239
|
+
const value = stack.pop()
|
|
240
|
+
if (value === undefined) throw new TypeError('table lexical tree has an incomplete fixed body')
|
|
241
|
+
return value
|
|
242
|
+
}
|
|
243
|
+
for (let at = 0; at < words.length;) {
|
|
244
|
+
const op = words[at++]!
|
|
245
|
+
if (op === LEX_NODE_TERMINAL) {
|
|
246
|
+
const matcher = matchers[words[at++]!], failure = expected[words[at++]!]
|
|
247
|
+
const tracks = words[at++]
|
|
248
|
+
if (matcher === undefined || failure === undefined || (tracks !== 0 && tracks !== 1)) {
|
|
249
|
+
throw new TypeError('table lexical tree terminal references an invalid pool entry')
|
|
250
|
+
}
|
|
251
|
+
stack.push(terminalNode(matcher, failure, tracks === 1))
|
|
252
|
+
continue
|
|
253
|
+
}
|
|
254
|
+
if (op >= LEX_NODE_SEQUENCE2 && op <= LEX_NODE_SEQUENCE5) {
|
|
255
|
+
const count = op - LEX_NODE_SEQUENCE2 + 2
|
|
256
|
+
const nodes = Array.from({ length: count }, pop).reverse()
|
|
257
|
+
if (count === 2) stack.push(sequence2(nodes[0]!, nodes[1]!))
|
|
258
|
+
else if (count === 3) stack.push(sequence3(nodes[0]!, nodes[1]!, nodes[2]!))
|
|
259
|
+
else if (count === 4) stack.push(sequence4(nodes[0]!, nodes[1]!, nodes[2]!, nodes[3]!))
|
|
260
|
+
else stack.push(sequence5(nodes[0]!, nodes[1]!, nodes[2]!, nodes[3]!, nodes[4]!))
|
|
261
|
+
continue
|
|
262
|
+
}
|
|
263
|
+
if (op >= LEX_NODE_CHOICE2 && op <= LEX_NODE_CHOICE8) {
|
|
264
|
+
const count = op === LEX_NODE_CHOICE2 ? 2 : op === LEX_NODE_CHOICE4 ? 4 : 8
|
|
265
|
+
const choiceFx = expected[words[at++]!]
|
|
266
|
+
if (choiceFx === undefined) throw new TypeError('table lexical tree choice has an invalid expected set')
|
|
267
|
+
const armClasses: Array<ResolvedClass | undefined> = []
|
|
268
|
+
const armFx: Array<readonly string[]> = []
|
|
269
|
+
for (let i = 0; i < count; i++) {
|
|
270
|
+
const cls = words[at++]!, fx = expected[words[at++]!]
|
|
271
|
+
if (cls < -1 || (cls >= 0 && classes[cls] === undefined) || fx === undefined) {
|
|
272
|
+
throw new TypeError('table lexical tree choice references an invalid pool entry')
|
|
273
|
+
}
|
|
274
|
+
armClasses.push(cls < 0 ? undefined : classes[cls])
|
|
275
|
+
armFx.push(fx)
|
|
276
|
+
}
|
|
277
|
+
const arms = Array.from({ length: count }, pop).reverse()
|
|
278
|
+
stack.push(choiceNode(arms, armClasses, armFx, choiceFx))
|
|
279
|
+
continue
|
|
280
|
+
}
|
|
281
|
+
if (op === LEX_NODE_NOT) {
|
|
282
|
+
const failure = expected[words[at++]!]
|
|
283
|
+
if (failure === undefined) throw new TypeError('table lexical tree assertion has an invalid expected set')
|
|
284
|
+
stack.push(notNode(pop(), failure))
|
|
285
|
+
continue
|
|
286
|
+
}
|
|
287
|
+
if (op === LEX_NODE_OPTIONAL) { stack.push(optionalNode(pop())); continue }
|
|
288
|
+
throw new TypeError('table lexical tree has an unknown fixed body')
|
|
289
|
+
}
|
|
290
|
+
if (stack.length !== 1) throw new TypeError('table lexical tree does not resolve to one root body')
|
|
291
|
+
return stack[0]!.run
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/** Decode one fixed selected-body tuple. There is no lexical opcode loop on
|
|
295
|
+
* the parse path: each supported tuple becomes one scalar closure here. */
|
|
296
|
+
export function buildLexProgramRunner(
|
|
297
|
+
spec: LexProgramSpec,
|
|
298
|
+
matchers: readonly LexBodyRecognizer[],
|
|
299
|
+
classes: readonly ResolvedClass[],
|
|
300
|
+
expected: readonly (readonly string[])[],
|
|
301
|
+
): LexProgramRunner {
|
|
302
|
+
if ((spec[0] !== 0 && spec[0] !== 1 && spec[0] !== 2 && spec[0] !== 3)
|
|
303
|
+
|| (spec[0] === 0 && spec.length !== 13)
|
|
304
|
+
|| (spec[0] === 1 && spec.length !== 12)
|
|
305
|
+
|| (spec[0] === 2 && spec.length < 6)
|
|
306
|
+
|| (spec[0] === 3 && spec.length !== 3)) {
|
|
307
|
+
throw new TypeError('table lexical program has an invalid fixed body width')
|
|
308
|
+
}
|
|
309
|
+
if (spec[1] !== lexProgramDigest(spec.slice(2))) {
|
|
310
|
+
throw new TypeError('table lexical program semantic digest is inconsistent')
|
|
311
|
+
}
|
|
312
|
+
if (spec[0] === 3) {
|
|
313
|
+
if (!Number.isInteger(spec[2]) || spec[2] < 0) {
|
|
314
|
+
throw new TypeError('table balanced lexical program has an invalid scan reference')
|
|
315
|
+
}
|
|
316
|
+
const run: LexProgramRunner = (_input, _pos, ctx, scan) => {
|
|
317
|
+
if (scan === undefined) throw new TypeError('table balanced lexical program lost its scan body')
|
|
318
|
+
const result = scan.parse(_input, _pos, ctx)
|
|
319
|
+
if (!result.ok) {
|
|
320
|
+
ctx._fe = result.span.start
|
|
321
|
+
ctx._fx = (result.expected ?? []) as string[]
|
|
322
|
+
return -1
|
|
323
|
+
}
|
|
324
|
+
return result.span.end
|
|
325
|
+
}
|
|
326
|
+
return Object.assign(run, { scan: spec[2] })
|
|
327
|
+
}
|
|
328
|
+
if (spec[0] === 2) return fixedTreeRunner(spec.slice(2), matchers, classes, expected)
|
|
329
|
+
if (spec[0] === 1) {
|
|
330
|
+
const g00 = matchers[spec[2]], g01 = matchers[spec[3]]
|
|
331
|
+
const g02 = matchers[spec[4]], g03 = matchers[spec[5]]
|
|
332
|
+
const guard0Fx = expected[spec[6]]
|
|
333
|
+
const g1 = matchers[spec[7]], guard1Fx = expected[spec[8]]
|
|
334
|
+
const terminal = matchers[spec[9]], terminalFx = expected[spec[10]]
|
|
335
|
+
const tracks = spec[11]
|
|
336
|
+
if (g00 === undefined || g01 === undefined || g02 === undefined || g03 === undefined
|
|
337
|
+
|| guard0Fx === undefined || g1 === undefined || guard1Fx === undefined
|
|
338
|
+
|| terminal === undefined || terminalFx === undefined || (tracks !== 0 && tracks !== 1)) {
|
|
339
|
+
throw new TypeError('table lexical program references an invalid pool entry')
|
|
340
|
+
}
|
|
341
|
+
return (input, pos, ctx) => {
|
|
342
|
+
ctx._fc = false
|
|
343
|
+
if (g00(input, pos) >= 0 || g01(input, pos) >= 0
|
|
344
|
+
|| g02(input, pos) >= 0 || g03(input, pos) >= 0) {
|
|
345
|
+
ctx._fe = pos
|
|
346
|
+
ctx._fx = guard0Fx as string[]
|
|
347
|
+
return -1
|
|
348
|
+
}
|
|
349
|
+
ctx._fc = false
|
|
350
|
+
if (g1(input, pos) >= 0) {
|
|
351
|
+
ctx._fe = pos
|
|
352
|
+
ctx._fx = guard1Fx as string[]
|
|
353
|
+
return -1
|
|
354
|
+
}
|
|
355
|
+
ctx._fc = false
|
|
356
|
+
const result = terminal(input, pos)
|
|
357
|
+
if (result < 0) {
|
|
358
|
+
ctx._fe = pos
|
|
359
|
+
ctx._fx = terminalFx as string[]
|
|
360
|
+
if (ctx._probe !== undefined) failAt(ctx, ctx._fx, pos)
|
|
361
|
+
return -1
|
|
362
|
+
}
|
|
363
|
+
const end = result / 2
|
|
364
|
+
if (tracks === 1) trackLexLines(ctx, input, end)
|
|
365
|
+
return end
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
const cls = classes[spec[2]]
|
|
369
|
+
const choiceFx = expected[spec[3]]
|
|
370
|
+
const m0 = matchers[spec[4]], x0 = expected[spec[5]]
|
|
371
|
+
const m1 = matchers[spec[6]], x1 = expected[spec[7]]
|
|
372
|
+
const m2 = matchers[spec[8]], x2 = expected[spec[9]]
|
|
373
|
+
const m3 = matchers[spec[10]], x3 = expected[spec[11]]
|
|
374
|
+
const tracks = spec[12]
|
|
375
|
+
if (cls === undefined || choiceFx === undefined
|
|
376
|
+
|| m0 === undefined || x0 === undefined || m1 === undefined || x1 === undefined
|
|
377
|
+
|| m2 === undefined || x2 === undefined || m3 === undefined || x3 === undefined
|
|
378
|
+
|| (tracks !== 0 && tracks !== 1)) {
|
|
379
|
+
throw new TypeError('table lexical program references an invalid pool entry')
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
return (input, pos, ctx) => {
|
|
383
|
+
const lead = pos < input.length ? input.codePointAt(pos)! : -1
|
|
384
|
+
if (!classHas(cls, lead)) {
|
|
385
|
+
ctx._fe = pos
|
|
386
|
+
ctx._fx = choiceFx.slice()
|
|
387
|
+
return -1
|
|
388
|
+
}
|
|
389
|
+
let acc: string[] | undefined
|
|
390
|
+
const failed = (fx: readonly string[]): void => {
|
|
391
|
+
ctx._fe = pos
|
|
392
|
+
ctx._fx = fx as string[]
|
|
393
|
+
if (ctx._probe !== undefined) failAt(ctx, ctx._fx, pos)
|
|
394
|
+
if (acc === undefined) acc = fx.slice()
|
|
395
|
+
else acc.push(...fx)
|
|
396
|
+
}
|
|
397
|
+
ctx._fc = false
|
|
398
|
+
const success = (result: number): number => {
|
|
399
|
+
const end = result / 2
|
|
400
|
+
if (tracks === 1) trackLexLines(ctx, input, end)
|
|
401
|
+
return end
|
|
402
|
+
}
|
|
403
|
+
let result = m0(input, pos)
|
|
404
|
+
if (result >= 0) return success(result)
|
|
405
|
+
failed(x0)
|
|
406
|
+
ctx._fc = false
|
|
407
|
+
result = m1(input, pos)
|
|
408
|
+
if (result >= 0) return success(result)
|
|
409
|
+
failed(x1)
|
|
410
|
+
ctx._fc = false
|
|
411
|
+
result = m2(input, pos)
|
|
412
|
+
if (result >= 0) return success(result)
|
|
413
|
+
failed(x2)
|
|
414
|
+
ctx._fc = false
|
|
415
|
+
result = m3(input, pos)
|
|
416
|
+
if (result >= 0) return success(result)
|
|
417
|
+
failed(x3)
|
|
418
|
+
ctx._fe = pos
|
|
419
|
+
ctx._fx = acc ?? choiceFx as string[]
|
|
420
|
+
return -1
|
|
421
|
+
}
|
|
422
|
+
}
|
package/src/table/ops.ts
CHANGED
|
@@ -37,8 +37,11 @@ export const OP_SEQV = 4
|
|
|
37
37
|
*/
|
|
38
38
|
export const OP_CHOICE = 5
|
|
39
39
|
/**
|
|
40
|
-
* `REP c min max sep flags` — `sep` is a child offset or −1; `max`
|
|
41
|
-
* `
|
|
40
|
+
* `REP c min max sep flags fx sepClass` — `sep` is a child offset or −1; `max`
|
|
41
|
+
* −1 = ∞. `fx` is recovery metadata. The last class is the separator sentinel
|
|
42
|
+
* for a separated list, otherwise the finite/non-nullable optional-item class
|
|
43
|
+
* (or −1). `flags` bit 0 = trailing separator allowed, bit 1 =
|
|
44
|
+
* `keepSeparators`, bit 2 = item expected reporting.
|
|
42
45
|
*/
|
|
43
46
|
export const OP_REP = 6
|
|
44
47
|
/** `REPV …` — `REP` with `valueUnused`. */
|
|
@@ -57,7 +60,8 @@ export const OP_XFORM = 9
|
|
|
57
60
|
* none), `type` and `tags` index the const pool (`tags` −1 = none).
|
|
58
61
|
*
|
|
59
62
|
* `flags` is a BIT FIELD, not a boolean: bit 0 (`&1`) = grammar-owned explicit
|
|
60
|
-
* `captureTrivia`, bit
|
|
63
|
+
* `captureTrivia`, bit 1 (`&2`) = a direct builder is proven not to read
|
|
64
|
+
* `rawChildren`, bit 2 (`&4`) = the builder reads
|
|
61
65
|
* `triviaLog`, bit 3 (`&8`) = it reads `ctx.state`, bit 4 (`&16`) = the node has
|
|
62
66
|
* read fields, bit 5 (`&32`) = `collapse`, bit 6 (`&64`) = `unwrap`, bit 7
|
|
63
67
|
* (`&128`) = `trailingTrivia`. Bit 0 distinguishes capture a host may not
|
|
@@ -104,12 +108,18 @@ export const OP_LIT_TRACK = 17
|
|
|
104
108
|
export const OP_RX_TRACK = 18
|
|
105
109
|
export const OP_NODE_TRACK = 19
|
|
106
110
|
/**
|
|
107
|
-
* `SCOPE k c` — a `parser({ trivia })`
|
|
111
|
+
* `SCOPE k c policy` — a policy-bearing `parser({ trivia })` scope.
|
|
108
112
|
*
|
|
109
113
|
* `k` is the scope's trivia COMBINATOR in the const pool. The driver installs it
|
|
110
114
|
* on `ctx.trivia` for the duration and restores the outer one after, which is
|
|
111
115
|
* how the runtime's own `advanceTrivia` fast scanner gets reached — the same
|
|
112
116
|
* shared machinery the interpreter uses, not a second copy of it.
|
|
117
|
+
*
|
|
118
|
+
* `policy` bit 0 suppresses selected root capture for an opaque scope; bit 1
|
|
119
|
+
* refuses an unclassified local scope while selected root capture is active.
|
|
120
|
+
* Synthetic rule-entry and cross-rule restoration scopes have no policy and use
|
|
121
|
+
* the three-word `OP_SCOPE_PLAIN` row below. Keeping the opcodes distinct means
|
|
122
|
+
* a zero policy costs no extra program word without changing this opcode's ABI.
|
|
113
123
|
*/
|
|
114
124
|
export const OP_SCOPE = 20
|
|
115
125
|
/**
|
|
@@ -120,6 +130,11 @@ export const OP_EXPECT = 21
|
|
|
120
130
|
/**
|
|
121
131
|
* `SEQX f n c1 … cn` — a `transform()` whose child is a `sequence()`.
|
|
122
132
|
*
|
|
133
|
+
* `f >= 0` indexes the reducer in `prog.fns`. `f < 0` is the descriptor
|
|
134
|
+
* `~childIndex`: the transform is the exact direct projection
|
|
135
|
+
* `([…, value, …]) => value`, so the row returns that already-parsed child and
|
|
136
|
+
* carries no reducer. Both forms have the same row width.
|
|
137
|
+
*
|
|
123
138
|
* That pair is the dominant shape in every grammar here (json is nine of them),
|
|
124
139
|
* and running it as two rows costs two switch dispatches and two JS call frames
|
|
125
140
|
* per rule invocation where the emitted code pays neither. Fusing them into one
|
|
@@ -425,6 +440,34 @@ export const OP_LABEL = 39
|
|
|
425
440
|
* which is the granularity the IDs are minted at.
|
|
426
441
|
*/
|
|
427
442
|
export const OP_COV = 40
|
|
443
|
+
/**
|
|
444
|
+
* `SCOPE_PLAIN k c` — a synthetic zero-policy ambient-trivia scope.
|
|
445
|
+
*
|
|
446
|
+
* This is the same scanner/context swap as `OP_SCOPE`, with policy fixed to
|
|
447
|
+
* zero at assembly. `encodeRule()` uses it for a rule entry and `scopedRef()`
|
|
448
|
+
* for the lexical-trivia restoration around a reference reached from
|
|
449
|
+
* `noTrivia()`. Neither row represents an authored `parser()` scope, so neither
|
|
450
|
+
* can be opaque or require the unclassified-scope refusal.
|
|
451
|
+
*
|
|
452
|
+
* A separate opcode is load-bearing: treating a three-word synthetic row as the
|
|
453
|
+
* four-word `OP_SCOPE` makes the following row's opcode become the policy bits.
|
|
454
|
+
*/
|
|
455
|
+
export const OP_SCOPE_PLAIN = 41
|
|
456
|
+
/**
|
|
457
|
+
* `LEX_BODY b fx suffixFx` — one compiler-selected childless lexical replacement
|
|
458
|
+
* body. `b` indexes `prog.lex`; `fx` is the exact base-recognizer failure set;
|
|
459
|
+
* `suffixFx` is optional()'s swallowed literal failure publication.
|
|
460
|
+
*
|
|
461
|
+
* There is deliberately no child operand and no mode bit. The complete
|
|
462
|
+
* CHARACTER and TOKEN candidates were compared before serialization; this row
|
|
463
|
+
* is only the winning TOKEN body. A program without a selected TOKEN body has
|
|
464
|
+
* no row or lexical pool.
|
|
465
|
+
*/
|
|
466
|
+
/** `LEX_BODY body fx suffixFx lineFlags` — selected childless lexical body.
|
|
467
|
+
* lineFlags bit 0 publishes the regex range; bit 1 publishes a matched suffix. */
|
|
468
|
+
export const OP_LEX_BODY = 42
|
|
469
|
+
/** `LEX_PROGRAM p` — selected fixed composite lexical body, childless. */
|
|
470
|
+
export const OP_LEX_PROGRAM = 43
|
|
428
471
|
/**
|
|
429
472
|
* `DISPATCH sel d other otherRouted n a1 … an` — `dispatch()`.
|
|
430
473
|
*
|
|
@@ -456,4 +499,7 @@ export const OP_NAMES: Record<number, string> = {
|
|
|
456
499
|
[OP_ADJ]: 'ADJ',
|
|
457
500
|
[OP_GREEDY]: 'GREEDY', [OP_REJECT]: 'REJECT', [OP_ARMGATE]: 'ARMGATE',
|
|
458
501
|
[OP_LIVE]: 'LIVE', [OP_ATTEMPT]: 'ATTEMPT', [OP_LABEL]: 'LABEL', [OP_COV]: 'COV',
|
|
502
|
+
[OP_SCOPE_PLAIN]: 'SCOPE_PLAIN',
|
|
503
|
+
[OP_LEX_BODY]: 'LEX_BODY',
|
|
504
|
+
[OP_LEX_PROGRAM]: 'LEX_PROGRAM',
|
|
459
505
|
}
|
package/src/table/program.ts
CHANGED
|
@@ -5,6 +5,8 @@ import { regex } from '../combinators/regex.ts'
|
|
|
5
5
|
import { choice } from '../combinators/choice.ts'
|
|
6
6
|
import { many } from '../combinators/repeat.ts'
|
|
7
7
|
import type { EmittedFactory, PoolPlan } from './emit-assembly.ts'
|
|
8
|
+
import { buildLexBodyRecognizer, type LexBodyRecognizer } from './lex-body.ts'
|
|
9
|
+
import { buildLexProgramRunner, type LexProgramRunner } from './lex-program.ts'
|
|
8
10
|
|
|
9
11
|
/**
|
|
10
12
|
* One assembly the BUILD already compiled, so the run does not have to.
|
|
@@ -57,6 +59,11 @@ export type TableProgram = {
|
|
|
57
59
|
* at `resolveTable`, exactly like the char-class ASCII lookups.
|
|
58
60
|
*/
|
|
59
61
|
readonly dsp: readonly DispatchSpec[]
|
|
62
|
+
/** Selected childless lexical bodies. Losing compiler candidates never enter
|
|
63
|
+
* this pool. The first row is `[regexConst, suffixCodeUnit]`. */
|
|
64
|
+
readonly lex?: readonly LexBodySpec[]
|
|
65
|
+
/** Selected composite lexical programs. Losing candidates never enter it. */
|
|
66
|
+
readonly lexPrograms?: readonly LexProgramSpec[]
|
|
60
67
|
/** Rule name → entry offset in `code`. */
|
|
61
68
|
readonly rules: Readonly<Record<string, number>>
|
|
62
69
|
/**
|
|
@@ -124,7 +131,8 @@ export type TableProgram = {
|
|
|
124
131
|
*/
|
|
125
132
|
readonly runtimeOnly?: readonly string[]
|
|
126
133
|
/**
|
|
127
|
-
* Trivia specs, referenced by index from `OP_SCOPE` and
|
|
134
|
+
* Trivia specs, referenced by index from `OP_SCOPE`, `OP_SCOPE_CAP` and
|
|
135
|
+
* `OP_SCOPE_PLAIN` rule/restoration entries.
|
|
128
136
|
*
|
|
129
137
|
* `classifiedTrivia()` is `trivia(oneOrMore(choice(label(name, arm)…)))` with
|
|
130
138
|
* regex arms — entirely structural, so it lowers to DATA and is rebuilt at
|
|
@@ -290,6 +298,8 @@ export type CompactProgram = {
|
|
|
290
298
|
readonly f: readonly unknown[]
|
|
291
299
|
readonly l?: 0 | 1
|
|
292
300
|
readonly p?: readonly DispatchSpec[]
|
|
301
|
+
readonly lx?: readonly LexBodySpec[]
|
|
302
|
+
readonly lp?: readonly LexProgramSpec[]
|
|
293
303
|
readonly lb?: readonly string[]
|
|
294
304
|
readonly rc?: 0 | 1
|
|
295
305
|
readonly h?: 'ast' | 'cst'
|
|
@@ -308,6 +318,8 @@ export function expandCompact(p: TableProgram | CompactProgram): TableProgram {
|
|
|
308
318
|
return ownTableProgram({
|
|
309
319
|
code: p.c, k: p.k, cc: p.x, fx: p.e, disp: p.d, rules: p.r, fns: p.f,
|
|
310
320
|
lines: p.l ?? 0, dsp: p.p ?? [],
|
|
321
|
+
...(p.lx === undefined ? {} : { lex: p.lx }),
|
|
322
|
+
...(p.lp === undefined ? {} : { lexPrograms: p.lp }),
|
|
311
323
|
...(p.lb === undefined ? {} : { labels: p.lb }),
|
|
312
324
|
...(p.rc === undefined ? {} : { classified: p.rc }),
|
|
313
325
|
...(p.h === undefined ? {} : { hostMode: p.h }),
|
|
@@ -321,6 +333,28 @@ export function expandCompact(p: TableProgram | CompactProgram): TableProgram {
|
|
|
321
333
|
})
|
|
322
334
|
}
|
|
323
335
|
|
|
336
|
+
/** Selected lexical body. `suffixCodeUnit === -1` is a direct regex terminal;
|
|
337
|
+
* 0..0xFFFF is the legacy regex + optional(one code unit) spelling. */
|
|
338
|
+
export type LexBodySpec = readonly [regexConst: number, suffixCodeUnit: number]
|
|
339
|
+
|
|
340
|
+
/** `kind=0`: fixed ordered four-terminal selected body. */
|
|
341
|
+
export type LexProgramSpec = readonly [
|
|
342
|
+
kind: 0, digest: number, commonClass: number, choiceFx: number,
|
|
343
|
+
arm0Matcher: number, arm0Fx: number,
|
|
344
|
+
arm1Matcher: number, arm1Fx: number,
|
|
345
|
+
arm2Matcher: number, arm2Fx: number,
|
|
346
|
+
arm3Matcher: number, arm3Fx: number,
|
|
347
|
+
trackLines: 0 | 1,
|
|
348
|
+
] | readonly [
|
|
349
|
+
kind: 1, digest: number,
|
|
350
|
+
guard0Arm0: number, guard0Arm1: number, guard0Arm2: number, guard0Arm3: number,
|
|
351
|
+
guard0Fx: number,
|
|
352
|
+
guard1Matcher: number, guard1Fx: number,
|
|
353
|
+
terminalMatcher: number, terminalFx: number,
|
|
354
|
+
trackLines: 0 | 1,
|
|
355
|
+
] | readonly [kind: 2, digest: number, ...fixedTreeWords: number[]]
|
|
356
|
+
| readonly [kind: 3, digest: number, scan: number]
|
|
357
|
+
|
|
324
358
|
/** One `dispatch()`'s routing tables, in serialisable form. */
|
|
325
359
|
export type DispatchSpec = {
|
|
326
360
|
/** Exact keys, parallel to `keyArm`. */
|
|
@@ -352,6 +386,31 @@ export type ResolvedDispatchSpec = {
|
|
|
352
386
|
readonly expected: readonly string[]
|
|
353
387
|
}
|
|
354
388
|
|
|
389
|
+
/**
|
|
390
|
+
* Refuse malformed fixed arm references before closure or emitted linking.
|
|
391
|
+
*
|
|
392
|
+
* This is deliberately an assembly-time trust boundary. `execRules()` stays an
|
|
393
|
+
* independent semantic oracle and never pays artifact validation per parse.
|
|
394
|
+
*/
|
|
395
|
+
export function validateDispatchSpec(
|
|
396
|
+
spec: ResolvedDispatchSpec | undefined,
|
|
397
|
+
arity: number,
|
|
398
|
+
fallbackRouted: number,
|
|
399
|
+
): asserts spec is ResolvedDispatchSpec {
|
|
400
|
+
const invalid = (arms: Iterable<number>): boolean => {
|
|
401
|
+
for (const arm of arms) if (!Number.isInteger(arm) || arm < 0 || arm >= arity) return true
|
|
402
|
+
return false
|
|
403
|
+
}
|
|
404
|
+
if (spec === undefined || !Number.isInteger(arity) || arity < 0
|
|
405
|
+
|| (fallbackRouted !== 0 && fallbackRouted !== 1)
|
|
406
|
+
|| spec.routed.length !== arity
|
|
407
|
+
|| spec.routed.some(value => value !== 0 && value !== 1)
|
|
408
|
+
|| invalid(spec.byKey.values()) || invalid(spec.byFold.values())
|
|
409
|
+
|| spec.match.some(matcher => invalid([matcher[3]]))) {
|
|
410
|
+
throw new TypeError('table: malformed dispatch')
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
|
|
355
414
|
/** A char class expanded for execution: O(1) for ASCII, ranges above it. */
|
|
356
415
|
export type ResolvedClass = {
|
|
357
416
|
readonly ascii: Uint8Array
|
|
@@ -407,6 +466,8 @@ export type ResolvedTable = {
|
|
|
407
466
|
readonly fx: readonly (readonly string[])[]
|
|
408
467
|
readonly disp: readonly ResolvedDispatch[]
|
|
409
468
|
readonly dsp: readonly ResolvedDispatchSpec[]
|
|
469
|
+
readonly lex: readonly LexBodyRecognizer[]
|
|
470
|
+
readonly lexPrograms: readonly LexProgramRunner[]
|
|
410
471
|
/** Trivia combinators rebuilt from `triviaSpecs`, once per table. */
|
|
411
472
|
readonly trivia: readonly Combinator<unknown>[]
|
|
412
473
|
/**
|
|
@@ -584,6 +645,7 @@ export function resolveTable(prog: TableProgram): ResolvedTable {
|
|
|
584
645
|
if (hit !== undefined) return hit
|
|
585
646
|
const cc = prog.cc.map(resolveClass)
|
|
586
647
|
const triviaBuilt = (prog.triviaSpecs ?? []).map(buildTrivia)
|
|
648
|
+
const lex = (prog.lex ?? []).map(spec => buildLexBodyRecognizer(spec, prog.k))
|
|
587
649
|
const built: ResolvedTable = {
|
|
588
650
|
prog,
|
|
589
651
|
code: Int32Array.from(prog.code),
|
|
@@ -602,6 +664,9 @@ export function resolveTable(prog: TableProgram): ResolvedTable {
|
|
|
602
664
|
routed: d.routed,
|
|
603
665
|
expected: d.expected,
|
|
604
666
|
})),
|
|
667
|
+
lex,
|
|
668
|
+
lexPrograms: (prog.lexPrograms ?? []).map(spec =>
|
|
669
|
+
buildLexProgramRunner(spec, lex, cc, prog.fx)),
|
|
605
670
|
rules: prog.rules,
|
|
606
671
|
}
|
|
607
672
|
if (owner !== undefined) owner.resolved = built
|
|
@@ -666,7 +731,7 @@ export type CompactFolded = {
|
|
|
666
731
|
* Named rather than derived so the refusal can say which one broke.
|
|
667
732
|
*/
|
|
668
733
|
const SHARED_FIELDS = [
|
|
669
|
-
'k', 'cc', 'fx', 'disp', 'dsp', 'rules', 'labels', 'classified',
|
|
734
|
+
'k', 'cc', 'fx', 'disp', 'dsp', 'lex', 'lexPrograms', 'rules', 'labels', 'classified',
|
|
670
735
|
'scanSkip', 'scanSkipOf', 'scans', 'triviaSpecs', 'runtimeOnly',
|
|
671
736
|
// `rec` is a property of the GRAMMAR BUILD, not of the trackLines/hostMode
|
|
672
737
|
// axis a fold varies — every variant of one export is encoded with the same
|