parseman 0.46.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 (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 +9929 -7465
  9. package/dist/analysis/diagnostics.cjs.map +3 -4
  10. package/dist/analysis/diagnostics.d.ts +2 -2
  11. package/dist/analysis/diagnostics.js +9929 -7465
  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 +10865 -8535
  24. package/dist/cli/index.cjs.map +3 -4
  25. package/dist/cli/index.js +10865 -8535
  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 +40 -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 +9224 -7203
  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 +9219 -7198
  106. package/dist/index.js.map +3 -4
  107. package/dist/language-service/index.cjs +311 -121
  108. package/dist/language-service/index.cjs.map +3 -4
  109. package/dist/language-service/index.js +311 -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 +11146 -9593
  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 +11146 -9593
  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 +2898 -1623
  131. package/dist/spec/index.cjs.map +3 -4
  132. package/dist/spec/index.js +2898 -1623
  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 +11375 -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 +11352 -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 +446 -0
  171. package/dist/table/ops.d.ts.map +1 -0
  172. package/dist/table/program.d.ts +493 -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 +25 -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 +322 -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 +221 -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 +821 -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 +2994 -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 +1766 -0
  286. package/src/table/emit.ts +418 -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 +1672 -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 +459 -0
  295. package/src/table/program.ts +855 -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,244 @@
1
+ import type { Combinator, CompiledParser, ParseContext, ParseError, ParseResult } from '../types.ts'
2
+ import type { HostMode } from '../cst/host-mode.ts'
3
+ import { createParseContext } from '../parse-context.ts'
4
+ import { encodeTableProgram, type TableSettings } from './encode.ts'
5
+ import { emitTableModule, emitTableExpression } from './emit.ts'
6
+ import { tableRules } from './assemble.ts'
7
+ import { closureArtifact } from './program.ts'
8
+ import { buildGrammarPlan } from '../compiler/grammar-coverage-ids.ts'
9
+ import { runDuplicationDiagnostic, type DuplicationOption } from './duplication-hook.ts'
10
+ import { beginCompileDegradationDrain } from '../compiler/degradation.ts'
11
+
12
+ /**
13
+ * `compile()` FOR THE TABLE LOWERING — same contract, different artifact.
14
+ *
15
+ * The entry rule is named rather than positional because the table indexes rules
16
+ * by name. A root combinator is simply a one-rule map: `{ Entry: root }` encodes
17
+ * and round-trips value-identically to the interpreter, which is what makes this
18
+ * a drop-in for the source-lowering `compile()` rather than a second API.
19
+ */
20
+ const ENTRY = 'Entry'
21
+
22
+ /**
23
+ * THE LAST RESORT FOR A REDUCER'S SOURCE — the function's own text.
24
+ *
25
+ * Third in precedence, behind the encoder's captured `fnSrc`/`buildSrc`/`predSrc`
26
+ * and behind a positional list. It exists because `compile` is the library
27
+ * `compile()` (`src/index.ts`), and a grammar BUILT AT RUNTIME has no captured
28
+ * sources at all — every `examples/*` fixture is one. Refusing them outright would
29
+ * make `.source` and `.inlineExpression` empty for every library caller, which
30
+ * `test/unit/table-compile.test.ts` pins against directly.
31
+ *
32
+ * WHAT IT IS NOT is a licence to print a placeholder. `() => {}` is only ever
33
+ * produced here if the author literally wrote it, so a reducer that silently
34
+ * returns nothing is not reachable through this path. A closure over its module's
35
+ * privates prints text that throws a ReferenceError when the module loads — LOUD,
36
+ * at load, naming the binding — which is the failure mode this project prefers
37
+ * over an artifact that parses and returns the wrong tree.
38
+ *
39
+ * Native and bound functions have no recoverable body (`[native code]`), so they
40
+ * return null and the caller refuses BY NAME.
41
+ */
42
+ function ownSource(fn: unknown): string | null {
43
+ if (typeof fn !== 'function') return null
44
+ const text = Function.prototype.toString.call(fn)
45
+ if (!text.includes('[native code]')) return text
46
+ // A BUILT-IN GLOBAL USED AS A REDUCER — `leaf(regex(…), parseFloat)` is exactly
47
+ // that, and `examples/graphql/parser.ts` writes it. It has no body to print, but
48
+ // its NAME is a global binding that denotes the identical function in any module,
49
+ // so the name IS the faithful source. Verified by identity against `globalThis`,
50
+ // never by name alone: `Number.parseFloat` and a shadowed local both fail that.
51
+ const name = (fn as { name?: unknown }).name
52
+ return typeof name === 'string' && name.length > 0
53
+ && (globalThis as unknown as Record<string, unknown>)[name] === fn
54
+ ? name
55
+ : null
56
+ }
57
+
58
+ /**
59
+ * THE CONTRACT DIVERGENCE, stated rather than hidden.
60
+ *
61
+ * `CompiledParser.inlineExpression` is documented as a self-contained IIFE with
62
+ * "no external references". A table artifact cannot honour that clause and stay
63
+ * a table: the shared driver being EXTERNAL is the entire reason the artifact is
64
+ * 0.56 MB instead of source lowering's 2.10 MB. Inlining the driver per grammar
65
+ * reproduces exactly the size this lowering exists to avoid.
66
+ *
67
+ * So the expression references `tableRules`, and the consumer is responsible for
68
+ * having imported it. That is a real change to what an inliner must do, and it
69
+ * is the reason this is a lowering swap rather than a rename.
70
+ *
71
+ * It is NOT null. Returning null would be the quiet failure: `plugin/index.ts`
72
+ * warns and SKIPS on null, leaving the grammar interpreted — 79 ms against 14 ms
73
+ * on `benchmark.less`, a ~5x regression that no test would have reported.
74
+ */
75
+ export type TableCompileOptions = {
76
+ readonly hostMode?: HostMode
77
+ readonly trackLines?: boolean
78
+ /**
79
+ * ACCEPTED AND IGNORED — recovery is ALWAYS lowered (see `TableSettings.recovery`).
80
+ * Kept so this stays a drop-in for `compile(g, …, { recovery: true })`.
81
+ */
82
+ readonly recovery?: boolean
83
+ /**
84
+ * GRAMMAR-COVERAGE COUNTERS, lowered as `OP_COV` rows plus the definition pool
85
+ * (`TableProgram.cov`). Same ids as the source lowering, because both mint them
86
+ * from the same `buildGrammarPlan` walk over the same combinator graph.
87
+ *
88
+ * COUNTERS ONLY — owner ruling. `_grammarTrace`'s six phases are emitted at
89
+ * ~40 fine-grained sites by codegen and are deferred to 0.48
90
+ * (`notes/RELEASE-0.48-TARGET.md` §1). A table entry rejects a context carrying
91
+ * a trace sink before parsing: silently returning an empty trace would look
92
+ * exactly like a real trace whose grammar sites were never visited. The
93
+ * plugin's coverage path reads `_grammarCoverage` and the definitions stamp,
94
+ * neither of which is trace.
95
+ *
96
+ * The remaining options below are accepted for signature compatibility with
97
+ * `compile()`. Any that the table lowering cannot honour THROWS rather than
98
+ * being ignored — a compile that silently drops the instrumentation its caller
99
+ * asked for is the failure class this project keeps finding, and coverage that
100
+ * silently reports nothing is worse than a build error that says why.
101
+ */
102
+ readonly coverage?: boolean
103
+ readonly duplication?: DuplicationOption
104
+ // NO `maxInline`. It bounded how large ONE emitted function could grow before the
105
+ // source lowering stopped inlining; a table has no emitted function to bound, so the
106
+ // option had nothing to read it and a caller setting it got silence.
107
+ /** Identifier the emitted expression expects to find `tableRules` under. */
108
+ readonly runtimeRef?: string
109
+ /** Source text per `prog.fns` entry, for a module/expression that must be printable. */
110
+ readonly fnSources?: readonly string[]
111
+ }
112
+
113
+ export function compile<T>(
114
+ combinator: Combinator<T>,
115
+ mapFnSources?: readonly string[],
116
+ opts: TableCompileOptions = {},
117
+ ): CompiledParser<T> {
118
+ // Argument order mirrors `compile(combinator, mapFnSources?, opts?)` so this is
119
+ // a drop-in, not a second API with a different shape.
120
+ // THE SAME WALK CODEGEN USES FOR A SINGLE ROOT (`codegen.ts:5377`): no winner
121
+ // map, so the paths are rooted at `entry` and the ids are the ones a caller
122
+ // switching lowerings already has. Building it differently here would produce a
123
+ // set that is internally consistent and lines up with nothing.
124
+ runDuplicationDiagnostic(combinator, opts.duplication)
125
+ // ONE AGGREGATED BLOCK per compile, as the source lowering drained it. Without this
126
+ // every degradation prints the instant it is recorded, so a grammar with twenty
127
+ // unconfirmable reducers produces twenty walls of text instead of the capped, counted
128
+ // summary — and `PARSEMAN_DEGRADATION=error` stops at the first instead of listing all.
129
+ const drain = beginCompileDegradationDrain()
130
+ let compiled = false
131
+ try {
132
+ const out = compileImpl(combinator, mapFnSources, opts)
133
+ compiled = true
134
+ return out
135
+ } finally { drain(compiled) }
136
+ }
137
+
138
+ function compileImpl<T>(
139
+ combinator: Combinator<T>,
140
+ mapFnSources?: readonly string[],
141
+ opts: TableCompileOptions = {},
142
+ ): CompiledParser<T> {
143
+ const plan = opts.coverage === true ? buildGrammarPlan(combinator as Combinator<unknown>) : undefined
144
+ const settings: TableSettings = {
145
+ ...(opts.hostMode === undefined ? {} : { hostMode: opts.hostMode }),
146
+ ...(opts.trackLines === undefined ? {} : { trackLines: opts.trackLines }),
147
+ ...(plan === undefined ? {} : { coverage: plan }),
148
+ }
149
+ // `encodeTableProgram`, NOT `encodeTable`. The plain `encodeTable` drops the
150
+ // `fnSrcs` side-channel on the floor, and dropping it is what made a printed
151
+ // module return NO VALUE: `emitTable*` falls back to `prog.fns.map(() => '() => {}')`
152
+ // (emit.ts:178/219/252), so every author reducer — `node` build fns, `transform`,
153
+ // `leaf` — became an empty arrow. The grammar still parsed, still returned `ok`,
154
+ // and returned `undefined` where the interpreter and codegen both returned a tree.
155
+ // `compileRuleMap` has guarded this since it was written (compile-rule-map.ts);
156
+ // this is the same guard on the single-root entry point.
157
+ const { prog, fnSrcs } = encodeTableProgram({ [ENTRY]: combinator as Combinator<unknown> }, settings)
158
+ /**
159
+ * `compile()` and the macro must hand the driver the SAME artifact shape.
160
+ *
161
+ * An absent `asm` previously selected runtime `new Function` emission while
162
+ * macro output printed `a:[]` and selected closure assembly. That made one
163
+ * grammar have two normal execution paths. The compact table artifact is now
164
+ * canonical: both paths carry an explicit empty inventory and use the shared
165
+ * closure assembler. Static factories remain an opt-in experiment until they
166
+ * can satisfy both semantic identity and the size budget.
167
+ */
168
+ const artifact = closureArtifact(prog)
169
+ const entry = tableRules(artifact)[ENTRY]!
170
+
171
+ // Captured-first, supplied-as-fill-in. The encoder records a source per author
172
+ // callback from the def (`fnSrc` / `buildSrc` / `predSrc` / `gateSrcs`), which the
173
+ // macro evaluator sets; the out-of-band list is the escape for a combinator built
174
+ // at runtime, and it only fills HOLES.
175
+ //
176
+ // The list is POSITIONAL, in `prog.fns` order, so it is only usable when it indexes
177
+ // THIS pool — a different length means it came from a different walk (the evaluator
178
+ // collects one entry per callback it SEES; the encoder pools one per callback it
179
+ // EMITS, and those need not coincide) and applying it would misassign reducers
180
+ // silently. A mismatched list is therefore not consulted at all; if that leaves a
181
+ // hole, the refusal below fires and says so, which is the safe direction.
182
+ const offered = opts.fnSources ?? mapFnSources
183
+ const supplied = offered !== undefined && offered.length === fnSrcs.length ? offered : undefined
184
+ const sources = fnSrcs.map((s, i) => s ?? supplied?.[i] ?? ownSource(prog.fns[i]))
185
+
186
+ // `runtimeOnly` reasons mean the program RUNS but cannot be printed. The parse
187
+ // functions below are unaffected; only `source` / `inlineExpression` are, and
188
+ // they degrade to null rather than throwing out of the printer at a call site
189
+ // that only wanted to parse.
190
+ //
191
+ // AN UNSOURCED REDUCER IS ONE OF THOSE REASONS. It is not a licence to print a
192
+ // placeholder: a module that loads, parses, and returns the wrong tree is worse
193
+ // than one that refuses, because nothing reports it. REFUSE, with a name.
194
+ const unsourced = sources.reduce<number[]>((acc, s, i) => (s === null ? [...acc, i] : acc), [])
195
+ const runtimeOnly = [
196
+ ...(prog.runtimeOnly ?? []),
197
+ ...(unsourced.length === 0
198
+ ? []
199
+ : [`author reducer source missing for prog.fns[${unsourced.join(', ')}] — printing would emit \`() => {}\` and the parse would return no value`]),
200
+ ]
201
+ const printable = runtimeOnly.length === 0
202
+ const emitOpts = { fnSources: sources as string[] }
203
+ const source = printable ? emitTableModule(artifact, { name: 'grammar', ...emitOpts }) : ''
204
+ const inlineExpression = printable
205
+ ? emitTableExpression(artifact, { entry: ENTRY, runtimeRef: opts.runtimeRef ?? 'tableRules', ...emitOpts })
206
+ : null
207
+
208
+ const runOnce = (input: string, pos: number, ctx: ParseContext): ParseResult<T> =>
209
+ entry(input, pos, ctx) as ParseResult<T>
210
+
211
+ return {
212
+ source,
213
+ inlineExpression,
214
+ ...(printable ? {} : { runtimeOnly }),
215
+ // The DENOMINATOR, handed back rather than scraped out of the emitted text.
216
+ // `plugin/index.ts` prefers a compiler-supplied `coverageDefinitions` and only
217
+ // falls back to regex-scanning generated hooks; a table has no hooks to scan,
218
+ // so this is the channel.
219
+ ...(plan === undefined ? {} : { coverageDefinitions: plan.definitions }),
220
+ parse(input: string, pos = 0): ParseResult<T> {
221
+ return runOnce(input, pos, createParseContext())
222
+ },
223
+ parseWithContext(input: string, parseCtx: ParseContext, pos = 0): ParseResult<T> {
224
+ return runOnce(input, pos, parseCtx)
225
+ },
226
+ /**
227
+ * NO LONGER REFUSES. It used to throw unless the caller passed
228
+ * `{ recovery: true }`, because setting `_tolerant` on a table with no
229
+ * recovery rows would report a clean parse with an empty error list whatever
230
+ * the input — refusing was the right answer to that, and OPTIONAL RECOVERY
231
+ * was the wrong question. `compile()` requires no such flag, so the refusal
232
+ * was a `CompiledParser` contract break; recovery is now always lowered and
233
+ * there is nothing left to refuse.
234
+ */
235
+ parseWithErrors(input: string, pos = 0): ParseResult<T> & { errors: ParseError[] } {
236
+ const ctx = createParseContext()
237
+ const errors: ParseError[] = []
238
+ ctx._errors = errors
239
+ ctx._tolerant = true
240
+ const r = runOnce(input, pos, ctx)
241
+ return { ...r, errors } as ParseResult<T> & { errors: ParseError[] }
242
+ },
243
+ }
244
+ }
@@ -0,0 +1,89 @@
1
+ import type { Combinator } from '../types.ts'
2
+ import { analyzeDuplication, analyzeDuplicationRules, formatDuplicationFindings, duplicationFindingCount, type DuplicationReport, type DuplicationWarnLevel } from '../analysis/duplication.ts'
3
+
4
+ /**
5
+ * THE DUPLICATION DIAGNOSTIC'S LOWERING HOOK.
6
+ *
7
+ * This ran inside the source lowering, so deleting that lowering would have silently
8
+ * taken it with it — the analysis in `analysis/duplication.ts` would survive and
9
+ * nothing would call it, which reads exactly like a clean grammar. It is not a
10
+ * property of how a grammar is lowered, so it lives with the lowering that remains.
11
+ */
12
+ /**
13
+ * The `duplication` compile option. A bare level is shorthand for `{ level }`; the
14
+ * object form adds the `accept` allowlist (finding ids acknowledged as intentional)
15
+ * and the ranking knobs.
16
+ *
17
+ * DEFAULT IS `'off'`, unlike gating. An ungated hot choice is a cliff with no other
18
+ * symptom; a duplicated subtree is a maintenance cost the author may have chosen.
19
+ * More to the point, most findings here are CANDIDATES that need an AST check
20
+ * before they are applied — a diagnostic that prints "candidate, verify" on every
21
+ * build teaches people to stop reading it. Run it deliberately (a lint script, a
22
+ * review pass, `PARSEMAN_DUPLICATION=warn`), not on every compile.
23
+ */
24
+ export type DuplicationOption =
25
+ | DuplicationWarnLevel
26
+ | { level?: DuplicationWarnLevel; accept?: Iterable<string>; minSize?: number; maxFindings?: number; entryName?: string }
27
+
28
+ function resolveDuplicationLevel(opt: DuplicationOption | undefined): DuplicationWarnLevel {
29
+ const explicit = typeof opt === 'string' ? opt : opt?.level
30
+ if (explicit !== undefined) return explicit
31
+ const env = typeof process !== 'undefined' ? (process.env?.PARSEMAN_DUPLICATION as DuplicationWarnLevel | undefined) : undefined
32
+ if (env === 'off' || env === 'warn' || env === 'error') return env
33
+ return 'off'
34
+ }
35
+
36
+ /**
37
+ * Run the duplication diagnostic and surface it per the resolved level. Never
38
+ * throws from the analysis itself — only `'error'` deliberately throws on a real
39
+ * finding. Shared by ALL THREE lowering paths (`compile`, `compileRuleMap`,
40
+ * `compileLinkable`): the macro build never calls `compile()`, and a diagnostic
41
+ * wired only there is a diagnostic that reports zero findings forever — which is
42
+ * exactly what happened to the gating diagnostic for two minor versions.
43
+ */
44
+ function reportDuplication(
45
+ opt: DuplicationOption | undefined,
46
+ analyze: (o: { accept?: Iterable<string>; minSize?: number; maxFindings?: number; entryName?: string } | undefined) => DuplicationReport,
47
+ ): DuplicationReport | undefined {
48
+ const level = resolveDuplicationLevel(opt)
49
+ if (level === 'off') return undefined
50
+ const obj = opt !== null && typeof opt === 'object' ? opt : undefined
51
+ const analyzeOpts = obj === undefined ? undefined : {
52
+ ...(obj.accept !== undefined ? { accept: obj.accept } : {}),
53
+ ...(obj.minSize !== undefined ? { minSize: obj.minSize } : {}),
54
+ ...(obj.maxFindings !== undefined ? { maxFindings: obj.maxFindings } : {}),
55
+ ...(obj.entryName !== undefined ? { entryName: obj.entryName } : {}),
56
+ }
57
+ let report: DuplicationReport
58
+ try { report = analyze(analyzeOpts) }
59
+ catch (err) {
60
+ // The analysis is ADVISORY — it must never break a compile that is otherwise
61
+ // correct, so this does not rethrow even at `'error'`. But it must not be
62
+ // SILENT either: `assertAnalyzable` throws a deliberately actionable TypeError
63
+ // when handed a composed (already-fused) map, and swallowing that reported the
64
+ // same "no findings" as a genuinely clean grammar. That is the exact failure
65
+ // the gating diagnostic shipped for two minor versions.
66
+ console.warn(
67
+ `parseman: the duplication diagnostic could not run, so NOTHING was checked — `
68
+ + `this is not a clean result.\n ${err instanceof Error ? err.message : String(err)}`,
69
+ )
70
+ return undefined
71
+ }
72
+ const lines = formatDuplicationFindings(report)
73
+ if (lines.length > 0) {
74
+ if (level === 'error') throw new Error(`parseman: ${duplicationFindingCount(report)} duplication/overlap finding(s)\n${lines.join('\n')}`)
75
+ for (const l of lines) console.warn(l)
76
+ }
77
+ return report
78
+ }
79
+
80
+ export function runDuplicationDiagnostic<T>(combinator: Combinator<T>, opt: DuplicationOption | undefined): DuplicationReport | undefined {
81
+ return reportDuplication(opt, o => analyzeDuplication(combinator as Combinator<unknown>, o))
82
+ }
83
+
84
+ export function runDuplicationDiagnosticRules(
85
+ ruleMap: ReadonlyArray<readonly [string, Combinator<unknown>]>,
86
+ opt: DuplicationOption | undefined,
87
+ ): DuplicationReport | undefined {
88
+ return reportDuplication(opt, o => analyzeDuplicationRules(ruleMap, o))
89
+ }