parseman 0.46.0 → 0.47.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -3
- package/dist/analysis/choice-cost-render.d.ts +12 -4
- package/dist/analysis/choice-cost-render.d.ts.map +1 -1
- package/dist/analysis/choice-cost.d.ts +2 -2
- package/dist/analysis/choice-cost.d.ts.map +1 -1
- package/dist/analysis/commitment.d.ts +131 -0
- package/dist/analysis/commitment.d.ts.map +1 -0
- package/dist/analysis/diagnostics.cjs +10339 -7787
- package/dist/analysis/diagnostics.cjs.map +3 -4
- package/dist/analysis/diagnostics.d.ts +2 -2
- package/dist/analysis/diagnostics.js +10339 -7787
- package/dist/analysis/diagnostics.js.map +3 -4
- package/dist/analysis/duplication.d.ts.map +1 -1
- package/dist/analysis/fix.d.ts +3 -3
- package/dist/analysis/fix.d.ts.map +1 -1
- package/dist/analysis/format-number.d.ts +23 -0
- package/dist/analysis/format-number.d.ts.map +1 -0
- package/dist/analysis/gating.d.ts +39 -2
- package/dist/analysis/gating.d.ts.map +1 -1
- package/dist/analysis/rebuild.d.ts.map +1 -1
- package/dist/analysis/terminal.d.ts +5 -2
- package/dist/analysis/terminal.d.ts.map +1 -1
- package/dist/cli/index.cjs +10924 -8506
- package/dist/cli/index.cjs.map +3 -4
- package/dist/cli/index.js +10924 -8506
- package/dist/cli/index.js.map +3 -4
- package/dist/combinators/adjacency.d.ts +100 -0
- package/dist/combinators/adjacency.d.ts.map +1 -0
- package/dist/combinators/choice.d.ts.map +1 -1
- package/dist/combinators/completions.d.ts.map +1 -1
- package/dist/combinators/dispatch.d.ts +8 -1
- package/dist/combinators/dispatch.d.ts.map +1 -1
- package/dist/combinators/expect.d.ts.map +1 -1
- package/dist/combinators/first-set.d.ts.map +1 -1
- package/dist/combinators/gate.d.ts.map +1 -1
- package/dist/combinators/grammar.d.ts +22 -0
- package/dist/combinators/grammar.d.ts.map +1 -1
- package/dist/combinators/literal.d.ts +8 -0
- package/dist/combinators/literal.d.ts.map +1 -1
- package/dist/combinators/map.d.ts +0 -1
- package/dist/combinators/map.d.ts.map +1 -1
- package/dist/combinators/node.d.ts +13 -3
- package/dist/combinators/node.d.ts.map +1 -1
- package/dist/combinators/parser.d.ts +1 -1
- package/dist/combinators/parser.d.ts.map +1 -1
- package/dist/combinators/ref.d.ts.map +1 -1
- package/dist/combinators/regex.d.ts +1 -16
- package/dist/combinators/regex.d.ts.map +1 -1
- package/dist/combinators/repeat.d.ts +30 -2
- package/dist/combinators/repeat.d.ts.map +1 -1
- package/dist/combinators/scanTo.d.ts +71 -1
- package/dist/combinators/scanTo.d.ts.map +1 -1
- package/dist/combinators/sequence.d.ts.map +1 -1
- package/dist/combinators/token.d.ts.map +1 -1
- package/dist/combinators/trivia-boundary.d.ts +26 -0
- package/dist/combinators/trivia-boundary.d.ts.map +1 -0
- package/dist/combinators/trivia-skip.d.ts +45 -1
- package/dist/combinators/trivia-skip.d.ts.map +1 -1
- package/dist/combinators/withCtx.d.ts.map +1 -1
- package/dist/compiler/compile-linkable-table.d.ts +104 -0
- package/dist/compiler/compile-linkable-table.d.ts.map +1 -0
- package/dist/compiler/degradation.d.ts +0 -2
- package/dist/compiler/degradation.d.ts.map +1 -1
- package/dist/compiler/fields.d.ts +13 -0
- package/dist/compiler/fields.d.ts.map +1 -1
- package/dist/compiler/grammar-coverage-ids.d.ts +5 -5
- package/dist/compiler/grammar-coverage-ids.d.ts.map +1 -1
- package/dist/compiler/ir-serialize.d.ts.map +1 -1
- package/dist/compiler/linker.d.ts +47 -53
- package/dist/compiler/linker.d.ts.map +1 -1
- package/dist/compiler/token-alphabet.d.ts +81 -0
- package/dist/compiler/token-alphabet.d.ts.map +1 -0
- package/dist/compiler/token-dispatch.d.ts +95 -0
- package/dist/compiler/token-dispatch.d.ts.map +1 -0
- package/dist/compiler/token-scanner.d.ts +53 -0
- package/dist/compiler/token-scanner.d.ts.map +1 -0
- package/dist/compiler/value-usage.d.ts.map +1 -1
- package/dist/coverage.d.ts +1 -1
- package/dist/coverage.d.ts.map +1 -1
- package/dist/cst/capture-buffer.d.ts +35 -0
- package/dist/cst/capture-buffer.d.ts.map +1 -1
- package/dist/cst/reflection-attach.d.ts +8 -0
- package/dist/cst/reflection-attach.d.ts.map +1 -0
- package/dist/cst/reflection-symbols.d.ts +10 -0
- package/dist/cst/reflection-symbols.d.ts.map +1 -0
- package/dist/cst/reflection.d.ts +2 -4
- package/dist/cst/reflection.d.ts.map +1 -1
- package/dist/cst/root-trivia-scope.d.ts +26 -0
- package/dist/cst/root-trivia-scope.d.ts.map +1 -0
- package/dist/cst/trivia-charscan.d.ts +18 -0
- package/dist/cst/trivia-charscan.d.ts.map +1 -0
- package/dist/cst/trivia-kinds.d.ts +19 -0
- package/dist/cst/trivia-kinds.d.ts.map +1 -1
- package/dist/functional/doc.d.ts.map +1 -1
- package/dist/functional/run-tabled.d.ts +12 -0
- package/dist/functional/run-tabled.d.ts.map +1 -0
- package/dist/functional/run.d.ts +25 -42
- package/dist/functional/run.d.ts.map +1 -1
- package/dist/grammar-metadata.d.ts +3 -0
- package/dist/grammar-metadata.d.ts.map +1 -0
- package/dist/index.cjs +9290 -7181
- package/dist/index.cjs.map +3 -4
- package/dist/index.d.ts +18 -9
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9300 -7191
- package/dist/index.js.map +3 -4
- package/dist/language-service/index.cjs +313 -121
- package/dist/language-service/index.cjs.map +3 -4
- package/dist/language-service/index.js +313 -121
- package/dist/language-service/index.js.map +3 -4
- package/dist/oracle/index.cjs.map +0 -1
- package/dist/oracle/index.js.map +0 -1
- package/dist/parse-context.d.ts +49 -0
- package/dist/parse-context.d.ts.map +1 -0
- package/dist/plugin/evaluator.d.ts +40 -1
- package/dist/plugin/evaluator.d.ts.map +1 -1
- package/dist/plugin/index.cjs +11175 -9534
- package/dist/plugin/index.cjs.map +3 -4
- package/dist/plugin/index.d.ts.map +1 -1
- package/dist/plugin/index.js +11175 -9534
- package/dist/plugin/index.js.map +3 -4
- package/dist/regex/classes.d.ts +28 -0
- package/dist/regex/classes.d.ts.map +1 -1
- package/dist/regex/first-set.d.ts +14 -0
- package/dist/regex/first-set.d.ts.map +1 -1
- package/dist/run/index.cjs +136 -51
- package/dist/run/index.cjs.map +2 -3
- package/dist/run/index.js +136 -51
- package/dist/run/index.js.map +2 -3
- package/dist/spec/index.cjs +2934 -1611
- package/dist/spec/index.cjs.map +3 -4
- package/dist/spec/index.js +2934 -1611
- package/dist/spec/index.js.map +3 -4
- package/dist/spec/model.d.ts +8 -0
- package/dist/spec/model.d.ts.map +1 -1
- package/dist/table/assemble.d.ts +307 -0
- package/dist/table/assemble.d.ts.map +1 -0
- package/dist/table/cell.d.ts +42 -0
- package/dist/table/cell.d.ts.map +1 -0
- package/dist/table/child-slots.d.ts +30 -0
- package/dist/table/child-slots.d.ts.map +1 -0
- package/dist/table/compile-rule-map.d.ts +136 -0
- package/dist/table/compile-rule-map.d.ts.map +1 -0
- package/dist/table/compile.d.ts +56 -0
- package/dist/table/compile.d.ts.map +1 -0
- package/dist/table/duplication-hook.d.ts +32 -0
- package/dist/table/duplication-hook.d.ts.map +1 -0
- package/dist/table/emit-assembly.d.ts +139 -0
- package/dist/table/emit-assembly.d.ts.map +1 -0
- package/dist/table/emit.d.ts +113 -0
- package/dist/table/emit.d.ts.map +1 -0
- package/dist/table/encode-baseline.d.ts +16 -0
- package/dist/table/encode-baseline.d.ts.map +1 -0
- package/dist/table/encode.d.ts +71 -0
- package/dist/table/encode.d.ts.map +1 -0
- package/dist/table/exec-baseline.d.ts +9 -0
- package/dist/table/exec-baseline.d.ts.map +1 -0
- package/dist/table/exec.d.ts +72 -0
- package/dist/table/exec.d.ts.map +1 -0
- package/dist/table/fold.d.ts +25 -0
- package/dist/table/fold.d.ts.map +1 -0
- package/dist/table/index.cjs +11463 -0
- package/dist/table/index.cjs.map +6 -0
- package/dist/table/index.d.ts +100 -0
- package/dist/table/index.d.ts.map +1 -0
- package/dist/table/index.js +11440 -0
- package/dist/table/index.js.map +6 -0
- package/dist/table/inspect.d.ts +17 -0
- package/dist/table/inspect.d.ts.map +1 -0
- package/dist/table/ops.d.ts +453 -0
- package/dist/table/ops.d.ts.map +1 -0
- package/dist/table/program.d.ts +496 -0
- package/dist/table/program.d.ts.map +1 -0
- package/dist/table/run-support.d.ts +38 -0
- package/dist/table/run-support.d.ts.map +1 -0
- package/dist/{compiler/scannable-run.d.ts → table/scan-shapes.d.ts} +14 -13
- package/dist/table/scan-shapes.d.ts.map +1 -0
- package/dist/table/site-labels.d.ts +58 -0
- package/dist/table/site-labels.d.ts.map +1 -0
- package/dist/table/stamp.d.ts +70 -0
- package/dist/table/stamp.d.ts.map +1 -0
- package/dist/types.d.ts +80 -24
- package/dist/types.d.ts.map +1 -1
- package/dist/version.d.ts +1 -1
- package/package.json +26 -2
- package/src/analysis/choice-cost-gate.ts +359 -0
- package/src/analysis/choice-cost-render.ts +293 -0
- package/src/analysis/choice-cost.ts +1008 -0
- package/src/analysis/commitment.ts +557 -0
- package/src/analysis/corpus.ts +130 -0
- package/src/analysis/diagnose-render.ts +625 -0
- package/src/analysis/diagnose.ts +317 -0
- package/src/analysis/diagnostics.ts +33 -0
- package/src/analysis/duplication.ts +1955 -0
- package/src/analysis/fix-render.ts +217 -0
- package/src/analysis/fix.ts +669 -0
- package/src/analysis/format-number.ts +31 -0
- package/src/analysis/gating.ts +792 -0
- package/src/analysis/grammar.ts +72 -0
- package/src/analysis/rebuild.ts +287 -0
- package/src/analysis/terminal.ts +336 -0
- package/src/cli/index.ts +413 -0
- package/src/combinators/adjacency.ts +171 -0
- package/src/combinators/attempt.ts +41 -0
- package/src/combinators/case-fold.ts +90 -0
- package/src/combinators/choice.ts +466 -0
- package/src/combinators/completions.ts +64 -0
- package/src/combinators/dispatch.ts +358 -0
- package/src/combinators/expect.ts +159 -0
- package/src/combinators/first-set.ts +348 -0
- package/src/combinators/gate.ts +50 -0
- package/src/combinators/grammar.ts +272 -0
- package/src/combinators/keywords.ts +145 -0
- package/src/combinators/literal.ts +102 -0
- package/src/combinators/map.ts +106 -0
- package/src/combinators/node.ts +345 -0
- package/src/combinators/not.ts +55 -0
- package/src/combinators/parser.ts +266 -0
- package/src/combinators/peek.ts +63 -0
- package/src/combinators/probe.ts +25 -0
- package/src/combinators/ref.ts +109 -0
- package/src/combinators/regex.ts +170 -0
- package/src/combinators/repeat.ts +561 -0
- package/src/combinators/scanTo.ts +393 -0
- package/src/combinators/sequence.ts +237 -0
- package/src/combinators/token.ts +120 -0
- package/src/combinators/trivia-boundary.ts +48 -0
- package/src/combinators/trivia-skip.ts +852 -0
- package/src/combinators/withCtx.ts +45 -0
- package/src/compiler/build-arity.ts +330 -0
- package/src/compiler/compile-linkable-table.ts +209 -0
- package/src/compiler/degradation.ts +266 -0
- package/src/compiler/fields.ts +191 -0
- package/src/compiler/grammar-coverage-ids.ts +129 -0
- package/src/compiler/ir-serialize.ts +615 -0
- package/src/compiler/linker.ts +954 -0
- package/src/compiler/token-alphabet.ts +209 -0
- package/src/compiler/token-dispatch.ts +353 -0
- package/src/compiler/token-scanner.ts +318 -0
- package/src/compiler/value-usage.ts +147 -0
- package/src/coverage.ts +383 -0
- package/src/cst/capture-buffer.ts +297 -0
- package/src/cst/host-mode.ts +69 -0
- package/src/cst/reflection-attach.ts +11 -0
- package/src/cst/reflection-symbols.ts +9 -0
- package/src/cst/reflection.ts +149 -0
- package/src/cst/relative-spans.ts +207 -0
- package/src/cst/root-trivia-scope.ts +30 -0
- package/src/cst/trivia-charscan.ts +312 -0
- package/src/cst/trivia-entries.ts +285 -0
- package/src/cst/trivia-index.ts +131 -0
- package/src/cst/trivia-kinds.ts +358 -0
- package/src/cst/types.ts +76 -0
- package/src/cst/walk.ts +101 -0
- package/src/functional/doc.ts +959 -0
- package/src/functional/run-tabled.ts +153 -0
- package/src/functional/run.ts +558 -0
- package/src/grammar-metadata.ts +2 -0
- package/src/index.ts +189 -0
- package/src/language-service/index.ts +221 -0
- package/src/line-index.ts +131 -0
- package/src/oracle/digest.ts +620 -0
- package/src/oracle/index.ts +55 -0
- package/src/parse-context.ts +91 -0
- package/src/plugin/direct-builder-static.ts +102 -0
- package/src/plugin/evaluator.ts +1505 -0
- package/src/plugin/free-identifiers.ts +338 -0
- package/src/plugin/index.ts +2281 -0
- package/src/plugin/reducer-resolver.ts +606 -0
- package/src/recovery/scan.ts +155 -0
- package/src/regex/classes.ts +183 -0
- package/src/regex/first-set.ts +316 -0
- package/src/run/index.ts +31 -0
- package/src/spec/ebnf.ts +128 -0
- package/src/spec/index.ts +54 -0
- package/src/spec/model.ts +636 -0
- package/src/spec/railroad-lib.ts +559 -0
- package/src/spec/railroad.ts +256 -0
- package/src/table/assemble.ts +3006 -0
- package/src/table/cell.ts +42 -0
- package/src/table/child-slots.ts +113 -0
- package/src/table/compile-rule-map.ts +336 -0
- package/src/table/compile.ts +244 -0
- package/src/table/duplication-hook.ts +89 -0
- package/src/table/emit-assembly.ts +1777 -0
- package/src/table/emit.ts +429 -0
- package/src/table/encode-baseline.ts +340 -0
- package/src/table/encode.ts +1569 -0
- package/src/table/exec-baseline.ts +537 -0
- package/src/table/exec.ts +1680 -0
- package/src/table/fold.ts +38 -0
- package/src/table/index.ts +108 -0
- package/src/table/inspect.ts +52 -0
- package/src/table/ops.ts +467 -0
- package/src/table/program.ts +876 -0
- package/src/table/run-support.ts +66 -0
- package/src/table/scan-shapes.ts +1578 -0
- package/src/table/site-labels.ts +228 -0
- package/src/table/stamp.ts +229 -0
- package/src/types.ts +563 -0
- package/src/version.ts +16 -0
- package/dist/compiler/codegen.d.ts +0 -334
- package/dist/compiler/codegen.d.ts.map +0 -1
- package/dist/compiler/inline-build.d.ts +0 -11
- package/dist/compiler/inline-build.d.ts.map +0 -1
- package/dist/compiler/inline-callback.d.ts +0 -21
- package/dist/compiler/inline-callback.d.ts.map +0 -1
- package/dist/compiler/line-index.d.ts +0 -2
- package/dist/compiler/line-index.d.ts.map +0 -1
- package/dist/compiler/module-hoist.d.ts +0 -106
- package/dist/compiler/module-hoist.d.ts.map +0 -1
- package/dist/compiler/scannable-run.d.ts.map +0 -1
- package/dist/compiler/scannable-terminal.d.ts +0 -23
- package/dist/compiler/scannable-terminal.d.ts.map +0 -1
- package/dist/compiler/trivia-fast-path.d.ts +0 -38
- package/dist/compiler/trivia-fast-path.d.ts.map +0 -1
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Railroad-diagram (syntax-diagram) emitter. Converts the spec model to a
|
|
3
|
+
* self-contained HTML page that renders SVG railroad diagrams — one per
|
|
4
|
+
* production — using a vendored copy of tabatkins/railroad-diagrams (CC0).
|
|
5
|
+
*
|
|
6
|
+
* The page has no external dependencies: the diagram library and its CSS are
|
|
7
|
+
* inlined, so it renders offline and drops straight into a docs site.
|
|
8
|
+
*/
|
|
9
|
+
import type { Production, SpecModel, SpecNode } from './model.ts'
|
|
10
|
+
import { renderExpr } from './ebnf.ts'
|
|
11
|
+
import { RAILROAD_CSS, RAILROAD_JS } from './railroad-lib.ts'
|
|
12
|
+
|
|
13
|
+
const j = (s: string): string => JSON.stringify(s)
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Human label for a bounded repeat, or `null` when the bounds are the default
|
|
17
|
+
* ones the diagram shape already conveys (`ZeroOrMore` = 0+, `OneOrMore` = 1+).
|
|
18
|
+
* Railroad diagrams have no repetition-count primitive, so a genuine bound rides
|
|
19
|
+
* the repeat's loop-back path as a Comment rather than going unrendered.
|
|
20
|
+
*/
|
|
21
|
+
function boundLabel(min: number, max: number | undefined): string | null {
|
|
22
|
+
if (max === undefined) return min <= 1 ? null : `${min}+ times`
|
|
23
|
+
if (max === min) return `${min}×`
|
|
24
|
+
return `${min}–${max} times`
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** Convert a spec node to a railroad-diagrams DSL expression string. */
|
|
28
|
+
function toDsl(node: SpecNode): string {
|
|
29
|
+
switch (node.kind) {
|
|
30
|
+
case 'terminal':
|
|
31
|
+
return `Terminal(${j(node.text)})`
|
|
32
|
+
case 'ref':
|
|
33
|
+
return `NonTerminal(${j(node.name)})`
|
|
34
|
+
case 'empty':
|
|
35
|
+
return `Skip()`
|
|
36
|
+
case 'annotation':
|
|
37
|
+
return `Comment(${j(node.text)})`
|
|
38
|
+
case 'seq':
|
|
39
|
+
return `Sequence(${node.items.map(toDsl).join(', ')})`
|
|
40
|
+
case 'choice':
|
|
41
|
+
return `Choice(0, ${node.items.map(toDsl).join(', ')})`
|
|
42
|
+
case 'star': {
|
|
43
|
+
const b = boundLabel(0, node.max)
|
|
44
|
+
return b ? `ZeroOrMore(${toDsl(node.item)}, Comment(${j(b)}))` : `ZeroOrMore(${toDsl(node.item)})`
|
|
45
|
+
}
|
|
46
|
+
case 'plus': {
|
|
47
|
+
const b = boundLabel(node.min ?? 1, node.max)
|
|
48
|
+
return b ? `OneOrMore(${toDsl(node.item)}, Comment(${j(b)}))` : `OneOrMore(${toDsl(node.item)})`
|
|
49
|
+
}
|
|
50
|
+
case 'opt':
|
|
51
|
+
return `Optional(${toDsl(node.item)})`
|
|
52
|
+
case 'sepBy': {
|
|
53
|
+
// OneOrMore(item, separator): the separator rides the repeat loop, carrying
|
|
54
|
+
// the item-count label with it when the list is bounded.
|
|
55
|
+
const b = boundLabel(node.min, node.max)
|
|
56
|
+
const loop = b ? `Sequence(${toDsl(node.sep)}, Comment(${j(b)}))` : toDsl(node.sep)
|
|
57
|
+
const rep = `OneOrMore(${toDsl(node.item)}, ${loop})`
|
|
58
|
+
const withTrail = node.trailing === undefined
|
|
59
|
+
? rep
|
|
60
|
+
: `Sequence(${rep}, Optional(${toDsl(node.sep)}))`
|
|
61
|
+
// ONLY a min-0 list can match nothing. Collapsing every `min > 1` into
|
|
62
|
+
// Optional too rendered a list that requires N items as nullable.
|
|
63
|
+
return node.min === 0 ? `Optional(${withTrail})` : withTrail
|
|
64
|
+
}
|
|
65
|
+
case 'not':
|
|
66
|
+
return `Sequence(Comment("not"), ${toDsl(node.item)})`
|
|
67
|
+
case 'peek':
|
|
68
|
+
return `Sequence(Comment("peek"), ${toDsl(node.item)})`
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Vendored railroad-diagrams builders, evaluated once. `.toString()` on the
|
|
73
|
+
// result walks an in-memory FakeSVG tree and emits SVG markup with no DOM — so a
|
|
74
|
+
// diagram can be rendered to a static SVG string at build time (Node), not just
|
|
75
|
+
// in the browser. The library exports onto `exports` when it's an object.
|
|
76
|
+
type RailroadBuilders = {
|
|
77
|
+
Diagram: (item: unknown) => { toString(): string }
|
|
78
|
+
Sequence: (...xs: unknown[]) => unknown
|
|
79
|
+
Choice: (normal: number, ...xs: unknown[]) => unknown
|
|
80
|
+
Optional: (x: unknown) => unknown
|
|
81
|
+
OneOrMore: (x: unknown, sep?: unknown) => unknown
|
|
82
|
+
ZeroOrMore: (x: unknown, rep?: unknown) => unknown
|
|
83
|
+
Terminal: (t: string) => unknown
|
|
84
|
+
NonTerminal: (t: string) => unknown
|
|
85
|
+
Comment: (t: string) => unknown
|
|
86
|
+
Skip: () => unknown
|
|
87
|
+
}
|
|
88
|
+
let _builders: RailroadBuilders | null = null
|
|
89
|
+
function builders(): RailroadBuilders {
|
|
90
|
+
if (_builders) return _builders
|
|
91
|
+
const mod = {} as Record<string, unknown>
|
|
92
|
+
// eslint-disable-next-line no-new-func
|
|
93
|
+
new Function('exports', RAILROAD_JS)(mod)
|
|
94
|
+
_builders = mod as unknown as RailroadBuilders
|
|
95
|
+
return _builders
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** Build a railroad-diagrams object from a spec node (mirrors `toDsl`, but live). */
|
|
99
|
+
function toDiagramNode(b: RailroadBuilders, node: SpecNode): unknown {
|
|
100
|
+
switch (node.kind) {
|
|
101
|
+
case 'terminal': return b.Terminal(node.text)
|
|
102
|
+
case 'ref': return b.NonTerminal(node.name)
|
|
103
|
+
case 'empty': return b.Skip()
|
|
104
|
+
case 'annotation': return b.Comment(node.text)
|
|
105
|
+
case 'seq': return b.Sequence(...node.items.map((n) => toDiagramNode(b, n)))
|
|
106
|
+
case 'choice': return b.Choice(0, ...node.items.map((n) => toDiagramNode(b, n)))
|
|
107
|
+
case 'star': {
|
|
108
|
+
const lbl = boundLabel(0, node.max)
|
|
109
|
+
return lbl ? b.ZeroOrMore(toDiagramNode(b, node.item), b.Comment(lbl)) : b.ZeroOrMore(toDiagramNode(b, node.item))
|
|
110
|
+
}
|
|
111
|
+
case 'plus': {
|
|
112
|
+
const lbl = boundLabel(node.min ?? 1, node.max)
|
|
113
|
+
return lbl ? b.OneOrMore(toDiagramNode(b, node.item), b.Comment(lbl)) : b.OneOrMore(toDiagramNode(b, node.item))
|
|
114
|
+
}
|
|
115
|
+
case 'opt': return b.Optional(toDiagramNode(b, node.item))
|
|
116
|
+
case 'sepBy': {
|
|
117
|
+
const lbl = boundLabel(node.min, node.max)
|
|
118
|
+
const sep = toDiagramNode(b, node.sep)
|
|
119
|
+
const rep = b.OneOrMore(toDiagramNode(b, node.item), lbl ? b.Sequence(sep, b.Comment(lbl)) : sep)
|
|
120
|
+
const withTrail = node.trailing === undefined
|
|
121
|
+
? rep
|
|
122
|
+
: b.Sequence(rep, b.Optional(toDiagramNode(b, node.sep)))
|
|
123
|
+
return node.min === 0 ? b.Optional(withTrail) : withTrail
|
|
124
|
+
}
|
|
125
|
+
case 'not': return b.Sequence(b.Comment('not'), toDiagramNode(b, node.item))
|
|
126
|
+
case 'peek': return b.Sequence(b.Comment('peek'), toDiagramNode(b, node.item))
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/** One production rendered to a static SVG string. */
|
|
131
|
+
export type RailroadSvg = { name: string; svg: string }
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Render each production to a self-contained **static SVG** string — no DOM and
|
|
135
|
+
* no client-side script, so a diagram can be inlined straight into a docs page,
|
|
136
|
+
* README, or any HTML. Pair each SVG with [`RAILROAD_CSS`](./railroad-lib) (scope
|
|
137
|
+
* it to a container) to style the strokes and boxes.
|
|
138
|
+
*/
|
|
139
|
+
export function renderRailroadSvg(model: SpecModel): RailroadSvg[] {
|
|
140
|
+
const b = builders()
|
|
141
|
+
return model.productions.map((p) => ({
|
|
142
|
+
name: p.name,
|
|
143
|
+
svg: b.Diagram(toDiagramNode(b, p.expr)).toString(),
|
|
144
|
+
}))
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function escapeHtml(s: string): string {
|
|
148
|
+
return s
|
|
149
|
+
.replace(/&/g, '&')
|
|
150
|
+
.replace(/</g, '<')
|
|
151
|
+
.replace(/>/g, '>')
|
|
152
|
+
.replace(/"/g, '"')
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function slug(name: string): string {
|
|
156
|
+
return 'rule-' + name.replace(/[^\w-]/g, '_')
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export type RailroadHtmlOptions = {
|
|
160
|
+
/** Page `<title>` and top heading. Default: "Grammar". */
|
|
161
|
+
title?: string
|
|
162
|
+
/** Show the EBNF production text under each diagram. Default: true. */
|
|
163
|
+
showEbnf?: boolean
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const PAGE_CSS = `
|
|
167
|
+
/* The railroad SVGs ship a fixed light background, so the page commits to a
|
|
168
|
+
light theme for a consistent look regardless of the viewer's OS setting. */
|
|
169
|
+
body { font: 14px/1.5 system-ui, sans-serif; margin: 0; padding: 2rem 2.5rem;
|
|
170
|
+
color: #1a1a1a; background: #fff; }
|
|
171
|
+
h1 { font-size: 1.6rem; margin: 0 0 0.25rem; }
|
|
172
|
+
.subtitle { color: #666; margin: 0 0 2rem; }
|
|
173
|
+
.toc { columns: 3 12rem; margin: 0 0 2.5rem; padding: 1rem 1.25rem; background: #f6f4f1; border-radius: 8px; }
|
|
174
|
+
.toc a { display: block; color: #0b6; text-decoration: none; font-family: monospace; padding: 1px 0; }
|
|
175
|
+
.toc a:hover { text-decoration: underline; }
|
|
176
|
+
.rule { margin: 0 0 2.75rem; padding-top: 0.5rem; border-top: 1px solid #eee; }
|
|
177
|
+
.rule h2 { font-size: 1.1rem; font-family: monospace; margin: 0 0 0.75rem; }
|
|
178
|
+
.rule h2 a { color: #bbb; text-decoration: none; font-weight: normal; }
|
|
179
|
+
.rule h2 a:hover { color: #0b6; }
|
|
180
|
+
.ebnf { margin: 0.5rem 0 0; padding: 0.5rem 0.75rem; background: #f6f4f1; border-radius: 6px;
|
|
181
|
+
font-family: monospace; font-size: 12px; white-space: pre-wrap; overflow-x: auto; }
|
|
182
|
+
.diagram { overflow-x: auto; }
|
|
183
|
+
/* The vendored library sizes each shape as (chars * 8 + padding), which assumes
|
|
184
|
+
an 8px glyph advance (0.5em at 16px). Its own 14px-bold CSS actually advances
|
|
185
|
+
~8.4px/char, so long terminals crowd the box edges. Drop the text to 13px
|
|
186
|
+
(advance ~7.8px) to restore comfortable side padding, and nudge the baked
|
|
187
|
+
baseline (y+4) down slightly so glyphs sit vertically centered in the shapes. */
|
|
188
|
+
svg.railroad-diagram text { font-size: 13px; }
|
|
189
|
+
svg.railroad-diagram text:not(.comment) { transform: translateY(0.75px); }
|
|
190
|
+
`
|
|
191
|
+
|
|
192
|
+
/** Render the spec model as a self-contained HTML page of railroad diagrams. */
|
|
193
|
+
export function renderRailroadHtml(model: SpecModel, options: RailroadHtmlOptions = {}): string {
|
|
194
|
+
const title = options.title ?? 'Grammar'
|
|
195
|
+
const showEbnf = options.showEbnf ?? true
|
|
196
|
+
|
|
197
|
+
const toc = model.productions
|
|
198
|
+
.map(p => `<a href="#${slug(p.name)}">${escapeHtml(p.name)}</a>`)
|
|
199
|
+
.join('\n ')
|
|
200
|
+
|
|
201
|
+
const sections = model.productions
|
|
202
|
+
.map((p: Production) => {
|
|
203
|
+
const ebnf = showEbnf
|
|
204
|
+
? `<div class="ebnf">${escapeHtml(`${p.name} ::= ${renderExpr(p.expr) || '/* empty */'}`)}</div>`
|
|
205
|
+
: ''
|
|
206
|
+
return ` <section class="rule" id="${slug(p.name)}">
|
|
207
|
+
<h2><a href="#${slug(p.name)}">§</a> ${escapeHtml(p.name)}</h2>
|
|
208
|
+
<div class="diagram" data-rule="${escapeHtml(p.name)}"></div>
|
|
209
|
+
${ebnf}
|
|
210
|
+
</section>`
|
|
211
|
+
})
|
|
212
|
+
.join('\n')
|
|
213
|
+
|
|
214
|
+
// One DSL expression per rule, built into its container at load time.
|
|
215
|
+
const builders = model.productions
|
|
216
|
+
.map(p => ` { name: ${j(p.name)}, dsl: function(){ return Diagram(${toDsl(p.expr)}); } }`)
|
|
217
|
+
.join(',\n')
|
|
218
|
+
|
|
219
|
+
return `<!doctype html>
|
|
220
|
+
<html lang="en">
|
|
221
|
+
<head>
|
|
222
|
+
<meta charset="utf-8">
|
|
223
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
224
|
+
<title>${escapeHtml(title)}</title>
|
|
225
|
+
<style>
|
|
226
|
+
${RAILROAD_CSS}
|
|
227
|
+
${PAGE_CSS}
|
|
228
|
+
</style>
|
|
229
|
+
</head>
|
|
230
|
+
<body>
|
|
231
|
+
<h1>${escapeHtml(title)}</h1>
|
|
232
|
+
<p class="subtitle">Syntax diagrams generated from the parser grammar.</p>
|
|
233
|
+
<nav class="toc">
|
|
234
|
+
${toc}
|
|
235
|
+
</nav>
|
|
236
|
+
${sections}
|
|
237
|
+
<script>
|
|
238
|
+
${RAILROAD_JS}
|
|
239
|
+
</script>
|
|
240
|
+
<script>
|
|
241
|
+
(function () {
|
|
242
|
+
var rules = [
|
|
243
|
+
${builders}
|
|
244
|
+
];
|
|
245
|
+
rules.forEach(function (r) {
|
|
246
|
+
var host = document.querySelector('.diagram[data-rule=' + CSS.escape(r.name) + ']');
|
|
247
|
+
if (!host) return;
|
|
248
|
+
try { r.dsl().addTo(host); }
|
|
249
|
+
catch (e) { host.textContent = 'diagram error: ' + e.message; }
|
|
250
|
+
});
|
|
251
|
+
})();
|
|
252
|
+
</script>
|
|
253
|
+
</body>
|
|
254
|
+
</html>
|
|
255
|
+
`
|
|
256
|
+
}
|