parseman 0.46.0 → 0.47.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/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 +10339 -7787
- package/dist/analysis/diagnostics.cjs.map +3 -4
- package/dist/analysis/diagnostics.d.ts +2 -2
- package/dist/analysis/diagnostics.js +10339 -7787
- 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 +10924 -8506
- package/dist/cli/index.cjs.map +3 -4
- package/dist/cli/index.js +10924 -8506
- 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 +45 -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 +9290 -7181
- 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 +9300 -7191
- package/dist/index.js.map +3 -4
- package/dist/language-service/index.cjs +313 -121
- package/dist/language-service/index.cjs.map +3 -4
- package/dist/language-service/index.js +313 -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 +11175 -9534
- package/dist/plugin/index.cjs.map +3 -4
- package/dist/plugin/index.d.ts.map +1 -1
- package/dist/plugin/index.js +11175 -9534
- 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 +2934 -1611
- package/dist/spec/index.cjs.map +3 -4
- package/dist/spec/index.js +2934 -1611
- 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 +11463 -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 +11440 -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 +453 -0
- package/dist/table/ops.d.ts.map +1 -0
- package/dist/table/program.d.ts +496 -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 +26 -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 +348 -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 +237 -0
- package/src/combinators/token.ts +120 -0
- package/src/combinators/trivia-boundary.ts +48 -0
- package/src/combinators/trivia-skip.ts +852 -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 +3006 -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 +1777 -0
- package/src/table/emit.ts +429 -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 +1680 -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 +467 -0
- package/src/table/program.ts +876 -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,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The compiled-artifact ↔ host contract, in ONE place.
|
|
3
|
+
*
|
|
4
|
+
* This lives on its own — rather than in `compiler/codegen.ts` where it started —
|
|
5
|
+
* because the DRIVER has to enforce it too. `functional/run.ts` is the `parseman/run`
|
|
6
|
+
* entry, whose whole purpose is to stay small (see `test/unit/run-entry-closure.test.ts`),
|
|
7
|
+
* so it cannot import the compiler to reach an assertion. Neither can it carry its own
|
|
8
|
+
* copy: two spellings of one contract is how the two engines drift, and this contract
|
|
9
|
+
* exists precisely because the failure it prevents is silent.
|
|
10
|
+
*
|
|
11
|
+
* No imports, by design — that is what keeps it free for both sides to depend on.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Which consumer a compiled artifact was lowered FOR.
|
|
16
|
+
*
|
|
17
|
+
* - `'ast'` — the grammar's own `build` callbacks produce the result (the eval consumer).
|
|
18
|
+
* - `'cst'` — a positioned-CST `ctx.build` host produces it (linter / IDE / language
|
|
19
|
+
* service), and every node's capture is sized for that host.
|
|
20
|
+
*
|
|
21
|
+
* ONE grammar source serves both; they are two COMPILATIONS of it, not two grammars.
|
|
22
|
+
* Deciding at compile time is what keeps the eval-AST artifact free of per-node host
|
|
23
|
+
* probing — see the `hostMode` note on the codegen Ctx.
|
|
24
|
+
*/
|
|
25
|
+
export type HostMode = 'ast' | 'cst'
|
|
26
|
+
|
|
27
|
+
/** Host mode a fused rule map (and each of its rule functions) was lowered for;
|
|
28
|
+
* absent on hand-built maps → 'ast'. */
|
|
29
|
+
export const FUSED_HOST_MODE = Symbol.for('parseman.fusedHostMode')
|
|
30
|
+
|
|
31
|
+
/** Whether any fused piece omitted a direct builder's positioned-CST branch. */
|
|
32
|
+
export const FUSED_HOST_ELIDED = Symbol.for('parseman.fusedHostElided')
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* A compiled artifact and the host it is driven with must agree — and when they do not,
|
|
36
|
+
* that has to be an ERROR, never a quietly-degraded tree.
|
|
37
|
+
*
|
|
38
|
+
* An `'ast'` artifact does not emit the positioned-CST branch at all, so attaching a
|
|
39
|
+
* `_parsemanCstOutput` host to one would silently get the grammar's own AST objects
|
|
40
|
+
* where the caller asked for a CST. A `'cst'` artifact builds every node through the
|
|
41
|
+
* host, so running one without a host would call `undefined`. Both are compile/run
|
|
42
|
+
* mismatches with an obvious fix, so both say so.
|
|
43
|
+
*
|
|
44
|
+
* Called once per parse from the TypeScript entry points, never from generated code.
|
|
45
|
+
*/
|
|
46
|
+
export function assertHostModeCompatible(mode: HostMode, build: unknown, hostBranchElided = true): void {
|
|
47
|
+
const isCstHost = (build as { _parsemanCstOutput?: true } | undefined)?._parsemanCstOutput === true
|
|
48
|
+
// A purely STRUCTURAL artifact never had a direct-builder host branch to drop, so it
|
|
49
|
+
// serves a CST host in either mode — that is the long-standing `node(parser)` contract
|
|
50
|
+
// and this change does not touch it. The INTERPRETER passes `false` for the same
|
|
51
|
+
// reason from the other direction: it re-decides per parse and never elides anything.
|
|
52
|
+
if (mode === 'ast' && isCstHost && hostBranchElided) {
|
|
53
|
+
throw new Error(
|
|
54
|
+
'parseman: this parser was compiled for host mode "ast" (the default), so its nodes '
|
|
55
|
+
+ 'build through their own `build` callbacks and no positioned-CST branch was emitted. '
|
|
56
|
+
+ 'It cannot be driven with a positioned-CST host (cstBuildHost / a language-service '
|
|
57
|
+
+ 'host). Compile a second artifact from the SAME grammar with '
|
|
58
|
+
+ "`compile(grammar, { hostMode: 'cst' })` — or, under the macro, a second "
|
|
59
|
+
+ "`rules({ hostMode: 'cst' }, factory)` call site — and drive that one instead.",
|
|
60
|
+
)
|
|
61
|
+
}
|
|
62
|
+
if (mode === 'cst' && !isCstHost) {
|
|
63
|
+
throw new Error(
|
|
64
|
+
'parseman: this parser was compiled for host mode "cst", so every node builds through '
|
|
65
|
+
+ 'a positioned-CST `ctx.build` host. Pass one (e.g. `{ build: cstBuildHost }`), or use '
|
|
66
|
+
+ 'an artifact compiled with the default `hostMode: "ast"` to get the grammar\'s own AST.',
|
|
67
|
+
)
|
|
68
|
+
}
|
|
69
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { GRAMMAR_REFLECTION, type GrammarReflection } from './reflection.ts'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Legacy interpreted-rule-map attachment. It is deliberately outside the shared
|
|
5
|
+
* table runtime graph: macro/table artifacts construct reflection in their
|
|
6
|
+
* metadata prototype instead of mutating an already-built map.
|
|
7
|
+
*/
|
|
8
|
+
export function attachGrammarReflection<T extends object>(grammar: T, reflection: GrammarReflection): T {
|
|
9
|
+
Object.defineProperty(grammar, GRAMMAR_REFLECTION, { value: reflection, enumerable: false, configurable: true })
|
|
10
|
+
return grammar
|
|
11
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type-level CST identity lives on the hot table-runtime import graph. Keep the
|
|
3
|
+
* symbols separate from reflection collection/attachment: the latter is a
|
|
4
|
+
* grammar-construction concern and must not make every emitted parser load its
|
|
5
|
+
* descriptor-based legacy helper.
|
|
6
|
+
*/
|
|
7
|
+
export const GRAMMAR_REFLECTION: unique symbol = Symbol.for('parseman.grammarReflection') as never
|
|
8
|
+
export const NODE_TYPE: unique symbol = Symbol.for('parseman.type.nodeType') as never
|
|
9
|
+
export const NODE_TAG: unique symbol = Symbol.for('parseman.type.nodeTag') as never
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import type { Combinator, ParserDef } from '../types.ts'
|
|
2
|
+
import { GRAMMAR_REFLECTION, NODE_TAG, NODE_TYPE } from './reflection-symbols.ts'
|
|
3
|
+
|
|
4
|
+
export { GRAMMAR_REFLECTION, NODE_TYPE, NODE_TAG } from './reflection-symbols.ts'
|
|
5
|
+
|
|
6
|
+
export type GrammarNodeReflection = {
|
|
7
|
+
type: string
|
|
8
|
+
tags: readonly string[]
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export type GrammarReflection = {
|
|
12
|
+
nodes: readonly GrammarNodeReflection[]
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export type GrammarWithReflection<
|
|
16
|
+
NodeType extends string = string,
|
|
17
|
+
NodeTag extends string = string,
|
|
18
|
+
> = {
|
|
19
|
+
readonly [GRAMMAR_REFLECTION]?: GrammarReflection
|
|
20
|
+
readonly [NODE_TYPE]?: NodeType
|
|
21
|
+
readonly [NODE_TAG]?: NodeTag
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
type NodeDef = Extract<ParserDef, { tag: 'node' }>
|
|
25
|
+
|
|
26
|
+
function childrenOf(def: ParserDef): readonly Combinator<unknown>[] {
|
|
27
|
+
switch (def.tag) {
|
|
28
|
+
case 'sequence':
|
|
29
|
+
case 'choice':
|
|
30
|
+
return def.parsers
|
|
31
|
+
case 'dispatch':
|
|
32
|
+
return [
|
|
33
|
+
def.selector,
|
|
34
|
+
...def.cases.map(c => c.parser),
|
|
35
|
+
...(def.matchers ? def.matchers.map(c => c.parser) : []),
|
|
36
|
+
...(def.otherwise ? [def.otherwise] : []),
|
|
37
|
+
]
|
|
38
|
+
case 'many':
|
|
39
|
+
case 'oneOrMore':
|
|
40
|
+
case 'optional':
|
|
41
|
+
case 'attempt':
|
|
42
|
+
case 'transform':
|
|
43
|
+
case 'trivia':
|
|
44
|
+
case 'token':
|
|
45
|
+
case 'leaf':
|
|
46
|
+
case 'label':
|
|
47
|
+
case 'field':
|
|
48
|
+
case 'not':
|
|
49
|
+
case 'peek':
|
|
50
|
+
case 'node':
|
|
51
|
+
case 'withCtx':
|
|
52
|
+
case 'expect':
|
|
53
|
+
return [def.parser]
|
|
54
|
+
case 'recover':
|
|
55
|
+
return [def.parser, def.sentinel]
|
|
56
|
+
case 'grammar':
|
|
57
|
+
return def.triviaParser ? [def.parser, def.triviaParser] : [def.parser]
|
|
58
|
+
case 'sepBy':
|
|
59
|
+
return [def.parser, def.separator]
|
|
60
|
+
case 'scanTo':
|
|
61
|
+
return [def.sentinel, ...def.skip]
|
|
62
|
+
case 'routed':
|
|
63
|
+
return def.fallback ? [def.fallback] : []
|
|
64
|
+
case 'literal':
|
|
65
|
+
case 'regex':
|
|
66
|
+
case 'keywords':
|
|
67
|
+
case 'guard':
|
|
68
|
+
case 'adjacency':
|
|
69
|
+
case 'lazy':
|
|
70
|
+
case 'unknown':
|
|
71
|
+
return []
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function addNode(out: Map<string, string[]>, def: NodeDef): void {
|
|
76
|
+
if (def.type === undefined) return
|
|
77
|
+
const existing = out.get(def.type)
|
|
78
|
+
if (existing === undefined) {
|
|
79
|
+
out.set(def.type, def.tags === undefined ? [] : [...def.tags])
|
|
80
|
+
return
|
|
81
|
+
}
|
|
82
|
+
if (def.tags === undefined) return
|
|
83
|
+
for (const tag of def.tags) {
|
|
84
|
+
if (!existing.includes(tag)) existing.push(tag)
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function collectFromCombinator(
|
|
89
|
+
parser: Combinator<unknown>,
|
|
90
|
+
out: Map<string, string[]>,
|
|
91
|
+
seen: Set<Combinator<unknown>>,
|
|
92
|
+
followLazy = true,
|
|
93
|
+
): void {
|
|
94
|
+
if (seen.has(parser)) return
|
|
95
|
+
seen.add(parser)
|
|
96
|
+
const def = parser._def
|
|
97
|
+
if (def.tag === 'lazy') {
|
|
98
|
+
if (followLazy) {
|
|
99
|
+
try { collectFromCombinator(def.thunk(), out, seen, followLazy) } catch {}
|
|
100
|
+
}
|
|
101
|
+
return
|
|
102
|
+
}
|
|
103
|
+
if (def.tag === 'node') addNode(out, def)
|
|
104
|
+
for (const child of childrenOf(def)) collectFromCombinator(child, out, seen, followLazy)
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export function collectGrammarReflection(
|
|
108
|
+
ruleMap: ReadonlyArray<readonly [string, Combinator<unknown>]>,
|
|
109
|
+
opts?: { followLazy?: boolean },
|
|
110
|
+
): GrammarReflection {
|
|
111
|
+
const byType = new Map<string, string[]>()
|
|
112
|
+
const followLazy = opts?.followLazy !== false
|
|
113
|
+
for (const [, rule] of ruleMap) {
|
|
114
|
+
if (!followLazy && rule._def.tag === 'lazy') {
|
|
115
|
+
try {
|
|
116
|
+
const resolved = rule._def.thunk()
|
|
117
|
+
if (resolved._def.tag !== 'lazy') collectFromCombinator(resolved, byType, new Set(), false)
|
|
118
|
+
} catch {}
|
|
119
|
+
continue
|
|
120
|
+
}
|
|
121
|
+
collectFromCombinator(rule, byType, new Set(), followLazy)
|
|
122
|
+
}
|
|
123
|
+
return { nodes: [...byType].map(([type, tags]) => ({ type, tags })) }
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export function mergeGrammarReflections(reflections: readonly GrammarReflection[]): GrammarReflection {
|
|
127
|
+
const byType = new Map<string, string[]>()
|
|
128
|
+
for (const reflection of reflections) {
|
|
129
|
+
for (const node of reflection.nodes) {
|
|
130
|
+
const tags = byType.get(node.type)
|
|
131
|
+
if (tags === undefined) {
|
|
132
|
+
byType.set(node.type, [...node.tags])
|
|
133
|
+
continue
|
|
134
|
+
}
|
|
135
|
+
for (const tag of node.tags) {
|
|
136
|
+
if (!tags.includes(tag)) tags.push(tag)
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
return { nodes: [...byType].map(([type, tags]) => ({ type, tags })) }
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export function grammarReflectionOf(grammar: object): GrammarReflection | undefined {
|
|
144
|
+
return (grammar as GrammarWithReflection)[GRAMMAR_REFLECTION]
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export function grammarReflectionSource(reflection: GrammarReflection): string {
|
|
148
|
+
return `{ nodes: ${JSON.stringify(reflection.nodes)} }`
|
|
149
|
+
}
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Relative span model for incremental edits.
|
|
3
|
+
*
|
|
4
|
+
* Absolute source offsets are simple but fragile: an insertion at offset `at`
|
|
5
|
+
* shifts EVERY downstream offset, so a whole-tree renumber is O(nodes-after-edit).
|
|
6
|
+
* Storing each node's span RELATIVE to its parent's start makes edits local: an
|
|
7
|
+
* insertion inside child k of a node only changes
|
|
8
|
+
* (a) child k's relative end,
|
|
9
|
+
* (b) the relative start of siblings after k,
|
|
10
|
+
* and leaves the *children* of those shifted siblings untouched — they move with
|
|
11
|
+
* their parent for free. So the mutation count is O(depth + trailing siblings),
|
|
12
|
+
* not O(all following nodes), and every unaffected subtree is shared by identity.
|
|
13
|
+
*
|
|
14
|
+
* An absolute view is recovered on query by accumulating the parent base as you
|
|
15
|
+
* descend (the `absolutize` / `absoluteSpanAt` cursor) — one source of truth,
|
|
16
|
+
* two manifestations.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
/** A node with an ABSOLUTE source span and ordered children. */
|
|
20
|
+
export type AbsNode = {
|
|
21
|
+
readonly start: number
|
|
22
|
+
readonly end: number
|
|
23
|
+
readonly children: readonly AbsNode[]
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** A node whose `start`/`end` are RELATIVE to its parent's start (root base = 0). */
|
|
27
|
+
export type RelNode = {
|
|
28
|
+
readonly start: number
|
|
29
|
+
readonly end: number
|
|
30
|
+
readonly children: readonly RelNode[]
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Convert an absolute tree to parent-relative spans. */
|
|
34
|
+
export function relativize(node: AbsNode, parentBase = 0): RelNode {
|
|
35
|
+
return {
|
|
36
|
+
start: node.start - parentBase,
|
|
37
|
+
end: node.end - parentBase,
|
|
38
|
+
children: node.children.map((c) => relativize(c, node.start)),
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** Reconstruct absolute spans from a relative tree. Inverse of `relativize`. */
|
|
43
|
+
export function absolutize(node: RelNode, parentBase = 0): AbsNode {
|
|
44
|
+
const start = parentBase + node.start
|
|
45
|
+
return {
|
|
46
|
+
start,
|
|
47
|
+
end: parentBase + node.end,
|
|
48
|
+
children: node.children.map((c) => absolutize(c, start)),
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Absolute span of the node at `path` (child indices from the root), accumulating
|
|
54
|
+
* the base as it descends — O(path length), no full absolutize. The relative-form
|
|
55
|
+
* cursor: query any node's absolute position without materializing the whole tree.
|
|
56
|
+
*/
|
|
57
|
+
export function absoluteSpanAt(root: RelNode, path: readonly number[]): { start: number; end: number } {
|
|
58
|
+
let base = 0
|
|
59
|
+
let node = root
|
|
60
|
+
for (const idx of path) {
|
|
61
|
+
base += node.start
|
|
62
|
+
const child = node.children[idx]
|
|
63
|
+
if (!child) throw new RangeError(`absoluteSpanAt: no child ${idx} on path ${path.join('.')}`)
|
|
64
|
+
node = child
|
|
65
|
+
}
|
|
66
|
+
return { start: base + node.start, end: base + node.end }
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// ---------------------------------------------------------------------------
|
|
70
|
+
// CST-shaped projection (parseDoc incremental trees)
|
|
71
|
+
//
|
|
72
|
+
// The bare Abs/RelNode helpers above are the reference model. The functions
|
|
73
|
+
// below apply the same relative-span algebra to real CST children — nodes,
|
|
74
|
+
// leaves, and error nodes — where the span is a nested `{ start, end }` and
|
|
75
|
+
// children are heterogeneous (leaves carry a span but no `children`). Trivia
|
|
76
|
+
// never appears in structural `children`, so every child has a span.
|
|
77
|
+
// ---------------------------------------------------------------------------
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* A CST child as far as span projection cares: a nested span + optional children.
|
|
81
|
+
* `children` is typed loosely (`unknown[]`) so the real heterogeneous CST unions
|
|
82
|
+
* (nodes, leaves, error nodes — a leaf has no `children`) satisfy it directly.
|
|
83
|
+
*/
|
|
84
|
+
type Spanned = {
|
|
85
|
+
readonly span: { readonly start: number; readonly end: number }
|
|
86
|
+
readonly children?: readonly unknown[]
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Rewrite a CST subtree's spans to be RELATIVE to `parentBase` (the absolute
|
|
91
|
+
* start of the node's parent; root base = 0). Each child's own children are
|
|
92
|
+
* relativized against *this* node's absolute start. Preserves every other field
|
|
93
|
+
* (`_tag`, `type`, `state`, leaf `value`, …) by shallow spread.
|
|
94
|
+
*/
|
|
95
|
+
export function relativizeCST<T extends Spanned>(node: T, parentBase = 0): T {
|
|
96
|
+
const span = { start: node.span.start - parentBase, end: node.span.end - parentBase }
|
|
97
|
+
if (node.children) {
|
|
98
|
+
const base = node.span.start
|
|
99
|
+
return { ...node, span, children: node.children.map((c) => relativizeCST(c as Spanned, base)) } as T
|
|
100
|
+
}
|
|
101
|
+
return { ...node, span } as T
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** Reconstruct absolute CST spans from a relative subtree. Inverse of `relativizeCST`. */
|
|
105
|
+
export function absolutizeCST<T extends Spanned>(node: T, parentBase = 0): T {
|
|
106
|
+
const start = parentBase + node.span.start
|
|
107
|
+
const span = { start, end: parentBase + node.span.end }
|
|
108
|
+
if (node.children) {
|
|
109
|
+
return { ...node, span, children: node.children.map((c) => absolutizeCST(c as Spanned, start)) } as T
|
|
110
|
+
}
|
|
111
|
+
return { ...node, span } as T
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Absolute span of the CST node at `path` (child indices from the root),
|
|
116
|
+
* accumulating the base as it descends — O(path length), no full absolutize.
|
|
117
|
+
* The relative-form cursor for a positioned CST: query one node's absolute
|
|
118
|
+
* position without materializing the whole tree.
|
|
119
|
+
*/
|
|
120
|
+
export function absoluteSpanCST(root: Spanned, path: readonly number[]): { start: number; end: number } {
|
|
121
|
+
let base = 0
|
|
122
|
+
let node: Spanned = root
|
|
123
|
+
for (const idx of path) {
|
|
124
|
+
base += node.span.start
|
|
125
|
+
const child = node.children?.[idx] as Spanned | undefined
|
|
126
|
+
if (!child) throw new RangeError(`absoluteSpanCST: no child ${idx} on path ${path.join('.')}`)
|
|
127
|
+
node = child
|
|
128
|
+
}
|
|
129
|
+
return { start: base + node.span.start, end: base + node.span.end }
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/** Naive absolute reshift (baseline): shift every offset >= `at` by `delta`. */
|
|
133
|
+
export function shiftAbsolute(node: AbsNode, at: number, delta: number): AbsNode {
|
|
134
|
+
const s = node.start >= at ? node.start + delta : node.start
|
|
135
|
+
const e = node.end >= at ? node.end + delta : node.end
|
|
136
|
+
return {
|
|
137
|
+
start: s,
|
|
138
|
+
end: e,
|
|
139
|
+
children: node.children.map((c) => shiftAbsolute(c, at, delta)),
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Apply an edit of `delta` characters at absolute offset `at` to a RELATIVE tree.
|
|
145
|
+
* Returns a new relative tree; every subtree that ends at/before `at` (in absolute
|
|
146
|
+
* terms) is returned by IDENTITY (unchanged), so the result structurally shares
|
|
147
|
+
* all unaffected subtrees with the input.
|
|
148
|
+
*
|
|
149
|
+
* `stats.allocated` counts nodes that had to be re-created — the incremental cost.
|
|
150
|
+
*/
|
|
151
|
+
export function applyEdit(
|
|
152
|
+
root: RelNode,
|
|
153
|
+
at: number,
|
|
154
|
+
delta: number,
|
|
155
|
+
stats?: { allocated: number },
|
|
156
|
+
): RelNode {
|
|
157
|
+
// The root has no parent; its base never shifts (base 0).
|
|
158
|
+
return editNode(root, at, delta, 0, 0, stats)
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function editNode(
|
|
162
|
+
node: RelNode,
|
|
163
|
+
at: number,
|
|
164
|
+
delta: number,
|
|
165
|
+
parentBase: number,
|
|
166
|
+
parentShift: number, // how much this node's parent base moved (0 or delta)
|
|
167
|
+
stats?: { allocated: number },
|
|
168
|
+
): RelNode {
|
|
169
|
+
const absStart = parentBase + node.start
|
|
170
|
+
const absEnd = parentBase + node.end
|
|
171
|
+
|
|
172
|
+
// Strictly before the edit → unchanged (its parent base is also unshifted).
|
|
173
|
+
// Uses `< at` (not `<=`) to match the shift convention: offsets `>= at` move,
|
|
174
|
+
// so a node ending exactly at `at` grows and must be rewritten.
|
|
175
|
+
if (absEnd < at) return node
|
|
176
|
+
|
|
177
|
+
// Fully after the edit AND the parent moved by `delta` too → this node moves as
|
|
178
|
+
// one unit with its parent, so its parent-relative offsets are identical. Share
|
|
179
|
+
// the ENTIRE subtree by identity — this is the O(depth) locality win.
|
|
180
|
+
const startShifts = absStart >= at
|
|
181
|
+
if (startShifts && parentShift === delta) return node
|
|
182
|
+
|
|
183
|
+
if (stats) stats.allocated++
|
|
184
|
+
|
|
185
|
+
const newAbsStart = startShifts ? absStart + delta : absStart
|
|
186
|
+
const newAbsEnd = absEnd + delta // absEnd > at here (fully-before returned above)
|
|
187
|
+
const newParentBase = parentBase + parentShift
|
|
188
|
+
const newStart = newAbsStart - newParentBase
|
|
189
|
+
const newEnd = newAbsEnd - newParentBase
|
|
190
|
+
|
|
191
|
+
// Children are relative to this node's absolute start. Our own shift (0 or delta)
|
|
192
|
+
// becomes their parentShift.
|
|
193
|
+
const ownShift = newAbsStart - absStart
|
|
194
|
+
let changed = false
|
|
195
|
+
const newChildren: RelNode[] = []
|
|
196
|
+
for (const c of node.children) {
|
|
197
|
+
const nc = editNode(c, at, delta, absStart, ownShift, stats)
|
|
198
|
+
if (nc !== c) changed = true
|
|
199
|
+
newChildren.push(nc)
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
return {
|
|
203
|
+
start: newStart,
|
|
204
|
+
end: newEnd,
|
|
205
|
+
children: changed ? newChildren : node.children,
|
|
206
|
+
}
|
|
207
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The ROOT-CAPTURE POLICY of one local trivia scope, in one place.
|
|
3
|
+
*
|
|
4
|
+
* Three engines install a `parser({ trivia })` scope — the interpreter
|
|
5
|
+
* (`combinators/grammar.ts`), source lowering (`compiler/codegen.ts`) and the
|
|
6
|
+
* table (`table/assemble.ts`, `table/exec.ts`) — and each of them has to answer
|
|
7
|
+
* the same two questions about selected root trivia. The refusal below was a
|
|
8
|
+
* string literal in two of them and ABSENT from the third, which is how a table
|
|
9
|
+
* parse came to accept a grammar the other two reject.
|
|
10
|
+
*
|
|
11
|
+
* Import-free, like the rest of `cst/`, so the drivers can reach it without
|
|
12
|
+
* pulling the combinator set in.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
/** The message `parser()` raises for an unclassified local scope. Exported so the
|
|
16
|
+
* three engines cannot drift a word apart. */
|
|
17
|
+
export const ROOT_TRIVIA_SCOPE_REFUSAL
|
|
18
|
+
= 'parser(): selected root trivia requires classifiedTrivia() for every local trivia scope, or rootCapture: \'opaque\'.'
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Refuse a local trivia scope that cannot be reported faithfully.
|
|
22
|
+
*
|
|
23
|
+
* With selected root capture active, a scope whose trivia is not classified and
|
|
24
|
+
* is not declared opaque would silently drop the markers inside it. Throwing is
|
|
25
|
+
* the contract (`combinators/grammar.ts:98`); returning a parse that quietly
|
|
26
|
+
* omits them is the failure this exists to prevent.
|
|
27
|
+
*/
|
|
28
|
+
export function refuseUnclassifiedRootScope(strict: boolean | undefined): void {
|
|
29
|
+
if (strict === true) throw new TypeError(ROOT_TRIVIA_SCOPE_REFUSAL)
|
|
30
|
+
}
|