parseman 0.45.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-gate.d.ts +158 -0
- package/dist/analysis/choice-cost-gate.d.ts.map +1 -0
- package/dist/analysis/choice-cost-render.d.ts +96 -0
- package/dist/analysis/choice-cost-render.d.ts.map +1 -0
- package/dist/analysis/choice-cost.d.ts +383 -0
- package/dist/analysis/choice-cost.d.ts.map +1 -0
- package/dist/analysis/commitment.d.ts +131 -0
- package/dist/analysis/commitment.d.ts.map +1 -0
- package/dist/analysis/corpus.d.ts +73 -0
- package/dist/analysis/corpus.d.ts.map +1 -0
- package/dist/analysis/diagnose-render.d.ts +65 -0
- package/dist/analysis/diagnose-render.d.ts.map +1 -0
- package/dist/analysis/diagnose.d.ts +15 -0
- package/dist/analysis/diagnose.d.ts.map +1 -1
- package/dist/analysis/diagnostics.cjs +13736 -0
- package/dist/analysis/diagnostics.cjs.map +6 -0
- package/dist/analysis/diagnostics.d.ts +27 -0
- package/dist/analysis/diagnostics.d.ts.map +1 -0
- package/dist/analysis/diagnostics.js +13713 -0
- package/dist/analysis/diagnostics.js.map +6 -0
- package/dist/analysis/duplication.d.ts.map +1 -1
- package/dist/analysis/fix-render.d.ts +43 -0
- package/dist/analysis/fix-render.d.ts.map +1 -0
- package/dist/analysis/fix.d.ts +172 -0
- package/dist/analysis/fix.d.ts.map +1 -0
- 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 +47 -2
- package/dist/analysis/gating.d.ts.map +1 -1
- package/dist/analysis/rebuild.d.ts +61 -0
- package/dist/analysis/rebuild.d.ts.map +1 -0
- package/dist/analysis/terminal.d.ts +145 -0
- package/dist/analysis/terminal.d.ts.map +1 -0
- package/dist/cli/index.cjs +14837 -0
- package/dist/cli/index.cjs.map +6 -0
- package/dist/cli/index.d.ts +3 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +14814 -0
- package/dist/cli/index.js.map +6 -0
- 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/build-arity.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 +79 -47
- 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 +10499 -7478
- package/dist/index.cjs.map +3 -4
- package/dist/index.d.ts +24 -9
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +10499 -7478
- 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 +41 -2
- package/dist/plugin/evaluator.d.ts.map +1 -1
- package/dist/plugin/free-identifiers.d.ts +23 -0
- package/dist/plugin/free-identifiers.d.ts.map +1 -0
- package/dist/plugin/index.cjs +11381 -9442
- package/dist/plugin/index.cjs.map +3 -4
- package/dist/plugin/index.d.ts.map +1 -1
- package/dist/plugin/index.js +11381 -9442
- package/dist/plugin/index.js.map +3 -4
- package/dist/plugin/reducer-resolver.d.ts +17 -1
- package/dist/plugin/reducer-resolver.d.ts.map +1 -1
- 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 -1622
- package/dist/spec/index.cjs.map +3 -4
- package/dist/spec/index.js +2898 -1622
- 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 +41 -3
- 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/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,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gating analysis for a WHOLE grammar value — including a `compose()` result.
|
|
3
|
+
*
|
|
4
|
+
* `analyzeGatingRules` walks combinators. A `compose()` result holds none: fusion
|
|
5
|
+
* lowers every rule to an executable function and the combinator graph is gone from
|
|
6
|
+
* the returned map. Walking one therefore saw nothing — and, because the default-on
|
|
7
|
+
* fuse-time diagnostic swallowed the resulting error, saw nothing SILENTLY. That is
|
|
8
|
+
* the shape every real composed grammar has (`compose([importedArtifact, rules(…)])`),
|
|
9
|
+
* so the diagnostic was blind on exactly the grammars it exists to serve.
|
|
10
|
+
*
|
|
11
|
+
* The graph is recoverable: `compose()` retains its inputs as carried IR, and that IR
|
|
12
|
+
* re-lowers to real combinators. This module does that recovery, and — where a piece
|
|
13
|
+
* is a genuinely opaque precompiled artifact carrying rule FUNCTIONS rather than IR —
|
|
14
|
+
* reports it as `unanalysable` instead of quietly dropping it.
|
|
15
|
+
*/
|
|
16
|
+
import type { Combinator } from '../types.ts'
|
|
17
|
+
import { analyzeGatingRules, type AnalyzeGatingOptions, type GatingReport, type Unanalysable } from './gating.ts'
|
|
18
|
+
import { recoverComposedRules } from '../compiler/linker.ts'
|
|
19
|
+
import { recordDegradation } from '../compiler/degradation.ts'
|
|
20
|
+
|
|
21
|
+
/** A `rules()` map, a `compose()` result, or any rule-name → value record. */
|
|
22
|
+
export type AnalysableGrammar = Record<string, unknown>
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Analyze a grammar's choices, resolving a composed grammar back to its carried IR
|
|
26
|
+
* first. Accepts a `rules()` map (walked directly) or a `compose()` result (recovered).
|
|
27
|
+
*
|
|
28
|
+
* The returned report's `unanalysable` is authoritative: non-empty means part of the
|
|
29
|
+
* grammar was never examined, and a clean `ungated` must NOT be read as a pass.
|
|
30
|
+
*/
|
|
31
|
+
export function analyzeGrammarGating(
|
|
32
|
+
grammar: AnalysableGrammar,
|
|
33
|
+
opts?: AnalyzeGatingOptions,
|
|
34
|
+
): GatingReport {
|
|
35
|
+
const recovered = recoverComposedRules(grammar)
|
|
36
|
+
if (recovered === undefined) {
|
|
37
|
+
// Not a composed grammar — a plain `rules()` map of combinators. Walk it as-is;
|
|
38
|
+
// any non-combinator value is reported by the walk itself.
|
|
39
|
+
return analyzeGatingRules(Object.entries(grammar) as Array<[string, Combinator<unknown>]>, opts)
|
|
40
|
+
}
|
|
41
|
+
const { rules: winners, opaque } = recovered
|
|
42
|
+
const entries = [...winners]
|
|
43
|
+
const report = analyzeGatingRules(entries, { ...opts, resolveRef: name => winners.get(name) })
|
|
44
|
+
const fromOpaque: Unanalysable[] = opaque.map(o => ({
|
|
45
|
+
rule: o.ruleNames.length > 0 ? o.ruleNames.join(', ') : `<artifact ${o.ns}>`,
|
|
46
|
+
kind: 'opaque-artifact' as const,
|
|
47
|
+
reason: `precompiled artifact "${o.ns}" carries compiled rule functions, not re-lowerable IR — `
|
|
48
|
+
+ 'its choices cannot be examined. Recompile the contributing grammar so it carries IR '
|
|
49
|
+
+ '(the macro emits IR by default) to bring it back into analysis.',
|
|
50
|
+
}))
|
|
51
|
+
// Also announce it on the degradation channel. `unanalysable` is structured and correct,
|
|
52
|
+
// but it is only seen by a caller that reads the report and knows to check that field;
|
|
53
|
+
// the degradation channel is the greppable, countable one a build gate asserts zero of.
|
|
54
|
+
// `'opaque-artifact'` was a DECLARED degradation code with no record site anywhere — a
|
|
55
|
+
// published diagnostic vocabulary that could never fire. This is its real trigger.
|
|
56
|
+
for (const o of opaque) {
|
|
57
|
+
recordDegradation({
|
|
58
|
+
code: 'opaque-artifact',
|
|
59
|
+
// `info`, not `warn`: the artifact is usually someone else's package, so the author
|
|
60
|
+
// of THIS grammar has nothing to act on — but the blind spot still has to be counted.
|
|
61
|
+
severity: 'info',
|
|
62
|
+
where: o.ruleNames.length > 0 ? o.ruleNames.join(', ') : `<artifact ${o.ns}>`,
|
|
63
|
+
subject: `precompiled artifact "${o.ns}"`,
|
|
64
|
+
fellBackTo: `it carries compiled rule functions rather than re-lowerable IR, so its `
|
|
65
|
+
+ `${o.ruleNames.length > 0 ? `${o.ruleNames.length} rule(s)` : 'rules'} were NOT analysed `
|
|
66
|
+
+ 'and a clean gating report must not be read as a pass',
|
|
67
|
+
otherwise: 'its choices would be walked like any other rule. Recompile the contributing '
|
|
68
|
+
+ 'grammar so it carries IR (the macro emits IR by default)',
|
|
69
|
+
})
|
|
70
|
+
}
|
|
71
|
+
return fromOpaque.length === 0 ? report : { ...report, unanalysable: [...report.unanalysable, ...fromOpaque] }
|
|
72
|
+
}
|
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rebuild a combinator graph THROUGH THE PUBLIC FACTORIES, substituting selected nodes.
|
|
3
|
+
*
|
|
4
|
+
* This exists so a proposed grammar fix can be APPLIED and then MEASURED, rather than
|
|
5
|
+
* printed and hoped for. Everything downstream — `proposeFixes()` — depends on one
|
|
6
|
+
* property: a rewritten grammar must be a real grammar, built the same way the author's
|
|
7
|
+
* was, so that compiling it and parsing with it means the same thing.
|
|
8
|
+
*
|
|
9
|
+
* WHY THE FACTORIES AND NOT A `_def` DEEP-COPY
|
|
10
|
+
* --------------------------------------------
|
|
11
|
+
* A combinator is two things at once: a `_def` descriptor the COMPILER reads, and a
|
|
12
|
+
* `parse` closure the INTERPRETER runs, which captured its children when the factory
|
|
13
|
+
* ran. Patching `_def.parser` moves the compiled engine and leaves the interpreted one
|
|
14
|
+
* on the old graph — the two engines would then disagree, and a verification that runs
|
|
15
|
+
* either one would certify a rewrite that does not exist in the other. Re-running the
|
|
16
|
+
* factory produces both halves from the same inputs, and also recomputes everything
|
|
17
|
+
* DERIVED that the fix is trying to move in the first place: `_meta.firstSet`,
|
|
18
|
+
* `choice._def.disjoint`, and the choice strategy.
|
|
19
|
+
*
|
|
20
|
+
* FROZEN SUBTREES, NOT A FAILED REBUILD
|
|
21
|
+
* -------------------------------------
|
|
22
|
+
* Some node kinds have no faithful public reconstruction — `dispatch` (its arms carry
|
|
23
|
+
* matcher objects), `recover`, `scanTo`, `guard`, `withCtx`, `trivia` (classified
|
|
24
|
+
* trivia carries labels derived at construction). Refusing to rebuild a whole grammar
|
|
25
|
+
* because it contains one of them would make this useless on every real grammar. So
|
|
26
|
+
* such a node is REUSED VERBATIM: nothing inside it changed, so reusing it is exact.
|
|
27
|
+
* The cost is precise and reported — a rewrite target that sits inside a frozen subtree
|
|
28
|
+
* cannot be applied, and comes back in `unapplied` so the caller can say WHY rather
|
|
29
|
+
* than silently dropping it.
|
|
30
|
+
*
|
|
31
|
+
* THE HONESTY BACKSTOP
|
|
32
|
+
* --------------------
|
|
33
|
+
* This module does not claim to be faithful; it is CHECKED. `proposeFixes()` first
|
|
34
|
+
* rebuilds with NO substitutions and requires that identity rebuild to produce
|
|
35
|
+
* byte-identical parse output over the caller's corpus. If any option threading below
|
|
36
|
+
* is wrong, that check fails and NO fix is offered for that grammar. A rebuilder that
|
|
37
|
+
* is wrong makes the tool silent, never wrong.
|
|
38
|
+
*/
|
|
39
|
+
import type { Combinator, ParserDef } from '../types.ts'
|
|
40
|
+
import { literal } from '../combinators/literal.ts'
|
|
41
|
+
import { regex } from '../combinators/regex.ts'
|
|
42
|
+
import { keywords } from '../combinators/keywords.ts'
|
|
43
|
+
import { sequence } from '../combinators/sequence.ts'
|
|
44
|
+
import { choice } from '../combinators/choice.ts'
|
|
45
|
+
import { attempt } from '../combinators/attempt.ts'
|
|
46
|
+
import { many, oneOrMore, optional, sepBy } from '../combinators/repeat.ts'
|
|
47
|
+
import { ref } from '../combinators/ref.ts'
|
|
48
|
+
import { not } from '../combinators/not.ts'
|
|
49
|
+
import { peek } from '../combinators/peek.ts'
|
|
50
|
+
import { node } from '../combinators/node.ts'
|
|
51
|
+
import { transform, label, field } from '../combinators/map.ts'
|
|
52
|
+
import { token, leaf } from '../combinators/token.ts'
|
|
53
|
+
import { expect } from '../combinators/expect.ts'
|
|
54
|
+
import { adjacent, notAdjacent } from '../combinators/adjacency.ts'
|
|
55
|
+
import { parser } from '../combinators/grammar.ts'
|
|
56
|
+
|
|
57
|
+
/** A node kind reused verbatim: no public factory reconstructs it without loss. */
|
|
58
|
+
const FROZEN_TAGS: ReadonlySet<string> = new Set([
|
|
59
|
+
'dispatch', 'recover', 'scanTo', 'guard', 'withCtx', 'routed', 'trivia', 'unknown',
|
|
60
|
+
])
|
|
61
|
+
|
|
62
|
+
/** A subtree that was reused verbatim instead of rebuilt. */
|
|
63
|
+
export type FrozenSubtree = {
|
|
64
|
+
tag: string
|
|
65
|
+
/** Nearest enclosing rule name, so the report can point at it. */
|
|
66
|
+
rule: string
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export type RebuildResult = {
|
|
70
|
+
root: Combinator<unknown>
|
|
71
|
+
/** Reused-verbatim subtrees, deduplicated by (rule, tag) and sorted. */
|
|
72
|
+
frozen: FrozenSubtree[]
|
|
73
|
+
/**
|
|
74
|
+
* Substitutions that could NOT be applied because their target sits inside a frozen
|
|
75
|
+
* subtree. Non-empty means the returned graph is NOT the graph the caller asked for.
|
|
76
|
+
*/
|
|
77
|
+
unapplied: Combinator<unknown>[]
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** `_meta` fields stamped by `rules()` / `parser()` that a factory does not recompute. */
|
|
81
|
+
const CARRIED_META = [
|
|
82
|
+
'grammarTrivia', 'grammarScanSkip', 'grammarHostMode', 'grammarTrackLines',
|
|
83
|
+
'rootTriviaClassified',
|
|
84
|
+
] as const
|
|
85
|
+
|
|
86
|
+
function carryStamps(from: Combinator<unknown>, to: Combinator<unknown>): void {
|
|
87
|
+
const fromRule = (from as { _ruleName?: string })._ruleName
|
|
88
|
+
if (fromRule !== undefined) (to as { _ruleName?: string })._ruleName = fromRule
|
|
89
|
+
const fm = from._meta as Record<string, unknown>
|
|
90
|
+
const tm = to._meta as Record<string, unknown>
|
|
91
|
+
for (const k of CARRIED_META) if (fm[k] !== undefined && tm[k] === undefined) tm[k] = fm[k]
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** Every combinator reachable from `p`, without resolving refs (used to test containment). */
|
|
95
|
+
function subtreeContains(p: Combinator<unknown>, targets: ReadonlySet<Combinator<unknown>>): Combinator<unknown>[] {
|
|
96
|
+
const hit: Combinator<unknown>[] = []
|
|
97
|
+
const seen = new Set<Combinator<unknown>>()
|
|
98
|
+
const walk = (c: Combinator<unknown>): void => {
|
|
99
|
+
if (seen.has(c)) return
|
|
100
|
+
seen.add(c)
|
|
101
|
+
if (targets.has(c)) hit.push(c)
|
|
102
|
+
const d = (c as { _def?: unknown })._def as ParserDef | undefined
|
|
103
|
+
if (d === undefined || typeof d !== 'object') return
|
|
104
|
+
const rec = d as unknown as Record<string, unknown>
|
|
105
|
+
const kids: Combinator<unknown>[] = []
|
|
106
|
+
if (Array.isArray(rec.parsers)) kids.push(...(rec.parsers as Combinator<unknown>[]))
|
|
107
|
+
if (Array.isArray(rec.skip)) kids.push(...(rec.skip as Combinator<unknown>[]))
|
|
108
|
+
for (const k of ['parser', 'main', 'skipped', 'separator', 'sentinel', 'selector', 'otherwise', 'fallback', 'triviaParser'])
|
|
109
|
+
if (rec[k]) kids.push(rec[k] as Combinator<unknown>)
|
|
110
|
+
if (d.tag === 'dispatch') {
|
|
111
|
+
for (const c2 of d.cases) kids.push(c2.parser)
|
|
112
|
+
if (d.matchers) for (const c2 of d.matchers) kids.push(c2.parser)
|
|
113
|
+
}
|
|
114
|
+
for (const k of kids) walk(k)
|
|
115
|
+
}
|
|
116
|
+
walk(p)
|
|
117
|
+
return hit
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Rebuild `root`, replacing every combinator that is a key of `replacements` with its
|
|
122
|
+
* value. The replacement is spliced WITHOUT descending into the original target.
|
|
123
|
+
*/
|
|
124
|
+
export function rebuildCombinator(
|
|
125
|
+
root: Combinator<unknown>,
|
|
126
|
+
replacements: ReadonlyMap<Combinator<unknown>, Combinator<unknown>>,
|
|
127
|
+
): RebuildResult {
|
|
128
|
+
const memo = new Map<Combinator<unknown>, Combinator<unknown>>()
|
|
129
|
+
const frozenKeys = new Set<string>()
|
|
130
|
+
const frozen: FrozenSubtree[] = []
|
|
131
|
+
const unapplied: Combinator<unknown>[] = []
|
|
132
|
+
const targets = new Set(replacements.keys())
|
|
133
|
+
|
|
134
|
+
const go = (p: Combinator<unknown>, rule: string): Combinator<unknown> => {
|
|
135
|
+
const already = memo.get(p)
|
|
136
|
+
if (already !== undefined) return already
|
|
137
|
+
const sub = replacements.get(p)
|
|
138
|
+
if (sub !== undefined) { memo.set(p, sub); return sub }
|
|
139
|
+
|
|
140
|
+
const d = (p as { _def?: unknown })._def as ParserDef | undefined
|
|
141
|
+
if (d === undefined || typeof d !== 'object') { memo.set(p, p); return p }
|
|
142
|
+
const here = (p as { _ruleName?: string })._ruleName ?? rule
|
|
143
|
+
|
|
144
|
+
if (FROZEN_TAGS.has(d.tag)) {
|
|
145
|
+
const key = JSON.stringify([here, d.tag])
|
|
146
|
+
if (!frozenKeys.has(key)) { frozenKeys.add(key); frozen.push({ tag: d.tag, rule: here }) }
|
|
147
|
+
for (const t of subtreeContains(p, targets)) if (!unapplied.includes(t)) unapplied.push(t)
|
|
148
|
+
memo.set(p, p)
|
|
149
|
+
return p
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// A ref/lazy is the only place a cycle can close. Publish an EMPTY slot into the
|
|
153
|
+
// memo before descending, so a body that refers back to this rule binds to the
|
|
154
|
+
// new slot rather than re-entering (which would not terminate).
|
|
155
|
+
if (d.tag === 'lazy') {
|
|
156
|
+
let target: Combinator<unknown>
|
|
157
|
+
try { target = (d as { thunk(): Combinator<unknown> }).thunk() }
|
|
158
|
+
catch {
|
|
159
|
+
// An undefined ref: nothing to rebuild through, and nothing inside it to miss.
|
|
160
|
+
memo.set(p, p)
|
|
161
|
+
return p
|
|
162
|
+
}
|
|
163
|
+
const slot = ref<unknown>()
|
|
164
|
+
memo.set(p, slot)
|
|
165
|
+
slot.define(go(target, here))
|
|
166
|
+
carryStamps(p, slot)
|
|
167
|
+
return slot
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const one = (c: Combinator<unknown>): Combinator<unknown> => go(c, here)
|
|
171
|
+
let out: Combinator<unknown>
|
|
172
|
+
switch (d.tag) {
|
|
173
|
+
case 'literal':
|
|
174
|
+
out = literal(d.value, d.caseInsensitive ? { caseInsensitive: true } : {}); break
|
|
175
|
+
case 'regex':
|
|
176
|
+
out = regex(d.source, d.flags); break
|
|
177
|
+
case 'keywords':
|
|
178
|
+
out = keywords(d.words, {
|
|
179
|
+
...(d.caseInsensitive ? { caseInsensitive: true } : {}),
|
|
180
|
+
...(d.boundary === undefined ? {} : { boundary: d.boundary }),
|
|
181
|
+
}); break
|
|
182
|
+
case 'sequence': {
|
|
183
|
+
const kids = d.parsers.map(one) as [Combinator<unknown>, ...Combinator<unknown>[]]
|
|
184
|
+
out = sequence(...kids); break
|
|
185
|
+
}
|
|
186
|
+
case 'choice': {
|
|
187
|
+
// A per-arm gate predicate cannot be re-wrapped through the public `choice()`
|
|
188
|
+
// arity (it takes GatedArm objects the grammar built), and the whole point of
|
|
189
|
+
// rebuilding a choice is that its strategy is recomputed — which the gated path
|
|
190
|
+
// suppresses anyway. Freeze it rather than rebuild it wrong.
|
|
191
|
+
if (d.gates.some(g => g !== null)) {
|
|
192
|
+
const key = JSON.stringify([here, "choice(gated)"])
|
|
193
|
+
if (!frozenKeys.has(key)) { frozenKeys.add(key); frozen.push({ tag: 'choice(gated)', rule: here }) }
|
|
194
|
+
for (const t of subtreeContains(p, targets)) if (!unapplied.includes(t)) unapplied.push(t)
|
|
195
|
+
memo.set(p, p)
|
|
196
|
+
return p
|
|
197
|
+
}
|
|
198
|
+
const kids = d.parsers.map(one) as [Combinator<unknown>, ...Combinator<unknown>[]]
|
|
199
|
+
out = choice(...kids); break
|
|
200
|
+
}
|
|
201
|
+
// Reconstructible from public factories with no loss, so NOT frozen.
|
|
202
|
+
case 'adjacency':
|
|
203
|
+
out = d.polarity === 'adjacent'
|
|
204
|
+
? adjacent()
|
|
205
|
+
: d.kinds === undefined ? notAdjacent() : notAdjacent({ kinds: d.kinds })
|
|
206
|
+
break
|
|
207
|
+
case 'attempt': out = attempt(one(d.parser)); break
|
|
208
|
+
case 'not': out = not(one(d.parser)); break
|
|
209
|
+
case 'peek': out = peek(one(d.parser)); break
|
|
210
|
+
case 'optional': out = optional(one(d.parser)); break
|
|
211
|
+
case 'many':
|
|
212
|
+
out = many(one(d.parser), d.max === undefined ? {} : { max: d.max }); break
|
|
213
|
+
case 'oneOrMore':
|
|
214
|
+
out = oneOrMore(one(d.parser), { min: d.min, ...(d.max === undefined ? {} : { max: d.max }) }); break
|
|
215
|
+
case 'sepBy':
|
|
216
|
+
out = sepBy(one(d.parser), one(d.separator), {
|
|
217
|
+
min: d.min,
|
|
218
|
+
...(d.max === undefined ? {} : { max: d.max }),
|
|
219
|
+
...(d.trailing === undefined ? {} : { trailing: d.trailing }),
|
|
220
|
+
}); break
|
|
221
|
+
case 'transform': out = transform(one(d.parser), d.fn); break
|
|
222
|
+
case 'label': out = label(d.label, one(d.parser)); break
|
|
223
|
+
case 'field': out = field(d.name, one(d.parser)); break
|
|
224
|
+
case 'token': out = token(one(d.parser)); break
|
|
225
|
+
case 'leaf': out = leaf(one(d.parser), d.fn); break
|
|
226
|
+
// A LABELLED expect reproduces `d.expected` exactly (`expect` sets it to `[label]`).
|
|
227
|
+
// An unlabelled one RE-DERIVES it from the rebuilt inner parser, so it can differ
|
|
228
|
+
// from `d.expected` when derivation hit an undefined `lazy` thunk in a different
|
|
229
|
+
// order than the original construction did. That is diagnostic text, not parse
|
|
230
|
+
// output: `fix.ts` — the only consumer of this rebuild — compares failures by
|
|
231
|
+
// POSITION and excludes `expected` labels by design (see its module header).
|
|
232
|
+
// Preserving `d.expected` verbatim would need an expected-set override on the
|
|
233
|
+
// public `expect()` signature, which is not worth widening for a label.
|
|
234
|
+
case 'expect': out = expect(one(d.parser), d.label) as Combinator<unknown>; break
|
|
235
|
+
case 'node': {
|
|
236
|
+
const opts = {
|
|
237
|
+
...(d.unwrap ? { unwrap: true } : {}),
|
|
238
|
+
...(d.collapse ? { collapse: true } : {}),
|
|
239
|
+
...(d.project === undefined ? {} : { project: d.project }),
|
|
240
|
+
...(d.captureTrivia ? { captureTrivia: true } : {}),
|
|
241
|
+
...(d.trailingTrivia ? { trailingTrivia: true } : {}),
|
|
242
|
+
...(d.buildArity === undefined ? {} : { buildArity: d.buildArity }),
|
|
243
|
+
...(d.tags === undefined ? {} : { tags: d.tags }),
|
|
244
|
+
}
|
|
245
|
+
// `node(combinator, build, opts)` — the UNTYPED-with-build arity — drops `opts`
|
|
246
|
+
// (src/combinators/node.ts:159-160 reads the 4th positional argument, which that
|
|
247
|
+
// arity never receives). Reconstructing through it would silently lose
|
|
248
|
+
// unwrap/collapse/project/tags, so freeze that one shape instead.
|
|
249
|
+
if (d.type === undefined && d.build !== undefined && Object.keys(opts).length > 0) {
|
|
250
|
+
const key = `${here} node(untyped+build+opts)`
|
|
251
|
+
if (!frozenKeys.has(key)) { frozenKeys.add(key); frozen.push({ tag: 'node(untyped+build+opts)', rule: here }) }
|
|
252
|
+
for (const t of subtreeContains(p, targets)) if (!unapplied.includes(t)) unapplied.push(t)
|
|
253
|
+
memo.set(p, p)
|
|
254
|
+
return p
|
|
255
|
+
}
|
|
256
|
+
out = d.type === undefined
|
|
257
|
+
? (d.build === undefined ? node(one(d.parser), opts) : node(one(d.parser), d.build))
|
|
258
|
+
: node(d.type, one(d.parser), d.build, opts)
|
|
259
|
+
break
|
|
260
|
+
}
|
|
261
|
+
case 'grammar': {
|
|
262
|
+
const inner = one(d.parser)
|
|
263
|
+
out = parser({
|
|
264
|
+
...(d.clearTrivia ? { trivia: null } : d.triviaParser === undefined ? {} : { trivia: d.triviaParser }),
|
|
265
|
+
...(d.rootCapture === undefined ? {} : { rootCapture: d.rootCapture }),
|
|
266
|
+
...(d.captureTrivia ? { captureTrivia: true } : {}),
|
|
267
|
+
trackLines: d.trackLines,
|
|
268
|
+
}, inner) as unknown as Combinator<unknown>
|
|
269
|
+
break
|
|
270
|
+
}
|
|
271
|
+
default: {
|
|
272
|
+
const key = JSON.stringify([here, (d as { tag: string }).tag])
|
|
273
|
+
if (!frozenKeys.has(key)) { frozenKeys.add(key); frozen.push({ tag: (d as { tag: string }).tag, rule: here }) }
|
|
274
|
+
for (const t of subtreeContains(p, targets)) if (!unapplied.includes(t)) unapplied.push(t)
|
|
275
|
+
memo.set(p, p)
|
|
276
|
+
return p
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
carryStamps(p, out)
|
|
280
|
+
memo.set(p, out)
|
|
281
|
+
return out
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
const rebuilt = go(root, (root as { _ruleName?: string })._ruleName ?? '<entry>')
|
|
285
|
+
frozen.sort((a, b) => (a.rule < b.rule ? -1 : a.rule > b.rule ? 1 : a.tag < b.tag ? -1 : a.tag > b.tag ? 1 : 0))
|
|
286
|
+
return { root: rebuilt, frozen, unapplied }
|
|
287
|
+
}
|