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,42 @@
1
+ /**
2
+ * THE PROTOCOL THE THREE ENGINES SHARE — the failure sentinel and the
3
+ * end-position out-parameter.
4
+ *
5
+ * `exec.ts`, `assemble.ts` and `emit-assembly.ts` each ran a private copy of
6
+ * both: three `Symbol('pm.fail')` calls and three `let END = 0` closure slots.
7
+ * That was sound while an assembly picked ONE engine and ran it for the whole
8
+ * parse, which is what every shipped configuration does.
9
+ *
10
+ * It stops being sound the moment two engines are live in one parse. A piece
11
+ * from engine A returning A's `FAIL` to a caller in engine B is a VALUE to B —
12
+ * `!== FAIL` is true — so a failed match reads as a successful one carrying a
13
+ * symbol, and the parse continues from a position nobody wrote. Neither a type
14
+ * nor a test would catch it: the sentinels have the same description, and the
15
+ * three-way identity sweep only ever exercised one engine per process.
16
+ *
17
+ * So both halves move here:
18
+ *
19
+ * - `FAIL` is ONE symbol, module-level, imported by every engine. There is no
20
+ * per-assembly variation possible in a sentinel, so there is no reason for it
21
+ * to be per-assembly state.
22
+ * - `EndCell` is per-ASSEMBLY, not per-module. Two assemblies for two grammars
23
+ * may be live in one process and must not share a slot; the engines *within*
24
+ * one assembly must. `assemble()` mints one and hands it to whichever engines
25
+ * that assembly builds.
26
+ *
27
+ * A one-property object rather than an `Int32Array`: the store is monomorphic
28
+ * either way, and `e` reads in emitted text as `EC.e`, which is the same shape
29
+ * the closure engine writes.
30
+ */
31
+
32
+ /** The failure sentinel. ONE per process, shared by every engine. */
33
+ export const FAIL: unique symbol = Symbol('pm.fail')
34
+ export type Fail = typeof FAIL
35
+
36
+ /**
37
+ * The end-position out-parameter — `_pfEnd` in emitted text, `END` in the
38
+ * closure walk and the driver. One per assembly.
39
+ */
40
+ export type EndCell = { e: number }
41
+
42
+ export const newEndCell = (): EndCell => ({ e: 0 })
@@ -0,0 +1,113 @@
1
+ import {
2
+ OP_ADJ, OP_ARMGATE, OP_ATTEMPT, OP_CHOICE, OP_COV, OP_DISPATCH, OP_EMPTY, OP_EXPECT,
3
+ OP_FIELD, OP_GATE, OP_GREEDY, OP_GUARD, OP_LABEL, OP_LEAF, OP_LIT, OP_LIT_CI,
4
+ OP_LIT_CI_TRACK, OP_LIT_TRACK, OP_LIVE, OP_NODE, OP_NODE_TRACK, OP_NOT, OP_OPT,
5
+ OP_PEEK, OP_REJECT, OP_REP, OP_REPV, OP_ROUTED, OP_RULE, OP_RX, OP_RX_TRACK, OP_SCAN,
6
+ OP_SCOPE, OP_SCOPE_CAP, OP_SCOPE_PLAIN, OP_SEQ, OP_SEQV, OP_SEQX, OP_TOKEN, OP_WITHCTX, OP_XFORM,
7
+ } from './ops.ts'
8
+
9
+ /**
10
+ * THE EDGE TABLE. Which operand slots of an instruction are CHILD instructions.
11
+ *
12
+ * This is one fact about the operand layout, and it was written twice: once in
13
+ * `site-labels.ts` over the 35 opcodes `emit-assembly.ts` lowers, once fused into
14
+ * `inspect.ts`'s `reachableIps` traversal over all 40. The two agreed slot-for-slot
15
+ * on every opcode both covered — which is the problem, not the reassurance. Adding
16
+ * an opcode meant editing two switches in two files, and NOTHING failed if you
17
+ * edited one. `site-labels.ts`'s own header names that failure mode, then guards
18
+ * only against it happening WITHIN that file.
19
+ *
20
+ * The subset was never load-bearing. The five opcodes it omitted — COV, GREEDY,
21
+ * REJECT, ARMGATE, WITHCTX — are exactly the five `emit-assembly.ts` has no case
22
+ * for, so any program containing one throws `Unemittable` from the default arm and
23
+ * the whole assembly falls back to closures. Walking into their subtrees can
24
+ * therefore only affect a result that is about to be discarded. (Verified: none of
25
+ * the five is so much as imported by `emit-assembly.ts`. Its one consumer of these
26
+ * edges, the `fd`/`er` sink census, is computed before the lowering that throws.)
27
+ *
28
+ * `code` is `ArrayLike<number>` because the two callers hold different
29
+ * representations of the same stream — `TableProgram.code` is a `readonly number[]`,
30
+ * `ResolvedTable.code` is an `Int32Array`.
31
+ *
32
+ * Returns false for an opcode this table does not know, so each caller keeps its own
33
+ * answer to that: `inspect.ts` throws (a program it cannot decode is a bug it must
34
+ * report), `site-labels.ts` ignores it (an unwalked site resolves to `TOP` via
35
+ * `labelAt`, and the enclosing assembly is unemittable anyway).
36
+ */
37
+ export function childSlots(code: ArrayLike<number>, ip: number, out: number[]): boolean {
38
+ switch (code[ip]) {
39
+ // ZERO-WIDTH / TERMINAL ROWS — no successor. `OP_GUARD` and `OP_ADJ` are both
40
+ // childless tests. `OP_LIVE` holds a hand-written combinator in `prog.fns`; its
41
+ // structure is a closure, so there is no successor row to reach.
42
+ case OP_LIT: case OP_RX: case OP_LIT_TRACK: case OP_RX_TRACK: case OP_EMPTY:
43
+ case OP_SCAN: case OP_LIT_CI: case OP_LIT_CI_TRACK:
44
+ case OP_GUARD: case OP_ADJ: case OP_LIVE:
45
+ return true
46
+
47
+ // Single child at `ip + 1`. `OP_COV` is a wrapper on the same footing as the
48
+ // rest — a coverage-encoded program is a program, and omitting it made
49
+ // `opHistogram` throw on every table the coverage build produces.
50
+ case OP_RULE: case OP_OPT: case OP_NOT: case OP_PEEK: case OP_EXPECT:
51
+ case OP_TOKEN: case OP_ATTEMPT: case OP_LABEL: case OP_COV: case OP_REJECT:
52
+ out.push(code[ip + 1]!)
53
+ return true
54
+
55
+ // Single child at `ip + 2`.
56
+ case OP_GATE: case OP_SCOPE: case OP_SCOPE_CAP: case OP_SCOPE_PLAIN: case OP_WITHCTX: case OP_XFORM:
57
+ case OP_NODE: case OP_NODE_TRACK: case OP_FIELD: case OP_LEAF: case OP_ARMGATE:
58
+ out.push(code[ip + 2]!)
59
+ return true
60
+
61
+ // `ROUTED fallback` — the operand is an offset or −1. The routed token itself is
62
+ // data the enclosing `dispatch()` already consumed, so the only edge is the
63
+ // fallback.
64
+ case OP_ROUTED:
65
+ if (code[ip + 1]! >= 0) out.push(code[ip + 1]!)
66
+ return true
67
+
68
+ // `DISPATCH sel d other otherRouted n a1 … an` — the SELECTOR is a child on
69
+ // exactly the same footing as the arms: it runs inside whatever node and scope
70
+ // this site sits in.
71
+ case OP_DISPATCH: {
72
+ out.push(code[ip + 1]!)
73
+ if (code[ip + 3]! >= 0) out.push(code[ip + 3]!)
74
+ const n = code[ip + 5]!
75
+ for (let i = 0; i < n; i++) out.push(code[ip + 6 + i]!)
76
+ return true
77
+ }
78
+
79
+ case OP_SEQ: case OP_SEQV: {
80
+ const n = code[ip + 1]!
81
+ for (let i = 0; i < n; i++) out.push(code[ip + 2 + i]!)
82
+ return true
83
+ }
84
+
85
+ case OP_SEQX: {
86
+ const n = code[ip + 2]!
87
+ for (let i = 0; i < n; i++) out.push(code[ip + 3 + i]!)
88
+ return true
89
+ }
90
+
91
+ case OP_CHOICE: {
92
+ const n = code[ip + 2]!
93
+ for (let i = 0; i < n; i++) out.push(code[ip + 4 + i]!)
94
+ return true
95
+ }
96
+
97
+ // Arms are stride-2 from `ip + 4` — the gate word sits beside each arm offset.
98
+ case OP_GREEDY: {
99
+ out.push(code[ip + 1]!)
100
+ const n = code[ip + 2]!
101
+ for (let i = 0; i < n; i++) out.push(code[ip + 4 + 2 * i]!)
102
+ return true
103
+ }
104
+
105
+ case OP_REP: case OP_REPV:
106
+ out.push(code[ip + 1]!)
107
+ if (code[ip + 4]! >= 0) out.push(code[ip + 4]!)
108
+ return true
109
+
110
+ default:
111
+ return false
112
+ }
113
+ }
@@ -0,0 +1,336 @@
1
+ import type { Combinator } from '../types.ts'
2
+ import { hasDirectBuildDef } from '../analysis/commitment.ts'
3
+ import type { HostMode } from '../cst/host-mode.ts'
4
+ import { collectGrammarReflection, type GrammarReflection } from '../cst/reflection.ts'
5
+ import { encodeTableProgram, type TableSettings } from './encode.ts'
6
+ import { emitTableExpression } from './emit.ts'
7
+ import { tableRules } from './assemble.ts'
8
+ import { closureArtifact } from './program.ts'
9
+ import type { TableProgram, TableRule } from './program.ts'
10
+ import { buildGrammarPlan, type GrammarCoverageDefinition } from '../compiler/grammar-coverage-ids.ts'
11
+ import { runDuplicationDiagnosticRules, type DuplicationOption } from './duplication-hook.ts'
12
+
13
+ /**
14
+ * `compileRuleMap()` FOR THE TABLE LOWERING — the counterpart that did not exist.
15
+ *
16
+ * `compile` covered `compile()`, the SINGLE-ROOT entry point, which the
17
+ * plugin uses only for standalone combinators (`plugin/index.ts:1693`, `:1870`).
18
+ * The main path — every `rules()` grammar — goes through `compileRuleMap`, and
19
+ * with no table counterpart there was nothing for the macro build to point at.
20
+ * This is that function, and it is the EASY direction: `encodeTable` takes a
21
+ * named rule map natively, so nothing here adapts a shape. `compile` is the
22
+ * one that adapts (it wraps a root as `{ Entry: root }`).
23
+ *
24
+ * ── THE CONTRACT IT MATCHES ──────────────────────────────────────────────────
25
+ * `compileRuleMap` returns ONE `replacement` string: a self-contained expression
26
+ * that the plugin splices over the whole `rules(factory)` call-expression
27
+ * (`plugin/index.ts:1753`, and as an initialiser at `:1941`), evaluating ONCE to
28
+ * the `{ name: fn, … }` map. `keys` is the entry list, for the caller to check
29
+ * against the source's own keys. `hostMode` / `hostBranchElided` are what the
30
+ * macro stamps the emitted map with. `reflection` is per-grammar CST node
31
+ * reflection. It returns `null`, all-or-nothing, when the map cannot be inlined.
32
+ *
33
+ * Every one of those is reproduced here, with ONE stated divergence, the same
34
+ * one `compile` documents: the expression references `tableRules` and is
35
+ * therefore NOT self-contained. That reference is the entire reason a table
36
+ * artifact is small; inlining the driver per grammar rebuilds exactly the size
37
+ * this lowering exists to remove. The consumer owns the import.
38
+ *
39
+ * ── WHY `emitTableModule` WAS NOT ENOUGH ─────────────────────────────────────
40
+ * It emits a whole rule map as one module — `import` line, `export const` — and
41
+ * the plugin has no module to write; it has an expression slot inside an
42
+ * existing file. `emitTableExpression` was the right emitter and needed one
43
+ * thing: `entry: null`, meaning "the map itself" rather than one rule out of it.
44
+ * That is a two-line change to an existing emitter, not a new one.
45
+ */
46
+ export type TableRuleMapOptions = {
47
+ /** Grammar-level ambient trivia, as `rules({ trivia }, …)` declares it. */
48
+ readonly trivia?: Combinator<unknown>
49
+ /** Grammar-level ambient scan-skip, as `rules({ scanSkip }, …)` declares it. */
50
+ readonly scanSkip?: Combinator<unknown>[]
51
+ readonly recovery?: boolean
52
+ readonly hostMode?: HostMode
53
+ readonly trackLines?: boolean
54
+ /**
55
+ * GRAMMAR-COVERAGE COUNTERS. This is the MACRO's path — `plugin/index.ts` passes
56
+ * `coverage: grammarCoverage` here for every `rules()` grammar — so this is what
57
+ * makes `{ grammarCoverage: true }` a working build under the table lowering.
58
+ *
59
+ * COUNTERS ONLY, per the owner ruling recorded in `notes/RELEASE-0.48-TARGET.md`
60
+ * §1; `_grammarTrace` parity is 0.48 work. The plugin's coverage path needs the
61
+ * DEFINITIONS STAMP and the HITS, and both are here.
62
+ */
63
+ readonly coverage?: boolean
64
+ /** Identifier the emitted expression expects `tableRules` to be bound to. */
65
+ readonly runtimeRef?: string
66
+ /**
67
+ * Reducer sources in `prog.fns` order, for a caller that holds them OUT OF
68
+ * BAND — the same escape `compile(combinator, mapFnSources?)` provides.
69
+ *
70
+ * The encoder captures a source per callback from the def (`fnSrc` /
71
+ * `buildSrc` / `predSrc` / `gateSrcs`), which the macro evaluator sets and a
72
+ * runtime-built combinator does not have. Supplying them here is what lets a
73
+ * grammar constructed at runtime still PRINT; it does not override captured
74
+ * ones, it fills in for a pool that has none.
75
+ */
76
+ readonly fnSources?: readonly string[]
77
+ /**
78
+ * WHY THIS REFUSED, filled in on the `null` return.
79
+ *
80
+ * `null` means "leave this grammar interpreted", which is ~79 ms against ~14 ms
81
+ * on `benchmark.less` — a ~5x regression that produces no test failure and no
82
+ * warning content beyond "couldn't be inlined". The reasons exist inside the
83
+ * encoder (`prog.runtimeOnly`) and simply had no way out; this is that way out,
84
+ * so the plugin's warning can name the construct instead of the outcome.
85
+ *
86
+ * An out-parameter rather than a changed return type: every existing caller
87
+ * checks for `null` and that contract is unchanged.
88
+ */
89
+ readonly refusals?: string[]
90
+ /**
91
+ * The structural duplication diagnostic. Ran inside the source lowering; it runs
92
+ * here for the same reason, so a build that asked for it keeps getting it.
93
+ */
94
+ readonly duplication?: DuplicationOption
95
+ }
96
+
97
+ export type CompiledRuleMapTable = {
98
+ /** Every entry this saw, in order — same use as `compileRuleMap`'s `keys`. */
99
+ keys: string[]
100
+ /** The expression that replaces the whole `rules(factory)` call. */
101
+ replacement: string
102
+ /** Re-emit the same table call with construction-time artifact metadata. */
103
+ replacementWithMetadata(metadataSource: string): string
104
+ hostMode: HostMode
105
+ hostBranchElided: boolean
106
+ reflection: GrammarReflection
107
+ /**
108
+ * The RUNNABLE map, which the source lowering has no counterpart for — it
109
+ * hands back text and nothing else, because its artifact only exists once the
110
+ * emitted source is evaluated. A table exists as data before it is printed, so
111
+ * the same call that produces the replacement can also hand back the parser.
112
+ * That is what makes a differential against the interpreter possible without
113
+ * `eval`, and it is why the table-vs-interpreter test can compare `expected`
114
+ * sets rather than only accept/reject.
115
+ */
116
+ rules: Record<string, TableRule>
117
+ /** The encoded program, for a caller that wants to fold or inspect it. */
118
+ prog: TableProgram
119
+ /**
120
+ * The coverage DENOMINATOR — every id this map can hit — present only under
121
+ * `{ coverage: true }`. Same field and same meaning as `compileRuleMap`'s, so
122
+ * the plugin's stamp site needs no branch on which lowering produced it.
123
+ */
124
+ coverageDefinitions?: readonly GrammarCoverageDefinition[]
125
+ }
126
+
127
+ /**
128
+ * The host mode this map was lowered for, resolved the way `compileRuleMap`
129
+ * resolves it: an explicit option, else a `grammarHostMode` stamp left on the
130
+ * rules by `rules({ hostMode }, …)`. The stamp is the only channel a macro build
131
+ * has, since it passes no compile options.
132
+ */
133
+ function resolveHostMode(
134
+ ruleMap: ReadonlyArray<readonly [string, Combinator<unknown>]>,
135
+ explicit: HostMode | undefined,
136
+ ): HostMode {
137
+ return explicit
138
+ ?? ruleMap.map(([, r]) => r._meta.grammarHostMode).find(Boolean)
139
+ ?? 'ast'
140
+ }
141
+
142
+ function resolveTrackLines(
143
+ ruleMap: ReadonlyArray<readonly [string, Combinator<unknown>]>,
144
+ explicit: boolean | undefined,
145
+ ): boolean {
146
+ return explicit === true || ruleMap.some(([, r]) => r._meta.grammarTrackLines === true)
147
+ }
148
+
149
+ /**
150
+ * Apply grammar-level trivia / scan-skip passed as OPTIONS to ENCODING-LOCAL
151
+ * rule wrappers.
152
+ *
153
+ * `rules({ trivia }, …)` stamps `_meta.grammarTrivia` on every non-trivia rule
154
+ * (combinators/parser.ts:203) and `encodeTable` reads it from there, per rule.
155
+ * A caller that instead threads the declaration as an option — which the macro
156
+ * does, because it re-evaluates the factory itself — must land it in the same
157
+ * place or the whole grammar encodes with NO ambient trivia and every
158
+ * whitespace-bearing input fails identically in every engine, which makes an
159
+ * identity check agree while proving nothing.
160
+ *
161
+ * Only fills a GAP: a rule that already carries its own stamp keeps it, so a
162
+ * `composeLeaf` map whose pieces legitimately disagree is not flattened.
163
+ *
164
+ * The wrappers are load-bearing. This used to write directly to `rule._meta`,
165
+ * which made a compile option part of the caller's grammar forever: compile the
166
+ * same map once with whitespace trivia and a later compile with comma trivia
167
+ * would still use whitespace. Encoding is synchronous, but a copy is safer than
168
+ * a save/restore mutation (including on the exception path), and keeps compiler
169
+ * options out of long-lived grammar metadata altogether.
170
+ */
171
+ function withAmbient(
172
+ ruleMap: ReadonlyArray<readonly [string, Combinator<unknown>]>,
173
+ opts: TableRuleMapOptions,
174
+ ): ReadonlyArray<readonly [string, Combinator<unknown>]> {
175
+ if (opts.trivia === undefined && opts.scanSkip === undefined) return ruleMap
176
+
177
+ const wrappers = new Map<Combinator<unknown>, Combinator<unknown>>()
178
+ return ruleMap.map(([name, rule]) => {
179
+ if (rule._meta.isTrivia) return [name, rule] as const
180
+ const addTrivia = opts.trivia !== undefined && rule._meta.grammarTrivia === undefined
181
+ const addScanSkip = opts.scanSkip !== undefined && rule._meta.grammarScanSkip === undefined
182
+ if (!addTrivia && !addScanSkip) return [name, rule] as const
183
+
184
+ let wrapped = wrappers.get(rule)
185
+ if (wrapped === undefined) {
186
+ wrapped = {
187
+ ...rule,
188
+ _meta: {
189
+ ...rule._meta,
190
+ ...(addTrivia ? { grammarTrivia: opts.trivia } : {}),
191
+ ...(addScanSkip ? { grammarScanSkip: opts.scanSkip } : {}),
192
+ },
193
+ }
194
+ wrappers.set(rule, wrapped)
195
+ }
196
+ return [name, wrapped] as const
197
+ })
198
+ }
199
+
200
+ /**
201
+ * ENCODE FOR RUNNING, with no printability requirement.
202
+ *
203
+ * `compileRuleMap` refuses a map whose author callbacks have no captured SOURCE,
204
+ * because PRINTING one would emit `() => {}` and the parse would return the wrong tree.
205
+ * That gate is right for the macro, which prints, and wrong for every caller that only
206
+ * ever RUNS the result: a grammar built at runtime has live callbacks and no sources by
207
+ * construction, and `encodeTableProgram` parks the live function in the pool where the
208
+ * driver calls it perfectly well.
209
+ *
210
+ * Conflating the two is the single defect this project has now hit three times — a
211
+ * runtime `compose()`, `linkable()`, and the fuse — each presenting as "this grammar
212
+ * cannot be compiled" for a grammar that runs. The split lives here, once, so the next
213
+ * caller inherits the right answer instead of rediscovering it.
214
+ */
215
+ export function compileRuleMapRunnable(
216
+ ruleMap: ReadonlyArray<readonly [string, Combinator<unknown>]>,
217
+ opts: TableRuleMapOptions = {},
218
+ ): Omit<CompiledRuleMapTable, 'replacement' | 'replacementWithMetadata'> | null {
219
+ const encoded = encodeForRun(ruleMap, opts)
220
+ if (encoded === null) return null
221
+ const { prog, hostMode, plan } = encoded
222
+ const artifact = closureArtifact(prog)
223
+ return {
224
+ keys: ruleMap.map(([key]) => key),
225
+ hostMode,
226
+ hostBranchElided: hostMode === 'ast' && ruleMap.some(([, rule]) => hasDirectBuildDef(rule)),
227
+ reflection: collectGrammarReflection(ruleMap),
228
+ ...(plan === undefined ? {} : { coverageDefinitions: plan.definitions }),
229
+ rules: tableRules(artifact),
230
+ prog: artifact,
231
+ }
232
+ }
233
+
234
+ function encodeForRun(
235
+ ruleMap: ReadonlyArray<readonly [string, Combinator<unknown>]>,
236
+ opts: TableRuleMapOptions,
237
+ ): { prog: TableProgram; fnSrcs: (string | null)[]; hostMode: HostMode; plan: ReturnType<typeof buildGrammarPlan> | undefined } | null {
238
+ runDuplicationDiagnosticRules(ruleMap, opts.duplication)
239
+ const encodeMap = withAmbient(ruleMap, opts)
240
+ // WINNERS ARE THE NON-`lazy` ENTRIES, exactly as `codegen.ts:5639` picks them.
241
+ // A `rules()` map stores named lazy proxies beside resolved bodies, and the plan
242
+ // uses the winner map to give a shared subtree ONE owner; filtering differently
243
+ // here would mint `rule:` ids the source lowering does not, so a consumer
244
+ // comparing the two engines' coverage would be comparing two denominators.
245
+ const plan = opts.coverage === true
246
+ ? buildGrammarPlan(
247
+ encodeMap.map(([, rule]) => rule),
248
+ Object.fromEntries(encodeMap.filter(([, rule]) => rule._def.tag !== 'lazy')),
249
+ )
250
+ : undefined
251
+ const hostMode = resolveHostMode(ruleMap, opts.hostMode)
252
+ const settings: TableSettings = {
253
+ hostMode,
254
+ ...(resolveTrackLines(ruleMap, opts.trackLines) ? { trackLines: true } : {}),
255
+ ...(opts.recovery === true ? { recovery: true } : {}),
256
+ ...(plan === undefined ? {} : { coverage: plan }),
257
+ }
258
+ try {
259
+ const { prog, fnSrcs } = encodeTableProgram(Object.fromEntries(encodeMap), settings)
260
+ return { prog, fnSrcs, hostMode, plan }
261
+ } catch (e) {
262
+ opts.refusals?.push(e instanceof Error ? e.message : String(e))
263
+ return null
264
+ }
265
+ }
266
+
267
+ export function compileRuleMap(
268
+ ruleMap: ReadonlyArray<readonly [string, Combinator<unknown>]>,
269
+ opts: TableRuleMapOptions = {},
270
+ ): CompiledRuleMapTable | null {
271
+ // ALL-OR-NOTHING, exactly as `compileRuleMap` is. A construct with no opcode
272
+ // (`UnsupportedConstruct`) and a `g.X` reference that resolves to nothing
273
+ // (the lazy thunk throws) both mean this map cannot become a table, and the
274
+ // caller's existing "leave this rules() call interpreted" fallback covers it.
275
+ const encoded = encodeForRun(ruleMap, opts)
276
+ if (encoded === null) return null
277
+ const { prog, fnSrcs, hostMode, plan } = encoded
278
+ const artifact = closureArtifact(prog)
279
+
280
+ // RUNTIME-ONLY: the program parses but cannot be PRINTED (a live trivia
281
+ // combinator parked in the pool, say). `compileRuleMap` returns null for its
282
+ // own unprintable cases rather than emitting something that loads and
283
+ // misbehaves; this is the same answer to the same question.
284
+ if (artifact.runtimeOnly !== undefined && artifact.runtimeOnly.length > 0) {
285
+ opts.refusals?.push(...artifact.runtimeOnly)
286
+ return null
287
+ }
288
+ // The `mfCovered && buildCovered` gate, for the table's reducer pool: without
289
+ // a source per author callback the emitters print `() => {}`, which produces a
290
+ // module that loads and returns the wrong tree. An out-of-band list fills in
291
+ // only where the encoder captured nothing, and only if it is COMPLETE — a
292
+ // partial list would leave the placeholders it was passed to remove.
293
+ const supplied = opts.fnSources
294
+ const sources = fnSrcs.map((s, i) => s ?? supplied?.[i] ?? null)
295
+ if (sources.some(s => s === null)) {
296
+ const missing = sources.flatMap((s, i) => (s === null ? [i] : []))
297
+ opts.refusals?.push(
298
+ `author reducer source missing for prog.fns[${missing.join(', ')}] — printing would emit \`() => {}\` and the parse would return no value`,
299
+ )
300
+ return null
301
+ }
302
+ if (supplied !== undefined && supplied.length > fnSrcs.length) {
303
+ throw new Error(
304
+ `compileRuleMap: got ${supplied.length} fnSources for a pool of ${fnSrcs.length}. `
305
+ + 'The list is positional, in prog.fns order, so a longer one means it belongs to a '
306
+ + 'different encode — and the entries would be silently misassigned.',
307
+ )
308
+ }
309
+
310
+ const replacementWithMetadata = (metadataSource?: string): string => emitTableExpression(artifact, {
311
+ entry: null,
312
+ runtimeRef: opts.runtimeRef ?? 'tableRules',
313
+ fnSources: sources as string[],
314
+ ...(metadataSource === undefined ? {} : { metadataSource }),
315
+ })
316
+ const replacement = replacementWithMetadata()
317
+
318
+ return {
319
+ keys: ruleMap.map(([key]) => key),
320
+ replacement,
321
+ replacementWithMetadata,
322
+ hostMode,
323
+ // WHAT THE FLAG MEANS is "a DIRECT BUILDER's positioned-CST branch was
324
+ // dropped", which is what the driver's `'ast' artifact + CST host` check keys
325
+ // off. `mode === 'ast'` alone over-reports it: an all-STRUCTURAL grammar has no
326
+ // direct builder, so there was no branch to drop and it stays usable with either
327
+ // host — the long-standing `node(parser)` contract. Same predicate codegen uses
328
+ // for its own `hasDirectBuilders` (both read `analysis/commitment.ts`), so the two
329
+ // lowerings stamp the same artifact rather than disagreeing about it.
330
+ hostBranchElided: hostMode === 'ast' && ruleMap.some(([, rule]) => hasDirectBuildDef(rule)),
331
+ reflection: collectGrammarReflection(ruleMap),
332
+ ...(plan === undefined ? {} : { coverageDefinitions: plan.definitions }),
333
+ rules: tableRules(artifact),
334
+ prog: artifact,
335
+ }
336
+ }