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
package/src/index.ts
ADDED
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
export type { Combinator, CompiledParser, ParseResult, ParseOk, ParseFail, ParseContext, ParseError, Span, ParserMeta, FirstSet, CharRange, ParserDef, ChoiceStrategy, AutoNotCheck, GatedArm, DispatchCase, DispatchMatcherCase, BuildHost, CstCollapsePredicate, FieldCapture, FieldMap } from './types.ts'
|
|
2
|
+
|
|
3
|
+
export { literal } from './combinators/literal.ts'
|
|
4
|
+
export type { LiteralOptions } from './combinators/literal.ts'
|
|
5
|
+
|
|
6
|
+
// `regex()` derives its own first-set: the analyzer (`./regex/first-set.ts`) is
|
|
7
|
+
// imported directly by `./combinators/regex.ts`, NOT registered here. It used to
|
|
8
|
+
// be wired in from this entry, which made `regex()`'s first-set depend on whether
|
|
9
|
+
// the library entry was in the bundle's module graph — and `parseman/table` is a
|
|
10
|
+
// separate graph, so its `regex()` fell back to the permissive `any()` set and
|
|
11
|
+
// `classifiedTrivia()` rejected every table-lowered grammar. See the comment on
|
|
12
|
+
// the import in `./combinators/regex.ts`.
|
|
13
|
+
export { regex } from './combinators/regex.ts'
|
|
14
|
+
export { keywords, word, makeWord } from './combinators/keywords.ts'
|
|
15
|
+
export type { KeywordsOptions, WordOptions } from './combinators/keywords.ts'
|
|
16
|
+
|
|
17
|
+
export { sequence } from './combinators/sequence.ts'
|
|
18
|
+
export { choice } from './combinators/choice.ts'
|
|
19
|
+
export { dispatch, endsWith, makeWhen, matches, otherwise, routed, startsWith, when } from './combinators/dispatch.ts'
|
|
20
|
+
export type { DispatchArm, DispatchOtherwise, DispatchStringMatcher, DispatchWhen, DispatchWhenFactory, DispatchWhenOptions, DispatchWhenMatcher } from './combinators/dispatch.ts'
|
|
21
|
+
export { attempt } from './combinators/attempt.ts'
|
|
22
|
+
export { many, oneOrMore, optional, sepBy, oneOrMoreSep, keepSeparator } from './combinators/repeat.ts'
|
|
23
|
+
export type { RepeatOptions, SepByOptions, TrailingSeparator, KeptSeparator } from './combinators/repeat.ts'
|
|
24
|
+
export { rules } from './combinators/parser.ts'
|
|
25
|
+
export { ref } from './combinators/ref.ts'
|
|
26
|
+
export { not } from './combinators/not.ts'
|
|
27
|
+
export { peek } from './combinators/peek.ts'
|
|
28
|
+
export { node } from './combinators/node.ts'
|
|
29
|
+
export type { BuildNode, NodeCombinator, NodeOptions, NodeProjectOptions } from './combinators/node.ts'
|
|
30
|
+
// lazy() is intentionally NOT exported.
|
|
31
|
+
export { transform, trivia, classifiedTrivia, label, field } from './combinators/map.ts'
|
|
32
|
+
export { parse, parser, noTrivia } from './combinators/grammar.ts'
|
|
33
|
+
export type { ParseOptions, ParserOptions, ParsemanParser } from './combinators/grammar.ts'
|
|
34
|
+
export { token, leaf } from './combinators/token.ts'
|
|
35
|
+
|
|
36
|
+
export { compile } from './table/compile.ts'
|
|
37
|
+
/*
|
|
38
|
+
* `CompiledParser` is exported above, from `./types.ts` — it is the contract
|
|
39
|
+
* `compile()` answers to, so it lives with the library's types.
|
|
40
|
+
*
|
|
41
|
+
* TWO NAMES USED TO SIT HERE and no longer do. Neither was ever documented and
|
|
42
|
+
* nothing outside the source lowering referenced either; they were in the barrel
|
|
43
|
+
* because the barrel re-exported the engine, not because anything asked for them.
|
|
44
|
+
*
|
|
45
|
+
* `LinkablePieces` — the SOURCE-LOWERING linkable IR. Every field of it is
|
|
46
|
+
* source text or an input to resolving source text (see
|
|
47
|
+
* `compile-linkable-table.ts`), so it has no meaning for a
|
|
48
|
+
* table artifact and dies with the source lowering. The
|
|
49
|
+
* public composable-piece type is `LinkableTable`, below.
|
|
50
|
+
* `DuplicationOption` — an option bag for the source lowering's duplication
|
|
51
|
+
* diagnostic, used nowhere else. The public diagnostic
|
|
52
|
+
* surface is `analyzeDuplication` /
|
|
53
|
+
* `AnalyzeDuplicationOptions`, exported further down.
|
|
54
|
+
*
|
|
55
|
+
* `HostMode` stays: it names the `hostMode` option on `rules()` and `compile()`,
|
|
56
|
+
* which is public. It now comes from the artifact↔host contract module that has
|
|
57
|
+
* owned the definition all along.
|
|
58
|
+
*/
|
|
59
|
+
export type { HostMode } from './cst/host-mode.ts'
|
|
60
|
+
/**
|
|
61
|
+
* `linkable()` produces TABLES too — the table counterpart of `compileLinkable`.
|
|
62
|
+
* A piece is a table when it is self-contained, and always carries its IR, which
|
|
63
|
+
* is what makes table-to-table composition a rule-map merge and one encode rather
|
|
64
|
+
* than a merge of two already-encoded programs.
|
|
65
|
+
*/
|
|
66
|
+
export { compileLinkableTable, type LinkableTable, type LinkableTableOptions } from './compiler/compile-linkable-table.ts'
|
|
67
|
+
export type { DuplicationOption } from './table/duplication-hook.ts'
|
|
68
|
+
|
|
69
|
+
// THE diagnostic entry point. `compile()` never reports anything: diagnostics are a
|
|
70
|
+
// deliberate act, not a side effect of producing an artifact. `diagnoseGrammar` accepts
|
|
71
|
+
// any grammar shape, returns a deterministic machine-readable object that fails CLOSED,
|
|
72
|
+
// and `formatGrammarDiagnosis` renders it for a human.
|
|
73
|
+
//
|
|
74
|
+
// const d = diagnoseGrammar(myGrammar)
|
|
75
|
+
// if (!d.ok) { console.error(formatGrammarDiagnosis(d).join('\n')); process.exit(1) }
|
|
76
|
+
//
|
|
77
|
+
// `examinedNothing(d)` separates "measured, and it is bad" from "could not measure":
|
|
78
|
+
// `ok` is false for both, and a gate that treats them the same reports a confident
|
|
79
|
+
// finding count over a grammar it never opened.
|
|
80
|
+
//
|
|
81
|
+
// The `analyze*` functions below remain the lower-level surface it is built on.
|
|
82
|
+
export { diagnoseGrammar, examinedNothing, formatGrammarDiagnosis } from './analysis/diagnose.ts'
|
|
83
|
+
export type {
|
|
84
|
+
GrammarDiagnosis, DiagnosisFinding, DiagnosisCode, DiagnosisSeverity,
|
|
85
|
+
DiagnosableGrammar, DiagnoseOptions,
|
|
86
|
+
} from './analysis/diagnose.ts'
|
|
87
|
+
|
|
88
|
+
export { analyzeGating, analyzeGatingRules, formatGatingWarnings, firstSetToString } from './analysis/gating.ts'
|
|
89
|
+
export type {
|
|
90
|
+
GatingReport, ChoiceGating, AnyArm, Overlap, AntiPattern, Unanalysable,
|
|
91
|
+
FirstSetCause, ChoiceStrategyTag, AnalyzeGatingOptions,
|
|
92
|
+
} from './analysis/gating.ts'
|
|
93
|
+
// Analyze a WHOLE grammar, including a `compose()` result — whose fused map holds
|
|
94
|
+
// rule FUNCTIONS, not combinators, and so cannot be walked by `analyzeGatingRules`
|
|
95
|
+
// directly. This is the entry point a composed grammar's author wants.
|
|
96
|
+
export { analyzeGrammarGating } from './analysis/grammar.ts'
|
|
97
|
+
|
|
98
|
+
// Choice-cost diagnostics: the static shared-prefix inventory, the interpreted
|
|
99
|
+
// corpus wasted-work profile, and the gate policy over them. Quiet by default —
|
|
100
|
+
// nothing here runs or prints unless it is called.
|
|
101
|
+
export { analyzeChoiceInventory, profileWastedWork, choiceSiteKey, armLabel } from './analysis/choice-cost.ts'
|
|
102
|
+
export type {
|
|
103
|
+
ChoiceSite, ChoiceInventoryEntry, ChoiceInventoryReport, PrefixGroup,
|
|
104
|
+
ArmDeclineReason, SiteDeclineReason, WastedWorkCorpusEntry, ProfileWastedWorkOptions,
|
|
105
|
+
WastedWorkArm, WastedWorkSite, WastedWorkReport,
|
|
106
|
+
} from './analysis/choice-cost.ts'
|
|
107
|
+
export { renderChoiceInventory, renderWastedWork, leftFactorPreview } from './analysis/choice-cost-render.ts'
|
|
108
|
+
export type { RenderOptions } from './analysis/choice-cost-render.ts'
|
|
109
|
+
export { checkWastedWork, buildWastedWorkBaseline } from './analysis/choice-cost-gate.ts'
|
|
110
|
+
export type { WastedWorkBaseline, WastedWorkPolicy, GateBreach, GateVerdict } from './analysis/choice-cost-gate.ts'
|
|
111
|
+
export type { AnalysableGrammar } from './analysis/grammar.ts'
|
|
112
|
+
|
|
113
|
+
export {
|
|
114
|
+
analyzeDuplication, analyzeDuplicationRules, formatDuplicationFindings,
|
|
115
|
+
duplicationFindingCount, siteToString, alternationGroups, keywordRegexShape,
|
|
116
|
+
extractCharClasses, charClassMembers, keywordAlternationHazards,
|
|
117
|
+
} from './analysis/duplication.ts'
|
|
118
|
+
export type {
|
|
119
|
+
DuplicationReport, AnalyzeDuplicationOptions, DuplicationWarnLevel, Site,
|
|
120
|
+
DuplicateFinding, NearDuplicateFinding, RegexFragmentFinding,
|
|
121
|
+
RegexClassFinding, RegexClassVariant,
|
|
122
|
+
ArmOverlapFinding, RewriteFinding, RewriteKind, SepByVerdict, KeywordRegexFinding, DivergentNodeFinding,
|
|
123
|
+
StructureLossFinding,
|
|
124
|
+
} from './analysis/duplication.ts'
|
|
125
|
+
// `composeLeaf()` is terminal by design; ordinary reusable grammar composition is
|
|
126
|
+
// `compose()`. There is no à-la-carte rule selection: compose small pieces instead.
|
|
127
|
+
//
|
|
128
|
+
// `fuseInterpreted()` / `isInterpretedFuse()` are deliberately NOT re-exported. The
|
|
129
|
+
// interpreted fuse is a DIAGNOSTIC engine — it is how the differential harnesses and
|
|
130
|
+
// bench legs run a grammar without reaching codegen — not a supported way to ship a
|
|
131
|
+
// parser. Publishing it advertised a second engine with the same grammar and different
|
|
132
|
+
// runtime characteristics, and `isInterpretedFuse` existed as an escape hatch that not
|
|
133
|
+
// even Parseman called. Both stay internal (./compiler/linker.ts).
|
|
134
|
+
//
|
|
135
|
+
// Withdrawing the discriminator is only safe because `composeLeaf()` no longer LIES
|
|
136
|
+
// about which engine it returned: it is typed `Record<string, Runnable>`, true on both
|
|
137
|
+
// paths, and `Runnable` is what `run()`/`parseDoc()` take. Were that type ever narrowed
|
|
138
|
+
// back to `FusedRule`, `isInterpretedFuse` would have to be re-exported in the same
|
|
139
|
+
// change — a dual shape with no way to detect it is the worse of the two.
|
|
140
|
+
export { compose, composeLeaf, cstBuildHost } from './compiler/linker.ts'
|
|
141
|
+
export type { CstBuildHostOptions, FusedRule } from './compiler/linker.ts'
|
|
142
|
+
|
|
143
|
+
export { buildLineIndex, createLineIndex, recordLineRange, normalizeLineIndex, offsetToLineCol, annotateSpan, annotateTreeSpans } from './line-index.ts'
|
|
144
|
+
export type { LineIndex } from './line-index.ts'
|
|
145
|
+
|
|
146
|
+
export { gate } from './combinators/gate.ts'
|
|
147
|
+
export { adjacent, notAdjacent } from './combinators/adjacency.ts'
|
|
148
|
+
export { withCtx } from './combinators/withCtx.ts'
|
|
149
|
+
export { isParseError } from './combinators/expect.ts'
|
|
150
|
+
export { expect } from './combinators/expect.ts'
|
|
151
|
+
export { completionsAt } from './combinators/completions.ts'
|
|
152
|
+
export { scanTo, balanced } from './combinators/scanTo.ts'
|
|
153
|
+
export type { ScanToOptions } from './combinators/scanTo.ts'
|
|
154
|
+
|
|
155
|
+
export type { CSTNode, CSTLeaf, CSTError, CSTTrivia, CSTChild, CSTRawChild, NodeLike } from './cst/types.ts'
|
|
156
|
+
|
|
157
|
+
export { parseDoc } from './functional/doc.ts'
|
|
158
|
+
export type { ParseDoc, ParseDocOptions, Registry, RuleFn } from './functional/doc.ts'
|
|
159
|
+
/* THE PUBLIC PARSE PATH IS THE TABLE. `functional/run.ts` is the driver and
|
|
160
|
+
* still walks a combinator directly when handed one — that branch is the
|
|
161
|
+
* interpreter, and it stays reachable from `test/` and the identity sweep so a
|
|
162
|
+
* table/codegen divergence has something to be bisected against. It is not what
|
|
163
|
+
* the main entry hands out. See `functional/run-tabled.ts` for why the encode
|
|
164
|
+
* cannot live inside the driver (`parseman/run`'s closure) and why the cache is
|
|
165
|
+
* load-bearing rather than an optimisation. */
|
|
166
|
+
export { run } from './functional/run-tabled.ts'
|
|
167
|
+
export type { RootTriviaCapture, RunResult, RunOptions, Runnable } from './functional/run.ts'
|
|
168
|
+
export { GRAMMAR_COVERAGE_DEFINITIONS, compiledGrammarCoverageDefinitions, createGrammarCoverageCollector, createGrammarInstrumentationContext, createGrammarTraceSink, grammarCoverageDefinitions, composedGrammarCoverageDefinitions, runWithGrammarCoverage } from './coverage.ts'
|
|
169
|
+
export type { GrammarCoverageCollector, GrammarCoverageDefinition, GrammarCoverageSnapshot, GrammarInstrumentationContext, GrammarTraceEvent, GrammarTracePhase, GrammarTraceSink, GrammarTraceSnapshot } from './coverage.ts'
|
|
170
|
+
export { buildTriviaIndex } from './cst/trivia-index.ts'
|
|
171
|
+
export type { TriviaIndex, TriviaToken, TriviaIndexOptions } from './cst/trivia-index.ts'
|
|
172
|
+
export { createVisitor } from './cst/walk.ts'
|
|
173
|
+
export type { Walkable, VisitorHandler, VisitorSpec } from './cst/walk.ts'
|
|
174
|
+
export type { GrammarReflection, GrammarNodeReflection, GrammarWithReflection } from './cst/reflection.ts'
|
|
175
|
+
export { buildRootTriviaIndex, triviaEntries } from './cst/trivia-entries.ts'
|
|
176
|
+
export type { RootTriviaGap, RootTriviaIndex, TriviaEntriesView } from './cst/trivia-entries.ts'
|
|
177
|
+
export { triviaKindMask } from './cst/trivia-kinds.ts'
|
|
178
|
+
|
|
179
|
+
export {
|
|
180
|
+
relativize,
|
|
181
|
+
absolutize,
|
|
182
|
+
absoluteSpanAt,
|
|
183
|
+
shiftAbsolute,
|
|
184
|
+
applyEdit,
|
|
185
|
+
relativizeCST,
|
|
186
|
+
absolutizeCST,
|
|
187
|
+
absoluteSpanCST,
|
|
188
|
+
} from './cst/relative-spans.ts'
|
|
189
|
+
export type { AbsNode, RelNode } from './cst/relative-spans.ts'
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `parseman/language-service` — the external, grammar-agnostic IDE layer.
|
|
3
|
+
*
|
|
4
|
+
* A grammar is pure structure (it carries no recovery/completion/lint config).
|
|
5
|
+
* `languageService(grammar, config)` layers editor behaviour ON TOP, keyed by rule
|
|
6
|
+
* name = node type, without ever modifying the grammar:
|
|
7
|
+
*
|
|
8
|
+
* - `parse(src, { tolerant })` → CST + `ParseError[]` (recovers on the compiled
|
|
9
|
+
* fast path when `grammar` is a `compile(g, { recovery: true })` grammar).
|
|
10
|
+
* - `diagnostics(src)` → structural parse errors + your per-node-type lint rules.
|
|
11
|
+
* - `completionsAt(src, offset)` → the grammar's expected-token set at the cursor,
|
|
12
|
+
* mapped through your per-rule semantic completion handlers.
|
|
13
|
+
*
|
|
14
|
+
* The grammar can be the interpreter combinator OR a compiled grammar — the domain
|
|
15
|
+
* knowledge (what to suggest after `color:`, which nodes to lint) lives here, in the
|
|
16
|
+
* consumer's config, never in the grammar.
|
|
17
|
+
*/
|
|
18
|
+
import type { Combinator, ParseContext, ParseResult, Span, ParseError } from '../types.ts'
|
|
19
|
+
import { run } from '../functional/run.ts'
|
|
20
|
+
import { completionsAt as coreCompletionsAt } from '../combinators/completions.ts'
|
|
21
|
+
import { cstBuildHost } from '../compiler/linker.ts'
|
|
22
|
+
import type { Walkable } from '../cst/walk.ts'
|
|
23
|
+
import { parseDoc, type Registry } from '../functional/doc.ts'
|
|
24
|
+
import { absolutizeCST } from '../cst/relative-spans.ts'
|
|
25
|
+
import type { NodeLike } from '../cst/types.ts'
|
|
26
|
+
|
|
27
|
+
export type Severity = 'error' | 'warning' | 'info'
|
|
28
|
+
export type Diagnostic = { severity: Severity; message: string; span: Span }
|
|
29
|
+
export type CompletionItem = { label: string; detail?: string }
|
|
30
|
+
|
|
31
|
+
/** A CST node/leaf as a diagnostics handler sees it: the walk shape plus span/value. */
|
|
32
|
+
export type LsNode = Walkable & { readonly span: Span; readonly value?: string }
|
|
33
|
+
|
|
34
|
+
/** Context handed to a completion handler: which rule the cursor sits in and the
|
|
35
|
+
* grammar's raw expected-token labels there. */
|
|
36
|
+
export type CompletionContext = { rule: string | null; expected: string[]; offset: number }
|
|
37
|
+
|
|
38
|
+
export type LanguageServiceConfig = {
|
|
39
|
+
/** Lint/diagnostic rules keyed by node type (rule name). Return zero or more
|
|
40
|
+
* Diagnostics for each matching node. */
|
|
41
|
+
diagnostics?: Record<string, (node: LsNode) => Diagnostic | Diagnostic[] | null | undefined | void>
|
|
42
|
+
/** Semantic completion handlers keyed by the rule the cursor is in. Turn the
|
|
43
|
+
* grammar's structural expectation into domain suggestions. */
|
|
44
|
+
complete?: Record<string, (ctx: CompletionContext) => CompletionItem[]>
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** Grammar the service drives: an interpreter combinator or a compiled entry fn. */
|
|
48
|
+
type Grammar = Combinator<unknown> | ((input: string, pos: number, ctx: ParseContext) => ParseResult<unknown>)
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* What you hand `languageService`:
|
|
52
|
+
* - a single entry (combinator or `compile()`d fn) — stateless methods only, or
|
|
53
|
+
* - `{ rules, root }` (a `rules()` registry + its root rule name) — also unlocks
|
|
54
|
+
* `openDocument`, the live incremental editor document.
|
|
55
|
+
*/
|
|
56
|
+
export type GrammarInput = Grammar | { rules: Registry<NodeLike>; root: string }
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* A live incremental document — the fused editor backend. Holds a tolerant
|
|
60
|
+
* `parseDoc` whose tree survives broken input (recovery) and re-parses only the
|
|
61
|
+
* edited span on `edit()`. Diagnostics walk the maintained tree (recovered errors
|
|
62
|
+
* ride inside it as `parseError` nodes), so they stay complete across edits without
|
|
63
|
+
* re-parsing the whole document.
|
|
64
|
+
*/
|
|
65
|
+
export type LsDocument = {
|
|
66
|
+
/** The current CST with ABSOLUTE spans (recovered errors embedded as parseError nodes). */
|
|
67
|
+
readonly tree: LsNode | null
|
|
68
|
+
/** Apply an editor change (byte offsets in the CURRENT text); returns the next document. */
|
|
69
|
+
edit(from: number, to: number, replacement: string): LsDocument
|
|
70
|
+
/** Structural parse errors (from the tree) + your per-node-type lint rules. */
|
|
71
|
+
diagnostics(): Diagnostic[]
|
|
72
|
+
/** Completions at a cursor offset in the current text. */
|
|
73
|
+
completionsAt(offset: number): CompletionItem[]
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export type LanguageService = {
|
|
77
|
+
parse(src: string, opts?: { tolerant?: boolean }): ParseResult<unknown> & { errors: ParseError[] }
|
|
78
|
+
diagnostics(src: string): Diagnostic[]
|
|
79
|
+
completionsAt(src: string, offset: number): CompletionItem[]
|
|
80
|
+
/** Open a live incremental document. Requires `languageService({ rules, root }, …)`. */
|
|
81
|
+
openDocument(src: string): LsDocument
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const errorMessage = (expected: readonly string[]): string =>
|
|
85
|
+
expected.length ? `Unexpected input; expected ${expected.join(' or ')}` : 'Unexpected input'
|
|
86
|
+
|
|
87
|
+
function visitTree(root: LsNode, enter: (node: LsNode) => void): void {
|
|
88
|
+
const go = (node: LsNode): void => {
|
|
89
|
+
enter(node)
|
|
90
|
+
const children = node.children as ReadonlyArray<LsNode> | undefined
|
|
91
|
+
if (Array.isArray(children)) for (const child of children) go(child)
|
|
92
|
+
}
|
|
93
|
+
go(root)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Merge every syntax-error source into one deduped diagnostic list:
|
|
98
|
+
* (a) recovered `parseError` nodes embedded in the tree + per-node lint rules
|
|
99
|
+
* (via {@link diagnoseTree}),
|
|
100
|
+
* (b) residual FLAT recovery/failure errors from the run/doc (present when the
|
|
101
|
+
* parse produced no tree, or as the flat mirror of embedded errors), and
|
|
102
|
+
* (c) a sentinel over any trailing `unconsumedFrom..end` junk the grammar left.
|
|
103
|
+
* Deduped by span+message so an embedded error and its flat twin collapse to one,
|
|
104
|
+
* while distinct diagnostics at the same offset survive. With the reporting fixes
|
|
105
|
+
* (expect() embeds, speculative rollback is clean) (a) is already complete; the
|
|
106
|
+
* merge is what guarantees missing-closers and trailing junk are never invisible.
|
|
107
|
+
*/
|
|
108
|
+
function mergeDiagnostics(
|
|
109
|
+
tree: LsNode | null,
|
|
110
|
+
flat: ReadonlyArray<{ expected: readonly string[]; span: Span }>,
|
|
111
|
+
unconsumedFrom: number | null,
|
|
112
|
+
inputLength: number,
|
|
113
|
+
config: LanguageServiceConfig,
|
|
114
|
+
): Diagnostic[] {
|
|
115
|
+
const out: Diagnostic[] = []
|
|
116
|
+
const seen = new Set<string>()
|
|
117
|
+
const add = (d: Diagnostic): void => {
|
|
118
|
+
const key = `${d.severity}|${d.span.start}|${d.span.end}|${d.message}`
|
|
119
|
+
if (seen.has(key)) return
|
|
120
|
+
seen.add(key)
|
|
121
|
+
out.push(d)
|
|
122
|
+
}
|
|
123
|
+
if (tree) for (const d of diagnoseTree(tree, config)) add(d)
|
|
124
|
+
for (const e of flat) add({ severity: 'error', message: errorMessage(e.expected), span: e.span })
|
|
125
|
+
if (unconsumedFrom !== null) {
|
|
126
|
+
add({ severity: 'error', message: 'Unexpected input', span: { start: unconsumedFrom, end: inputLength } })
|
|
127
|
+
}
|
|
128
|
+
return out
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/** One walk over a CST: recovered `parseError` nodes → error diagnostics, and each
|
|
132
|
+
* node dispatched to its `config.diagnostics[type]` lint handler. Errors live in the
|
|
133
|
+
* tree, so this is the single source for both — no separate flat channel to merge. */
|
|
134
|
+
function diagnoseTree(root: LsNode, config: LanguageServiceConfig): Diagnostic[] {
|
|
135
|
+
const out: Diagnostic[] = []
|
|
136
|
+
const rules = config.diagnostics
|
|
137
|
+
visitTree(root, node => {
|
|
138
|
+
if (node._tag === 'parseError') {
|
|
139
|
+
out.push({ severity: 'error', message: errorMessage((node as { expected?: string[] }).expected ?? []), span: node.span })
|
|
140
|
+
return
|
|
141
|
+
}
|
|
142
|
+
const h = rules && node.type !== undefined ? rules[node.type] : undefined
|
|
143
|
+
if (h) {
|
|
144
|
+
const d = h(node)
|
|
145
|
+
if (d) out.push(...(Array.isArray(d) ? d : [d]))
|
|
146
|
+
}
|
|
147
|
+
})
|
|
148
|
+
return out
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export function languageService(grammar: GrammarInput, config: LanguageServiceConfig = {}): LanguageService {
|
|
152
|
+
const build = cstBuildHost()
|
|
153
|
+
const asReg = typeof grammar === 'object' && grammar !== null && 'rules' in grammar
|
|
154
|
+
const registry = asReg ? grammar.rules : undefined
|
|
155
|
+
const root = asReg ? grammar.root : undefined
|
|
156
|
+
const entry: Grammar = asReg ? (registry![root!] as Grammar) : grammar
|
|
157
|
+
|
|
158
|
+
// The grammar's ambient trivia (interpreter combinators carry it in _meta) —
|
|
159
|
+
// handed to run() so it skips a trailing whitespace/comment tail before
|
|
160
|
+
// reporting unconsumedFrom, matching parseDoc/run semantics.
|
|
161
|
+
const trivia = typeof entry !== 'function' ? (entry as Combinator<unknown>)._meta.grammarTrivia : undefined
|
|
162
|
+
const parseTolerant = (src: string) =>
|
|
163
|
+
run(entry as never, src, { tolerant: true, build, ...(trivia !== undefined ? { trivia } : {}) })
|
|
164
|
+
|
|
165
|
+
const complete = (input: string, offset: number, treeForCursor: unknown): CompletionItem[] => {
|
|
166
|
+
const expected = coreCompletionsAt(entry as never, input, offset, { tolerant: true })
|
|
167
|
+
const rule = ruleAtCursor(treeForCursor, offset)
|
|
168
|
+
const handler = rule !== null ? config.complete?.[rule] : undefined
|
|
169
|
+
if (handler) return handler({ rule, expected, offset })
|
|
170
|
+
return expected.map(label => ({ label }))
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
return {
|
|
174
|
+
parse(src, opts) {
|
|
175
|
+
return run(entry as never, src, { tolerant: opts?.tolerant ?? true, build }) as ParseResult<unknown> & { errors: ParseError[] }
|
|
176
|
+
},
|
|
177
|
+
|
|
178
|
+
diagnostics(src) {
|
|
179
|
+
const r = parseTolerant(src)
|
|
180
|
+
const tree = r.value && typeof r.value === 'object' ? (r.value as LsNode) : null
|
|
181
|
+
return mergeDiagnostics(tree, r.errors, r.unconsumedFrom, src.length, config)
|
|
182
|
+
},
|
|
183
|
+
|
|
184
|
+
completionsAt(src, offset) {
|
|
185
|
+
return complete(src, offset, parseTolerant(src.slice(0, offset)).value)
|
|
186
|
+
},
|
|
187
|
+
|
|
188
|
+
openDocument(src) {
|
|
189
|
+
if (!registry || root === undefined) {
|
|
190
|
+
throw new Error('openDocument requires languageService({ rules, root }, config) — a rules() registry, not a single entry')
|
|
191
|
+
}
|
|
192
|
+
const host = cstBuildHost()
|
|
193
|
+
const make = (doc: ReturnType<typeof parseDoc<NodeLike>>): LsDocument => {
|
|
194
|
+
// Absolutize once per document version (memoized in this closure).
|
|
195
|
+
const abs = doc.tree ? (absolutizeCST(doc.tree as never) as unknown as LsNode) : null
|
|
196
|
+
return {
|
|
197
|
+
get tree() { return abs },
|
|
198
|
+
edit: (from, to, replacement) => make(doc.edit(from, to, replacement)),
|
|
199
|
+
// Same unified merge as the stateless path: embedded parseError nodes +
|
|
200
|
+
// lint, the flat recovery errors the doc collected, and a trailing-junk
|
|
201
|
+
// sentinel — so a live editor document surfaces missing-closers and junk.
|
|
202
|
+
diagnostics: () => mergeDiagnostics(abs, doc.errors, doc.unconsumedFrom, doc.input.length, config),
|
|
203
|
+
completionsAt: (offset) => complete(doc.input, offset, abs),
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
return make(parseDoc<NodeLike>(registry, root, src, { tolerant: true, structuralReuse: true, build: host }))
|
|
207
|
+
},
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/** Deepest node whose span brackets the cursor — "which rule is the cursor in".
|
|
212
|
+
* The grammar carries no cursor knowledge, so we reconstruct it from the CST. */
|
|
213
|
+
function ruleAtCursor(root: unknown, offset: number): string | null {
|
|
214
|
+
if (!root || typeof root !== 'object') return null
|
|
215
|
+
let best: string | null = null
|
|
216
|
+
visitTree(root as LsNode, node => {
|
|
217
|
+
const s = node.span
|
|
218
|
+
if (s && s.start <= offset && offset <= s.end && node.type !== undefined) best = node.type
|
|
219
|
+
})
|
|
220
|
+
return best
|
|
221
|
+
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import type { Span } from './types.ts'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Precomputed index of newline positions for O(log n) offset->line/col lookup.
|
|
5
|
+
* lineStarts[i] is the byte offset of the first character on line i+1.
|
|
6
|
+
*/
|
|
7
|
+
export type LineIndex = { lineStarts: number[] }
|
|
8
|
+
type LineTrackingContext = {
|
|
9
|
+
trackLines?: boolean | undefined
|
|
10
|
+
_lineIndex?: LineIndex | undefined
|
|
11
|
+
_lineStarts?: number[] | undefined
|
|
12
|
+
_lineScannedTo?: number | undefined
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function createLineIndex(): LineIndex {
|
|
16
|
+
return { lineStarts: [0] }
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function buildLineIndex(input: string): LineIndex {
|
|
20
|
+
const lineStarts = [0]
|
|
21
|
+
for (let i = 0; i < input.length; i++) {
|
|
22
|
+
if (input.charCodeAt(i) === 10) lineStarts.push(i + 1)
|
|
23
|
+
}
|
|
24
|
+
return { lineStarts }
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Record newline offsets for a matched range. Intended for optional parsers that
|
|
29
|
+
* collect line starts while they consume terminals. The collector is deliberately
|
|
30
|
+
* append-only: speculative parses never roll it back. Normalize once at the
|
|
31
|
+
* driver boundary before doing offset->line/column lookup.
|
|
32
|
+
*/
|
|
33
|
+
export function recordLineRange(index: LineIndex, input: string, start: number, end: number): void {
|
|
34
|
+
const lineStarts = index.lineStarts
|
|
35
|
+
for (let i = start; i < end; i++) {
|
|
36
|
+
if (input.charCodeAt(i) === 10) lineStarts.push(i + 1)
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Records newlines from the context high-water mark through `end`. The start
|
|
42
|
+
* argument is only a caller hint: append-only tracking uses `_lineScannedTo` so
|
|
43
|
+
* overlapping calls can backfill gaps without double-scanning committed ranges.
|
|
44
|
+
*/
|
|
45
|
+
export function recordLineRangeFromContext(
|
|
46
|
+
ctx: LineTrackingContext,
|
|
47
|
+
input: string,
|
|
48
|
+
_startHint: number,
|
|
49
|
+
end: number,
|
|
50
|
+
): void {
|
|
51
|
+
if (!ctx.trackLines) return
|
|
52
|
+
const from = ctx._lineScannedTo ?? 0
|
|
53
|
+
if (end <= from) return
|
|
54
|
+
const index = ctx._lineIndex ?? (ctx._lineStarts ? { lineStarts: ctx._lineStarts } : undefined)
|
|
55
|
+
if (!index) return
|
|
56
|
+
recordLineRange(index, input, from, end)
|
|
57
|
+
ctx._lineScannedTo = end
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function normalizeLineIndex(index: LineIndex): LineIndex {
|
|
61
|
+
const starts = index.lineStarts
|
|
62
|
+
starts.sort((a, b) => a - b)
|
|
63
|
+
let write = 0
|
|
64
|
+
for (let read = 0; read < starts.length; read++) {
|
|
65
|
+
const value = starts[read]!
|
|
66
|
+
if (write === 0 || starts[write - 1] !== value) starts[write++] = value
|
|
67
|
+
}
|
|
68
|
+
starts.length = write
|
|
69
|
+
if (starts.length === 0 || starts[0] !== 0) starts.unshift(0)
|
|
70
|
+
return index
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function offsetToLineCol(
|
|
74
|
+
index: LineIndex,
|
|
75
|
+
offset: number
|
|
76
|
+
): { line: number; col: number } {
|
|
77
|
+
const { lineStarts } = index
|
|
78
|
+
let lo = 0
|
|
79
|
+
let hi = lineStarts.length - 1
|
|
80
|
+
while (lo < hi) {
|
|
81
|
+
const mid = (lo + hi + 1) >> 1
|
|
82
|
+
if (lineStarts[mid]! <= offset) lo = mid
|
|
83
|
+
else hi = mid - 1
|
|
84
|
+
}
|
|
85
|
+
return { line: lo + 1, col: offset - lineStarts[lo]! + 1 }
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** Fills startLine/startColumn/endLine/endColumn on a span in-place. */
|
|
89
|
+
export function annotateSpan(span: Span, index: LineIndex): Span {
|
|
90
|
+
const s = offsetToLineCol(index, span.start)
|
|
91
|
+
const e = offsetToLineCol(index, span.end)
|
|
92
|
+
return {
|
|
93
|
+
...span,
|
|
94
|
+
startLine: s.line,
|
|
95
|
+
startColumn: s.col,
|
|
96
|
+
endLine: e.line,
|
|
97
|
+
endColumn: e.col,
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export function annotateSpanFromLineContext(
|
|
102
|
+
span: Span,
|
|
103
|
+
ctx: LineTrackingContext,
|
|
104
|
+
): Span {
|
|
105
|
+
if (!ctx.trackLines) return span
|
|
106
|
+
const index = ctx._lineIndex ?? (ctx._lineStarts ? { lineStarts: ctx._lineStarts } : undefined)
|
|
107
|
+
return index ? annotateSpan(span, index) : span
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function isSpan(value: unknown): value is Span {
|
|
111
|
+
return !!value
|
|
112
|
+
&& typeof value === 'object'
|
|
113
|
+
&& typeof (value as { start?: unknown }).start === 'number'
|
|
114
|
+
&& typeof (value as { end?: unknown }).end === 'number'
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/** Annotate every span-shaped object reachable from a parse result value. */
|
|
118
|
+
export function annotateTreeSpans<T>(value: T, index: LineIndex, seen: WeakSet<object> = new WeakSet()): T {
|
|
119
|
+
if (!value || typeof value !== 'object') return value
|
|
120
|
+
if (seen.has(value)) return value
|
|
121
|
+
seen.add(value)
|
|
122
|
+
|
|
123
|
+
const obj = value as Record<string, unknown>
|
|
124
|
+
if (isSpan(obj.span)) obj.span = annotateSpan(obj.span, index)
|
|
125
|
+
|
|
126
|
+
for (const key of Object.keys(obj)) {
|
|
127
|
+
const child = obj[key]
|
|
128
|
+
if (child && typeof child === 'object') annotateTreeSpans(child, index, seen)
|
|
129
|
+
}
|
|
130
|
+
return value
|
|
131
|
+
}
|