parseman 0.46.0 → 0.47.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 +8 -3
- package/dist/analysis/choice-cost-render.d.ts +12 -4
- package/dist/analysis/choice-cost-render.d.ts.map +1 -1
- package/dist/analysis/choice-cost.d.ts +2 -2
- package/dist/analysis/choice-cost.d.ts.map +1 -1
- package/dist/analysis/commitment.d.ts +131 -0
- package/dist/analysis/commitment.d.ts.map +1 -0
- package/dist/analysis/diagnostics.cjs +9929 -7465
- package/dist/analysis/diagnostics.cjs.map +3 -4
- package/dist/analysis/diagnostics.d.ts +2 -2
- package/dist/analysis/diagnostics.js +9929 -7465
- package/dist/analysis/diagnostics.js.map +3 -4
- package/dist/analysis/duplication.d.ts.map +1 -1
- package/dist/analysis/fix.d.ts +3 -3
- package/dist/analysis/fix.d.ts.map +1 -1
- package/dist/analysis/format-number.d.ts +23 -0
- package/dist/analysis/format-number.d.ts.map +1 -0
- package/dist/analysis/gating.d.ts +39 -2
- package/dist/analysis/gating.d.ts.map +1 -1
- package/dist/analysis/rebuild.d.ts.map +1 -1
- package/dist/analysis/terminal.d.ts +5 -2
- package/dist/analysis/terminal.d.ts.map +1 -1
- package/dist/cli/index.cjs +10865 -8535
- package/dist/cli/index.cjs.map +3 -4
- package/dist/cli/index.js +10865 -8535
- package/dist/cli/index.js.map +3 -4
- package/dist/combinators/adjacency.d.ts +100 -0
- package/dist/combinators/adjacency.d.ts.map +1 -0
- package/dist/combinators/choice.d.ts.map +1 -1
- package/dist/combinators/completions.d.ts.map +1 -1
- package/dist/combinators/dispatch.d.ts +8 -1
- package/dist/combinators/dispatch.d.ts.map +1 -1
- package/dist/combinators/expect.d.ts.map +1 -1
- package/dist/combinators/first-set.d.ts.map +1 -1
- package/dist/combinators/gate.d.ts.map +1 -1
- package/dist/combinators/grammar.d.ts +22 -0
- package/dist/combinators/grammar.d.ts.map +1 -1
- package/dist/combinators/literal.d.ts +8 -0
- package/dist/combinators/literal.d.ts.map +1 -1
- package/dist/combinators/map.d.ts +0 -1
- package/dist/combinators/map.d.ts.map +1 -1
- package/dist/combinators/node.d.ts +13 -3
- package/dist/combinators/node.d.ts.map +1 -1
- package/dist/combinators/parser.d.ts +1 -1
- package/dist/combinators/parser.d.ts.map +1 -1
- package/dist/combinators/ref.d.ts.map +1 -1
- package/dist/combinators/regex.d.ts +1 -16
- package/dist/combinators/regex.d.ts.map +1 -1
- package/dist/combinators/repeat.d.ts +30 -2
- package/dist/combinators/repeat.d.ts.map +1 -1
- package/dist/combinators/scanTo.d.ts +71 -1
- package/dist/combinators/scanTo.d.ts.map +1 -1
- package/dist/combinators/sequence.d.ts.map +1 -1
- package/dist/combinators/token.d.ts.map +1 -1
- package/dist/combinators/trivia-boundary.d.ts +26 -0
- package/dist/combinators/trivia-boundary.d.ts.map +1 -0
- package/dist/combinators/trivia-skip.d.ts +40 -1
- package/dist/combinators/trivia-skip.d.ts.map +1 -1
- package/dist/combinators/withCtx.d.ts.map +1 -1
- package/dist/compiler/compile-linkable-table.d.ts +104 -0
- package/dist/compiler/compile-linkable-table.d.ts.map +1 -0
- package/dist/compiler/degradation.d.ts +0 -2
- package/dist/compiler/degradation.d.ts.map +1 -1
- package/dist/compiler/fields.d.ts +13 -0
- package/dist/compiler/fields.d.ts.map +1 -1
- package/dist/compiler/grammar-coverage-ids.d.ts +5 -5
- package/dist/compiler/grammar-coverage-ids.d.ts.map +1 -1
- package/dist/compiler/ir-serialize.d.ts.map +1 -1
- package/dist/compiler/linker.d.ts +47 -53
- package/dist/compiler/linker.d.ts.map +1 -1
- package/dist/compiler/token-alphabet.d.ts +81 -0
- package/dist/compiler/token-alphabet.d.ts.map +1 -0
- package/dist/compiler/token-dispatch.d.ts +95 -0
- package/dist/compiler/token-dispatch.d.ts.map +1 -0
- package/dist/compiler/token-scanner.d.ts +53 -0
- package/dist/compiler/token-scanner.d.ts.map +1 -0
- package/dist/compiler/value-usage.d.ts.map +1 -1
- package/dist/coverage.d.ts +1 -1
- package/dist/coverage.d.ts.map +1 -1
- package/dist/cst/capture-buffer.d.ts +35 -0
- package/dist/cst/capture-buffer.d.ts.map +1 -1
- package/dist/cst/reflection-attach.d.ts +8 -0
- package/dist/cst/reflection-attach.d.ts.map +1 -0
- package/dist/cst/reflection-symbols.d.ts +10 -0
- package/dist/cst/reflection-symbols.d.ts.map +1 -0
- package/dist/cst/reflection.d.ts +2 -4
- package/dist/cst/reflection.d.ts.map +1 -1
- package/dist/cst/root-trivia-scope.d.ts +26 -0
- package/dist/cst/root-trivia-scope.d.ts.map +1 -0
- package/dist/cst/trivia-charscan.d.ts +18 -0
- package/dist/cst/trivia-charscan.d.ts.map +1 -0
- package/dist/cst/trivia-kinds.d.ts +19 -0
- package/dist/cst/trivia-kinds.d.ts.map +1 -1
- package/dist/functional/doc.d.ts.map +1 -1
- package/dist/functional/run-tabled.d.ts +12 -0
- package/dist/functional/run-tabled.d.ts.map +1 -0
- package/dist/functional/run.d.ts +25 -42
- package/dist/functional/run.d.ts.map +1 -1
- package/dist/grammar-metadata.d.ts +3 -0
- package/dist/grammar-metadata.d.ts.map +1 -0
- package/dist/index.cjs +9224 -7203
- package/dist/index.cjs.map +3 -4
- package/dist/index.d.ts +18 -9
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9219 -7198
- package/dist/index.js.map +3 -4
- package/dist/language-service/index.cjs +311 -121
- package/dist/language-service/index.cjs.map +3 -4
- package/dist/language-service/index.js +311 -121
- package/dist/language-service/index.js.map +3 -4
- package/dist/oracle/index.cjs.map +0 -1
- package/dist/oracle/index.js.map +0 -1
- package/dist/parse-context.d.ts +49 -0
- package/dist/parse-context.d.ts.map +1 -0
- package/dist/plugin/evaluator.d.ts +40 -1
- package/dist/plugin/evaluator.d.ts.map +1 -1
- package/dist/plugin/index.cjs +11146 -9593
- package/dist/plugin/index.cjs.map +3 -4
- package/dist/plugin/index.d.ts.map +1 -1
- package/dist/plugin/index.js +11146 -9593
- package/dist/plugin/index.js.map +3 -4
- package/dist/regex/classes.d.ts +28 -0
- package/dist/regex/classes.d.ts.map +1 -1
- package/dist/regex/first-set.d.ts +14 -0
- package/dist/regex/first-set.d.ts.map +1 -1
- package/dist/run/index.cjs +136 -51
- package/dist/run/index.cjs.map +2 -3
- package/dist/run/index.js +136 -51
- package/dist/run/index.js.map +2 -3
- package/dist/spec/index.cjs +2898 -1623
- package/dist/spec/index.cjs.map +3 -4
- package/dist/spec/index.js +2898 -1623
- package/dist/spec/index.js.map +3 -4
- package/dist/spec/model.d.ts +8 -0
- package/dist/spec/model.d.ts.map +1 -1
- package/dist/table/assemble.d.ts +307 -0
- package/dist/table/assemble.d.ts.map +1 -0
- package/dist/table/cell.d.ts +42 -0
- package/dist/table/cell.d.ts.map +1 -0
- package/dist/table/child-slots.d.ts +30 -0
- package/dist/table/child-slots.d.ts.map +1 -0
- package/dist/table/compile-rule-map.d.ts +136 -0
- package/dist/table/compile-rule-map.d.ts.map +1 -0
- package/dist/table/compile.d.ts +56 -0
- package/dist/table/compile.d.ts.map +1 -0
- package/dist/table/duplication-hook.d.ts +32 -0
- package/dist/table/duplication-hook.d.ts.map +1 -0
- package/dist/table/emit-assembly.d.ts +139 -0
- package/dist/table/emit-assembly.d.ts.map +1 -0
- package/dist/table/emit.d.ts +113 -0
- package/dist/table/emit.d.ts.map +1 -0
- package/dist/table/encode-baseline.d.ts +16 -0
- package/dist/table/encode-baseline.d.ts.map +1 -0
- package/dist/table/encode.d.ts +71 -0
- package/dist/table/encode.d.ts.map +1 -0
- package/dist/table/exec-baseline.d.ts +9 -0
- package/dist/table/exec-baseline.d.ts.map +1 -0
- package/dist/table/exec.d.ts +72 -0
- package/dist/table/exec.d.ts.map +1 -0
- package/dist/table/fold.d.ts +25 -0
- package/dist/table/fold.d.ts.map +1 -0
- package/dist/table/index.cjs +11375 -0
- package/dist/table/index.cjs.map +6 -0
- package/dist/table/index.d.ts +100 -0
- package/dist/table/index.d.ts.map +1 -0
- package/dist/table/index.js +11352 -0
- package/dist/table/index.js.map +6 -0
- package/dist/table/inspect.d.ts +17 -0
- package/dist/table/inspect.d.ts.map +1 -0
- package/dist/table/ops.d.ts +446 -0
- package/dist/table/ops.d.ts.map +1 -0
- package/dist/table/program.d.ts +493 -0
- package/dist/table/program.d.ts.map +1 -0
- package/dist/table/run-support.d.ts +38 -0
- package/dist/table/run-support.d.ts.map +1 -0
- package/dist/{compiler/scannable-run.d.ts → table/scan-shapes.d.ts} +14 -13
- package/dist/table/scan-shapes.d.ts.map +1 -0
- package/dist/table/site-labels.d.ts +58 -0
- package/dist/table/site-labels.d.ts.map +1 -0
- package/dist/table/stamp.d.ts +70 -0
- package/dist/table/stamp.d.ts.map +1 -0
- package/dist/types.d.ts +80 -24
- package/dist/types.d.ts.map +1 -1
- package/dist/version.d.ts +1 -1
- package/package.json +25 -2
- package/src/analysis/choice-cost-gate.ts +359 -0
- package/src/analysis/choice-cost-render.ts +293 -0
- package/src/analysis/choice-cost.ts +1008 -0
- package/src/analysis/commitment.ts +557 -0
- package/src/analysis/corpus.ts +130 -0
- package/src/analysis/diagnose-render.ts +625 -0
- package/src/analysis/diagnose.ts +317 -0
- package/src/analysis/diagnostics.ts +33 -0
- package/src/analysis/duplication.ts +1955 -0
- package/src/analysis/fix-render.ts +217 -0
- package/src/analysis/fix.ts +669 -0
- package/src/analysis/format-number.ts +31 -0
- package/src/analysis/gating.ts +792 -0
- package/src/analysis/grammar.ts +72 -0
- package/src/analysis/rebuild.ts +287 -0
- package/src/analysis/terminal.ts +336 -0
- package/src/cli/index.ts +413 -0
- package/src/combinators/adjacency.ts +171 -0
- package/src/combinators/attempt.ts +41 -0
- package/src/combinators/case-fold.ts +90 -0
- package/src/combinators/choice.ts +466 -0
- package/src/combinators/completions.ts +64 -0
- package/src/combinators/dispatch.ts +358 -0
- package/src/combinators/expect.ts +159 -0
- package/src/combinators/first-set.ts +322 -0
- package/src/combinators/gate.ts +50 -0
- package/src/combinators/grammar.ts +272 -0
- package/src/combinators/keywords.ts +145 -0
- package/src/combinators/literal.ts +102 -0
- package/src/combinators/map.ts +106 -0
- package/src/combinators/node.ts +345 -0
- package/src/combinators/not.ts +55 -0
- package/src/combinators/parser.ts +266 -0
- package/src/combinators/peek.ts +63 -0
- package/src/combinators/probe.ts +25 -0
- package/src/combinators/ref.ts +109 -0
- package/src/combinators/regex.ts +170 -0
- package/src/combinators/repeat.ts +561 -0
- package/src/combinators/scanTo.ts +393 -0
- package/src/combinators/sequence.ts +221 -0
- package/src/combinators/token.ts +120 -0
- package/src/combinators/trivia-boundary.ts +48 -0
- package/src/combinators/trivia-skip.ts +821 -0
- package/src/combinators/withCtx.ts +45 -0
- package/src/compiler/build-arity.ts +330 -0
- package/src/compiler/compile-linkable-table.ts +209 -0
- package/src/compiler/degradation.ts +266 -0
- package/src/compiler/fields.ts +191 -0
- package/src/compiler/grammar-coverage-ids.ts +129 -0
- package/src/compiler/ir-serialize.ts +615 -0
- package/src/compiler/linker.ts +954 -0
- package/src/compiler/token-alphabet.ts +209 -0
- package/src/compiler/token-dispatch.ts +353 -0
- package/src/compiler/token-scanner.ts +318 -0
- package/src/compiler/value-usage.ts +147 -0
- package/src/coverage.ts +383 -0
- package/src/cst/capture-buffer.ts +297 -0
- package/src/cst/host-mode.ts +69 -0
- package/src/cst/reflection-attach.ts +11 -0
- package/src/cst/reflection-symbols.ts +9 -0
- package/src/cst/reflection.ts +149 -0
- package/src/cst/relative-spans.ts +207 -0
- package/src/cst/root-trivia-scope.ts +30 -0
- package/src/cst/trivia-charscan.ts +312 -0
- package/src/cst/trivia-entries.ts +285 -0
- package/src/cst/trivia-index.ts +131 -0
- package/src/cst/trivia-kinds.ts +358 -0
- package/src/cst/types.ts +76 -0
- package/src/cst/walk.ts +101 -0
- package/src/functional/doc.ts +959 -0
- package/src/functional/run-tabled.ts +153 -0
- package/src/functional/run.ts +558 -0
- package/src/grammar-metadata.ts +2 -0
- package/src/index.ts +189 -0
- package/src/language-service/index.ts +221 -0
- package/src/line-index.ts +131 -0
- package/src/oracle/digest.ts +620 -0
- package/src/oracle/index.ts +55 -0
- package/src/parse-context.ts +91 -0
- package/src/plugin/direct-builder-static.ts +102 -0
- package/src/plugin/evaluator.ts +1505 -0
- package/src/plugin/free-identifiers.ts +338 -0
- package/src/plugin/index.ts +2281 -0
- package/src/plugin/reducer-resolver.ts +606 -0
- package/src/recovery/scan.ts +155 -0
- package/src/regex/classes.ts +183 -0
- package/src/regex/first-set.ts +316 -0
- package/src/run/index.ts +31 -0
- package/src/spec/ebnf.ts +128 -0
- package/src/spec/index.ts +54 -0
- package/src/spec/model.ts +636 -0
- package/src/spec/railroad-lib.ts +559 -0
- package/src/spec/railroad.ts +256 -0
- package/src/table/assemble.ts +2994 -0
- package/src/table/cell.ts +42 -0
- package/src/table/child-slots.ts +113 -0
- package/src/table/compile-rule-map.ts +336 -0
- package/src/table/compile.ts +244 -0
- package/src/table/duplication-hook.ts +89 -0
- package/src/table/emit-assembly.ts +1766 -0
- package/src/table/emit.ts +418 -0
- package/src/table/encode-baseline.ts +340 -0
- package/src/table/encode.ts +1569 -0
- package/src/table/exec-baseline.ts +537 -0
- package/src/table/exec.ts +1672 -0
- package/src/table/fold.ts +38 -0
- package/src/table/index.ts +108 -0
- package/src/table/inspect.ts +52 -0
- package/src/table/ops.ts +459 -0
- package/src/table/program.ts +855 -0
- package/src/table/run-support.ts +66 -0
- package/src/table/scan-shapes.ts +1578 -0
- package/src/table/site-labels.ts +228 -0
- package/src/table/stamp.ts +229 -0
- package/src/types.ts +563 -0
- package/src/version.ts +16 -0
- package/dist/compiler/codegen.d.ts +0 -334
- package/dist/compiler/codegen.d.ts.map +0 -1
- package/dist/compiler/inline-build.d.ts +0 -11
- package/dist/compiler/inline-build.d.ts.map +0 -1
- package/dist/compiler/inline-callback.d.ts +0 -21
- package/dist/compiler/inline-callback.d.ts.map +0 -1
- package/dist/compiler/line-index.d.ts +0 -2
- package/dist/compiler/line-index.d.ts.map +0 -1
- package/dist/compiler/module-hoist.d.ts +0 -106
- package/dist/compiler/module-hoist.d.ts.map +0 -1
- package/dist/compiler/scannable-run.d.ts.map +0 -1
- package/dist/compiler/scannable-terminal.d.ts +0 -23
- package/dist/compiler/scannable-terminal.d.ts.map +0 -1
- package/dist/compiler/trivia-fast-path.d.ts +0 -38
- package/dist/compiler/trivia-fast-path.d.ts.map +0 -1
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
import type { Combinator, FirstSet, ParserDef } from '../types.ts'
|
|
2
|
+
import { firstSetOf, matchesEmpty } from '../combinators/first-set.ts'
|
|
3
|
+
import { deriveExpected } from '../combinators/expect.ts'
|
|
4
|
+
import { buildReadsState, buildReadsTrivia } from '../compiler/build-arity.ts'
|
|
5
|
+
import {
|
|
6
|
+
OP_CHOICE, OP_EMPTY, OP_GATE, OP_LEAF, OP_LIT, OP_NODE, OP_NOT, OP_OPT,
|
|
7
|
+
OP_PEEK, OP_REP, OP_REPV, OP_RULE, OP_RX, OP_SEQ, OP_SEQV, OP_XFORM,
|
|
8
|
+
OP_LIT_TRACK, OP_RX_TRACK, OP_NODE_TRACK, OP_SCOPE, OP_EXPECT,
|
|
9
|
+
} from './ops.ts'
|
|
10
|
+
import type { TableProgram } from './program.ts'
|
|
11
|
+
|
|
12
|
+
/** Raised when a construct has no opcode yet. Prototype scope is explicit. */
|
|
13
|
+
class UnsupportedConstructBaseline extends Error {
|
|
14
|
+
readonly tag: string
|
|
15
|
+
constructor(tag: string) { super(`table lowering: no opcode for '${tag}'`); this.tag = tag }
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** Settings that select the TABLE'S CONTENTS. They never reach the driver. */
|
|
19
|
+
export type TableSettings = {
|
|
20
|
+
readonly hostMode?: 'ast' | 'cst'
|
|
21
|
+
readonly trackLines?: boolean
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
type Emitted = { readonly ip: number }
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Do these first sets share no character?
|
|
28
|
+
*
|
|
29
|
+
* `any` and `empty` are never disjoint from anything — an over-approximating arm
|
|
30
|
+
* has to fall back to ordered first-match or the choice silently picks the wrong
|
|
31
|
+
* one. See the `Keyword`-ahead-of-`Num` case in the choice encoder.
|
|
32
|
+
*/
|
|
33
|
+
function disjointSets(sets: readonly FirstSet[]): boolean {
|
|
34
|
+
const seen: Array<{ lo: number; hi: number }> = []
|
|
35
|
+
for (const fs of sets) {
|
|
36
|
+
if (fs.kind !== 'ranges' || fs.ranges.length === 0) return false
|
|
37
|
+
for (const r of fs.ranges) {
|
|
38
|
+
for (const p of seen) if (r.lo <= p.hi && p.lo <= r.hi) return false
|
|
39
|
+
seen.push(r)
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return true
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
class Encoder {
|
|
46
|
+
code: number[] = []
|
|
47
|
+
k: unknown[] = []
|
|
48
|
+
fns: unknown[] = []
|
|
49
|
+
cc: string[] = []
|
|
50
|
+
fx: (readonly string[])[] = []
|
|
51
|
+
disp: (readonly number[])[] = []
|
|
52
|
+
rules: Record<string, number> = {}
|
|
53
|
+
|
|
54
|
+
private kIndex = new Map<unknown, number>()
|
|
55
|
+
private ccIndex = new Map<string, number>()
|
|
56
|
+
private fxIndex = new Map<string, number>()
|
|
57
|
+
/** Memoized by combinator identity: a shared sub-combinator is ONE row. */
|
|
58
|
+
private memo = new Map<Combinator<unknown>, number>()
|
|
59
|
+
private pending = new Map<Combinator<unknown>, number[]>()
|
|
60
|
+
|
|
61
|
+
readonly settings: TableSettings
|
|
62
|
+
/** Resolved once, HERE, at table-build time — never consulted at run time. */
|
|
63
|
+
readonly track: boolean
|
|
64
|
+
constructor(settings: TableSettings) {
|
|
65
|
+
this.settings = settings
|
|
66
|
+
this.track = settings.trackLines === true
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
private constant(v: unknown): number {
|
|
70
|
+
const key = v instanceof RegExp ? `re:${JSON.stringify([v.source, v.flags])}` : v
|
|
71
|
+
const hit = this.kIndex.get(key)
|
|
72
|
+
if (hit !== undefined) return hit
|
|
73
|
+
const i = this.k.length
|
|
74
|
+
this.k.push(v)
|
|
75
|
+
this.kIndex.set(key, i)
|
|
76
|
+
return i
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
private fn(v: unknown): number {
|
|
80
|
+
const i = this.fns.length
|
|
81
|
+
this.fns.push(v)
|
|
82
|
+
return i
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
private expected(list: readonly string[]): number {
|
|
86
|
+
const key = JSON.stringify(list)
|
|
87
|
+
const hit = this.fxIndex.get(key)
|
|
88
|
+
if (hit !== undefined) return hit
|
|
89
|
+
const i = this.fx.length
|
|
90
|
+
this.fx.push(list)
|
|
91
|
+
this.fxIndex.set(key, i)
|
|
92
|
+
return i
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** A first set becomes a char class STRING of `[lo, hi]` pairs, or −1 for `any`. */
|
|
96
|
+
private charClass(fs: FirstSet): number {
|
|
97
|
+
if (fs.kind !== 'ranges' || fs.ranges.length === 0) return -1
|
|
98
|
+
let spec = ''
|
|
99
|
+
for (const r of fs.ranges) spec += String.fromCharCode(r.lo, r.hi)
|
|
100
|
+
const hit = this.ccIndex.get(spec)
|
|
101
|
+
if (hit !== undefined) return hit
|
|
102
|
+
const i = this.cc.length
|
|
103
|
+
this.cc.push(spec)
|
|
104
|
+
this.ccIndex.set(spec, i)
|
|
105
|
+
return i
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
private emit(...words: number[]): number {
|
|
109
|
+
const ip = this.code.length
|
|
110
|
+
for (const w of words) this.code.push(w)
|
|
111
|
+
return ip
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** Reserve `n` operand slots to patch once children are laid out. */
|
|
115
|
+
private emitHead(op: number, n: number): number {
|
|
116
|
+
const ip = this.code.length
|
|
117
|
+
this.code.push(op)
|
|
118
|
+
for (let i = 0; i < n; i++) this.code.push(0)
|
|
119
|
+
return ip
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
encodeRule(name: string, p: Combinator<unknown>): void {
|
|
123
|
+
this.rules[name] = this.node(p).ip
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
node(p: Combinator<unknown>): Emitted {
|
|
127
|
+
const hit = this.memo.get(p)
|
|
128
|
+
if (hit !== undefined) return { ip: hit }
|
|
129
|
+
// Recursion: reserve a trampoline row now, patch its target when the real
|
|
130
|
+
// body lands. One extra indirection per recursive rule, zero per call site.
|
|
131
|
+
const inFlight = this.pending.get(p)
|
|
132
|
+
if (inFlight !== undefined) {
|
|
133
|
+
const ip = this.emitHead(OP_RULE, 1)
|
|
134
|
+
inFlight.push(ip + 1)
|
|
135
|
+
return { ip }
|
|
136
|
+
}
|
|
137
|
+
const patches: number[] = []
|
|
138
|
+
this.pending.set(p, patches)
|
|
139
|
+
const ip = this.encodeDef(p)
|
|
140
|
+
this.pending.delete(p)
|
|
141
|
+
this.memo.set(p, ip)
|
|
142
|
+
for (const slot of patches) this.code[slot] = ip
|
|
143
|
+
return { ip }
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
private encodeDef(p: Combinator<unknown>): number {
|
|
147
|
+
const d = p._def as ParserDef
|
|
148
|
+
// REFUSE what this frozen copy cannot lower, rather than lowering it wrong.
|
|
149
|
+
//
|
|
150
|
+
// `scanTo` and `token` have no `case` below, so they reach `default:` and throw
|
|
151
|
+
// — a refusal, which is safe. `balanced()` does NOT: it overrides `.parse` and
|
|
152
|
+
// leaves `_def` as the EAGER INTERIOR with no distinguishing tag, so the switch
|
|
153
|
+
// would encode the interior, report nothing, and produce a table that parses and
|
|
154
|
+
// yields a different tree. An ablation measurement taken on a grammar using
|
|
155
|
+
// `balanced()` would then be comparing against a parser that is not the one
|
|
156
|
+
// under test. Refused explicitly, since this copy has no OP_CALL to fall back on.
|
|
157
|
+
if ((p as { _balancedAmbient?: unknown })._balancedAmbient !== undefined) {
|
|
158
|
+
throw new UnsupportedConstructBaseline('balanced() — the frozen ablation encoder cannot lower it')
|
|
159
|
+
}
|
|
160
|
+
switch (d.tag) {
|
|
161
|
+
case 'literal': {
|
|
162
|
+
if (d.caseInsensitive) throw new UnsupportedConstructBaseline('literal(caseInsensitive)')
|
|
163
|
+
return this.emit(this.track ? OP_LIT_TRACK : OP_LIT, this.constant(d.value), this.expected(deriveExpected(p)))
|
|
164
|
+
}
|
|
165
|
+
case 'regex': {
|
|
166
|
+
const flags = d.flags.includes('y') ? d.flags : `${d.flags}y`
|
|
167
|
+
return this.emit(this.track ? OP_RX_TRACK : OP_RX, this.constant(new RegExp(d.source, flags)), this.expected(deriveExpected(p)))
|
|
168
|
+
}
|
|
169
|
+
case 'keywords': {
|
|
170
|
+
// `keywords()` compiles to ONE sticky regex and pushes a leaf — which is
|
|
171
|
+
// exactly what OP_RX does. Rebuilding the same regex here needs no new
|
|
172
|
+
// opcode. Construction mirrors src/combinators/keywords.ts:87-106; the
|
|
173
|
+
// words arrive already sorted longest-first on the def.
|
|
174
|
+
const alt = d.words.map(w => w.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')).join('|')
|
|
175
|
+
const boundary = d.boundary ? `(?![${d.boundary}])` : ''
|
|
176
|
+
const flags = d.caseInsensitive ? 'iy' : 'uy'
|
|
177
|
+
return this.emit(
|
|
178
|
+
this.track ? OP_RX_TRACK : OP_RX,
|
|
179
|
+
this.constant(new RegExp(`(?:${alt})${boundary}`, flags)),
|
|
180
|
+
this.expected(deriveExpected(p)),
|
|
181
|
+
)
|
|
182
|
+
}
|
|
183
|
+
case 'expect': {
|
|
184
|
+
// `expect()` NEVER fails: on a failed child it yields a ParseError value
|
|
185
|
+
// at zero width. That is a distinct outcome, so it gets its own row.
|
|
186
|
+
return this.emit(OP_EXPECT, this.node(d.parser).ip, this.expected([...d.expected]))
|
|
187
|
+
}
|
|
188
|
+
case 'sequence': {
|
|
189
|
+
const kids = d.parsers.map(c => this.node(c).ip)
|
|
190
|
+
const head = this.emitHead(d.valueUnused ? OP_SEQV : OP_SEQ, 1 + kids.length)
|
|
191
|
+
this.code[head + 1] = kids.length
|
|
192
|
+
for (let i = 0; i < kids.length; i++) this.code[head + 2 + i] = kids[i]!
|
|
193
|
+
return head
|
|
194
|
+
}
|
|
195
|
+
case 'choice': {
|
|
196
|
+
if (d.gates.some(g => g !== null)) throw new UnsupportedConstructBaseline('choice(gate:)')
|
|
197
|
+
// Arm ORDER is semantics. `literalsLongestFirst` / `sharedPrefix` /
|
|
198
|
+
// `greedyClassify` reorder or restructure the arms, so encoding them as
|
|
199
|
+
// a plain ordered choice would pick a different arm and build a
|
|
200
|
+
// different tree while still parsing. Refuse them rather than lower them
|
|
201
|
+
// wrong.
|
|
202
|
+
// `greedyClassify` runs ONE arm and then re-attributes the match to a
|
|
203
|
+
// DIFFERENT arm by string equality, re-applying that arm's transforms.
|
|
204
|
+
// That is a different execution, not a different order, so it is refused
|
|
205
|
+
// rather than approximated.
|
|
206
|
+
if (d.strategy.tag === 'greedyClassify') throw new UnsupportedConstructBaseline('choice(strategy=greedyClassify)')
|
|
207
|
+
// `autoNot` rejects an arm that matched but is followed by a char in a
|
|
208
|
+
// sibling's first set, so a later arm can win. Ignoring it would pick a
|
|
209
|
+
// different arm and build a different tree behind a successful parse.
|
|
210
|
+
if (d.autoNot.some(a => a !== null)) throw new UnsupportedConstructBaseline('choice(autoNot)')
|
|
211
|
+
// ORDER is the only thing the remaining strategies change, and order is
|
|
212
|
+
// table data. `literalsLongestFirst` carries its order explicitly;
|
|
213
|
+
// `sharedPrefix` is documented in choice.ts:52 as a firstMatch
|
|
214
|
+
// specialization, so declared order is already correct for it.
|
|
215
|
+
const order = d.strategy.tag === 'literalsLongestFirst'
|
|
216
|
+
? d.strategy.sortedIndices
|
|
217
|
+
: d.parsers.map((_, i) => i)
|
|
218
|
+
const arms = order.map(i => d.parsers[i]!)
|
|
219
|
+
const kids = arms.map(c => this.node(c).ip)
|
|
220
|
+
// O(1) first-char dispatch is only SOUND when the arms are disjoint.
|
|
221
|
+
// With overlapping first sets, "the first arm whose class contains this
|
|
222
|
+
// char" is not "the first arm that matches": a `Keyword` arm whose first
|
|
223
|
+
// set over-approximates to include digits was selected ahead of `Num`
|
|
224
|
+
// for the input `0` — the parse still succeeded, and only the TREE moved.
|
|
225
|
+
// Disjointness is computed HERE, not read off `d.disjoint`. That flag is
|
|
226
|
+
// set when `choice()` is CONSTRUCTED, at which point `g.X` arms are
|
|
227
|
+
// unresolved refs whose first set is `any` — so every recursive grammar
|
|
228
|
+
// reports non-disjoint and loses its dispatch. (Codegen has the same
|
|
229
|
+
// problem and solves it the same way: a placeholder resolved at fuse.)
|
|
230
|
+
let dispIdx = -1
|
|
231
|
+
if (arms.every(a => !matchesEmpty(a))) {
|
|
232
|
+
const sets = arms.map(a => firstSetOf(a))
|
|
233
|
+
if (disjointSets(sets)) {
|
|
234
|
+
const classes = sets.map(fs => this.charClass(fs))
|
|
235
|
+
if (classes.every(c => c >= 0)) {
|
|
236
|
+
dispIdx = this.disp.length
|
|
237
|
+
this.disp.push(classes)
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
const head = this.emitHead(OP_CHOICE, 2 + kids.length)
|
|
242
|
+
this.code[head + 1] = dispIdx
|
|
243
|
+
this.code[head + 2] = kids.length
|
|
244
|
+
for (let i = 0; i < kids.length; i++) this.code[head + 3 + i] = kids[i]!
|
|
245
|
+
return head
|
|
246
|
+
}
|
|
247
|
+
case 'many':
|
|
248
|
+
case 'oneOrMore': {
|
|
249
|
+
const child = this.node(d.parser).ip
|
|
250
|
+
return this.emit(
|
|
251
|
+
d.valueUnused ? OP_REPV : OP_REP,
|
|
252
|
+
child, d.tag === 'many' ? 0 : d.min, d.max ?? -1, -1, 0,
|
|
253
|
+
)
|
|
254
|
+
}
|
|
255
|
+
case 'sepBy': {
|
|
256
|
+
const child = this.node(d.parser).ip
|
|
257
|
+
const sep = this.node(d.separator).ip
|
|
258
|
+
return this.emit(OP_REP, child, d.min, d.max ?? -1, sep, d.trailing === 'allow' ? 1 : 0)
|
|
259
|
+
}
|
|
260
|
+
case 'optional':
|
|
261
|
+
return this.emit(OP_OPT, this.node(d.parser).ip)
|
|
262
|
+
case 'transform': {
|
|
263
|
+
const child = this.node(d.parser).ip
|
|
264
|
+
return this.emit(OP_XFORM, this.fn(d.fn), child)
|
|
265
|
+
}
|
|
266
|
+
case 'leaf': {
|
|
267
|
+
const child = this.node(d.parser).ip
|
|
268
|
+
return this.emit(OP_LEAF, this.fn(d.fn), child)
|
|
269
|
+
}
|
|
270
|
+
case 'node': {
|
|
271
|
+
if (d.unwrap || d.collapse || d.project !== undefined) throw new UnsupportedConstructBaseline('node(unwrap|collapse|project)')
|
|
272
|
+
if (d.build === undefined) throw new UnsupportedConstructBaseline('node(no build)')
|
|
273
|
+
const child = this.node(d.parser).ip
|
|
274
|
+
// Capture flags, resolved HERE from the reducer's declared arity using the
|
|
275
|
+
// same analysis codegen runs (`src/compiler/build-arity.ts`). `hostMode:
|
|
276
|
+
// 'cst'` forces them on, exactly as the emitted `cstOut` path does. The
|
|
277
|
+
// driver reads a bit; it re-derives nothing and sees no setting.
|
|
278
|
+
const cstOut = this.settings.hostMode === 'cst'
|
|
279
|
+
const flags = (cstOut || buildReadsTrivia(d) ? 4 : 0) | (cstOut || buildReadsState(d) ? 8 : 0)
|
|
280
|
+
const body = this.emit(this.track ? OP_NODE_TRACK : OP_NODE, this.fn(d.build), child, flags)
|
|
281
|
+
// The rule's own first-set gate — the emitted code's `_ngc` test, as data.
|
|
282
|
+
// A NULLABLE rule has no gate: it succeeds on input its first set does
|
|
283
|
+
// not contain (including EOF), so gating it would reject a legal empty
|
|
284
|
+
// match. Caught by the ladder's `empty` and `garbage` cases.
|
|
285
|
+
if (matchesEmpty(p)) return body
|
|
286
|
+
const cls = this.charClass(firstSetOf(p))
|
|
287
|
+
if (cls < 0) return body
|
|
288
|
+
return this.emit(OP_GATE, cls, body, this.expected(deriveExpected(p)))
|
|
289
|
+
}
|
|
290
|
+
case 'lazy':
|
|
291
|
+
// A named reference is not a hop: it resolves to the target's row.
|
|
292
|
+
// Emitting a trampoline here cost one dispatch per reference for nothing.
|
|
293
|
+
return this.node(d.thunk()).ip
|
|
294
|
+
case 'not':
|
|
295
|
+
return this.emit(OP_NOT, this.node(d.parser).ip)
|
|
296
|
+
case 'peek':
|
|
297
|
+
return this.emit(OP_PEEK, this.node(d.parser).ip)
|
|
298
|
+
// Transparent wrappers: no row of their own, no dispatch at run time.
|
|
299
|
+
case 'token':
|
|
300
|
+
case 'attempt':
|
|
301
|
+
case 'label':
|
|
302
|
+
case 'trivia':
|
|
303
|
+
return this.node(d.parser).ip
|
|
304
|
+
case 'grammar': {
|
|
305
|
+
// A trivia scope is a ROW, not a lowering decision: the scope's trivia
|
|
306
|
+
// combinator goes in the const pool and the driver installs it.
|
|
307
|
+
if (d.clearTrivia === true) return this.emit(OP_SCOPE, -1, this.node(d.parser).ip)
|
|
308
|
+
if (d.triviaParser === undefined) return this.node(d.parser).ip
|
|
309
|
+
return this.emit(OP_SCOPE, this.constant(d.triviaParser), this.node(d.parser).ip)
|
|
310
|
+
}
|
|
311
|
+
default:
|
|
312
|
+
throw new UnsupportedConstructBaseline(d.tag)
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
finish(): TableProgram {
|
|
317
|
+
if (this.code.length === 0) this.emit(OP_EMPTY)
|
|
318
|
+
return {
|
|
319
|
+
code: this.code, k: this.k, fns: this.fns, cc: this.cc,
|
|
320
|
+
fx: this.fx, disp: this.disp, dsp: [], rules: this.rules,
|
|
321
|
+
lines: this.track ? 1 : 0,
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* Encode a rule map into ONE table for ONE settings pair.
|
|
328
|
+
*
|
|
329
|
+
* Two settings pairs give two programs and therefore two cached reference
|
|
330
|
+
* tables; the driver that reads them is the same function in both cases and
|
|
331
|
+
* never sees `settings`.
|
|
332
|
+
*/
|
|
333
|
+
export function encodeTableBaseline(
|
|
334
|
+
ruleMap: Record<string, Combinator<unknown>>,
|
|
335
|
+
settings: TableSettings = {},
|
|
336
|
+
): TableProgram {
|
|
337
|
+
const enc = new Encoder(settings)
|
|
338
|
+
for (const name of Object.keys(ruleMap)) enc.encodeRule(name, ruleMap[name]!)
|
|
339
|
+
return enc.finish()
|
|
340
|
+
}
|