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,358 @@
|
|
|
1
|
+
import type { Combinator, DispatchCase, DispatchMatcherCase, ParseContext, ParseResult, ParserMeta } from '../types.ts'
|
|
2
|
+
import { cstCaptureActive, pushCstLeaf } from '../cst/capture-buffer.ts'
|
|
3
|
+
import { rollbackTrivia, saveTriviaMark } from './trivia-skip.ts'
|
|
4
|
+
|
|
5
|
+
export type DispatchWhen<T> = {
|
|
6
|
+
readonly kind: 'when'
|
|
7
|
+
readonly keys: readonly string[]
|
|
8
|
+
readonly parser: Combinator<T>
|
|
9
|
+
readonly caseInsensitive: boolean
|
|
10
|
+
readonly usesRouted?: boolean
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export type DispatchWhenMatcher<T> = {
|
|
14
|
+
readonly kind: 'whenMatcher'
|
|
15
|
+
readonly matcher: DispatchStringMatcher
|
|
16
|
+
readonly parser: Combinator<T>
|
|
17
|
+
readonly caseInsensitive: boolean
|
|
18
|
+
readonly usesRouted?: boolean
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export type DispatchStringMatcher =
|
|
22
|
+
| { readonly kind: 'startsWith'; readonly value: string }
|
|
23
|
+
| { readonly kind: 'endsWith'; readonly value: string }
|
|
24
|
+
| { readonly kind: 'matches'; readonly value: string; readonly flags: string }
|
|
25
|
+
|
|
26
|
+
export type DispatchOtherwise<T> = {
|
|
27
|
+
readonly kind: 'otherwise'
|
|
28
|
+
readonly parser: Combinator<T>
|
|
29
|
+
readonly usesRouted?: boolean
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export type DispatchArm<T = unknown> = DispatchWhen<T> | DispatchWhenMatcher<T> | DispatchOtherwise<T>
|
|
33
|
+
export type DispatchWhenOptions = {
|
|
34
|
+
caseInsensitive?: boolean
|
|
35
|
+
}
|
|
36
|
+
export type DispatchWhenFactory = <T>(
|
|
37
|
+
key: string | readonly string[],
|
|
38
|
+
parser: Combinator<T>,
|
|
39
|
+
) => DispatchWhen<T>
|
|
40
|
+
export type DispatchWhenKey = string | readonly string[]
|
|
41
|
+
export type DispatchWhenSelector = DispatchWhenKey | DispatchStringMatcher
|
|
42
|
+
|
|
43
|
+
type ArmValue<T> = T extends DispatchWhen<infer U>
|
|
44
|
+
? U
|
|
45
|
+
: T extends DispatchWhenMatcher<infer U>
|
|
46
|
+
? U
|
|
47
|
+
: T extends DispatchOtherwise<infer U>
|
|
48
|
+
? U
|
|
49
|
+
: never
|
|
50
|
+
type UnionArms<T extends readonly DispatchArm<unknown>[]> = {
|
|
51
|
+
[K in keyof T]: ArmValue<T[K]>
|
|
52
|
+
}[number]
|
|
53
|
+
|
|
54
|
+
export function parserUsesRouted(parser: Combinator<unknown>, seen: Set<Combinator<unknown>> = new Set()): boolean {
|
|
55
|
+
if (seen.has(parser)) return false
|
|
56
|
+
seen.add(parser)
|
|
57
|
+
const def = parser._def
|
|
58
|
+
switch (def.tag) {
|
|
59
|
+
case 'routed':
|
|
60
|
+
return true
|
|
61
|
+
case 'sequence':
|
|
62
|
+
case 'choice':
|
|
63
|
+
return def.parsers.some(entry => parserUsesRouted(entry, seen))
|
|
64
|
+
case 'dispatch':
|
|
65
|
+
return false
|
|
66
|
+
case 'sepBy':
|
|
67
|
+
return parserUsesRouted(def.parser, seen) || parserUsesRouted(def.separator, seen)
|
|
68
|
+
case 'grammar':
|
|
69
|
+
return parserUsesRouted(def.parser, seen) || (def.triviaParser ? parserUsesRouted(def.triviaParser, seen) : false)
|
|
70
|
+
case 'scanTo':
|
|
71
|
+
return parserUsesRouted(def.sentinel, seen) || def.skip.some(entry => parserUsesRouted(entry, seen))
|
|
72
|
+
case 'recover':
|
|
73
|
+
return parserUsesRouted(def.parser, seen) || parserUsesRouted(def.sentinel, seen)
|
|
74
|
+
case 'lazy':
|
|
75
|
+
try { return parserUsesRouted(def.thunk(), seen) } catch { return false }
|
|
76
|
+
case 'many':
|
|
77
|
+
case 'oneOrMore':
|
|
78
|
+
case 'optional':
|
|
79
|
+
case 'attempt':
|
|
80
|
+
case 'transform':
|
|
81
|
+
case 'trivia':
|
|
82
|
+
case 'token':
|
|
83
|
+
case 'leaf':
|
|
84
|
+
case 'label':
|
|
85
|
+
case 'field':
|
|
86
|
+
case 'not':
|
|
87
|
+
case 'peek':
|
|
88
|
+
case 'node':
|
|
89
|
+
case 'withCtx':
|
|
90
|
+
case 'expect':
|
|
91
|
+
return parserUsesRouted(def.parser, seen)
|
|
92
|
+
default:
|
|
93
|
+
return false
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export function branchUsesRouted(branch: { parser: Combinator<unknown>; usesRouted?: boolean | undefined }): boolean {
|
|
98
|
+
return branch.usesRouted === true || parserUsesRouted(branch.parser)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export function asciiFoldKey(key: string): string {
|
|
102
|
+
let out = ''
|
|
103
|
+
for (let i = 0; i < key.length; i++) {
|
|
104
|
+
const c = key.charCodeAt(i)
|
|
105
|
+
out += String.fromCharCode(c >= 65 && c <= 90 ? c + 32 : c)
|
|
106
|
+
}
|
|
107
|
+
return out
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export function matchesDispatchMatcher(value: string, matcher: DispatchMatcherCase): boolean {
|
|
111
|
+
const candidate = matcher.caseInsensitive ? asciiFoldKey(value) : value
|
|
112
|
+
const expected = matcher.caseInsensitive ? asciiFoldKey(matcher.value) : matcher.value
|
|
113
|
+
switch (matcher.kind) {
|
|
114
|
+
case 'startsWith':
|
|
115
|
+
return candidate.startsWith(expected)
|
|
116
|
+
case 'endsWith':
|
|
117
|
+
return candidate.endsWith(expected)
|
|
118
|
+
case 'matches': {
|
|
119
|
+
const flags = matcher.caseInsensitive && !matcher.flags?.includes('i')
|
|
120
|
+
? `${matcher.flags ?? ''}i`
|
|
121
|
+
: matcher.flags ?? ''
|
|
122
|
+
return new RegExp(matcher.value, flags).test(value)
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function isStringMatcher(value: unknown): value is DispatchStringMatcher {
|
|
128
|
+
if (typeof value !== 'object' || value === null) return false
|
|
129
|
+
const kind = (value as { kind?: unknown }).kind
|
|
130
|
+
return kind === 'startsWith' || kind === 'endsWith' || kind === 'matches'
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function fixedMatcher(kind: 'startsWith' | 'endsWith', value: string): DispatchStringMatcher {
|
|
134
|
+
if (typeof value !== 'string') throw new TypeError(`parseman: ${kind}() value must be a string`)
|
|
135
|
+
if (value.length === 0) throw new RangeError(`parseman: ${kind}() requires a non-empty value`)
|
|
136
|
+
return { kind, value }
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export function startsWith(prefix: string): DispatchStringMatcher {
|
|
140
|
+
return fixedMatcher('startsWith', prefix)
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export function endsWith(suffix: string): DispatchStringMatcher {
|
|
144
|
+
return fixedMatcher('endsWith', suffix)
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export function matches(pattern: RegExp): DispatchStringMatcher {
|
|
148
|
+
if (!(pattern instanceof RegExp)) throw new TypeError('parseman: matches() requires a RegExp')
|
|
149
|
+
if (pattern.flags.includes('g') || pattern.flags.includes('y')) {
|
|
150
|
+
throw new TypeError('parseman: matches() does not accept global or sticky regex flags')
|
|
151
|
+
}
|
|
152
|
+
return { kind: 'matches', value: pattern.source, flags: pattern.flags }
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export function when<T>(
|
|
156
|
+
key: DispatchWhenKey,
|
|
157
|
+
parser: Combinator<T>,
|
|
158
|
+
opts?: DispatchWhenOptions,
|
|
159
|
+
): DispatchWhen<T>
|
|
160
|
+
export function when<T>(
|
|
161
|
+
matcher: DispatchStringMatcher,
|
|
162
|
+
parser: Combinator<T>,
|
|
163
|
+
opts?: DispatchWhenOptions,
|
|
164
|
+
): DispatchWhenMatcher<T>
|
|
165
|
+
export function when<T>(
|
|
166
|
+
key: DispatchWhenSelector,
|
|
167
|
+
parser: Combinator<T>,
|
|
168
|
+
opts: DispatchWhenOptions = {},
|
|
169
|
+
): DispatchWhen<T> | DispatchWhenMatcher<T> {
|
|
170
|
+
const usesRouted = parserUsesRouted(parser as Combinator<unknown>)
|
|
171
|
+
if (isStringMatcher(key)) {
|
|
172
|
+
return { kind: 'whenMatcher', matcher: key, parser, caseInsensitive: opts.caseInsensitive ?? false, usesRouted }
|
|
173
|
+
}
|
|
174
|
+
const keys = Array.isArray(key) ? [...key] : [key]
|
|
175
|
+
if (keys.length === 0) throw new RangeError('parseman: when() requires at least one key')
|
|
176
|
+
for (const item of keys) {
|
|
177
|
+
if (typeof item !== 'string') throw new TypeError('parseman: when() keys must be strings')
|
|
178
|
+
}
|
|
179
|
+
return { kind: 'when', keys, parser, caseInsensitive: opts.caseInsensitive ?? false, usesRouted }
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
export function makeWhen(opts: DispatchWhenOptions = {}): DispatchWhenFactory {
|
|
183
|
+
return (key, parser) => when(key, parser, opts)
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
export function otherwise<T>(parser: Combinator<T>): DispatchOtherwise<T> {
|
|
187
|
+
return { kind: 'otherwise', parser, usesRouted: parserUsesRouted(parser as Combinator<unknown>) }
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Reuse the token the enclosing `dispatch()` already consumed to select this branch,
|
|
192
|
+
* instead of re-recognizing it.
|
|
193
|
+
*
|
|
194
|
+
* With no argument this is dispatch-only: outside a dispatch branch (or at a position
|
|
195
|
+
* other than the selector's) it fails with `expected: ['routed()']`.
|
|
196
|
+
*
|
|
197
|
+
* `routed(fallback)` makes the SAME production usable in both contexts: inside a
|
|
198
|
+
* dispatch branch it reuses the routed token; anywhere else it parses `fallback` in
|
|
199
|
+
* place. That is what collapses a `Routed<X>` twin production into its original —
|
|
200
|
+
* `sequence(routed(Name), Prelude, ';')` replaces the pair
|
|
201
|
+
* `sequence(Name, Prelude, ';')` / `sequence(routed(), Prelude, ';')`, which
|
|
202
|
+
* otherwise differ by exactly one element and duplicate a whole production (and a
|
|
203
|
+
* whole compiled emission) for a one-token difference.
|
|
204
|
+
*
|
|
205
|
+
* A `routed()` in a dispatch SELECTOR is still an error with or without a fallback:
|
|
206
|
+
* the selector is what produces the routed token, so reading it there is misuse.
|
|
207
|
+
*/
|
|
208
|
+
export function routed<T = string>(fallback?: Combinator<T>): Combinator<T> {
|
|
209
|
+
return {
|
|
210
|
+
_tag: 'routed',
|
|
211
|
+
_meta: {
|
|
212
|
+
// The routed token is whatever the selector matched, so the first set stays
|
|
213
|
+
// `any` even with a fallback — `any` already subsumes the fallback's.
|
|
214
|
+
firstSet: { kind: 'any' },
|
|
215
|
+
canMatchNewline: fallback?._meta.canMatchNewline ?? false,
|
|
216
|
+
isTrivia: false,
|
|
217
|
+
},
|
|
218
|
+
_def: fallback === undefined
|
|
219
|
+
? { tag: 'routed' }
|
|
220
|
+
: { tag: 'routed', fallback: fallback as Combinator<unknown> },
|
|
221
|
+
parse(_input: string, pos: number, ctx: ParseContext): ParseResult<T> {
|
|
222
|
+
const item = ctx._routed
|
|
223
|
+
if (item === undefined || pos !== item.span.start) {
|
|
224
|
+
if (fallback !== undefined) return fallback.parse(_input, pos, ctx)
|
|
225
|
+
return { ok: false, expected: ['routed()'], span: { start: pos, end: pos } }
|
|
226
|
+
}
|
|
227
|
+
if (cstCaptureActive(ctx)) {
|
|
228
|
+
pushCstLeaf(ctx, { _tag: 'leaf', value: item.value, span: item.span })
|
|
229
|
+
}
|
|
230
|
+
return { ok: true, value: item.value as T, span: item.span }
|
|
231
|
+
},
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
export function dispatch<S extends string, T extends readonly DispatchArm<unknown>[]>(
|
|
236
|
+
selector: Combinator<S>,
|
|
237
|
+
...arms: T
|
|
238
|
+
): Combinator<[S, UnionArms<T>]> {
|
|
239
|
+
if (parserUsesRouted(selector as Combinator<unknown>)) {
|
|
240
|
+
throw new Error('parseman: routed() can only appear inside a dispatch() branch')
|
|
241
|
+
}
|
|
242
|
+
let fallback: Combinator<unknown> | undefined
|
|
243
|
+
let fallbackUsesRouted = false
|
|
244
|
+
const matchers: DispatchMatcherCase[] = []
|
|
245
|
+
const cases: DispatchCase[] = []
|
|
246
|
+
const seen: Array<{ raw: string; folded: string; caseInsensitive: boolean }> = []
|
|
247
|
+
|
|
248
|
+
for (let i = 0; i < arms.length; i++) {
|
|
249
|
+
const arm = arms[i]!
|
|
250
|
+
if (arm.kind === 'otherwise') {
|
|
251
|
+
if (fallback !== undefined) throw new Error('parseman: dispatch() accepts at most one otherwise() arm')
|
|
252
|
+
if (i !== arms.length - 1) throw new Error('parseman: otherwise() must be the last dispatch() arm')
|
|
253
|
+
fallback = arm.parser
|
|
254
|
+
fallbackUsesRouted = arm.usesRouted === true
|
|
255
|
+
continue
|
|
256
|
+
}
|
|
257
|
+
if (arm.kind === 'whenMatcher') {
|
|
258
|
+
const matcher = arm.matcher
|
|
259
|
+
matchers.push({
|
|
260
|
+
kind: matcher.kind,
|
|
261
|
+
value: matcher.value,
|
|
262
|
+
...(matcher.kind === 'matches' ? { flags: matcher.flags } : {}),
|
|
263
|
+
parser: arm.parser as Combinator<unknown>,
|
|
264
|
+
caseInsensitive: arm.caseInsensitive,
|
|
265
|
+
...(arm.usesRouted === true ? { usesRouted: true } : {}),
|
|
266
|
+
})
|
|
267
|
+
continue
|
|
268
|
+
}
|
|
269
|
+
for (const key of arm.keys) {
|
|
270
|
+
const folded = asciiFoldKey(key)
|
|
271
|
+
for (const prior of seen) {
|
|
272
|
+
const overlaps = arm.caseInsensitive || prior.caseInsensitive
|
|
273
|
+
? folded === prior.folded
|
|
274
|
+
: key === prior.raw
|
|
275
|
+
if (overlaps) throw new Error(`parseman: duplicate dispatch key ${JSON.stringify(key)}`)
|
|
276
|
+
}
|
|
277
|
+
seen.push({ raw: key, folded, caseInsensitive: arm.caseInsensitive })
|
|
278
|
+
}
|
|
279
|
+
cases.push({
|
|
280
|
+
keys: arm.keys,
|
|
281
|
+
parser: arm.parser as Combinator<unknown>,
|
|
282
|
+
caseInsensitive: arm.caseInsensitive,
|
|
283
|
+
...(arm.usesRouted === true ? { usesRouted: true } : {}),
|
|
284
|
+
})
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
const meta: ParserMeta = {
|
|
288
|
+
firstSet: selector._meta.firstSet,
|
|
289
|
+
canMatchNewline: selector._meta.canMatchNewline ||
|
|
290
|
+
cases.some(entry => entry.parser._meta.canMatchNewline) ||
|
|
291
|
+
matchers.some(entry => entry.parser._meta.canMatchNewline) ||
|
|
292
|
+
(fallback?._meta.canMatchNewline ?? false),
|
|
293
|
+
isTrivia: false,
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
const byKey = new Map<string, DispatchCase>()
|
|
297
|
+
const byFoldedKey = new Map<string, DispatchCase>()
|
|
298
|
+
for (const entry of cases) {
|
|
299
|
+
for (const key of entry.keys) {
|
|
300
|
+
if (entry.caseInsensitive) byFoldedKey.set(asciiFoldKey(key), entry)
|
|
301
|
+
else byKey.set(key, entry)
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
return {
|
|
306
|
+
_tag: 'dispatch',
|
|
307
|
+
_meta: meta,
|
|
308
|
+
_def: {
|
|
309
|
+
tag: 'dispatch',
|
|
310
|
+
selector: selector as Combinator<string>,
|
|
311
|
+
cases,
|
|
312
|
+
...(matchers.length === 0 ? {} : { matchers }),
|
|
313
|
+
...(fallback === undefined ? {} : { otherwise: fallback, ...(fallbackUsesRouted ? { otherwiseUsesRouted: true } : {}) }),
|
|
314
|
+
},
|
|
315
|
+
parse(input: string, pos: number, ctx: ParseContext): ParseResult<[S, UnionArms<T>]> {
|
|
316
|
+
if (parserUsesRouted(selector as Combinator<unknown>)) {
|
|
317
|
+
throw new Error('parseman: routed() can only appear inside a dispatch() branch')
|
|
318
|
+
}
|
|
319
|
+
const selectorMark = saveTriviaMark(ctx)
|
|
320
|
+
const selected = selector.parse(input, pos, ctx)
|
|
321
|
+
if (!selected.ok) return selected
|
|
322
|
+
|
|
323
|
+
const branch = byKey.get(selected.value) ??
|
|
324
|
+
(byFoldedKey.size === 0 ? undefined : byFoldedKey.get(asciiFoldKey(selected.value))) ??
|
|
325
|
+
matchers.find(matcher => matchesDispatchMatcher(selected.value, matcher)) ??
|
|
326
|
+
(fallback === undefined ? undefined : { keys: [], parser: fallback, caseInsensitive: false, usesRouted: fallbackUsesRouted })
|
|
327
|
+
if (branch === undefined) {
|
|
328
|
+
const expected = cases.flatMap(entry => entry.keys.map(key => JSON.stringify(key)))
|
|
329
|
+
return { ok: false, expected, span: { start: selected.span.end, end: selected.span.end } }
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
const savedRouted = ctx._routed
|
|
333
|
+
const usesRouted = branchUsesRouted(branch)
|
|
334
|
+
let mark = saveTriviaMark(ctx)
|
|
335
|
+
if (usesRouted) {
|
|
336
|
+
rollbackTrivia(ctx, selectorMark)
|
|
337
|
+
mark = saveTriviaMark(ctx)
|
|
338
|
+
ctx._routed = { value: selected.value, span: selected.span }
|
|
339
|
+
}
|
|
340
|
+
let result: ParseResult<unknown>
|
|
341
|
+
try {
|
|
342
|
+
result = branch.parser.parse(input, usesRouted ? pos : selected.span.end, ctx)
|
|
343
|
+
} finally {
|
|
344
|
+
if (usesRouted) ctx._routed = savedRouted
|
|
345
|
+
}
|
|
346
|
+
if (!result.ok) {
|
|
347
|
+
rollbackTrivia(ctx, mark)
|
|
348
|
+
return { ...result, committed: true }
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
return {
|
|
352
|
+
ok: true,
|
|
353
|
+
value: [selected.value, result.value as UnionArms<T>],
|
|
354
|
+
span: { start: pos, end: result.span.end },
|
|
355
|
+
}
|
|
356
|
+
},
|
|
357
|
+
}
|
|
358
|
+
}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import type { Combinator, ParseContext, ParseResult, ParserMeta, ParseError } from '../types.ts'
|
|
2
|
+
import { captureError } from '../recovery/scan.ts'
|
|
3
|
+
import { annotateSpanFromLineContext } from '../line-index.ts'
|
|
4
|
+
import { matchesEmpty, isZeroWidthAssertion } from './first-set.ts'
|
|
5
|
+
|
|
6
|
+
export type { ParseError }
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Statically derive the "expected" token set from a combinator's structure,
|
|
10
|
+
* so a failure can name what it wanted without running anything:
|
|
11
|
+
*
|
|
12
|
+
* literal('}') -> ['"}"']
|
|
13
|
+
* keywords(['a','b']) -> ['"a"', '"b"']
|
|
14
|
+
* choice(x, y) -> expected(x) ++ expected(y) (all alternatives)
|
|
15
|
+
* sequence(a, b, …) -> expected(a) ++ … through the NULLABLE prefix
|
|
16
|
+
* label('s', x) -> ['s'] (an explicit name wins)
|
|
17
|
+
*
|
|
18
|
+
* This is what lets expect() report a meaningful, IDENTICAL expectation in both
|
|
19
|
+
* the interpreter and the compiled output (the compiled path does not rebuild the
|
|
20
|
+
* runtime `expected` array, so it reads this precomputed set instead).
|
|
21
|
+
*
|
|
22
|
+
* The result is DEDUPLICATED. Deriving through a nullable prefix re-reaches the
|
|
23
|
+
* same token from several terms — a value grammar whose leading terms are all
|
|
24
|
+
* optional names its value-start set once per term — and an expectation list that
|
|
25
|
+
* offers `"@{"` five times is wrong for a reader and expensive for a parser: a
|
|
26
|
+
* choice that loses every arm materializes `[...arm0, ...arm1, …]` at runtime, and
|
|
27
|
+
* those arrays feed the enclosing choice's concat in turn, so duplicates compound
|
|
28
|
+
* multiplicatively up the nesting. On jess's Less grammar the un-deduped sets cost
|
|
29
|
+
* 32% of parse time on `benchmark.less` (medians 20.7 → 14.2 ms over 100
|
|
30
|
+
* interleaved pairs, 99/100 wins, min 19.1 → 13.0), which is the whole of the Less
|
|
31
|
+
* regression 0.35.0 shipped. Dedup is not an optimisation bolted onto the
|
|
32
|
+
* derivation — a SET is what the derivation always meant.
|
|
33
|
+
*/
|
|
34
|
+
export function deriveExpected(c: Combinator<unknown>): string[] {
|
|
35
|
+
return [...new Set(derive(c, new Set()))]
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function derive(c: Combinator<unknown>, seen: Set<Combinator<unknown>>): string[] {
|
|
39
|
+
const deriveExpected = (p: Combinator<unknown>): string[] => derive(p, seen)
|
|
40
|
+
const def = c._def
|
|
41
|
+
switch (def.tag) {
|
|
42
|
+
case 'literal': return [JSON.stringify(def.value)]
|
|
43
|
+
case 'regex': return [`/${def.source}/`]
|
|
44
|
+
case 'keywords': return def.words.map(w => JSON.stringify(w))
|
|
45
|
+
case 'label': return [def.label]
|
|
46
|
+
case 'choice': return def.parsers.flatMap(deriveExpected)
|
|
47
|
+
case 'dispatch': return deriveExpected(def.selector)
|
|
48
|
+
// Union through the NULLABLE prefix, mirroring `sequenceFirstSet`. A leading
|
|
49
|
+
// `optional(…)`/`many(…)` can match nothing, so the term after it is equally
|
|
50
|
+
// able to fail first — deriving from term 0 alone named a token the parse
|
|
51
|
+
// never actually required. A leading `not(…)` is zero-width and contributes
|
|
52
|
+
// nothing (its own expectation is about what must NOT be here), but it is
|
|
53
|
+
// nullable, so keep scanning past it. Stop at the first term that must match.
|
|
54
|
+
case 'sequence': {
|
|
55
|
+
const out: string[] = []
|
|
56
|
+
for (const term of def.parsers) {
|
|
57
|
+
if (!isZeroWidthAssertion(term)) out.push(...deriveExpected(term))
|
|
58
|
+
if (!matchesEmpty(term)) break
|
|
59
|
+
}
|
|
60
|
+
return out
|
|
61
|
+
}
|
|
62
|
+
case 'attempt': return deriveExpected(def.parser)
|
|
63
|
+
// Delegating wrappers whose first-token failure is their inner parser's — must
|
|
64
|
+
// pass through so a start-failure (and the first-set-miss fast-path in attempt/
|
|
65
|
+
// node, which reads this) names the real expected token, not the wrapper tag.
|
|
66
|
+
case 'field':
|
|
67
|
+
case 'withCtx':
|
|
68
|
+
case 'recover': return deriveExpected(def.parser)
|
|
69
|
+
case 'expect': return def.expected
|
|
70
|
+
case 'node':
|
|
71
|
+
case 'grammar':
|
|
72
|
+
case 'trivia':
|
|
73
|
+
case 'token':
|
|
74
|
+
case 'leaf':
|
|
75
|
+
case 'optional':
|
|
76
|
+
case 'many':
|
|
77
|
+
case 'oneOrMore':
|
|
78
|
+
case 'transform':
|
|
79
|
+
case 'not':
|
|
80
|
+
case 'peek': return deriveExpected(def.parser)
|
|
81
|
+
// A bare routed() can only fail as "there is no routed token here". With a
|
|
82
|
+
// fallback, THAT is the branch that can fail, so its expectations are the useful
|
|
83
|
+
// ones — the routed path either succeeds or is not taken.
|
|
84
|
+
case 'routed': return def.fallback === undefined ? ['routed()'] : deriveExpected(def.fallback)
|
|
85
|
+
// A bare adjacency assertion names the JOIN it requires. Inside a sequence it is
|
|
86
|
+
// skipped entirely (isZeroWidthAssertion, above), which is the normal path.
|
|
87
|
+
case 'adjacency': return [def.polarity]
|
|
88
|
+
case 'lazy': {
|
|
89
|
+
// An EXTERNAL ref (a rule from a composed base grammar) has no local
|
|
90
|
+
// definition yet — its `thunk()` throws until fusion supplies it. Fall back
|
|
91
|
+
// to the rule name as the expected label instead of descending.
|
|
92
|
+
const name = (c as { _ruleName?: string })._ruleName
|
|
93
|
+
// Cycle guard: a RECURSIVE rule re-enters its own definition forever. Reaching
|
|
94
|
+
// one is newly possible now that `sequence` derives through a nullable prefix
|
|
95
|
+
// (`list = choice('end', sequence(optional(item), list))` — term-0-only
|
|
96
|
+
// derivation used to stop short of the self-reference). Falling back to the rule
|
|
97
|
+
// name, exactly as for an unresolved external ref, cuts the cycle. Removed again
|
|
98
|
+
// on the way out, so a rule referenced twice NON-cyclically still derives fully.
|
|
99
|
+
if (seen.has(c)) return name ? [name] : []
|
|
100
|
+
seen.add(c)
|
|
101
|
+
try { return deriveExpected(def.thunk()) }
|
|
102
|
+
catch { return name ? [name] : [] }
|
|
103
|
+
finally { seen.delete(c) }
|
|
104
|
+
}
|
|
105
|
+
default: return []
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Required-token combinator. Tries `combinator`; on success returns it verbatim.
|
|
111
|
+
* On failure it does NOT fail — it records a {@link ParseError} (the statically
|
|
112
|
+
* derived expected set, or `label` when given) into `ctx._errors` and RECOVERS IN
|
|
113
|
+
* PLACE, returning a zero-width success so the enclosing sequence continues as if
|
|
114
|
+
* the token were present.
|
|
115
|
+
*
|
|
116
|
+
* Use it to mark required delimiters/terminators (`}` `)` `;`, a selector before a
|
|
117
|
+
* block) so a missing one is reported with position + expectation rather than
|
|
118
|
+
* aborting the whole parse or being silently swallowed by a catch-all.
|
|
119
|
+
*
|
|
120
|
+
* sequence(literal('{'), declList, expect(literal('}')))
|
|
121
|
+
*
|
|
122
|
+
* Errors are only collected when the parse runs with `{ recover: true }`; without
|
|
123
|
+
* it, expect() still recovers in place but records nothing (zero overhead beyond
|
|
124
|
+
* the inner attempt).
|
|
125
|
+
*/
|
|
126
|
+
export function expect<T>(combinator: Combinator<T>, label?: string): Combinator<T | ParseError> {
|
|
127
|
+
const expected = label !== undefined ? [label] : deriveExpected(combinator)
|
|
128
|
+
const meta: ParserMeta = {
|
|
129
|
+
firstSet: combinator._meta.firstSet,
|
|
130
|
+
canMatchNewline: combinator._meta.canMatchNewline,
|
|
131
|
+
isTrivia: false,
|
|
132
|
+
}
|
|
133
|
+
return {
|
|
134
|
+
_tag: 'expect',
|
|
135
|
+
_meta: meta,
|
|
136
|
+
_def: { tag: 'expect', parser: combinator as Combinator<unknown>, label, expected },
|
|
137
|
+
parse(input: string, pos: number, ctx: ParseContext): ParseResult<T | ParseError> {
|
|
138
|
+
const result = combinator.parse(input, pos, ctx)
|
|
139
|
+
if (result.ok) return result as ParseResult<T | ParseError>
|
|
140
|
+
const span = { start: pos, end: pos }
|
|
141
|
+
const error: ParseError = { _tag: 'parseError', span: ctx.trackLines ? annotateSpanFromLineContext(span, ctx) : span, expected }
|
|
142
|
+
ctx._errors?.push(error)
|
|
143
|
+
// Embed the missing-token error as a `parseError` CST child (no-op when CST
|
|
144
|
+
// capture is off), so it rides the tree exactly like list-recovery errors —
|
|
145
|
+
// a tree walk finds every diagnostic and the error survives incremental
|
|
146
|
+
// reuse, rather than living only in the flat ctx._errors side-channel. Gated
|
|
147
|
+
// on tolerant like the list-recovery embed sites, so a strict CST build is
|
|
148
|
+
// byte-identical to before (and stays in parity with the compiled path,
|
|
149
|
+
// which can only embed when the recovery bundle `_rec` is installed).
|
|
150
|
+
if (ctx._tolerant) captureError(ctx, error)
|
|
151
|
+
return { ok: true, value: error, span: { start: pos, end: pos } }
|
|
152
|
+
},
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/** True when `value` is a recovery {@link ParseError} node. */
|
|
157
|
+
export function isParseError(value: unknown): value is ParseError {
|
|
158
|
+
return typeof value === 'object' && value !== null && (value as ParseError)._tag === 'parseError'
|
|
159
|
+
}
|