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.
Files changed (316) hide show
  1. package/README.md +8 -3
  2. package/dist/analysis/choice-cost-render.d.ts +12 -4
  3. package/dist/analysis/choice-cost-render.d.ts.map +1 -1
  4. package/dist/analysis/choice-cost.d.ts +2 -2
  5. package/dist/analysis/choice-cost.d.ts.map +1 -1
  6. package/dist/analysis/commitment.d.ts +131 -0
  7. package/dist/analysis/commitment.d.ts.map +1 -0
  8. package/dist/analysis/diagnostics.cjs +10339 -7787
  9. package/dist/analysis/diagnostics.cjs.map +3 -4
  10. package/dist/analysis/diagnostics.d.ts +2 -2
  11. package/dist/analysis/diagnostics.js +10339 -7787
  12. package/dist/analysis/diagnostics.js.map +3 -4
  13. package/dist/analysis/duplication.d.ts.map +1 -1
  14. package/dist/analysis/fix.d.ts +3 -3
  15. package/dist/analysis/fix.d.ts.map +1 -1
  16. package/dist/analysis/format-number.d.ts +23 -0
  17. package/dist/analysis/format-number.d.ts.map +1 -0
  18. package/dist/analysis/gating.d.ts +39 -2
  19. package/dist/analysis/gating.d.ts.map +1 -1
  20. package/dist/analysis/rebuild.d.ts.map +1 -1
  21. package/dist/analysis/terminal.d.ts +5 -2
  22. package/dist/analysis/terminal.d.ts.map +1 -1
  23. package/dist/cli/index.cjs +10924 -8506
  24. package/dist/cli/index.cjs.map +3 -4
  25. package/dist/cli/index.js +10924 -8506
  26. package/dist/cli/index.js.map +3 -4
  27. package/dist/combinators/adjacency.d.ts +100 -0
  28. package/dist/combinators/adjacency.d.ts.map +1 -0
  29. package/dist/combinators/choice.d.ts.map +1 -1
  30. package/dist/combinators/completions.d.ts.map +1 -1
  31. package/dist/combinators/dispatch.d.ts +8 -1
  32. package/dist/combinators/dispatch.d.ts.map +1 -1
  33. package/dist/combinators/expect.d.ts.map +1 -1
  34. package/dist/combinators/first-set.d.ts.map +1 -1
  35. package/dist/combinators/gate.d.ts.map +1 -1
  36. package/dist/combinators/grammar.d.ts +22 -0
  37. package/dist/combinators/grammar.d.ts.map +1 -1
  38. package/dist/combinators/literal.d.ts +8 -0
  39. package/dist/combinators/literal.d.ts.map +1 -1
  40. package/dist/combinators/map.d.ts +0 -1
  41. package/dist/combinators/map.d.ts.map +1 -1
  42. package/dist/combinators/node.d.ts +13 -3
  43. package/dist/combinators/node.d.ts.map +1 -1
  44. package/dist/combinators/parser.d.ts +1 -1
  45. package/dist/combinators/parser.d.ts.map +1 -1
  46. package/dist/combinators/ref.d.ts.map +1 -1
  47. package/dist/combinators/regex.d.ts +1 -16
  48. package/dist/combinators/regex.d.ts.map +1 -1
  49. package/dist/combinators/repeat.d.ts +30 -2
  50. package/dist/combinators/repeat.d.ts.map +1 -1
  51. package/dist/combinators/scanTo.d.ts +71 -1
  52. package/dist/combinators/scanTo.d.ts.map +1 -1
  53. package/dist/combinators/sequence.d.ts.map +1 -1
  54. package/dist/combinators/token.d.ts.map +1 -1
  55. package/dist/combinators/trivia-boundary.d.ts +26 -0
  56. package/dist/combinators/trivia-boundary.d.ts.map +1 -0
  57. package/dist/combinators/trivia-skip.d.ts +45 -1
  58. package/dist/combinators/trivia-skip.d.ts.map +1 -1
  59. package/dist/combinators/withCtx.d.ts.map +1 -1
  60. package/dist/compiler/compile-linkable-table.d.ts +104 -0
  61. package/dist/compiler/compile-linkable-table.d.ts.map +1 -0
  62. package/dist/compiler/degradation.d.ts +0 -2
  63. package/dist/compiler/degradation.d.ts.map +1 -1
  64. package/dist/compiler/fields.d.ts +13 -0
  65. package/dist/compiler/fields.d.ts.map +1 -1
  66. package/dist/compiler/grammar-coverage-ids.d.ts +5 -5
  67. package/dist/compiler/grammar-coverage-ids.d.ts.map +1 -1
  68. package/dist/compiler/ir-serialize.d.ts.map +1 -1
  69. package/dist/compiler/linker.d.ts +47 -53
  70. package/dist/compiler/linker.d.ts.map +1 -1
  71. package/dist/compiler/token-alphabet.d.ts +81 -0
  72. package/dist/compiler/token-alphabet.d.ts.map +1 -0
  73. package/dist/compiler/token-dispatch.d.ts +95 -0
  74. package/dist/compiler/token-dispatch.d.ts.map +1 -0
  75. package/dist/compiler/token-scanner.d.ts +53 -0
  76. package/dist/compiler/token-scanner.d.ts.map +1 -0
  77. package/dist/compiler/value-usage.d.ts.map +1 -1
  78. package/dist/coverage.d.ts +1 -1
  79. package/dist/coverage.d.ts.map +1 -1
  80. package/dist/cst/capture-buffer.d.ts +35 -0
  81. package/dist/cst/capture-buffer.d.ts.map +1 -1
  82. package/dist/cst/reflection-attach.d.ts +8 -0
  83. package/dist/cst/reflection-attach.d.ts.map +1 -0
  84. package/dist/cst/reflection-symbols.d.ts +10 -0
  85. package/dist/cst/reflection-symbols.d.ts.map +1 -0
  86. package/dist/cst/reflection.d.ts +2 -4
  87. package/dist/cst/reflection.d.ts.map +1 -1
  88. package/dist/cst/root-trivia-scope.d.ts +26 -0
  89. package/dist/cst/root-trivia-scope.d.ts.map +1 -0
  90. package/dist/cst/trivia-charscan.d.ts +18 -0
  91. package/dist/cst/trivia-charscan.d.ts.map +1 -0
  92. package/dist/cst/trivia-kinds.d.ts +19 -0
  93. package/dist/cst/trivia-kinds.d.ts.map +1 -1
  94. package/dist/functional/doc.d.ts.map +1 -1
  95. package/dist/functional/run-tabled.d.ts +12 -0
  96. package/dist/functional/run-tabled.d.ts.map +1 -0
  97. package/dist/functional/run.d.ts +25 -42
  98. package/dist/functional/run.d.ts.map +1 -1
  99. package/dist/grammar-metadata.d.ts +3 -0
  100. package/dist/grammar-metadata.d.ts.map +1 -0
  101. package/dist/index.cjs +9290 -7181
  102. package/dist/index.cjs.map +3 -4
  103. package/dist/index.d.ts +18 -9
  104. package/dist/index.d.ts.map +1 -1
  105. package/dist/index.js +9300 -7191
  106. package/dist/index.js.map +3 -4
  107. package/dist/language-service/index.cjs +313 -121
  108. package/dist/language-service/index.cjs.map +3 -4
  109. package/dist/language-service/index.js +313 -121
  110. package/dist/language-service/index.js.map +3 -4
  111. package/dist/oracle/index.cjs.map +0 -1
  112. package/dist/oracle/index.js.map +0 -1
  113. package/dist/parse-context.d.ts +49 -0
  114. package/dist/parse-context.d.ts.map +1 -0
  115. package/dist/plugin/evaluator.d.ts +40 -1
  116. package/dist/plugin/evaluator.d.ts.map +1 -1
  117. package/dist/plugin/index.cjs +11175 -9534
  118. package/dist/plugin/index.cjs.map +3 -4
  119. package/dist/plugin/index.d.ts.map +1 -1
  120. package/dist/plugin/index.js +11175 -9534
  121. package/dist/plugin/index.js.map +3 -4
  122. package/dist/regex/classes.d.ts +28 -0
  123. package/dist/regex/classes.d.ts.map +1 -1
  124. package/dist/regex/first-set.d.ts +14 -0
  125. package/dist/regex/first-set.d.ts.map +1 -1
  126. package/dist/run/index.cjs +136 -51
  127. package/dist/run/index.cjs.map +2 -3
  128. package/dist/run/index.js +136 -51
  129. package/dist/run/index.js.map +2 -3
  130. package/dist/spec/index.cjs +2934 -1611
  131. package/dist/spec/index.cjs.map +3 -4
  132. package/dist/spec/index.js +2934 -1611
  133. package/dist/spec/index.js.map +3 -4
  134. package/dist/spec/model.d.ts +8 -0
  135. package/dist/spec/model.d.ts.map +1 -1
  136. package/dist/table/assemble.d.ts +307 -0
  137. package/dist/table/assemble.d.ts.map +1 -0
  138. package/dist/table/cell.d.ts +42 -0
  139. package/dist/table/cell.d.ts.map +1 -0
  140. package/dist/table/child-slots.d.ts +30 -0
  141. package/dist/table/child-slots.d.ts.map +1 -0
  142. package/dist/table/compile-rule-map.d.ts +136 -0
  143. package/dist/table/compile-rule-map.d.ts.map +1 -0
  144. package/dist/table/compile.d.ts +56 -0
  145. package/dist/table/compile.d.ts.map +1 -0
  146. package/dist/table/duplication-hook.d.ts +32 -0
  147. package/dist/table/duplication-hook.d.ts.map +1 -0
  148. package/dist/table/emit-assembly.d.ts +139 -0
  149. package/dist/table/emit-assembly.d.ts.map +1 -0
  150. package/dist/table/emit.d.ts +113 -0
  151. package/dist/table/emit.d.ts.map +1 -0
  152. package/dist/table/encode-baseline.d.ts +16 -0
  153. package/dist/table/encode-baseline.d.ts.map +1 -0
  154. package/dist/table/encode.d.ts +71 -0
  155. package/dist/table/encode.d.ts.map +1 -0
  156. package/dist/table/exec-baseline.d.ts +9 -0
  157. package/dist/table/exec-baseline.d.ts.map +1 -0
  158. package/dist/table/exec.d.ts +72 -0
  159. package/dist/table/exec.d.ts.map +1 -0
  160. package/dist/table/fold.d.ts +25 -0
  161. package/dist/table/fold.d.ts.map +1 -0
  162. package/dist/table/index.cjs +11463 -0
  163. package/dist/table/index.cjs.map +6 -0
  164. package/dist/table/index.d.ts +100 -0
  165. package/dist/table/index.d.ts.map +1 -0
  166. package/dist/table/index.js +11440 -0
  167. package/dist/table/index.js.map +6 -0
  168. package/dist/table/inspect.d.ts +17 -0
  169. package/dist/table/inspect.d.ts.map +1 -0
  170. package/dist/table/ops.d.ts +453 -0
  171. package/dist/table/ops.d.ts.map +1 -0
  172. package/dist/table/program.d.ts +496 -0
  173. package/dist/table/program.d.ts.map +1 -0
  174. package/dist/table/run-support.d.ts +38 -0
  175. package/dist/table/run-support.d.ts.map +1 -0
  176. package/dist/{compiler/scannable-run.d.ts → table/scan-shapes.d.ts} +14 -13
  177. package/dist/table/scan-shapes.d.ts.map +1 -0
  178. package/dist/table/site-labels.d.ts +58 -0
  179. package/dist/table/site-labels.d.ts.map +1 -0
  180. package/dist/table/stamp.d.ts +70 -0
  181. package/dist/table/stamp.d.ts.map +1 -0
  182. package/dist/types.d.ts +80 -24
  183. package/dist/types.d.ts.map +1 -1
  184. package/dist/version.d.ts +1 -1
  185. package/package.json +26 -2
  186. package/src/analysis/choice-cost-gate.ts +359 -0
  187. package/src/analysis/choice-cost-render.ts +293 -0
  188. package/src/analysis/choice-cost.ts +1008 -0
  189. package/src/analysis/commitment.ts +557 -0
  190. package/src/analysis/corpus.ts +130 -0
  191. package/src/analysis/diagnose-render.ts +625 -0
  192. package/src/analysis/diagnose.ts +317 -0
  193. package/src/analysis/diagnostics.ts +33 -0
  194. package/src/analysis/duplication.ts +1955 -0
  195. package/src/analysis/fix-render.ts +217 -0
  196. package/src/analysis/fix.ts +669 -0
  197. package/src/analysis/format-number.ts +31 -0
  198. package/src/analysis/gating.ts +792 -0
  199. package/src/analysis/grammar.ts +72 -0
  200. package/src/analysis/rebuild.ts +287 -0
  201. package/src/analysis/terminal.ts +336 -0
  202. package/src/cli/index.ts +413 -0
  203. package/src/combinators/adjacency.ts +171 -0
  204. package/src/combinators/attempt.ts +41 -0
  205. package/src/combinators/case-fold.ts +90 -0
  206. package/src/combinators/choice.ts +466 -0
  207. package/src/combinators/completions.ts +64 -0
  208. package/src/combinators/dispatch.ts +358 -0
  209. package/src/combinators/expect.ts +159 -0
  210. package/src/combinators/first-set.ts +348 -0
  211. package/src/combinators/gate.ts +50 -0
  212. package/src/combinators/grammar.ts +272 -0
  213. package/src/combinators/keywords.ts +145 -0
  214. package/src/combinators/literal.ts +102 -0
  215. package/src/combinators/map.ts +106 -0
  216. package/src/combinators/node.ts +345 -0
  217. package/src/combinators/not.ts +55 -0
  218. package/src/combinators/parser.ts +266 -0
  219. package/src/combinators/peek.ts +63 -0
  220. package/src/combinators/probe.ts +25 -0
  221. package/src/combinators/ref.ts +109 -0
  222. package/src/combinators/regex.ts +170 -0
  223. package/src/combinators/repeat.ts +561 -0
  224. package/src/combinators/scanTo.ts +393 -0
  225. package/src/combinators/sequence.ts +237 -0
  226. package/src/combinators/token.ts +120 -0
  227. package/src/combinators/trivia-boundary.ts +48 -0
  228. package/src/combinators/trivia-skip.ts +852 -0
  229. package/src/combinators/withCtx.ts +45 -0
  230. package/src/compiler/build-arity.ts +330 -0
  231. package/src/compiler/compile-linkable-table.ts +209 -0
  232. package/src/compiler/degradation.ts +266 -0
  233. package/src/compiler/fields.ts +191 -0
  234. package/src/compiler/grammar-coverage-ids.ts +129 -0
  235. package/src/compiler/ir-serialize.ts +615 -0
  236. package/src/compiler/linker.ts +954 -0
  237. package/src/compiler/token-alphabet.ts +209 -0
  238. package/src/compiler/token-dispatch.ts +353 -0
  239. package/src/compiler/token-scanner.ts +318 -0
  240. package/src/compiler/value-usage.ts +147 -0
  241. package/src/coverage.ts +383 -0
  242. package/src/cst/capture-buffer.ts +297 -0
  243. package/src/cst/host-mode.ts +69 -0
  244. package/src/cst/reflection-attach.ts +11 -0
  245. package/src/cst/reflection-symbols.ts +9 -0
  246. package/src/cst/reflection.ts +149 -0
  247. package/src/cst/relative-spans.ts +207 -0
  248. package/src/cst/root-trivia-scope.ts +30 -0
  249. package/src/cst/trivia-charscan.ts +312 -0
  250. package/src/cst/trivia-entries.ts +285 -0
  251. package/src/cst/trivia-index.ts +131 -0
  252. package/src/cst/trivia-kinds.ts +358 -0
  253. package/src/cst/types.ts +76 -0
  254. package/src/cst/walk.ts +101 -0
  255. package/src/functional/doc.ts +959 -0
  256. package/src/functional/run-tabled.ts +153 -0
  257. package/src/functional/run.ts +558 -0
  258. package/src/grammar-metadata.ts +2 -0
  259. package/src/index.ts +189 -0
  260. package/src/language-service/index.ts +221 -0
  261. package/src/line-index.ts +131 -0
  262. package/src/oracle/digest.ts +620 -0
  263. package/src/oracle/index.ts +55 -0
  264. package/src/parse-context.ts +91 -0
  265. package/src/plugin/direct-builder-static.ts +102 -0
  266. package/src/plugin/evaluator.ts +1505 -0
  267. package/src/plugin/free-identifiers.ts +338 -0
  268. package/src/plugin/index.ts +2281 -0
  269. package/src/plugin/reducer-resolver.ts +606 -0
  270. package/src/recovery/scan.ts +155 -0
  271. package/src/regex/classes.ts +183 -0
  272. package/src/regex/first-set.ts +316 -0
  273. package/src/run/index.ts +31 -0
  274. package/src/spec/ebnf.ts +128 -0
  275. package/src/spec/index.ts +54 -0
  276. package/src/spec/model.ts +636 -0
  277. package/src/spec/railroad-lib.ts +559 -0
  278. package/src/spec/railroad.ts +256 -0
  279. package/src/table/assemble.ts +3006 -0
  280. package/src/table/cell.ts +42 -0
  281. package/src/table/child-slots.ts +113 -0
  282. package/src/table/compile-rule-map.ts +336 -0
  283. package/src/table/compile.ts +244 -0
  284. package/src/table/duplication-hook.ts +89 -0
  285. package/src/table/emit-assembly.ts +1777 -0
  286. package/src/table/emit.ts +429 -0
  287. package/src/table/encode-baseline.ts +340 -0
  288. package/src/table/encode.ts +1569 -0
  289. package/src/table/exec-baseline.ts +537 -0
  290. package/src/table/exec.ts +1680 -0
  291. package/src/table/fold.ts +38 -0
  292. package/src/table/index.ts +108 -0
  293. package/src/table/inspect.ts +52 -0
  294. package/src/table/ops.ts +467 -0
  295. package/src/table/program.ts +876 -0
  296. package/src/table/run-support.ts +66 -0
  297. package/src/table/scan-shapes.ts +1578 -0
  298. package/src/table/site-labels.ts +228 -0
  299. package/src/table/stamp.ts +229 -0
  300. package/src/types.ts +563 -0
  301. package/src/version.ts +16 -0
  302. package/dist/compiler/codegen.d.ts +0 -334
  303. package/dist/compiler/codegen.d.ts.map +0 -1
  304. package/dist/compiler/inline-build.d.ts +0 -11
  305. package/dist/compiler/inline-build.d.ts.map +0 -1
  306. package/dist/compiler/inline-callback.d.ts +0 -21
  307. package/dist/compiler/inline-callback.d.ts.map +0 -1
  308. package/dist/compiler/line-index.d.ts +0 -2
  309. package/dist/compiler/line-index.d.ts.map +0 -1
  310. package/dist/compiler/module-hoist.d.ts +0 -106
  311. package/dist/compiler/module-hoist.d.ts.map +0 -1
  312. package/dist/compiler/scannable-run.d.ts.map +0 -1
  313. package/dist/compiler/scannable-terminal.d.ts +0 -23
  314. package/dist/compiler/scannable-terminal.d.ts.map +0 -1
  315. package/dist/compiler/trivia-fast-path.d.ts +0 -38
  316. 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, '&amp;')
150
+ .replace(/</g, '&lt;')
151
+ .replace(/>/g, '&gt;')
152
+ .replace(/"/g, '&quot;')
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
+ }