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.
Files changed (333) hide show
  1. package/README.md +8 -3
  2. package/dist/analysis/choice-cost-gate.d.ts +158 -0
  3. package/dist/analysis/choice-cost-gate.d.ts.map +1 -0
  4. package/dist/analysis/choice-cost-render.d.ts +96 -0
  5. package/dist/analysis/choice-cost-render.d.ts.map +1 -0
  6. package/dist/analysis/choice-cost.d.ts +383 -0
  7. package/dist/analysis/choice-cost.d.ts.map +1 -0
  8. package/dist/analysis/commitment.d.ts +131 -0
  9. package/dist/analysis/commitment.d.ts.map +1 -0
  10. package/dist/analysis/corpus.d.ts +73 -0
  11. package/dist/analysis/corpus.d.ts.map +1 -0
  12. package/dist/analysis/diagnose-render.d.ts +65 -0
  13. package/dist/analysis/diagnose-render.d.ts.map +1 -0
  14. package/dist/analysis/diagnose.d.ts +15 -0
  15. package/dist/analysis/diagnose.d.ts.map +1 -1
  16. package/dist/analysis/diagnostics.cjs +13736 -0
  17. package/dist/analysis/diagnostics.cjs.map +6 -0
  18. package/dist/analysis/diagnostics.d.ts +27 -0
  19. package/dist/analysis/diagnostics.d.ts.map +1 -0
  20. package/dist/analysis/diagnostics.js +13713 -0
  21. package/dist/analysis/diagnostics.js.map +6 -0
  22. package/dist/analysis/duplication.d.ts.map +1 -1
  23. package/dist/analysis/fix-render.d.ts +43 -0
  24. package/dist/analysis/fix-render.d.ts.map +1 -0
  25. package/dist/analysis/fix.d.ts +172 -0
  26. package/dist/analysis/fix.d.ts.map +1 -0
  27. package/dist/analysis/format-number.d.ts +23 -0
  28. package/dist/analysis/format-number.d.ts.map +1 -0
  29. package/dist/analysis/gating.d.ts +47 -2
  30. package/dist/analysis/gating.d.ts.map +1 -1
  31. package/dist/analysis/rebuild.d.ts +61 -0
  32. package/dist/analysis/rebuild.d.ts.map +1 -0
  33. package/dist/analysis/terminal.d.ts +145 -0
  34. package/dist/analysis/terminal.d.ts.map +1 -0
  35. package/dist/cli/index.cjs +14837 -0
  36. package/dist/cli/index.cjs.map +6 -0
  37. package/dist/cli/index.d.ts +3 -0
  38. package/dist/cli/index.d.ts.map +1 -0
  39. package/dist/cli/index.js +14814 -0
  40. package/dist/cli/index.js.map +6 -0
  41. package/dist/combinators/adjacency.d.ts +100 -0
  42. package/dist/combinators/adjacency.d.ts.map +1 -0
  43. package/dist/combinators/choice.d.ts.map +1 -1
  44. package/dist/combinators/completions.d.ts.map +1 -1
  45. package/dist/combinators/dispatch.d.ts +8 -1
  46. package/dist/combinators/dispatch.d.ts.map +1 -1
  47. package/dist/combinators/expect.d.ts.map +1 -1
  48. package/dist/combinators/first-set.d.ts.map +1 -1
  49. package/dist/combinators/gate.d.ts.map +1 -1
  50. package/dist/combinators/grammar.d.ts +22 -0
  51. package/dist/combinators/grammar.d.ts.map +1 -1
  52. package/dist/combinators/literal.d.ts +8 -0
  53. package/dist/combinators/literal.d.ts.map +1 -1
  54. package/dist/combinators/map.d.ts +0 -1
  55. package/dist/combinators/map.d.ts.map +1 -1
  56. package/dist/combinators/node.d.ts +13 -3
  57. package/dist/combinators/node.d.ts.map +1 -1
  58. package/dist/combinators/parser.d.ts +1 -1
  59. package/dist/combinators/parser.d.ts.map +1 -1
  60. package/dist/combinators/ref.d.ts.map +1 -1
  61. package/dist/combinators/regex.d.ts +1 -16
  62. package/dist/combinators/regex.d.ts.map +1 -1
  63. package/dist/combinators/repeat.d.ts +30 -2
  64. package/dist/combinators/repeat.d.ts.map +1 -1
  65. package/dist/combinators/scanTo.d.ts +71 -1
  66. package/dist/combinators/scanTo.d.ts.map +1 -1
  67. package/dist/combinators/sequence.d.ts.map +1 -1
  68. package/dist/combinators/token.d.ts.map +1 -1
  69. package/dist/combinators/trivia-boundary.d.ts +26 -0
  70. package/dist/combinators/trivia-boundary.d.ts.map +1 -0
  71. package/dist/combinators/trivia-skip.d.ts +40 -1
  72. package/dist/combinators/trivia-skip.d.ts.map +1 -1
  73. package/dist/combinators/withCtx.d.ts.map +1 -1
  74. package/dist/compiler/build-arity.d.ts.map +1 -1
  75. package/dist/compiler/compile-linkable-table.d.ts +104 -0
  76. package/dist/compiler/compile-linkable-table.d.ts.map +1 -0
  77. package/dist/compiler/degradation.d.ts +0 -2
  78. package/dist/compiler/degradation.d.ts.map +1 -1
  79. package/dist/compiler/fields.d.ts +13 -0
  80. package/dist/compiler/fields.d.ts.map +1 -1
  81. package/dist/compiler/grammar-coverage-ids.d.ts +5 -5
  82. package/dist/compiler/grammar-coverage-ids.d.ts.map +1 -1
  83. package/dist/compiler/ir-serialize.d.ts.map +1 -1
  84. package/dist/compiler/linker.d.ts +79 -47
  85. package/dist/compiler/linker.d.ts.map +1 -1
  86. package/dist/compiler/token-alphabet.d.ts +81 -0
  87. package/dist/compiler/token-alphabet.d.ts.map +1 -0
  88. package/dist/compiler/token-dispatch.d.ts +95 -0
  89. package/dist/compiler/token-dispatch.d.ts.map +1 -0
  90. package/dist/compiler/token-scanner.d.ts +53 -0
  91. package/dist/compiler/token-scanner.d.ts.map +1 -0
  92. package/dist/compiler/value-usage.d.ts.map +1 -1
  93. package/dist/coverage.d.ts +1 -1
  94. package/dist/coverage.d.ts.map +1 -1
  95. package/dist/cst/capture-buffer.d.ts +35 -0
  96. package/dist/cst/capture-buffer.d.ts.map +1 -1
  97. package/dist/cst/reflection-attach.d.ts +8 -0
  98. package/dist/cst/reflection-attach.d.ts.map +1 -0
  99. package/dist/cst/reflection-symbols.d.ts +10 -0
  100. package/dist/cst/reflection-symbols.d.ts.map +1 -0
  101. package/dist/cst/reflection.d.ts +2 -4
  102. package/dist/cst/reflection.d.ts.map +1 -1
  103. package/dist/cst/root-trivia-scope.d.ts +26 -0
  104. package/dist/cst/root-trivia-scope.d.ts.map +1 -0
  105. package/dist/cst/trivia-charscan.d.ts +18 -0
  106. package/dist/cst/trivia-charscan.d.ts.map +1 -0
  107. package/dist/cst/trivia-kinds.d.ts +19 -0
  108. package/dist/cst/trivia-kinds.d.ts.map +1 -1
  109. package/dist/functional/doc.d.ts.map +1 -1
  110. package/dist/functional/run-tabled.d.ts +12 -0
  111. package/dist/functional/run-tabled.d.ts.map +1 -0
  112. package/dist/functional/run.d.ts +25 -42
  113. package/dist/functional/run.d.ts.map +1 -1
  114. package/dist/grammar-metadata.d.ts +3 -0
  115. package/dist/grammar-metadata.d.ts.map +1 -0
  116. package/dist/index.cjs +10499 -7478
  117. package/dist/index.cjs.map +3 -4
  118. package/dist/index.d.ts +24 -9
  119. package/dist/index.d.ts.map +1 -1
  120. package/dist/index.js +10499 -7478
  121. package/dist/index.js.map +3 -4
  122. package/dist/language-service/index.cjs +311 -121
  123. package/dist/language-service/index.cjs.map +3 -4
  124. package/dist/language-service/index.js +311 -121
  125. package/dist/language-service/index.js.map +3 -4
  126. package/dist/oracle/index.cjs.map +0 -1
  127. package/dist/oracle/index.js.map +0 -1
  128. package/dist/parse-context.d.ts +49 -0
  129. package/dist/parse-context.d.ts.map +1 -0
  130. package/dist/plugin/evaluator.d.ts +41 -2
  131. package/dist/plugin/evaluator.d.ts.map +1 -1
  132. package/dist/plugin/free-identifiers.d.ts +23 -0
  133. package/dist/plugin/free-identifiers.d.ts.map +1 -0
  134. package/dist/plugin/index.cjs +11381 -9442
  135. package/dist/plugin/index.cjs.map +3 -4
  136. package/dist/plugin/index.d.ts.map +1 -1
  137. package/dist/plugin/index.js +11381 -9442
  138. package/dist/plugin/index.js.map +3 -4
  139. package/dist/plugin/reducer-resolver.d.ts +17 -1
  140. package/dist/plugin/reducer-resolver.d.ts.map +1 -1
  141. package/dist/regex/classes.d.ts +28 -0
  142. package/dist/regex/classes.d.ts.map +1 -1
  143. package/dist/regex/first-set.d.ts +14 -0
  144. package/dist/regex/first-set.d.ts.map +1 -1
  145. package/dist/run/index.cjs +136 -51
  146. package/dist/run/index.cjs.map +2 -3
  147. package/dist/run/index.js +136 -51
  148. package/dist/run/index.js.map +2 -3
  149. package/dist/spec/index.cjs +2898 -1622
  150. package/dist/spec/index.cjs.map +3 -4
  151. package/dist/spec/index.js +2898 -1622
  152. package/dist/spec/index.js.map +3 -4
  153. package/dist/spec/model.d.ts +8 -0
  154. package/dist/spec/model.d.ts.map +1 -1
  155. package/dist/table/assemble.d.ts +307 -0
  156. package/dist/table/assemble.d.ts.map +1 -0
  157. package/dist/table/cell.d.ts +42 -0
  158. package/dist/table/cell.d.ts.map +1 -0
  159. package/dist/table/child-slots.d.ts +30 -0
  160. package/dist/table/child-slots.d.ts.map +1 -0
  161. package/dist/table/compile-rule-map.d.ts +136 -0
  162. package/dist/table/compile-rule-map.d.ts.map +1 -0
  163. package/dist/table/compile.d.ts +56 -0
  164. package/dist/table/compile.d.ts.map +1 -0
  165. package/dist/table/duplication-hook.d.ts +32 -0
  166. package/dist/table/duplication-hook.d.ts.map +1 -0
  167. package/dist/table/emit-assembly.d.ts +139 -0
  168. package/dist/table/emit-assembly.d.ts.map +1 -0
  169. package/dist/table/emit.d.ts +113 -0
  170. package/dist/table/emit.d.ts.map +1 -0
  171. package/dist/table/encode-baseline.d.ts +16 -0
  172. package/dist/table/encode-baseline.d.ts.map +1 -0
  173. package/dist/table/encode.d.ts +71 -0
  174. package/dist/table/encode.d.ts.map +1 -0
  175. package/dist/table/exec-baseline.d.ts +9 -0
  176. package/dist/table/exec-baseline.d.ts.map +1 -0
  177. package/dist/table/exec.d.ts +72 -0
  178. package/dist/table/exec.d.ts.map +1 -0
  179. package/dist/table/fold.d.ts +25 -0
  180. package/dist/table/fold.d.ts.map +1 -0
  181. package/dist/table/index.cjs +11375 -0
  182. package/dist/table/index.cjs.map +6 -0
  183. package/dist/table/index.d.ts +100 -0
  184. package/dist/table/index.d.ts.map +1 -0
  185. package/dist/table/index.js +11352 -0
  186. package/dist/table/index.js.map +6 -0
  187. package/dist/table/inspect.d.ts +17 -0
  188. package/dist/table/inspect.d.ts.map +1 -0
  189. package/dist/table/ops.d.ts +446 -0
  190. package/dist/table/ops.d.ts.map +1 -0
  191. package/dist/table/program.d.ts +493 -0
  192. package/dist/table/program.d.ts.map +1 -0
  193. package/dist/table/run-support.d.ts +38 -0
  194. package/dist/table/run-support.d.ts.map +1 -0
  195. package/dist/{compiler/scannable-run.d.ts → table/scan-shapes.d.ts} +14 -13
  196. package/dist/table/scan-shapes.d.ts.map +1 -0
  197. package/dist/table/site-labels.d.ts +58 -0
  198. package/dist/table/site-labels.d.ts.map +1 -0
  199. package/dist/table/stamp.d.ts +70 -0
  200. package/dist/table/stamp.d.ts.map +1 -0
  201. package/dist/types.d.ts +80 -24
  202. package/dist/types.d.ts.map +1 -1
  203. package/dist/version.d.ts +1 -1
  204. package/package.json +41 -3
  205. package/src/analysis/choice-cost-gate.ts +359 -0
  206. package/src/analysis/choice-cost-render.ts +293 -0
  207. package/src/analysis/choice-cost.ts +1008 -0
  208. package/src/analysis/commitment.ts +557 -0
  209. package/src/analysis/corpus.ts +130 -0
  210. package/src/analysis/diagnose-render.ts +625 -0
  211. package/src/analysis/diagnose.ts +317 -0
  212. package/src/analysis/diagnostics.ts +33 -0
  213. package/src/analysis/duplication.ts +1955 -0
  214. package/src/analysis/fix-render.ts +217 -0
  215. package/src/analysis/fix.ts +669 -0
  216. package/src/analysis/format-number.ts +31 -0
  217. package/src/analysis/gating.ts +792 -0
  218. package/src/analysis/grammar.ts +72 -0
  219. package/src/analysis/rebuild.ts +287 -0
  220. package/src/analysis/terminal.ts +336 -0
  221. package/src/cli/index.ts +413 -0
  222. package/src/combinators/adjacency.ts +171 -0
  223. package/src/combinators/attempt.ts +41 -0
  224. package/src/combinators/case-fold.ts +90 -0
  225. package/src/combinators/choice.ts +466 -0
  226. package/src/combinators/completions.ts +64 -0
  227. package/src/combinators/dispatch.ts +358 -0
  228. package/src/combinators/expect.ts +159 -0
  229. package/src/combinators/first-set.ts +322 -0
  230. package/src/combinators/gate.ts +50 -0
  231. package/src/combinators/grammar.ts +272 -0
  232. package/src/combinators/keywords.ts +145 -0
  233. package/src/combinators/literal.ts +102 -0
  234. package/src/combinators/map.ts +106 -0
  235. package/src/combinators/node.ts +345 -0
  236. package/src/combinators/not.ts +55 -0
  237. package/src/combinators/parser.ts +266 -0
  238. package/src/combinators/peek.ts +63 -0
  239. package/src/combinators/probe.ts +25 -0
  240. package/src/combinators/ref.ts +109 -0
  241. package/src/combinators/regex.ts +170 -0
  242. package/src/combinators/repeat.ts +561 -0
  243. package/src/combinators/scanTo.ts +393 -0
  244. package/src/combinators/sequence.ts +221 -0
  245. package/src/combinators/token.ts +120 -0
  246. package/src/combinators/trivia-boundary.ts +48 -0
  247. package/src/combinators/trivia-skip.ts +821 -0
  248. package/src/combinators/withCtx.ts +45 -0
  249. package/src/compiler/build-arity.ts +330 -0
  250. package/src/compiler/compile-linkable-table.ts +209 -0
  251. package/src/compiler/degradation.ts +266 -0
  252. package/src/compiler/fields.ts +191 -0
  253. package/src/compiler/grammar-coverage-ids.ts +129 -0
  254. package/src/compiler/ir-serialize.ts +615 -0
  255. package/src/compiler/linker.ts +954 -0
  256. package/src/compiler/token-alphabet.ts +209 -0
  257. package/src/compiler/token-dispatch.ts +353 -0
  258. package/src/compiler/token-scanner.ts +318 -0
  259. package/src/compiler/value-usage.ts +147 -0
  260. package/src/coverage.ts +383 -0
  261. package/src/cst/capture-buffer.ts +297 -0
  262. package/src/cst/host-mode.ts +69 -0
  263. package/src/cst/reflection-attach.ts +11 -0
  264. package/src/cst/reflection-symbols.ts +9 -0
  265. package/src/cst/reflection.ts +149 -0
  266. package/src/cst/relative-spans.ts +207 -0
  267. package/src/cst/root-trivia-scope.ts +30 -0
  268. package/src/cst/trivia-charscan.ts +312 -0
  269. package/src/cst/trivia-entries.ts +285 -0
  270. package/src/cst/trivia-index.ts +131 -0
  271. package/src/cst/trivia-kinds.ts +358 -0
  272. package/src/cst/types.ts +76 -0
  273. package/src/cst/walk.ts +101 -0
  274. package/src/functional/doc.ts +959 -0
  275. package/src/functional/run-tabled.ts +153 -0
  276. package/src/functional/run.ts +558 -0
  277. package/src/grammar-metadata.ts +2 -0
  278. package/src/index.ts +189 -0
  279. package/src/language-service/index.ts +221 -0
  280. package/src/line-index.ts +131 -0
  281. package/src/oracle/digest.ts +620 -0
  282. package/src/oracle/index.ts +55 -0
  283. package/src/parse-context.ts +91 -0
  284. package/src/plugin/direct-builder-static.ts +102 -0
  285. package/src/plugin/evaluator.ts +1505 -0
  286. package/src/plugin/free-identifiers.ts +338 -0
  287. package/src/plugin/index.ts +2281 -0
  288. package/src/plugin/reducer-resolver.ts +606 -0
  289. package/src/recovery/scan.ts +155 -0
  290. package/src/regex/classes.ts +183 -0
  291. package/src/regex/first-set.ts +316 -0
  292. package/src/run/index.ts +31 -0
  293. package/src/spec/ebnf.ts +128 -0
  294. package/src/spec/index.ts +54 -0
  295. package/src/spec/model.ts +636 -0
  296. package/src/spec/railroad-lib.ts +559 -0
  297. package/src/spec/railroad.ts +256 -0
  298. package/src/table/assemble.ts +2994 -0
  299. package/src/table/cell.ts +42 -0
  300. package/src/table/child-slots.ts +113 -0
  301. package/src/table/compile-rule-map.ts +336 -0
  302. package/src/table/compile.ts +244 -0
  303. package/src/table/duplication-hook.ts +89 -0
  304. package/src/table/emit-assembly.ts +1766 -0
  305. package/src/table/emit.ts +418 -0
  306. package/src/table/encode-baseline.ts +340 -0
  307. package/src/table/encode.ts +1569 -0
  308. package/src/table/exec-baseline.ts +537 -0
  309. package/src/table/exec.ts +1672 -0
  310. package/src/table/fold.ts +38 -0
  311. package/src/table/index.ts +108 -0
  312. package/src/table/inspect.ts +52 -0
  313. package/src/table/ops.ts +459 -0
  314. package/src/table/program.ts +855 -0
  315. package/src/table/run-support.ts +66 -0
  316. package/src/table/scan-shapes.ts +1578 -0
  317. package/src/table/site-labels.ts +228 -0
  318. package/src/table/stamp.ts +229 -0
  319. package/src/types.ts +563 -0
  320. package/src/version.ts +16 -0
  321. package/dist/compiler/codegen.d.ts +0 -334
  322. package/dist/compiler/codegen.d.ts.map +0 -1
  323. package/dist/compiler/inline-build.d.ts +0 -11
  324. package/dist/compiler/inline-build.d.ts.map +0 -1
  325. package/dist/compiler/inline-callback.d.ts +0 -21
  326. package/dist/compiler/inline-callback.d.ts.map +0 -1
  327. package/dist/compiler/line-index.d.ts +0 -2
  328. package/dist/compiler/line-index.d.ts.map +0 -1
  329. package/dist/compiler/scannable-run.d.ts.map +0 -1
  330. package/dist/compiler/scannable-terminal.d.ts +0 -23
  331. package/dist/compiler/scannable-terminal.d.ts.map +0 -1
  332. package/dist/compiler/trivia-fast-path.d.ts +0 -38
  333. package/dist/compiler/trivia-fast-path.d.ts.map +0 -1
@@ -0,0 +1,317 @@
1
+ /**
2
+ * The DELIBERATE grammar diagnostic — the one call that asks parseman "is anything
3
+ * wrong with this grammar?" and answers in a form a machine can gate on.
4
+ *
5
+ * Why this exists as its own entry point, and why nothing in `compile()` prints any
6
+ * more: a diagnostic that rides along with the thing that produces the artifact is a
7
+ * diagnostic nobody chose to run. Importing one example grammar used to print ~51
8
+ * lines of gating advice through `console.warn` before a single byte was parsed —
9
+ * advice that was correct, detailed, and read by no one, because it arrived unasked
10
+ * in the middle of an unrelated build log. The same principle is already settled one
11
+ * layer down for codegen ("anything 'diagnostic' doesn't end up in codegen"); this is
12
+ * that principle applied at build time. Compiling produces an artifact and says
13
+ * nothing. Asking for a diagnosis produces a diagnosis.
14
+ *
15
+ * Design rules this surface holds itself to:
16
+ *
17
+ * 1. MACHINE-READABLE FIRST. `diagnoseGrammar()` returns a plain, JSON-serializable
18
+ * object with a versioned `schema` tag. The human rendering is a separate,
19
+ * optional function over that object — never the primary product. A CI job gates
20
+ * on `ok`; a person reads `formatGrammarDiagnosis()`.
21
+ * 2. DETERMINISTIC. Findings are sorted by (severity, code, id). Two runs over the
22
+ * same grammar produce byte-identical JSON, so a diagnosis can be committed as a
23
+ * snapshot and diffed.
24
+ * 3. FAILS CLOSED. An analysis that could not run is NOT a pass. `unanalysable` stays
25
+ * authoritative (see `GatingReport.unanalysable`), it is a BLOCKING finding, and a
26
+ * diagnosis whose analysis THREW is reported as a blocking finding rather than as
27
+ * an empty, clean-looking report.
28
+ * 4. ONE ENTRY POINT. It accepts a combinator, a rule-name→combinator map, a `rules()`
29
+ * map, or a `compose()` result, and figures out which it got. Choosing between
30
+ * `analyzeGating` / `analyzeGatingRules` / `analyzeGrammarGating` is exactly the
31
+ * kind of decision that makes people not bother.
32
+ */
33
+ import type { Combinator } from '../types.ts'
34
+ import {
35
+ analyzeGating, analyzeGatingRules, firstSetToString,
36
+ type AnalyzeGatingOptions, type GatingReport,
37
+ } from './gating.ts'
38
+ import { analyzeGrammarGating, type AnalysableGrammar } from './grammar.ts'
39
+ import {
40
+ beginDegradationCapture, degradationCaptureDepth, unwindDegradationCapture,
41
+ formatDegradation, type Degradation,
42
+ } from '../compiler/degradation.ts'
43
+
44
+ /** Anything `diagnoseGrammar()` knows how to read. */
45
+ export type DiagnosableGrammar =
46
+ | Combinator<unknown>
47
+ | AnalysableGrammar
48
+ | ReadonlyArray<readonly [string, Combinator<unknown>]>
49
+
50
+ /**
51
+ * `blocking` fails `ok` (and therefore a CI gate). `advisory` is reported but does not
52
+ * fail: the author either already acknowledged it (`accepted`) or has nothing to act on.
53
+ */
54
+ export type DiagnosisSeverity = 'blocking' | 'advisory'
55
+
56
+ /** Stable, greppable finding class. New codes may be added; existing ones are not renamed. */
57
+ export type DiagnosisCode =
58
+ /** A hot choice with no first-char dispatch — every position enters doomed arms. */
59
+ | 'ungated-choice'
60
+ /** An API-misuse pattern in a choice's arms (double-not, leading-not, keyword-regex). */
61
+ | 'anti-pattern'
62
+ /** Part of the grammar could not be examined. A clean report over it is NOT a pass. */
63
+ | 'unanalysable'
64
+ /** The compiler took a correct-but-slower path. Mirrors the `[parseman] degraded` channel. */
65
+ | 'degraded'
66
+ /** An `accept` entry that matched no ungated choice — a stale snapshot line to prune. */
67
+ | 'stale-accept'
68
+
69
+ export type DiagnosisFinding = {
70
+ /** Stable identity: the choice id, `rule#arm`, the rule name, or the degradation code. */
71
+ id: string
72
+ code: DiagnosisCode
73
+ severity: DiagnosisSeverity
74
+ /** Rule / node type the finding lands on. */
75
+ rule: string
76
+ /** One-line statement of what is wrong. */
77
+ message: string
78
+ /** Arm-level evidence and concrete fixes, one entry per contributing cause. */
79
+ details: string[]
80
+ /** The `accept` snapshot key that would silence this finding, when one exists. */
81
+ acceptKey?: string
82
+ }
83
+
84
+ export type GrammarDiagnosis = {
85
+ /** Versioned so a committed snapshot can be migrated rather than silently reinterpreted. */
86
+ schema: 'parseman.diagnosis/1'
87
+ /**
88
+ * True only when there is no blocking finding. A CI gate is
89
+ * `process.exit(diagnoseGrammar(g).ok ? 0 : 1)` — nothing else to remember.
90
+ */
91
+ ok: boolean
92
+ summary: {
93
+ totalChoices: number
94
+ gated: number
95
+ recoverable: number
96
+ ungated: number
97
+ accepted: number
98
+ deferred: number
99
+ antiPatterns: number
100
+ unanalysable: number
101
+ degraded: number
102
+ staleAccepts: number
103
+ }
104
+ /** Sorted (severity, code, id). Deterministic across runs. */
105
+ findings: DiagnosisFinding[]
106
+ /**
107
+ * Every blocking-choice id, sorted — paste straight into `{ accept: [...] }` to
108
+ * acknowledge the current state as intentional.
109
+ */
110
+ acceptSnapshot: string[]
111
+ /** The full underlying gating report, for callers that want the raw per-choice detail. */
112
+ gating: GatingReport
113
+ /**
114
+ * Degradations recorded WHILE this analysis ran (e.g. an opaque composed artifact).
115
+ * NOT the compile-time set — compiling is a separate act; see `PARSEMAN_DEGRADATION`.
116
+ *
117
+ * Empty when `PARSEMAN_DEGRADATION=off`, because `recordDegradation` short-circuits on
118
+ * the level. That is not a blind spot: everything this analysis can record is ALSO
119
+ * present in `gating.unanalysable`, which no env var can switch off.
120
+ */
121
+ degradations: Degradation[]
122
+ }
123
+
124
+ export type DiagnoseOptions = Pick<AnalyzeGatingOptions, 'accept' | 'entryName'>
125
+
126
+ const isCombinator = (g: DiagnosableGrammar): g is Combinator<unknown> =>
127
+ typeof g === 'object' && g !== null && '_def' in (g as object)
128
+
129
+ const isRuleEntries = (g: DiagnosableGrammar): g is ReadonlyArray<readonly [string, Combinator<unknown>]> =>
130
+ Array.isArray(g)
131
+
132
+ /**
133
+ * Diagnose a grammar. Never throws: an analysis that cannot run is reported as a
134
+ * blocking `unanalysable` finding, because a thrown diagnostic and a clean grammar
135
+ * must not look the same to a caller that wrapped this in a try/catch.
136
+ */
137
+ export function diagnoseGrammar(grammar: DiagnosableGrammar, opts?: DiagnoseOptions): GrammarDiagnosis {
138
+ // Collect rather than print/throw. `analyzeGrammarGating` records an `opaque-artifact`
139
+ // degradation, and with no sink open `PARSEMAN_DEGRADATION=error` makes that THROW —
140
+ // a deliberate diagnostic must report that fact, not die of it. The depth/unwind pair
141
+ // (not a bare begin/end) is what keeps an analysis that throws from leaving the sink
142
+ // open for the rest of the process.
143
+ const depth = degradationCaptureDepth()
144
+ beginDegradationCapture()
145
+ let report: GatingReport
146
+ let degradations: Degradation[]
147
+ try {
148
+ report = runAnalysis(grammar, opts)
149
+ }
150
+ catch (e) {
151
+ const reason = e instanceof Error ? `${e.name}: ${e.message}` : String(e)
152
+ report = emptyReport(`gating analysis threw — ${reason}`)
153
+ }
154
+ finally {
155
+ degradations = unwindDegradationCapture(depth)
156
+ }
157
+ return assemble(report, degradations)
158
+ }
159
+
160
+ /**
161
+ * The analysis examined NOTHING: no choice was walked, and rules were skipped.
162
+ *
163
+ * This is NOT "problems were found" and must never be presented as a finding count. A
164
+ * diagnosis over a fully opaque grammar has one blocking finding per skipped rule, which
165
+ * `findings.length` then reports as "176 problems, 176 failing the check" — a sentence
166
+ * that reads as 176 discovered defects and actually means the tool inspected zero
167
+ * choices. `ok` is correctly false either way, so `ok` alone cannot tell the two apart;
168
+ * a caller that needs to distinguish "measured, and it is bad" from "could not measure"
169
+ * asks here. The CLI maps this to exit 2 (COULD NOT ANALYSE), not exit 1.
170
+ *
171
+ * `unanalysable > 0` is required, so a genuinely choice-free grammar (nothing to walk,
172
+ * nothing skipped) stays an ordinary clean pass rather than a measurement failure.
173
+ */
174
+ export function examinedNothing(d: GrammarDiagnosis): boolean {
175
+ return d.summary.totalChoices === 0 && d.summary.unanalysable > 0
176
+ }
177
+
178
+ /** Render a diagnosis for a human. The structured object stays the product of record. */
179
+ export function formatGrammarDiagnosis(d: GrammarDiagnosis): string[] {
180
+ const s = d.summary
181
+ const lines: string[] = []
182
+ lines.push(
183
+ examinedNothing(d)
184
+ // NOT a finding count. See `examinedNothing`.
185
+ ? `parseman: COULD NOT ANALYSE — 0 choice(s) examined; ${s.unanalysable} rule(s) `
186
+ + 'unreadable. No verdict about this grammar is available, good or bad.'
187
+ : d.ok
188
+ ? `parseman: grammar OK — ${s.gated}/${s.totalChoices} choice(s) gate on first char`
189
+ + `${s.recoverable > 0 ? `, ${s.recoverable} recoverable` : ''}`
190
+ + `${s.accepted > 0 ? `, ${s.accepted} accepted` : ''}`
191
+ + `${s.deferred > 0 ? `, ${s.deferred} deferred to the fusing artifact` : ''}.`
192
+ : `parseman: grammar NOT OK — ${blockingOf(d).length - s.unanalysable} blocking finding(s) over `
193
+ + `${s.totalChoices} examined choice(s).`,
194
+ )
195
+ // Unanalysable first, always: "no findings" over a grammar that was never walked is
196
+ // precisely the failure being reported, and it must not read as a clean bill of health.
197
+ if (s.unanalysable > 0 && !examinedNothing(d)) {
198
+ lines.push(
199
+ ` ${s.unanalysable} rule(s) UNANALYSABLE — THIS REPORT IS PARTIAL. `
200
+ + 'An empty finding list below does NOT mean the grammar is clean.',
201
+ )
202
+ }
203
+ for (const f of d.findings) {
204
+ lines.push(`${f.severity === 'blocking' ? '✗' : '·'} [${f.code}] ${f.id}: ${f.message}`)
205
+ for (const detail of f.details) lines.push(` ${detail.replace(/\n/g, '\n ')}`)
206
+ if (f.acceptKey !== undefined) {
207
+ lines.push(` intentional? add to the gating snapshot: { accept: ['${f.acceptKey}'] }`)
208
+ }
209
+ }
210
+ if (!d.ok && d.acceptSnapshot.length > 0) {
211
+ lines.push(` accept-all snapshot: { accept: [${d.acceptSnapshot.map(i => `'${i}'`).join(', ')}] }`)
212
+ }
213
+ return lines
214
+ }
215
+
216
+ // ── internals ──
217
+
218
+ function runAnalysis(grammar: DiagnosableGrammar, opts?: DiagnoseOptions): GatingReport {
219
+ if (isCombinator(grammar)) return analyzeGating(grammar, opts)
220
+ if (isRuleEntries(grammar)) return analyzeGatingRules(grammar, opts)
221
+ // A `rules()` map or a `compose()` result. `analyzeGrammarGating` recovers the carried
222
+ // IR of a composed grammar (whose fused map holds rule FUNCTIONS, not combinators) and
223
+ // re-asks every deferred question with the holes bound — the fuse-time analysis that
224
+ // used to run implicitly inside `compose()`.
225
+ return analyzeGrammarGating(grammar as AnalysableGrammar, opts)
226
+ }
227
+
228
+ function emptyReport(reason: string): GatingReport {
229
+ return {
230
+ totalChoices: 0, gated: 0, recoverable: 0,
231
+ unanalysable: [{ rule: '<whole grammar>', kind: 'not-a-combinator', reason }],
232
+ ungated: [], accepted: [], deferred: [], acceptedUnused: [], choices: [], antiPatterns: [],
233
+ }
234
+ }
235
+
236
+ const blockingOf = (d: GrammarDiagnosis): DiagnosisFinding[] => d.findings.filter(f => f.severity === 'blocking')
237
+
238
+ /** Sort key order — blocking before advisory, then by code, then by id. */
239
+ const SEVERITY_RANK: Record<DiagnosisSeverity, number> = { blocking: 0, advisory: 1 }
240
+ const CODE_RANK: Record<DiagnosisCode, number> = {
241
+ unanalysable: 0, 'ungated-choice': 1, 'anti-pattern': 2, degraded: 3, 'stale-accept': 4,
242
+ }
243
+
244
+ function assemble(report: GatingReport, degradations: Degradation[]): GrammarDiagnosis {
245
+ const findings: DiagnosisFinding[] = []
246
+
247
+ for (const u of report.unanalysable) {
248
+ findings.push({
249
+ id: u.rule, code: 'unanalysable', severity: 'blocking', rule: u.rule,
250
+ message: `could not be examined [${u.kind}] — no verdict about it is available`,
251
+ details: [u.reason],
252
+ })
253
+ }
254
+ for (const c of report.ungated) {
255
+ findings.push({
256
+ id: c.id, code: 'ungated-choice', severity: 'blocking', rule: c.rule,
257
+ message: `choice is UNGATED [${c.strategy}] — no first-char dispatch; `
258
+ + 'every position speculatively enters doomed arms',
259
+ details: [
260
+ ...c.anyArms.map(a => `arm[${a.index}] first-set ANY (${a.cause}): ${a.detail}\nfix: ${a.suggestion}`),
261
+ ...c.overlaps.map(o => `arm[${o.a}] ∩ arm[${o.b}] overlap on ${firstSetToString(o.on)}\nfix: ${o.suggestion}`),
262
+ ],
263
+ acceptKey: c.id,
264
+ })
265
+ }
266
+ for (const ap of report.antiPatterns) {
267
+ findings.push({
268
+ id: `${ap.rule}#arm${ap.armIndex}`, code: 'anti-pattern', severity: 'blocking', rule: ap.rule,
269
+ message: `[${ap.kind}] ${ap.message}`,
270
+ details: [],
271
+ })
272
+ }
273
+ for (const d of degradations) {
274
+ findings.push({
275
+ // `where` + `code` is the degradation channel's own identity; reuse it so the two
276
+ // surfaces name the same thing the same way.
277
+ id: `${d.code}@${d.where}`, code: 'degraded',
278
+ // Mirrors `DegradationSeverity`: 'warn' is a real cost the author can remove,
279
+ // 'info' is a fact they cannot act on (someone else's package).
280
+ severity: d.severity === 'warn' ? 'blocking' : 'advisory',
281
+ rule: d.where, message: formatDegradation(d), details: [],
282
+ })
283
+ }
284
+ for (const id of report.acceptedUnused) {
285
+ findings.push({
286
+ id, code: 'stale-accept', severity: 'advisory', rule: id,
287
+ message: 'accept entry matched no ungated choice — the grammar was fixed; prune this line',
288
+ details: [],
289
+ })
290
+ }
291
+
292
+ findings.sort((a, b) =>
293
+ SEVERITY_RANK[a.severity] - SEVERITY_RANK[b.severity]
294
+ || CODE_RANK[a.code] - CODE_RANK[b.code]
295
+ || (a.id < b.id ? -1 : a.id > b.id ? 1 : 0))
296
+
297
+ return {
298
+ schema: 'parseman.diagnosis/1',
299
+ ok: !findings.some(f => f.severity === 'blocking'),
300
+ summary: {
301
+ totalChoices: report.totalChoices,
302
+ gated: report.gated,
303
+ recoverable: report.recoverable,
304
+ ungated: report.ungated.length,
305
+ accepted: report.accepted.length,
306
+ deferred: report.deferred.length,
307
+ antiPatterns: report.antiPatterns.length,
308
+ unanalysable: report.unanalysable.length,
309
+ degraded: degradations.length,
310
+ staleAccepts: report.acceptedUnused.length,
311
+ },
312
+ findings,
313
+ acceptSnapshot: report.ungated.map(c => c.id).sort(),
314
+ gating: report,
315
+ degradations,
316
+ }
317
+ }
@@ -0,0 +1,33 @@
1
+ /**
2
+ * `parseman/diagnostics` — the library twin of the `parseman` bin.
3
+ *
4
+ * A SEPARATE ENTRY POINT, deliberately. The verified-fix loop recompiles the grammar to
5
+ * check its own rewrite, so everything here reaches the compiler. Re-exporting it from
6
+ * `parseman` would put codegen on the import graph of every consumer who only wanted to
7
+ * parse something — and parseman has just spent a release discovering what unasked-for
8
+ * bytes cost. A diagnostics surface is a development tool; it should be findable, and it
9
+ * should cost nothing to the people not using it.
10
+ *
11
+ * Everything here is also reachable from the CLI, which is the surface most people
12
+ * want:
13
+ *
14
+ * parseman diagnose examples/css/parser.ts
15
+ * parseman fix examples/css/parser.ts --corpus test/fixtures
16
+ */
17
+ export { proposeFixes, applyFixEdits } from './fix.ts'
18
+ export type {
19
+ FixReport, VerifiedFix, LocatedFinding, FixEdit, FixBenefit, FixEvidence,
20
+ FixSample, FixCode, FixEngine, ProposeFixOptions,
21
+ } from './fix.ts'
22
+
23
+ export { renderFixReport, fixReportLines } from './fix-render.ts'
24
+ export type { FixRenderOptions } from './fix-render.ts'
25
+
26
+ export { renderDiagnosis, diagnosisLines, groupDigits } from './diagnose-render.ts'
27
+ export type { DiagnoseRenderOptions } from './diagnose-render.ts'
28
+
29
+ export { measureChoiceCost, armFirstSets } from './corpus.ts'
30
+ export type { CorpusSample, CorpusSite, ChoiceCorpusCost, ArmCorpusCost } from './corpus.ts'
31
+
32
+ export { rebuildCombinator } from './rebuild.ts'
33
+ export type { RebuildResult, FrozenSubtree } from './rebuild.ts'