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,340 @@
1
+ import type { Combinator, FirstSet, ParserDef } from '../types.ts'
2
+ import { firstSetOf, matchesEmpty } from '../combinators/first-set.ts'
3
+ import { deriveExpected } from '../combinators/expect.ts'
4
+ import { buildReadsState, buildReadsTrivia } from '../compiler/build-arity.ts'
5
+ import {
6
+ OP_CHOICE, OP_EMPTY, OP_GATE, OP_LEAF, OP_LIT, OP_NODE, OP_NOT, OP_OPT,
7
+ OP_PEEK, OP_REP, OP_REPV, OP_RULE, OP_RX, OP_SEQ, OP_SEQV, OP_XFORM,
8
+ OP_LIT_TRACK, OP_RX_TRACK, OP_NODE_TRACK, OP_SCOPE, OP_EXPECT,
9
+ } from './ops.ts'
10
+ import type { TableProgram } from './program.ts'
11
+
12
+ /** Raised when a construct has no opcode yet. Prototype scope is explicit. */
13
+ class UnsupportedConstructBaseline extends Error {
14
+ readonly tag: string
15
+ constructor(tag: string) { super(`table lowering: no opcode for '${tag}'`); this.tag = tag }
16
+ }
17
+
18
+ /** Settings that select the TABLE'S CONTENTS. They never reach the driver. */
19
+ export type TableSettings = {
20
+ readonly hostMode?: 'ast' | 'cst'
21
+ readonly trackLines?: boolean
22
+ }
23
+
24
+ type Emitted = { readonly ip: number }
25
+
26
+ /**
27
+ * Do these first sets share no character?
28
+ *
29
+ * `any` and `empty` are never disjoint from anything — an over-approximating arm
30
+ * has to fall back to ordered first-match or the choice silently picks the wrong
31
+ * one. See the `Keyword`-ahead-of-`Num` case in the choice encoder.
32
+ */
33
+ function disjointSets(sets: readonly FirstSet[]): boolean {
34
+ const seen: Array<{ lo: number; hi: number }> = []
35
+ for (const fs of sets) {
36
+ if (fs.kind !== 'ranges' || fs.ranges.length === 0) return false
37
+ for (const r of fs.ranges) {
38
+ for (const p of seen) if (r.lo <= p.hi && p.lo <= r.hi) return false
39
+ seen.push(r)
40
+ }
41
+ }
42
+ return true
43
+ }
44
+
45
+ class Encoder {
46
+ code: number[] = []
47
+ k: unknown[] = []
48
+ fns: unknown[] = []
49
+ cc: string[] = []
50
+ fx: (readonly string[])[] = []
51
+ disp: (readonly number[])[] = []
52
+ rules: Record<string, number> = {}
53
+
54
+ private kIndex = new Map<unknown, number>()
55
+ private ccIndex = new Map<string, number>()
56
+ private fxIndex = new Map<string, number>()
57
+ /** Memoized by combinator identity: a shared sub-combinator is ONE row. */
58
+ private memo = new Map<Combinator<unknown>, number>()
59
+ private pending = new Map<Combinator<unknown>, number[]>()
60
+
61
+ readonly settings: TableSettings
62
+ /** Resolved once, HERE, at table-build time — never consulted at run time. */
63
+ readonly track: boolean
64
+ constructor(settings: TableSettings) {
65
+ this.settings = settings
66
+ this.track = settings.trackLines === true
67
+ }
68
+
69
+ private constant(v: unknown): number {
70
+ const key = v instanceof RegExp ? `re:${JSON.stringify([v.source, v.flags])}` : v
71
+ const hit = this.kIndex.get(key)
72
+ if (hit !== undefined) return hit
73
+ const i = this.k.length
74
+ this.k.push(v)
75
+ this.kIndex.set(key, i)
76
+ return i
77
+ }
78
+
79
+ private fn(v: unknown): number {
80
+ const i = this.fns.length
81
+ this.fns.push(v)
82
+ return i
83
+ }
84
+
85
+ private expected(list: readonly string[]): number {
86
+ const key = JSON.stringify(list)
87
+ const hit = this.fxIndex.get(key)
88
+ if (hit !== undefined) return hit
89
+ const i = this.fx.length
90
+ this.fx.push(list)
91
+ this.fxIndex.set(key, i)
92
+ return i
93
+ }
94
+
95
+ /** A first set becomes a char class STRING of `[lo, hi]` pairs, or −1 for `any`. */
96
+ private charClass(fs: FirstSet): number {
97
+ if (fs.kind !== 'ranges' || fs.ranges.length === 0) return -1
98
+ let spec = ''
99
+ for (const r of fs.ranges) spec += String.fromCharCode(r.lo, r.hi)
100
+ const hit = this.ccIndex.get(spec)
101
+ if (hit !== undefined) return hit
102
+ const i = this.cc.length
103
+ this.cc.push(spec)
104
+ this.ccIndex.set(spec, i)
105
+ return i
106
+ }
107
+
108
+ private emit(...words: number[]): number {
109
+ const ip = this.code.length
110
+ for (const w of words) this.code.push(w)
111
+ return ip
112
+ }
113
+
114
+ /** Reserve `n` operand slots to patch once children are laid out. */
115
+ private emitHead(op: number, n: number): number {
116
+ const ip = this.code.length
117
+ this.code.push(op)
118
+ for (let i = 0; i < n; i++) this.code.push(0)
119
+ return ip
120
+ }
121
+
122
+ encodeRule(name: string, p: Combinator<unknown>): void {
123
+ this.rules[name] = this.node(p).ip
124
+ }
125
+
126
+ node(p: Combinator<unknown>): Emitted {
127
+ const hit = this.memo.get(p)
128
+ if (hit !== undefined) return { ip: hit }
129
+ // Recursion: reserve a trampoline row now, patch its target when the real
130
+ // body lands. One extra indirection per recursive rule, zero per call site.
131
+ const inFlight = this.pending.get(p)
132
+ if (inFlight !== undefined) {
133
+ const ip = this.emitHead(OP_RULE, 1)
134
+ inFlight.push(ip + 1)
135
+ return { ip }
136
+ }
137
+ const patches: number[] = []
138
+ this.pending.set(p, patches)
139
+ const ip = this.encodeDef(p)
140
+ this.pending.delete(p)
141
+ this.memo.set(p, ip)
142
+ for (const slot of patches) this.code[slot] = ip
143
+ return { ip }
144
+ }
145
+
146
+ private encodeDef(p: Combinator<unknown>): number {
147
+ const d = p._def as ParserDef
148
+ // REFUSE what this frozen copy cannot lower, rather than lowering it wrong.
149
+ //
150
+ // `scanTo` and `token` have no `case` below, so they reach `default:` and throw
151
+ // — a refusal, which is safe. `balanced()` does NOT: it overrides `.parse` and
152
+ // leaves `_def` as the EAGER INTERIOR with no distinguishing tag, so the switch
153
+ // would encode the interior, report nothing, and produce a table that parses and
154
+ // yields a different tree. An ablation measurement taken on a grammar using
155
+ // `balanced()` would then be comparing against a parser that is not the one
156
+ // under test. Refused explicitly, since this copy has no OP_CALL to fall back on.
157
+ if ((p as { _balancedAmbient?: unknown })._balancedAmbient !== undefined) {
158
+ throw new UnsupportedConstructBaseline('balanced() — the frozen ablation encoder cannot lower it')
159
+ }
160
+ switch (d.tag) {
161
+ case 'literal': {
162
+ if (d.caseInsensitive) throw new UnsupportedConstructBaseline('literal(caseInsensitive)')
163
+ return this.emit(this.track ? OP_LIT_TRACK : OP_LIT, this.constant(d.value), this.expected(deriveExpected(p)))
164
+ }
165
+ case 'regex': {
166
+ const flags = d.flags.includes('y') ? d.flags : `${d.flags}y`
167
+ return this.emit(this.track ? OP_RX_TRACK : OP_RX, this.constant(new RegExp(d.source, flags)), this.expected(deriveExpected(p)))
168
+ }
169
+ case 'keywords': {
170
+ // `keywords()` compiles to ONE sticky regex and pushes a leaf — which is
171
+ // exactly what OP_RX does. Rebuilding the same regex here needs no new
172
+ // opcode. Construction mirrors src/combinators/keywords.ts:87-106; the
173
+ // words arrive already sorted longest-first on the def.
174
+ const alt = d.words.map(w => w.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')).join('|')
175
+ const boundary = d.boundary ? `(?![${d.boundary}])` : ''
176
+ const flags = d.caseInsensitive ? 'iy' : 'uy'
177
+ return this.emit(
178
+ this.track ? OP_RX_TRACK : OP_RX,
179
+ this.constant(new RegExp(`(?:${alt})${boundary}`, flags)),
180
+ this.expected(deriveExpected(p)),
181
+ )
182
+ }
183
+ case 'expect': {
184
+ // `expect()` NEVER fails: on a failed child it yields a ParseError value
185
+ // at zero width. That is a distinct outcome, so it gets its own row.
186
+ return this.emit(OP_EXPECT, this.node(d.parser).ip, this.expected([...d.expected]))
187
+ }
188
+ case 'sequence': {
189
+ const kids = d.parsers.map(c => this.node(c).ip)
190
+ const head = this.emitHead(d.valueUnused ? OP_SEQV : OP_SEQ, 1 + kids.length)
191
+ this.code[head + 1] = kids.length
192
+ for (let i = 0; i < kids.length; i++) this.code[head + 2 + i] = kids[i]!
193
+ return head
194
+ }
195
+ case 'choice': {
196
+ if (d.gates.some(g => g !== null)) throw new UnsupportedConstructBaseline('choice(gate:)')
197
+ // Arm ORDER is semantics. `literalsLongestFirst` / `sharedPrefix` /
198
+ // `greedyClassify` reorder or restructure the arms, so encoding them as
199
+ // a plain ordered choice would pick a different arm and build a
200
+ // different tree while still parsing. Refuse them rather than lower them
201
+ // wrong.
202
+ // `greedyClassify` runs ONE arm and then re-attributes the match to a
203
+ // DIFFERENT arm by string equality, re-applying that arm's transforms.
204
+ // That is a different execution, not a different order, so it is refused
205
+ // rather than approximated.
206
+ if (d.strategy.tag === 'greedyClassify') throw new UnsupportedConstructBaseline('choice(strategy=greedyClassify)')
207
+ // `autoNot` rejects an arm that matched but is followed by a char in a
208
+ // sibling's first set, so a later arm can win. Ignoring it would pick a
209
+ // different arm and build a different tree behind a successful parse.
210
+ if (d.autoNot.some(a => a !== null)) throw new UnsupportedConstructBaseline('choice(autoNot)')
211
+ // ORDER is the only thing the remaining strategies change, and order is
212
+ // table data. `literalsLongestFirst` carries its order explicitly;
213
+ // `sharedPrefix` is documented in choice.ts:52 as a firstMatch
214
+ // specialization, so declared order is already correct for it.
215
+ const order = d.strategy.tag === 'literalsLongestFirst'
216
+ ? d.strategy.sortedIndices
217
+ : d.parsers.map((_, i) => i)
218
+ const arms = order.map(i => d.parsers[i]!)
219
+ const kids = arms.map(c => this.node(c).ip)
220
+ // O(1) first-char dispatch is only SOUND when the arms are disjoint.
221
+ // With overlapping first sets, "the first arm whose class contains this
222
+ // char" is not "the first arm that matches": a `Keyword` arm whose first
223
+ // set over-approximates to include digits was selected ahead of `Num`
224
+ // for the input `0` — the parse still succeeded, and only the TREE moved.
225
+ // Disjointness is computed HERE, not read off `d.disjoint`. That flag is
226
+ // set when `choice()` is CONSTRUCTED, at which point `g.X` arms are
227
+ // unresolved refs whose first set is `any` — so every recursive grammar
228
+ // reports non-disjoint and loses its dispatch. (Codegen has the same
229
+ // problem and solves it the same way: a placeholder resolved at fuse.)
230
+ let dispIdx = -1
231
+ if (arms.every(a => !matchesEmpty(a))) {
232
+ const sets = arms.map(a => firstSetOf(a))
233
+ if (disjointSets(sets)) {
234
+ const classes = sets.map(fs => this.charClass(fs))
235
+ if (classes.every(c => c >= 0)) {
236
+ dispIdx = this.disp.length
237
+ this.disp.push(classes)
238
+ }
239
+ }
240
+ }
241
+ const head = this.emitHead(OP_CHOICE, 2 + kids.length)
242
+ this.code[head + 1] = dispIdx
243
+ this.code[head + 2] = kids.length
244
+ for (let i = 0; i < kids.length; i++) this.code[head + 3 + i] = kids[i]!
245
+ return head
246
+ }
247
+ case 'many':
248
+ case 'oneOrMore': {
249
+ const child = this.node(d.parser).ip
250
+ return this.emit(
251
+ d.valueUnused ? OP_REPV : OP_REP,
252
+ child, d.tag === 'many' ? 0 : d.min, d.max ?? -1, -1, 0,
253
+ )
254
+ }
255
+ case 'sepBy': {
256
+ const child = this.node(d.parser).ip
257
+ const sep = this.node(d.separator).ip
258
+ return this.emit(OP_REP, child, d.min, d.max ?? -1, sep, d.trailing === 'allow' ? 1 : 0)
259
+ }
260
+ case 'optional':
261
+ return this.emit(OP_OPT, this.node(d.parser).ip)
262
+ case 'transform': {
263
+ const child = this.node(d.parser).ip
264
+ return this.emit(OP_XFORM, this.fn(d.fn), child)
265
+ }
266
+ case 'leaf': {
267
+ const child = this.node(d.parser).ip
268
+ return this.emit(OP_LEAF, this.fn(d.fn), child)
269
+ }
270
+ case 'node': {
271
+ if (d.unwrap || d.collapse || d.project !== undefined) throw new UnsupportedConstructBaseline('node(unwrap|collapse|project)')
272
+ if (d.build === undefined) throw new UnsupportedConstructBaseline('node(no build)')
273
+ const child = this.node(d.parser).ip
274
+ // Capture flags, resolved HERE from the reducer's declared arity using the
275
+ // same analysis codegen runs (`src/compiler/build-arity.ts`). `hostMode:
276
+ // 'cst'` forces them on, exactly as the emitted `cstOut` path does. The
277
+ // driver reads a bit; it re-derives nothing and sees no setting.
278
+ const cstOut = this.settings.hostMode === 'cst'
279
+ const flags = (cstOut || buildReadsTrivia(d) ? 4 : 0) | (cstOut || buildReadsState(d) ? 8 : 0)
280
+ const body = this.emit(this.track ? OP_NODE_TRACK : OP_NODE, this.fn(d.build), child, flags)
281
+ // The rule's own first-set gate — the emitted code's `_ngc` test, as data.
282
+ // A NULLABLE rule has no gate: it succeeds on input its first set does
283
+ // not contain (including EOF), so gating it would reject a legal empty
284
+ // match. Caught by the ladder's `empty` and `garbage` cases.
285
+ if (matchesEmpty(p)) return body
286
+ const cls = this.charClass(firstSetOf(p))
287
+ if (cls < 0) return body
288
+ return this.emit(OP_GATE, cls, body, this.expected(deriveExpected(p)))
289
+ }
290
+ case 'lazy':
291
+ // A named reference is not a hop: it resolves to the target's row.
292
+ // Emitting a trampoline here cost one dispatch per reference for nothing.
293
+ return this.node(d.thunk()).ip
294
+ case 'not':
295
+ return this.emit(OP_NOT, this.node(d.parser).ip)
296
+ case 'peek':
297
+ return this.emit(OP_PEEK, this.node(d.parser).ip)
298
+ // Transparent wrappers: no row of their own, no dispatch at run time.
299
+ case 'token':
300
+ case 'attempt':
301
+ case 'label':
302
+ case 'trivia':
303
+ return this.node(d.parser).ip
304
+ case 'grammar': {
305
+ // A trivia scope is a ROW, not a lowering decision: the scope's trivia
306
+ // combinator goes in the const pool and the driver installs it.
307
+ if (d.clearTrivia === true) return this.emit(OP_SCOPE, -1, this.node(d.parser).ip)
308
+ if (d.triviaParser === undefined) return this.node(d.parser).ip
309
+ return this.emit(OP_SCOPE, this.constant(d.triviaParser), this.node(d.parser).ip)
310
+ }
311
+ default:
312
+ throw new UnsupportedConstructBaseline(d.tag)
313
+ }
314
+ }
315
+
316
+ finish(): TableProgram {
317
+ if (this.code.length === 0) this.emit(OP_EMPTY)
318
+ return {
319
+ code: this.code, k: this.k, fns: this.fns, cc: this.cc,
320
+ fx: this.fx, disp: this.disp, dsp: [], rules: this.rules,
321
+ lines: this.track ? 1 : 0,
322
+ }
323
+ }
324
+ }
325
+
326
+ /**
327
+ * Encode a rule map into ONE table for ONE settings pair.
328
+ *
329
+ * Two settings pairs give two programs and therefore two cached reference
330
+ * tables; the driver that reads them is the same function in both cases and
331
+ * never sees `settings`.
332
+ */
333
+ export function encodeTableBaseline(
334
+ ruleMap: Record<string, Combinator<unknown>>,
335
+ settings: TableSettings = {},
336
+ ): TableProgram {
337
+ const enc = new Encoder(settings)
338
+ for (const name of Object.keys(ruleMap)) enc.encodeRule(name, ruleMap[name]!)
339
+ return enc.finish()
340
+ }