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
package/src/table/emit.ts
CHANGED
|
@@ -15,6 +15,29 @@ function jsString(s: string): string {
|
|
|
15
15
|
return JSON.stringify(s)
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
+
/**
|
|
19
|
+
* Emit the char-class pool in the shorter of two equivalent forms.
|
|
20
|
+
*
|
|
21
|
+
* A normal array repeats two quote bytes around every class. Real grammars have
|
|
22
|
+
* dozens of classes, and the recursion-stack first-set correction legitimately
|
|
23
|
+
* discovers more of them. One delimiter-joined string pays for `.split()` once
|
|
24
|
+
* and removes those repeated quotes; the resulting value handed to
|
|
25
|
+
* `expandCompact` is still the exact `string[]` its wire contract declares.
|
|
26
|
+
*
|
|
27
|
+
* Pick a delimiter absent from every encoded class. These strings contain RANGE
|
|
28
|
+
* ENDPOINTS, not regex source, so no character is universally reserved. If this
|
|
29
|
+
* small printable set is exhausted, keep the ordinary array — never escape or
|
|
30
|
+
* reinterpret class data to force the compact form.
|
|
31
|
+
*/
|
|
32
|
+
function emitClassPool(classes: readonly string[]): string {
|
|
33
|
+
const array = `[${classes.map(jsString).join(',')}]`
|
|
34
|
+
if (classes.length < 2) return array
|
|
35
|
+
const delimiter = '|/~?^`'.split('').find(ch => classes.every(spec => !spec.includes(ch)))
|
|
36
|
+
if (delimiter === undefined) return array
|
|
37
|
+
const split = `${jsString(classes.join(delimiter))}.split(${jsString(delimiter)})`
|
|
38
|
+
return split.length < array.length ? split : array
|
|
39
|
+
}
|
|
40
|
+
|
|
18
41
|
/**
|
|
19
42
|
* Serialise one const-pool entry, or REFUSE it.
|
|
20
43
|
*
|
|
@@ -264,7 +287,7 @@ function programFields(prog: TableProgram, fns: readonly string[], opts: EmitOpt
|
|
|
264
287
|
...emitAssemblies(prog, opts.assemblies ?? []),
|
|
265
288
|
`c:[${prog.code.join(',')}],`,
|
|
266
289
|
`k:[${prog.k.map(emitConst).join(',')}],`,
|
|
267
|
-
`x
|
|
290
|
+
`x:${emitClassPool(prog.cc)},`,
|
|
268
291
|
`e:[${prog.fx.map(f => `[${f.map(jsString).join(',')}]`).join(',')}],`,
|
|
269
292
|
`d:[${prog.disp.map(a => `[${a.join(',')}]`).join(',')}],`,
|
|
270
293
|
`r:${JSON.stringify(prog.rules)},`,
|
|
@@ -276,6 +299,8 @@ function programFields(prog: TableProgram, fns: readonly string[], opts: EmitOpt
|
|
|
276
299
|
// `run({ rootTrivia })` rejected a grammar that plainly has labels — the
|
|
277
300
|
// exact failure the root-trivia work exists to prevent, one hop downstream.
|
|
278
301
|
...(prog.dsp.length === 0 ? [] : [`p:[${prog.dsp.map(emitDispatchSpec).join(',')}],`]),
|
|
302
|
+
...(prog.lex === undefined ? [] : [`lx:[${prog.lex.map(row => `[${row.join(',')}]`).join(',')}],`]),
|
|
303
|
+
...(prog.lexPrograms === undefined ? [] : [`lp:[${prog.lexPrograms.map(row => `[${row.join(',')}]`).join(',')}],`]),
|
|
279
304
|
...(prog.labels === undefined ? [] : [`lb:[${prog.labels.map(jsString).join(',')}],`]),
|
|
280
305
|
...(prog.classified === 1 ? ['rc:1,'] : []),
|
|
281
306
|
// Without this a recovery table's MODULE loads as a strict one: the extra
|
package/src/table/encode.ts
CHANGED
|
@@ -1,27 +1,41 @@
|
|
|
1
1
|
import type { AutoNotCheck, Combinator, FirstSet, ParserDef } from '../types.ts'
|
|
2
|
-
import { firstSetOf, matchesEmpty, union, type RefResolver } from '../combinators/first-set.ts'
|
|
2
|
+
import { classifyFinalChoice, firstSetOf, matchesEmpty, union, type RefResolver } from '../combinators/first-set.ts'
|
|
3
3
|
import { childrenOf } from '../analysis/gating.ts'
|
|
4
4
|
import { getCoreLiteralValue } from '../combinators/choice.ts'
|
|
5
5
|
import { deriveExpected } from '../combinators/expect.ts'
|
|
6
|
-
import {
|
|
6
|
+
import { assertionFailureExpected, directTerminalFailureExpected } from '../combinators/expected.ts'
|
|
7
|
+
import { buildReadsRaw, buildReadsState, buildReadsTrivia } from '../compiler/build-arity.ts'
|
|
7
8
|
import { buildReadsFields, parserHasOwnFields } from '../compiler/fields.ts'
|
|
8
9
|
import { asciiFoldKey, branchUsesRouted, parserUsesRouted } from '../combinators/dispatch.ts'
|
|
9
10
|
import {
|
|
10
11
|
OP_CHOICE, OP_EMPTY, OP_GATE, OP_LEAF, OP_LIT, OP_NODE, OP_NOT, OP_OPT,
|
|
11
12
|
OP_PEEK, OP_REP, OP_REPV, OP_RULE, OP_RX, OP_SEQ, OP_SEQV, OP_XFORM,
|
|
12
|
-
OP_LIT_TRACK, OP_RX_TRACK, OP_NODE_TRACK, OP_SCOPE, OP_SCOPE_CAP, OP_EXPECT, OP_SEQX, OP_SCAN,
|
|
13
|
+
OP_LIT_TRACK, OP_RX_TRACK, OP_NODE_TRACK, OP_SCOPE, OP_SCOPE_CAP, OP_SCOPE_PLAIN, OP_EXPECT, OP_SEQX, OP_SCAN,
|
|
13
14
|
OP_LIVE, OP_ATTEMPT, OP_LABEL,
|
|
14
15
|
OP_FIELD, OP_DISPATCH, OP_ROUTED, OP_LIT_CI, OP_LIT_CI_TRACK, OP_TOKEN, OP_WITHCTX, OP_GUARD,
|
|
15
16
|
OP_ADJ, OP_GREEDY, OP_REJECT, OP_ARMGATE, OP_COV,
|
|
17
|
+
OP_LEX_BODY, OP_LEX_PROGRAM,
|
|
16
18
|
} from './ops.ts'
|
|
17
19
|
import { adjacencyExpected } from '../combinators/adjacency.ts'
|
|
18
20
|
import { resolveAdjacencyKindMask } from '../cst/trivia-kinds.ts'
|
|
19
21
|
import { missingInferredType } from '../combinators/node.ts'
|
|
20
22
|
import { hasOwnTriviaBoundary } from '../combinators/trivia-boundary.ts'
|
|
21
23
|
import type { BalancedSpec } from '../combinators/scanTo.ts'
|
|
22
|
-
import type { DispatchSpec, ScanSpec, SubtreeRef, TableProgram, TriviaSpec } from './program.ts'
|
|
24
|
+
import type { DispatchSpec, LexBodySpec, LexProgramSpec, ScanSpec, SubtreeRef, TableProgram, TriviaSpec } from './program.ts'
|
|
25
|
+
import {
|
|
26
|
+
lexProgramDigest,
|
|
27
|
+
LEX_NODE_CHOICE2, LEX_NODE_CHOICE4, LEX_NODE_CHOICE8,
|
|
28
|
+
LEX_NODE_NOT, LEX_NODE_OPTIONAL,
|
|
29
|
+
LEX_NODE_SEQUENCE2, LEX_NODE_SEQUENCE3, LEX_NODE_SEQUENCE4, LEX_NODE_SEQUENCE5,
|
|
30
|
+
LEX_NODE_TERMINAL,
|
|
31
|
+
} from './lex-program.ts'
|
|
23
32
|
import { covKindCode, encodeClassSpec, ownTableProgram } from './program.ts'
|
|
24
33
|
import type { GrammarCoveragePlan } from '../compiler/grammar-coverage-ids.ts'
|
|
34
|
+
import { directArrayProjection } from '../compiler/direct-projection.ts'
|
|
35
|
+
import {
|
|
36
|
+
assertLexicalCapabilityClosure, collectLexicalCapabilities, winnerWrapsReference,
|
|
37
|
+
} from '../compiler/token-capability.ts'
|
|
38
|
+
import { directExecutableTokenBody, type ExecutableLexNode } from '../compiler/token-alphabet.ts'
|
|
25
39
|
|
|
26
40
|
/**
|
|
27
41
|
* Can `emitConst` print this? Mirrors the guard in `emit.ts` — scalars, arrays
|
|
@@ -42,37 +56,6 @@ function emittableConst(v: unknown): boolean {
|
|
|
42
56
|
return false
|
|
43
57
|
}
|
|
44
58
|
|
|
45
|
-
/**
|
|
46
|
-
* Does the rule-map entry `winner` bottom out AT the reference `p`?
|
|
47
|
-
*
|
|
48
|
-
* The question `Encoder.winners` has to ask before resolving a `g.X` by name. A
|
|
49
|
-
* map entry that IS the reference — directly, or under the scope wrappers that
|
|
50
|
-
* `rules()` puts there — is not an override to resolve to; it is the reference's
|
|
51
|
-
* own binding, and the only thing that reaches the rule's BODY from there is the
|
|
52
|
-
* ref's thunk. Resolving by name instead hands back the row already in flight for
|
|
53
|
-
* that entry, which is an alias cycle rather than a parser (see `winners`).
|
|
54
|
-
*
|
|
55
|
-
* ONLY WRAPPERS `rules()` ITSELF INTRODUCES are unwrapped — `parser()`/`noTrivia`
|
|
56
|
-
* scopes (`grammar`) and `trivia()`. Both are single-child and both can encode to
|
|
57
|
-
* NO ROW, which is what makes the cycle degenerate. Walking further, into a
|
|
58
|
-
* `seq()` or a `choice()` arm, would answer a different question: a rule whose
|
|
59
|
-
* body legitimately contains a reference to itself is ordinary recursion and must
|
|
60
|
-
* keep resolving by name.
|
|
61
|
-
*/
|
|
62
|
-
function wrapsRef(winner: Combinator<unknown>, p: Combinator<unknown>): boolean {
|
|
63
|
-
let cur = winner
|
|
64
|
-
// Bounded rather than `while (true)`: `_def.parser` is author-supplied and a
|
|
65
|
-
// hand-built cycle of scopes would otherwise hang the encoder. Nothing legal
|
|
66
|
-
// nests these more than a couple deep.
|
|
67
|
-
for (let n = 0; n < 16; n++) {
|
|
68
|
-
if (cur === p) return true
|
|
69
|
-
const d = cur._def as ParserDef
|
|
70
|
-
if (d.tag !== 'grammar' && d.tag !== 'trivia') return false
|
|
71
|
-
cur = d.parser
|
|
72
|
-
}
|
|
73
|
-
return false
|
|
74
|
-
}
|
|
75
|
-
|
|
76
59
|
/** Raised when a construct has no opcode yet. Prototype scope is explicit. */
|
|
77
60
|
export class UnsupportedConstruct extends Error {
|
|
78
61
|
readonly tag: string
|
|
@@ -154,6 +137,10 @@ class Encoder {
|
|
|
154
137
|
fx: (readonly string[])[] = []
|
|
155
138
|
disp: (readonly number[])[] = []
|
|
156
139
|
dsp: DispatchSpec[] = []
|
|
140
|
+
lex: LexBodySpec[] = []
|
|
141
|
+
private lexIndex = new Map<string, number>()
|
|
142
|
+
lexPrograms: LexProgramSpec[] = []
|
|
143
|
+
private lexProgramIndex = new Map<string, number>()
|
|
157
144
|
labels: readonly string[] | undefined = undefined
|
|
158
145
|
/** Trivia table in scope at the row being encoded — codegen's `ctx.activeTrivia`. */
|
|
159
146
|
activeTrivia: Combinator<unknown> | undefined = undefined
|
|
@@ -207,7 +194,6 @@ class Encoder {
|
|
|
207
194
|
winners: Readonly<Record<string, Combinator<unknown>>> | undefined = undefined
|
|
208
195
|
triviaSpecs: TriviaSpec[] = []
|
|
209
196
|
private triviaIndex = new Map<Combinator<unknown>, number>()
|
|
210
|
-
|
|
211
197
|
/**
|
|
212
198
|
* A trivia combinator as DATA where its shape allows.
|
|
213
199
|
*
|
|
@@ -226,6 +212,160 @@ class Encoder {
|
|
|
226
212
|
return idx
|
|
227
213
|
}
|
|
228
214
|
|
|
215
|
+
private lexicalBodySlot(p: Combinator<unknown>): {
|
|
216
|
+
kind: 'body'
|
|
217
|
+
body: number
|
|
218
|
+
expectedFx: number
|
|
219
|
+
suffixFx: number
|
|
220
|
+
lineFlags: number
|
|
221
|
+
} | { kind: 'program'; program: number } | undefined {
|
|
222
|
+
if (!this.selectLexicalBodies) return undefined
|
|
223
|
+
const body = directExecutableTokenBody(p, name => this.winners?.[name])
|
|
224
|
+
if (body === undefined) return undefined
|
|
225
|
+
const tokenDef = p._def
|
|
226
|
+
if (tokenDef.tag !== 'token') throw new Error('parseman: selected lexical body lost token boundary')
|
|
227
|
+
const matcher = (arm: Extract<typeof body, { kind: 'regex-terminal' }>): number => {
|
|
228
|
+
const flags = arm.flags.includes('y') ? arm.flags : `${arm.flags.replace(/g/g, '')}y`
|
|
229
|
+
const regex = this.constant(new RegExp(arm.source, flags))
|
|
230
|
+
const key = `${regex}:-1`
|
|
231
|
+
const prior = this.lexIndex.get(key)
|
|
232
|
+
if (prior !== undefined) return prior
|
|
233
|
+
const id = this.lex.length
|
|
234
|
+
this.lexIndex.set(key, id)
|
|
235
|
+
this.lex.push([regex, -1])
|
|
236
|
+
return id
|
|
237
|
+
}
|
|
238
|
+
if (body.kind === 'balanced-scan') {
|
|
239
|
+
const scan = this.scanSlot({
|
|
240
|
+
kind: 1,
|
|
241
|
+
flags: (body.raw ? 1 : 0) | (body.strict ? 4 : 0),
|
|
242
|
+
skip: body.ownSkip.map(combinator => this.subtree(combinator)),
|
|
243
|
+
open: body.open,
|
|
244
|
+
close: body.close,
|
|
245
|
+
})
|
|
246
|
+
const words = [scan]
|
|
247
|
+
const spec = [3, lexProgramDigest(words), scan] as LexProgramSpec
|
|
248
|
+
const key = spec.join(',')
|
|
249
|
+
const prior = this.lexProgramIndex.get(key)
|
|
250
|
+
if (prior !== undefined) return { kind: 'program', program: prior }
|
|
251
|
+
const id = this.lexPrograms.length
|
|
252
|
+
this.lexProgramIndex.set(key, id)
|
|
253
|
+
this.lexPrograms.push(spec)
|
|
254
|
+
return { kind: 'program', program: id }
|
|
255
|
+
}
|
|
256
|
+
if (body.kind === 'fixed-tree') {
|
|
257
|
+
const words: number[] = []
|
|
258
|
+
const emitNode = (node: ExecutableLexNode): boolean => {
|
|
259
|
+
switch (node.kind) {
|
|
260
|
+
case 'terminal':
|
|
261
|
+
words.push(
|
|
262
|
+
LEX_NODE_TERMINAL,
|
|
263
|
+
matcher(node.terminal),
|
|
264
|
+
this.expected(node.terminal.expected),
|
|
265
|
+
this.track ? 1 : 0,
|
|
266
|
+
)
|
|
267
|
+
return true
|
|
268
|
+
case 'sequence': {
|
|
269
|
+
for (const part of node.parts) if (!emitNode(part)) return false
|
|
270
|
+
const op = node.parts.length === 2 ? LEX_NODE_SEQUENCE2
|
|
271
|
+
: node.parts.length === 3 ? LEX_NODE_SEQUENCE3
|
|
272
|
+
: node.parts.length === 4 ? LEX_NODE_SEQUENCE4
|
|
273
|
+
: node.parts.length === 5 ? LEX_NODE_SEQUENCE5 : -1
|
|
274
|
+
if (op < 0) return false
|
|
275
|
+
words.push(op)
|
|
276
|
+
return true
|
|
277
|
+
}
|
|
278
|
+
case 'choice': {
|
|
279
|
+
for (const arm of node.arms) if (!emitNode(arm)) return false
|
|
280
|
+
const op = node.arms.length === 2 ? LEX_NODE_CHOICE2
|
|
281
|
+
: node.arms.length === 4 ? LEX_NODE_CHOICE4
|
|
282
|
+
: node.arms.length === 8 ? LEX_NODE_CHOICE8 : -1
|
|
283
|
+
if (op < 0) return false
|
|
284
|
+
words.push(op, this.expected(node.expected))
|
|
285
|
+
for (let i = 0; i < node.arms.length; i++) {
|
|
286
|
+
const first = node.firstSets[i]!
|
|
287
|
+
words.push(first.kind === 'ranges' ? this.charClass(first) : -1)
|
|
288
|
+
words.push(this.expected(node.armExpected[i]!))
|
|
289
|
+
}
|
|
290
|
+
return true
|
|
291
|
+
}
|
|
292
|
+
case 'not':
|
|
293
|
+
if (!emitNode(node.body)) return false
|
|
294
|
+
words.push(LEX_NODE_NOT, this.expected(node.expected))
|
|
295
|
+
return true
|
|
296
|
+
case 'optional':
|
|
297
|
+
if (!emitNode(node.body)) return false
|
|
298
|
+
words.push(LEX_NODE_OPTIONAL)
|
|
299
|
+
return true
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
if (!emitNode(body.root)) return undefined
|
|
303
|
+
const spec = [2, lexProgramDigest(words), ...words] as LexProgramSpec
|
|
304
|
+
const key = spec.join(',')
|
|
305
|
+
const prior = this.lexProgramIndex.get(key)
|
|
306
|
+
if (prior !== undefined) return { kind: 'program', program: prior }
|
|
307
|
+
const id = this.lexPrograms.length
|
|
308
|
+
this.lexProgramIndex.set(key, id)
|
|
309
|
+
this.lexPrograms.push(spec)
|
|
310
|
+
return { kind: 'program', program: id }
|
|
311
|
+
}
|
|
312
|
+
if (body.kind === 'not2-ordered4-terminal') {
|
|
313
|
+
const words = [
|
|
314
|
+
...body.guard0.map(matcher), this.expected(body.guard0Expected),
|
|
315
|
+
matcher(body.guard1), this.expected(body.guard1Expected),
|
|
316
|
+
matcher(body.terminal), this.expected(body.terminalExpected),
|
|
317
|
+
this.track ? 1 : 0,
|
|
318
|
+
]
|
|
319
|
+
const spec = [1, lexProgramDigest(words), ...words] as unknown as LexProgramSpec
|
|
320
|
+
const key = spec.join(',')
|
|
321
|
+
const prior = this.lexProgramIndex.get(key)
|
|
322
|
+
if (prior !== undefined) return { kind: 'program', program: prior }
|
|
323
|
+
const id = this.lexPrograms.length
|
|
324
|
+
this.lexProgramIndex.set(key, id)
|
|
325
|
+
this.lexPrograms.push(spec)
|
|
326
|
+
return { kind: 'program', program: id }
|
|
327
|
+
}
|
|
328
|
+
if (body.kind === 'ordered4-terminals') {
|
|
329
|
+
const commonClass = this.charClass(body.commonFirstSet)
|
|
330
|
+
if (commonClass < 0) return undefined
|
|
331
|
+
const words: number[] = [commonClass, this.expected(body.commonMissExpected)]
|
|
332
|
+
for (const arm of body.arms) {
|
|
333
|
+
words.push(matcher(arm), this.expected(arm.expected))
|
|
334
|
+
}
|
|
335
|
+
words.push(this.track ? 1 : 0)
|
|
336
|
+
const spec = [0, lexProgramDigest(words), ...words] as unknown as LexProgramSpec
|
|
337
|
+
const key = spec.join(',')
|
|
338
|
+
const prior = this.lexProgramIndex.get(key)
|
|
339
|
+
if (prior !== undefined) return { kind: 'program', program: prior }
|
|
340
|
+
const id = this.lexPrograms.length
|
|
341
|
+
this.lexProgramIndex.set(key, id)
|
|
342
|
+
this.lexPrograms.push(spec)
|
|
343
|
+
return { kind: 'program', program: id }
|
|
344
|
+
}
|
|
345
|
+
const expectedFx = this.expected(body.expected)
|
|
346
|
+
const flags = body.flags.includes('y') ? body.flags : `${body.flags.replace(/g/g, '')}y`
|
|
347
|
+
const regex = this.constant(new RegExp(body.source, flags))
|
|
348
|
+
const suffix = body.kind === 'regex-terminal' ? -1 : body.suffix.charCodeAt(0)
|
|
349
|
+
const key = `${regex}:${suffix}`
|
|
350
|
+
const prior = this.lexIndex.get(key)
|
|
351
|
+
const suffixFx = this.expected(body.kind === 'regex-terminal' ? [] : directTerminalFailureExpected({
|
|
352
|
+
tag: 'literal', value: body.suffix, caseInsensitive: false,
|
|
353
|
+
}))
|
|
354
|
+
// Tracked terminal rows advance the high-water mark even when their static
|
|
355
|
+
// language cannot contain a newline. Otherwise a preceding newline stays
|
|
356
|
+
// behind an incorrectly advanced boundary and the next CST node receives a
|
|
357
|
+
// stale line. The suffix advances only when it actually matched (reader bit
|
|
358
|
+
// 2), exactly like the authored optional literal.
|
|
359
|
+
const lineFlags = this.track
|
|
360
|
+
? (body.kind === 'regex-terminal' ? 1 : 1 | 2 | 4)
|
|
361
|
+
: body.kind === 'regex-terminal' ? 0 : 4
|
|
362
|
+
if (prior !== undefined) return { kind: 'body', body: prior, expectedFx, suffixFx, lineFlags }
|
|
363
|
+
const id = this.lex.length
|
|
364
|
+
this.lexIndex.set(key, id)
|
|
365
|
+
this.lex.push([regex, suffix])
|
|
366
|
+
return { kind: 'body', body: id, expectedFx, suffixFx, lineFlags }
|
|
367
|
+
}
|
|
368
|
+
|
|
229
369
|
private triviaSpecOf(t: Combinator<unknown>): TriviaSpec {
|
|
230
370
|
const unlowered = (why: string): TriviaSpec => {
|
|
231
371
|
this.runtimeOnly.add(`rules({ trivia }) — ${why}`)
|
|
@@ -345,6 +485,9 @@ class Encoder {
|
|
|
345
485
|
readonly settings: TableSettings
|
|
346
486
|
/** Resolved once, HERE, at table-build time — never consulted at run time. */
|
|
347
487
|
readonly track: boolean
|
|
488
|
+
/** TOKEN bodies are an all-program decision. A complete occurrence inside an
|
|
489
|
+
* otherwise incomplete final graph stays on CHARACTER with every sibling. */
|
|
490
|
+
readonly selectLexicalBodies: boolean
|
|
348
491
|
/**
|
|
349
492
|
* Decides the extra operands laid down below. ALWAYS TRUE — see
|
|
350
493
|
* `TableSettings.recovery`. Kept as a field rather than folded away because it
|
|
@@ -352,9 +495,10 @@ class Encoder {
|
|
|
352
495
|
* row is stated.
|
|
353
496
|
*/
|
|
354
497
|
readonly rec: boolean
|
|
355
|
-
constructor(settings: TableSettings) {
|
|
498
|
+
constructor(settings: TableSettings, selectLexicalBodies: boolean) {
|
|
356
499
|
this.settings = settings
|
|
357
500
|
this.track = settings.trackLines === true
|
|
501
|
+
this.selectLexicalBodies = selectLexicalBodies
|
|
358
502
|
this.rec = true
|
|
359
503
|
this.plan = settings.coverage
|
|
360
504
|
this.cov = settings.coverage === undefined
|
|
@@ -483,6 +627,16 @@ class Encoder {
|
|
|
483
627
|
return i
|
|
484
628
|
}
|
|
485
629
|
|
|
630
|
+
/** Bind a guard only when its class is already part of the program. Repeat
|
|
631
|
+
* gating must not grow the compact artifact merely to avoid a failed call. */
|
|
632
|
+
private existingCharClass(fs: FirstSet): number {
|
|
633
|
+
if (fs.kind !== 'ranges' || fs.ranges.length === 0) return -1
|
|
634
|
+
const hit = this.ccIndex.get(encodeClassSpec(fs.ranges))
|
|
635
|
+
// This reuses the recovery row's existing `-1` slot. Keep its decimal width
|
|
636
|
+
// at two bytes or less so a speed hint cannot grow a compact artifact.
|
|
637
|
+
return hit !== undefined && hit < 100 ? hit : -1
|
|
638
|
+
}
|
|
639
|
+
|
|
486
640
|
/**
|
|
487
641
|
* Wrap one choice arm in its `autoNot` checks (`OP_REJECT`).
|
|
488
642
|
*
|
|
@@ -610,7 +764,7 @@ class Encoder {
|
|
|
610
764
|
// straight to the body's offset (`case 'lazy'`), so every recursive call and
|
|
611
765
|
// every cross-rule reference would jump past the counter, and a grammar with
|
|
612
766
|
// one entry would report one hit rule however much of it ran.
|
|
613
|
-
this.rules[name] = amb === undefined ? body : this.emit(
|
|
767
|
+
this.rules[name] = amb === undefined ? body : this.emit(OP_SCOPE_PLAIN, this.triviaSlot(amb), body)
|
|
614
768
|
}
|
|
615
769
|
|
|
616
770
|
/**
|
|
@@ -618,7 +772,7 @@ class Encoder {
|
|
|
618
772
|
* in the target rule's own ambient trivia when that differs from the trivia
|
|
619
773
|
* lexically active at the reference.
|
|
620
774
|
*
|
|
621
|
-
* `encodeRule` wraps a rule ENTRY in `
|
|
775
|
+
* `encodeRule` wraps a rule ENTRY in `OP_SCOPE_PLAIN`, but only the entry: a `g.X`
|
|
622
776
|
* reference resolves straight to the body's offset and jumps past it (the note
|
|
623
777
|
* at `encodeRule`). So a rule referenced from inside a `noTrivia(...)` region ran
|
|
624
778
|
* with NO ambient trivia of its own — the table scoped trivia DYNAMICALLY, by
|
|
@@ -656,7 +810,7 @@ class Encoder {
|
|
|
656
810
|
const amb = p._meta.grammarTrivia ?? target._meta.grammarTrivia
|
|
657
811
|
if (amb === undefined || this.activeTrivia !== undefined) return ip
|
|
658
812
|
if (!hasOwnTriviaBoundary(target)) return ip
|
|
659
|
-
return this.emit(
|
|
813
|
+
return this.emit(OP_SCOPE_PLAIN, this.triviaSlot(amb), ip)
|
|
660
814
|
}
|
|
661
815
|
|
|
662
816
|
node(p: Combinator<unknown>): Emitted {
|
|
@@ -713,6 +867,20 @@ class Encoder {
|
|
|
713
867
|
|
|
714
868
|
private encodeDef(p: Combinator<unknown>): number {
|
|
715
869
|
const d = p._def as ParserDef
|
|
870
|
+
// `balanced()` returns a token carrying `_balancedSpec` on that same
|
|
871
|
+
// object. Give a complete selected token shell first refusal; otherwise the
|
|
872
|
+
// generic scan row below would permanently shadow it before the `token`
|
|
873
|
+
// switch is reached.
|
|
874
|
+
if (d.tag === 'token') {
|
|
875
|
+
const selected = this.lexicalBodySlot(p)
|
|
876
|
+
if (selected !== undefined) {
|
|
877
|
+
if (selected.kind === 'program') return this.emit(OP_LEX_PROGRAM, selected.program)
|
|
878
|
+
return this.emit(
|
|
879
|
+
OP_LEX_BODY, selected.body, selected.expectedFx,
|
|
880
|
+
selected.suffixFx, selected.lineFlags,
|
|
881
|
+
)
|
|
882
|
+
}
|
|
883
|
+
}
|
|
716
884
|
// THE SCANNING CONSTRUCTS, as their constructor arguments.
|
|
717
885
|
//
|
|
718
886
|
// Neither is recoverable from `_def` alone — `balanced()` overrides `.parse`
|
|
@@ -746,13 +914,13 @@ class Encoder {
|
|
|
746
914
|
switch (d.tag) {
|
|
747
915
|
case 'literal': {
|
|
748
916
|
if (d.caseInsensitive) {
|
|
749
|
-
return this.emit(this.track ? OP_LIT_CI_TRACK : OP_LIT_CI, this.constant(d.value), this.expected(
|
|
917
|
+
return this.emit(this.track ? OP_LIT_CI_TRACK : OP_LIT_CI, this.constant(d.value), this.expected(directTerminalFailureExpected(d)))
|
|
750
918
|
}
|
|
751
|
-
return this.emit(this.track ? OP_LIT_TRACK : OP_LIT, this.constant(d.value), this.expected(
|
|
919
|
+
return this.emit(this.track ? OP_LIT_TRACK : OP_LIT, this.constant(d.value), this.expected(directTerminalFailureExpected(d)))
|
|
752
920
|
}
|
|
753
921
|
case 'regex': {
|
|
754
922
|
const flags = d.flags.includes('y') ? d.flags : `${d.flags}y`
|
|
755
|
-
return this.emit(this.track ? OP_RX_TRACK : OP_RX, this.constant(new RegExp(d.source, flags)), this.expected(
|
|
923
|
+
return this.emit(this.track ? OP_RX_TRACK : OP_RX, this.constant(new RegExp(d.source, flags)), this.expected(directTerminalFailureExpected(d)))
|
|
756
924
|
}
|
|
757
925
|
case 'keywords': {
|
|
758
926
|
// `keywords()` compiles to ONE sticky regex and pushes a leaf — which is
|
|
@@ -770,7 +938,7 @@ class Encoder {
|
|
|
770
938
|
// failing keyword arm names the CATEGORY rather than reciting every
|
|
771
939
|
// literal in the family. `deriveExpected` reports the literals, so the
|
|
772
940
|
// table said `'"media"'` where both other engines say `'keyword'`.
|
|
773
|
-
this.expected(
|
|
941
|
+
this.expected(directTerminalFailureExpected(d)),
|
|
774
942
|
)
|
|
775
943
|
}
|
|
776
944
|
case 'expect': {
|
|
@@ -875,6 +1043,7 @@ class Encoder {
|
|
|
875
1043
|
// covered on a parse where the choice picked somebody else.
|
|
876
1044
|
return this.covWrap(gated, armCovIds?.[src], 1)
|
|
877
1045
|
})
|
|
1046
|
+
const rr = this.refResolver()
|
|
878
1047
|
// O(1) first-char dispatch is only SOUND when the arms are disjoint.
|
|
879
1048
|
// With overlapping first sets, "the first arm whose class contains this
|
|
880
1049
|
// char" is not "the first arm that matches": a `Keyword` arm whose first
|
|
@@ -904,8 +1073,9 @@ class Encoder {
|
|
|
904
1073
|
// the O(1) table (`exclusive`) or falls to the ordered per-arm path.
|
|
905
1074
|
// Arm ORDER is preserved on both, which is what makes this a PEG-safe
|
|
906
1075
|
// change rather than a reordering.
|
|
907
|
-
const
|
|
908
|
-
const classes = arms.map(
|
|
1076
|
+
const finalChoice = classifyFinalChoice(arms, rr)
|
|
1077
|
+
const classes = arms.map((_, i) => finalChoice.nullable[i]
|
|
1078
|
+
? -1 : this.charClass(finalChoice.firstSets[i]!))
|
|
909
1079
|
const dispIdx = this.disp.length
|
|
910
1080
|
this.disp.push(classes)
|
|
911
1081
|
// PER-ARM EXPECTED SETS RIDE ALONG, after the arm offsets. The ordered
|
|
@@ -932,6 +1102,10 @@ class Encoder {
|
|
|
932
1102
|
case 'many':
|
|
933
1103
|
case 'oneOrMore': {
|
|
934
1104
|
const child = this.node(d.parser).ip
|
|
1105
|
+
const rr = this.refResolver()
|
|
1106
|
+
const itemClass = matchesEmpty(d.parser, new Set(), rr)
|
|
1107
|
+
? -1
|
|
1108
|
+
: this.existingCharClass(firstSetOf(d.parser, new Set(), rr))
|
|
935
1109
|
const op = d.valueUnused ? OP_REPV : OP_REP
|
|
936
1110
|
const min = d.tag === 'many' ? 0 : d.min
|
|
937
1111
|
// ip + 6 is the ITEM's expected set and ip + 7 the separator's sentinel
|
|
@@ -940,7 +1114,7 @@ class Encoder {
|
|
|
940
1114
|
// set — `deriveExpected(combinator)` in repeat.ts:170, and
|
|
941
1115
|
// `deriveExpectedArr([def.parser])` in codegen's emitMany.
|
|
942
1116
|
return this.rec
|
|
943
|
-
? this.emit(op, child, min, d.max ?? -1, -1, 0, this.expected(deriveExpected(d.parser)),
|
|
1117
|
+
? this.emit(op, child, min, d.max ?? -1, -1, 0, this.expected(deriveExpected(d.parser)), itemClass)
|
|
944
1118
|
: this.emit(op, child, min, d.max ?? -1, -1, 0)
|
|
945
1119
|
}
|
|
946
1120
|
case 'sepBy': {
|
|
@@ -987,7 +1161,14 @@ class Encoder {
|
|
|
987
1161
|
if (inner.tag === 'sequence' && !this.memo.has(d.parser) && !this.pending.has(d.parser)) {
|
|
988
1162
|
const kids = inner.parsers.map(c => this.node(c).ip)
|
|
989
1163
|
const head = this.emitHead(OP_SEQX, 2 + kids.length + (this.rec ? kids.length : 0))
|
|
990
|
-
|
|
1164
|
+
const projection = directArrayProjection(d.fn, d.fnSrc, kids.length)
|
|
1165
|
+
// A negative operand describes an already-parsed child (`~index`),
|
|
1166
|
+
// while every function-pool index remains non-negative. The row is
|
|
1167
|
+
// otherwise unchanged, so projection fusion costs no table words and
|
|
1168
|
+
// does not create a second closure/factory shape.
|
|
1169
|
+
this.code[head + 1] = projection === null
|
|
1170
|
+
? this.fn(d.fn, d.fnSrc ?? null)
|
|
1171
|
+
: ~projection
|
|
991
1172
|
this.code[head + 2] = kids.length
|
|
992
1173
|
for (let i = 0; i < kids.length; i++) this.code[head + 3 + i] = kids[i]!
|
|
993
1174
|
if (this.rec) {
|
|
@@ -1046,12 +1227,14 @@ class Encoder {
|
|
|
1046
1227
|
// a `project` replaces the value outright.
|
|
1047
1228
|
const noBuildCaptures = d.project === undefined
|
|
1048
1229
|
const derivedTrivia = d.build !== undefined ? buildReadsTrivia(d) : noBuildCaptures
|
|
1230
|
+
const omitsRaw = d.build !== undefined && !buildReadsRaw(d)
|
|
1049
1231
|
const derivedState = d.build !== undefined ? buildReadsState(d) : noBuildCaptures
|
|
1050
1232
|
const derivedFields = d.build !== undefined ? buildReadsFields(d) : noBuildCaptures
|
|
1051
1233
|
// Field capture additionally requires the body to CONTAIN `field()`
|
|
1052
1234
|
// captures: a node that reads fields but has none allocates nothing.
|
|
1053
1235
|
const wantsFields = parserHasOwnFields(d.parser) && (cstOut || derivedFields)
|
|
1054
1236
|
const flags = (cstOut || d.captureTrivia === true || d.trailingTrivia === true || derivedTrivia ? 4 : 0)
|
|
1237
|
+
| (!cstOut && omitsRaw ? 2 : 0)
|
|
1055
1238
|
| (cstOut || derivedState ? 8 : 0)
|
|
1056
1239
|
| (wantsFields ? 16 : 0)
|
|
1057
1240
|
| (d.collapse === true ? 32 : 0)
|
|
@@ -1091,8 +1274,9 @@ class Encoder {
|
|
|
1091
1274
|
const e = deriveExpected(d.parser)
|
|
1092
1275
|
return this.emit(OP_GATE, cls, body, this.expected(e.length > 0 ? e : [d.parser._tag]))
|
|
1093
1276
|
}
|
|
1094
|
-
case 'token':
|
|
1277
|
+
case 'token': {
|
|
1095
1278
|
return this.emit(OP_TOKEN, this.node(d.parser).ip)
|
|
1279
|
+
}
|
|
1096
1280
|
case 'routed':
|
|
1097
1281
|
return this.emit(OP_ROUTED, d.fallback === undefined ? -1 : this.node(d.fallback).ip)
|
|
1098
1282
|
case 'dispatch': {
|
|
@@ -1204,7 +1388,7 @@ class Encoder {
|
|
|
1204
1388
|
// merged (composed) map, and why the identity guard is required.
|
|
1205
1389
|
const refName = (p as unknown as { _ruleName?: string })._ruleName
|
|
1206
1390
|
const winner = refName === undefined ? undefined : this.winners?.[refName]
|
|
1207
|
-
if (winner !== undefined && !
|
|
1391
|
+
if (winner !== undefined && !winnerWrapsReference(winner, p)) return this.scopedRef(p, winner)
|
|
1208
1392
|
let resolved: Combinator<unknown>
|
|
1209
1393
|
try { resolved = d.thunk() }
|
|
1210
1394
|
catch {
|
|
@@ -1219,7 +1403,7 @@ class Encoder {
|
|
|
1219
1403
|
// other engine reported `['not(literal)']`. It hid because `expected` is a
|
|
1220
1404
|
// TOP-LEVEL field on `RunResult` and is NOT part of the identity digest.
|
|
1221
1405
|
case 'not':
|
|
1222
|
-
return this.emit(OP_NOT, this.node(d.parser).ip, this.expected(
|
|
1406
|
+
return this.emit(OP_NOT, this.node(d.parser).ip, this.expected(assertionFailureExpected(false, d.parser._tag)))
|
|
1223
1407
|
case 'guard':
|
|
1224
1408
|
// `'gate'`, the public name — see gate.ts. The def tag stays `'guard'`.
|
|
1225
1409
|
return this.emit(OP_GUARD, this.fn(d.predicate, d.predSrc ?? null), this.expected(['gate']))
|
|
@@ -1259,7 +1443,7 @@ class Encoder {
|
|
|
1259
1443
|
// lookahead's failure is "the guard did not hold", and naming the body's
|
|
1260
1444
|
// internals offers a token the parse never asked the author for. The
|
|
1261
1445
|
// table propagated the body's set instead.
|
|
1262
|
-
return this.emit(OP_PEEK, this.node(d.parser).ip, this.expected(
|
|
1446
|
+
return this.emit(OP_PEEK, this.node(d.parser).ip, this.expected(assertionFailureExpected(true, d.parser._tag)))
|
|
1263
1447
|
// A TRANSACTION IS A ROW. See `OP_ATTEMPT` for why the transparent
|
|
1264
1448
|
// lowering was correct only for a choice arm.
|
|
1265
1449
|
case 'attempt': {
|
|
@@ -1399,7 +1583,7 @@ class Encoder {
|
|
|
1399
1583
|
switch (this.code[ip]) {
|
|
1400
1584
|
case OP_GATE: return [ip + 2]
|
|
1401
1585
|
case OP_RULE: case OP_OPT: case OP_NOT: case OP_PEEK: case OP_EXPECT: case OP_ATTEMPT: case OP_LABEL: case OP_COV: return [ip + 1]
|
|
1402
|
-
case OP_SCOPE: case OP_SCOPE_CAP: case OP_WITHCTX: case OP_XFORM: case OP_LEAF: case OP_NODE: case OP_NODE_TRACK: return [ip + 2]
|
|
1586
|
+
case OP_SCOPE: case OP_SCOPE_CAP: case OP_SCOPE_PLAIN: case OP_WITHCTX: case OP_XFORM: case OP_LEAF: case OP_NODE: case OP_NODE_TRACK: return [ip + 2]
|
|
1403
1587
|
case OP_SEQ: case OP_SEQV: return Array.from({ length: this.code[ip + 1]! }, (_, i) => ip + 2 + i)
|
|
1404
1588
|
case OP_SEQX: return Array.from({ length: this.code[ip + 2]! }, (_, i) => ip + 3 + i)
|
|
1405
1589
|
// ARMS START AT ip+4. `ip+3` is the choice's own EXPECTED-SET index, and
|
|
@@ -1458,6 +1642,8 @@ class Encoder {
|
|
|
1458
1642
|
return ownTableProgram({
|
|
1459
1643
|
code: this.code, k: this.k, fns: this.fns, cc: this.cc,
|
|
1460
1644
|
fx: this.fx, disp: this.disp, dsp: this.dsp, rules: this.rules,
|
|
1645
|
+
...(this.lex.length === 0 ? {} : { lex: this.lex }),
|
|
1646
|
+
...(this.lexPrograms.length === 0 ? {} : { lexPrograms: this.lexPrograms }),
|
|
1461
1647
|
...(this.labels === undefined ? {} : { labels: this.labels }),
|
|
1462
1648
|
...(this.classified ? { classified: 1 as const } : {}),
|
|
1463
1649
|
...(this.scanSkipSets.length === 0
|
|
@@ -1561,7 +1747,13 @@ export function encodeTableProgram(
|
|
|
1561
1747
|
...(hostMode === undefined ? {} : { hostMode }),
|
|
1562
1748
|
...(track ? { trackLines: true } : {}),
|
|
1563
1749
|
}
|
|
1564
|
-
|
|
1750
|
+
// Collect once from the FINAL winner-resolved graph. Sorting affects only
|
|
1751
|
+
// content-id discovery; ordinary rule/code order remains the caller's order.
|
|
1752
|
+
const lexicalRoots = [...names].sort().map(name => ruleMap[name]!)
|
|
1753
|
+
const resolveLexical = (name: string): Combinator<unknown> | undefined => ruleMap[name]
|
|
1754
|
+
const capabilities = collectLexicalCapabilities(lexicalRoots, resolveLexical)
|
|
1755
|
+
assertLexicalCapabilityClosure(lexicalRoots, capabilities, resolveLexical)
|
|
1756
|
+
const enc = new Encoder(resolvedSettings, capabilities.capabilityComplete)
|
|
1565
1757
|
enc.winners = ruleMap
|
|
1566
1758
|
for (const name of names) enc.encodeRule(name, ruleMap[name]!)
|
|
1567
1759
|
const prog = enc.finish()
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ParseContext, ParseResult } from '../types.ts'
|
|
2
|
-
import { advanceTrivia, needsDeferredTriviaCommit, rollbackTrivia, saveTriviaMark,
|
|
2
|
+
import { advanceTrivia, commitTriviaScan, needsDeferredTriviaCommit, rollbackTrivia, saveTriviaMark, scanTriviaCompact } from '../combinators/trivia-skip.ts'
|
|
3
3
|
import {
|
|
4
4
|
beginCstNodeCapture, cstCaptureActive, endCstNodeCapture, pushCstChild,
|
|
5
5
|
pushCstLeaf, rollbackCstCapture, saveCstMark, type CstRollbackMark,
|
|
@@ -142,9 +142,7 @@ function makeDriver(
|
|
|
142
142
|
*/
|
|
143
143
|
function skipTrivia(input: string, cur: number, ctx: ParseContext): number {
|
|
144
144
|
if (needsDeferredTriviaCommit(ctx)) {
|
|
145
|
-
|
|
146
|
-
scan.commit()
|
|
147
|
-
return scan.end
|
|
145
|
+
return commitTriviaScan(scanTriviaCompact(input, cur, ctx))
|
|
148
146
|
}
|
|
149
147
|
return advanceTrivia(input, cur, ctx)
|
|
150
148
|
}
|
|
@@ -256,9 +254,7 @@ function makeDriver(
|
|
|
256
254
|
const mark = rollbackNeeded(ctx) ? saveTriviaMark(ctx) : null
|
|
257
255
|
let scanEnd: number
|
|
258
256
|
if (needsDeferredTriviaCommit(ctx)) {
|
|
259
|
-
|
|
260
|
-
scan.commit()
|
|
261
|
-
scanEnd = scan.end
|
|
257
|
+
scanEnd = commitTriviaScan(scanTriviaCompact(input, cur, ctx))
|
|
262
258
|
} else {
|
|
263
259
|
scanEnd = advanceTrivia(input, cur, ctx)
|
|
264
260
|
}
|