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,345 @@
1
+ import type { Combinator, FieldMap, ParseContext, ParseResult, ParserMeta, ParserDef } from '../types.ts'
2
+ import { beginCstNodeCapture, endCstNodeCapture, pushCstChild } from '../cst/capture-buffer.ts'
3
+ import { buildReadsTrivia, buildReadsState, cstOutputHost } from '../compiler/build-arity.ts'
4
+ import { buildFieldMap, buildReadsFields, parserHasOwnFields } from '../compiler/fields.ts'
5
+ import { consumeTrivia } from './trivia-skip.ts'
6
+ import { matchesEmpty, startsFirstSet } from './first-set.ts'
7
+ import { deriveExpected } from './expect.ts'
8
+ import { annotateSpanFromLineContext } from '../line-index.ts'
9
+ import { NODE_TAG, NODE_TYPE } from '../cst/reflection-symbols.ts'
10
+
11
+ /**
12
+ * A CST/AST node rule. Runs `combinator` while collecting its terminals into
13
+ * `children` / `rawChildren` arrays and trivia spans into `triviaLog`, then
14
+ * calls `build(children, fields, span, rawChildren, triviaLog, state)` to produce the node.
15
+ *
16
+ * - `children` — structural items in source order: spanned CSTLeaf terminals
17
+ * and sub-nodes (whatever `build` returned for inner nodes).
18
+ * - `rawChildren` — structural children only (same items as `children`).
19
+ * - `triviaLog` — flat log of trivia entries: each entry is `[start, end, insertIdx]`
20
+ * consumed between terms. `insertIdx` is the rawChildren index
21
+ * before which the trivia was consumed. Use `buildTriviaIndex`
22
+ * to turn this into a before/after lookup table.
23
+ *
24
+ * If `build` returns a non-node value (e.g. a bare string for an unwrapped rule),
25
+ * the parent records it as a spanned leaf so its source span is still recoverable.
26
+ */
27
+ export type BuildNode<N> = (
28
+ children: ReadonlyArray<unknown>,
29
+ fields: FieldMap | undefined,
30
+ span: { start: number; end: number },
31
+ rawChildren: ReadonlyArray<unknown>,
32
+ triviaLog: readonly number[],
33
+ state: unknown,
34
+ ) => N
35
+
36
+ /**
37
+ * Options for `node()`.
38
+ * - `unwrap` — an AST/value wrapper rule that IS its single child when it
39
+ * captured exactly one. A leaf unwraps to its string value; a sub-node is
40
+ * returned as-is.
41
+ * - `collapse` — a structural wrapper rule that returns its single child exactly
42
+ * as captured. A leaf stays a CSTLeaf; a node stays a node.
43
+ * - `project` — return one captured semantic child by index while retaining the
44
+ * complete node frame for CST hosts. Leaves unwrap to their string value.
45
+ * - `captureTrivia` — capture trivia consumed inside this node even when its
46
+ * enclosing `parser()` did not opt into document-wide trivia capture. This is
47
+ * scoped to the node; sibling and parent nodes retain their own setting.
48
+ * - `trailingTrivia` — after a successful node body, consume the active grammar
49
+ * trivia once into THIS node's log. Intended for a document root whose body is
50
+ * a repetition; do not set it on a block that already has a closing delimiter.
51
+ * - `tags` — grammar-level CST categories for visitor dispatch. Stored once in
52
+ * grammar reflection; not copied onto every CST node.
53
+ * Both skip `build` only for a one-child match; zero or two-plus children go
54
+ * through `build` normally.
55
+ */
56
+ type ParserValue<P> = P extends Combinator<infer T> ? T : never
57
+ type ProjectValue<P extends Combinator<unknown>, I extends number> =
58
+ ParserValue<P> extends readonly unknown[]
59
+ ? I extends keyof ParserValue<P> ? ParserValue<P>[I] : unknown
60
+ : I extends 0 ? ParserValue<P> : unknown
61
+
62
+ export type NodeCombinator<T, NodeType extends string = never, NodeTag extends string = never> =
63
+ Combinator<T> & {
64
+ /** Type-only CST node identity carried for grammar-aware visitors. */
65
+ readonly [NODE_TYPE]?: NodeType
66
+ /** Type-only CST tags carried for grammar-aware visitors. */
67
+ readonly [NODE_TAG]?: NodeTag
68
+ }
69
+
70
+ export type NodeOptions<Tags extends readonly string[] = readonly never[]> = {
71
+ unwrap?: boolean
72
+ collapse?: boolean
73
+ project?: number
74
+ captureTrivia?: boolean
75
+ trailingTrivia?: boolean
76
+ tags?: Tags
77
+ /**
78
+ * Declared positional arity of `build` — the ESCAPE HATCH for a reducer parseman
79
+ * cannot read.
80
+ *
81
+ * Arity gates five capture tiers (`>= 1` children, `>= 2` fields, `>= 4` rawChildren,
82
+ * `>= 5` triviaLog, `>= 6` a clone of `_ctx.state`), and parseman normally derives it
83
+ * from the reducer's declaration — including across module boundaries. A few shapes
84
+ * are genuinely undecidable: a rest parameter (`...args`), a body that reads
85
+ * `arguments`, a reassigned or dynamically constructed reducer. Those fail OPEN, which
86
+ * is safe but pays for every tier on every match, forever.
87
+ *
88
+ * Declaring the arity here turns "undecidable" into "declared". You are asserting the
89
+ * highest positional argument the reducer reads; parseman then elides everything above
90
+ * it exactly as if it had read the parameter list.
91
+ *
92
+ * DECLARING TOO LOW UNDER-CAPTURES: the reducer will receive an empty `rawChildren` /
93
+ * `triviaLog` or an absent `state` rather than a wrong value. Count the parameters.
94
+ */
95
+ buildArity?: number
96
+ }
97
+ export type NodeProjectOptions<I extends number = number> =
98
+ Omit<NodeOptions, 'project' | 'unwrap' | 'collapse'> & { project: I; unwrap?: never; collapse?: never }
99
+
100
+ /** A captured child's value form: a leaf unwraps to its string value, else as-is. */
101
+ export function unwrapChild(child: unknown): unknown {
102
+ return child !== null && typeof child === 'object' && (child as { _tag?: string })._tag === 'leaf'
103
+ ? (child as { value: unknown }).value
104
+ : child
105
+ }
106
+
107
+ function isCstChild(value: unknown): boolean {
108
+ return typeof value === 'object'
109
+ && value !== null
110
+ && ((value as { _tag?: string })._tag === 'node'
111
+ || (value as { _tag?: string })._tag === 'leaf'
112
+ || (value as { _tag?: string })._tag === 'parseError')
113
+ }
114
+
115
+ /**
116
+ * The authoring error for `node()` with no rules() key, raised IDENTICALLY by
117
+ * every engine. Exported because the table encoder used to answer it with
118
+ * `UnsupportedConstruct: no opcode for 'node(inferred type)'` — a message that
119
+ * reads as a table gap and names neither the mistake nor the fix.
120
+ */
121
+ export function missingInferredType(): never {
122
+ throw new Error('node(): inferred node type requires a rules() key; pass node("Type", parser) outside rules()')
123
+ }
124
+
125
+ function normalizeBuildArity(arity: number | undefined): number | undefined {
126
+ if (arity === undefined) return undefined
127
+ if (!Number.isInteger(arity) || arity < 0 || arity > 6) {
128
+ throw new Error('node() buildArity must be an integer 0..6 (children, fields, span, rawChildren, triviaLog, state)')
129
+ }
130
+ return arity
131
+ }
132
+
133
+ function normalizeProject(project: number | undefined): number | undefined {
134
+ if (project === undefined) return undefined
135
+ if (!Number.isInteger(project) || project < 0) {
136
+ throw new Error('node() project child must be a non-negative integer')
137
+ }
138
+ return project
139
+ }
140
+
141
+ export function projectChild(children: ReadonlyArray<unknown>, project: number, type: string): unknown {
142
+ if (!(project in children)) {
143
+ throw new Error(`node(${JSON.stringify(type)}) project child ${project} was not captured`)
144
+ }
145
+ return unwrapChild(children[project])
146
+ }
147
+
148
+ export function node<P extends Combinator<unknown>, const I extends number, const Tags extends readonly string[] = readonly never[]>(combinator: P, opts: NodeProjectOptions<I> & { tags?: Tags }): NodeCombinator<ProjectValue<P, I>, never, Tags[number]>
149
+ export function node<N, const Tags extends readonly string[] = readonly never[]>(combinator: Combinator<unknown>, build?: BuildNode<N>, opts?: NodeOptions<Tags>): NodeCombinator<N, never, Tags[number]>
150
+ export function node<N, const Tags extends readonly string[] = readonly never[]>(combinator: Combinator<unknown>, opts?: NodeOptions<Tags>): NodeCombinator<N, never, Tags[number]>
151
+ export function node<const Type extends string, P extends Combinator<unknown>, const I extends number, const Tags extends readonly string[] = readonly never[]>(type: Type, combinator: P, opts: NodeProjectOptions<I> & { tags?: Tags }): NodeCombinator<ProjectValue<P, I>, Type, Tags[number]>
152
+ // `Type` carries a default so that `node<N>('Type', …)` — one explicit type
153
+ // argument, the node value type — still MATCHES these signatures. Without it
154
+ // the arity check rejects them, resolution falls back to the combinator-first
155
+ // overloads above, and the caller gets `TS2345 string is not assignable to
156
+ // Combinator` plus a TS7006 implicit-any reducer from the rejected contextual
157
+ // type. See test/unit/node-type-argument.test.ts.
158
+ //
159
+ // RESIDUAL COST: `= string` is the only shape available. TypeScript fills a
160
+ // missing type argument from its DEFAULT and never infers it (there is no
161
+ // partial type-argument inference), so `node<N>('Type', …)` resolves `Type` to
162
+ // `string` and loses the string literal. Write `node('Type', …)` (no explicit
163
+ // type arguments) or `node<N, 'Type'>('Type', …)` where the literal identity
164
+ // matters to a grammar-aware visitor. The only literal-preserving alternative
165
+ // is a curried call form, which would change the public call surface.
166
+ export function node<N, const Type extends string = string, const Tags extends readonly string[] = readonly never[]>(type: Type, combinator: Combinator<unknown>, build?: BuildNode<N>, opts?: NodeOptions<Tags>): NodeCombinator<N, Type, Tags[number]>
167
+ export function node<N, const Type extends string = string, const Tags extends readonly string[] = readonly never[]>(type: Type, combinator: Combinator<unknown>, opts?: NodeOptions<Tags>): NodeCombinator<N, Type, Tags[number]>
168
+ export function node<N>(
169
+ typeOrCombinator: string | Combinator<unknown>,
170
+ combinatorOrBuild?: Combinator<unknown> | BuildNode<N> | NodeOptions,
171
+ buildOrOpts?: BuildNode<N> | NodeOptions,
172
+ maybeOpts?: NodeOptions,
173
+ ): Combinator<N> {
174
+ const hasExplicitType = typeof typeOrCombinator === 'string'
175
+ const type = hasExplicitType ? typeOrCombinator : undefined
176
+ const combinator = (hasExplicitType ? combinatorOrBuild : typeOrCombinator) as Combinator<unknown>
177
+ const buildArg = hasExplicitType ? buildOrOpts : combinatorOrBuild
178
+ const build = typeof buildArg === 'function' ? buildArg as BuildNode<N> : undefined
179
+ const opts = (typeof buildArg === 'function' ? maybeOpts : buildArg ?? maybeOpts) as NodeOptions | undefined
180
+ const project = normalizeProject(opts?.project)
181
+ if (project !== undefined && build !== undefined) {
182
+ throw new Error('node() options cannot combine project with a build callback')
183
+ }
184
+ const baseDef = { tag: 'node' as const, parser: combinator, ...(type === undefined ? {} : { type }), ...(build === undefined ? {} : { build }) }
185
+ const meta: ParserMeta = {
186
+ firstSet: combinator._meta.firstSet,
187
+ canMatchNewline: combinator._meta.canMatchNewline,
188
+ isTrivia: false,
189
+ }
190
+ const unwrap = opts?.unwrap === true
191
+ const collapse = opts?.collapse === true
192
+ const captureTrivia = opts?.captureTrivia === true
193
+ const trailingTrivia = opts?.trailingTrivia === true
194
+ const tags = opts?.tags
195
+ const buildArity = normalizeBuildArity(opts?.buildArity)
196
+ if (unwrap && collapse) {
197
+ throw new Error('node() options cannot set both unwrap and collapse')
198
+ }
199
+ if (project !== undefined && (unwrap || collapse)) {
200
+ throw new Error('node() options cannot combine project with unwrap or collapse')
201
+ }
202
+ const def: Extract<ParserDef, { tag: 'node' }> = unwrap || collapse || project !== undefined || captureTrivia || trailingTrivia || buildArity !== undefined || (tags !== undefined && tags.length > 0)
203
+ ? { ...baseDef, ...(unwrap ? { unwrap: true } : {}), ...(collapse ? { collapse: true } : {}), ...(project !== undefined ? { project } : {}), ...(captureTrivia ? { captureTrivia: true } : {}), ...(trailingTrivia ? { trailingTrivia: true } : {}), ...(buildArity !== undefined ? { buildArity } : {}), ...(tags !== undefined && tags.length > 0 ? { tags } : {}) }
204
+ : baseDef
205
+ // Arity-gated elision — decided once, identically to the compiler (build-arity.ts).
206
+ // When the build never reads the trivia (4th) arg, disable per-node CST-trivia
207
+ // capture for the inner scope; when it never reads state (5th), skip the state clone.
208
+ // A STRUCTURAL node (no own build) defers to `ctx.build` / a default CST, which
209
+ // may read either, so capture both.
210
+ // These are the DIRECT builder's own needs, and they are not the whole story. A node
211
+ // with its own build is re-routed through `ctx.build` when that host marks itself
212
+ // `_parsemanCstOutput` (see the dispatch below) — and then the HOST, not the builder,
213
+ // is the consumer. Nearly every AST builder is `children => …`, arity 1, so under a CST
214
+ // host these nodes handed the host an EMPTY triviaLog and absent fields and state, and
215
+ // an empty trivia log is indistinguishable from a node that genuinely had none.
216
+ //
217
+ // The COMPILED engine settles this at build time (`compile(g, { hostMode: 'cst' })`,
218
+ // added in 0.37.0), where it costs nothing. The interpreter has no compile step and
219
+ // stays dynamic, so it re-decides per parse — which is the same answer, reached the
220
+ // only way this engine can reach it.
221
+ const capturesTrivia = captureTrivia || trailingTrivia || (build ? buildReadsTrivia(def) : project === undefined)
222
+ const clonesState = build ? buildReadsState(def) : project === undefined
223
+ const hasOwnFields = parserHasOwnFields(combinator)
224
+ const capturesFields = hasOwnFields && (build ? buildReadsFields(def) : project === undefined)
225
+ // First-set fail-fast (mirrors emitNode's codegen guard): a node whose body can't
226
+ // match empty and whose first set can't start here can only fail, so reject BEFORE
227
+ // allocating the CST capture frame. Sound and output-neutral — the failing body
228
+ // would capture nothing and beginCstNodeCapture saves/restores every buffer, so
229
+ // skipping returns the same failure the body's start-fail produces. Skipped under a
230
+ // completions probe / tolerant recovery, where the swallowed failure still feeds the
231
+ // probe (matching the codegen guard's `!ctx.recovery` gate).
232
+ const guardable = combinator._meta.firstSet.kind !== 'any' && !matchesEmpty(combinator)
233
+ let failExpected: string[] | undefined
234
+ return {
235
+ _tag: 'node',
236
+ _meta: meta,
237
+ _def: def,
238
+ parse(input: string, pos: number, ctx: ParseContext): ParseResult<N> {
239
+ if (guardable && ctx._probe === undefined && !ctx._tolerant && !startsFirstSet(combinator, input, pos)) {
240
+ if (failExpected === undefined) {
241
+ const e = deriveExpected(combinator)
242
+ failExpected = e.length > 0 ? e : [combinator._tag]
243
+ }
244
+ return { ok: false, expected: failExpected, span: { start: pos, end: pos } }
245
+ }
246
+ // Under a positioned-CST host a direct builder is bypassed, so capture must follow
247
+ // what the HOST reads. Mirrors `hostMode: 'cst'` in the compiled engine, which
248
+ // captures unconditionally there because it knows the mode at compile time.
249
+ const hasDirectValue = build !== undefined || project !== undefined
250
+ const hostCst = hasDirectValue && cstOutputHost(ctx.build)
251
+ const structural = !hasDirectValue
252
+ const hostCapturesThisType = structural
253
+ && def.type !== undefined
254
+ && ctx.build?._parsemanCaptureTrivia !== undefined
255
+ ? ctx.build._parsemanCaptureTrivia(def.type)
256
+ : undefined
257
+ // Structural nodes capture for their host by default, but the host may
258
+ // narrow that default by node type. Grammar-owned capture is authoritative:
259
+ // an explicit `captureTrivia`/`trailingTrivia` request cannot be disabled by
260
+ // the host. Direct builders (including a CST host that bypasses one) retain
261
+ // their existing arity/CST-host decision and never consult this predicate.
262
+ const effTrivia = structural
263
+ ? captureTrivia || trailingTrivia || hostCapturesThisType !== false
264
+ : capturesTrivia || hostCst
265
+ const effFields = capturesFields || (hostCst && hasOwnFields)
266
+ const saved = beginCstNodeCapture(ctx)
267
+ const savedFields = ctx._fields
268
+ ctx._fields = effFields ? [] : undefined
269
+ // Per-node-type trivia-kind mask: a structural (host-built) node may want
270
+ // only certain kinds captured (comments for Ruleset, whitespace for
271
+ // CompoundSelector). Scoped here, restored below — matches the compiled path.
272
+ const savedMask = ctx._triviaCaptureMask
273
+ if (build === undefined && project === undefined && ctx.build?._parsemanTriviaKinds !== undefined && def.type !== undefined) {
274
+ ctx._triviaCaptureMask = ctx.build._parsemanTriviaKinds(def.type)
275
+ }
276
+ // Short-circuit the per-node trivia push (scanTrivia gates on captureTrivia)
277
+ // without touching the global _triviaLog, which is committed independently.
278
+ if (!effTrivia) ctx.captureTrivia = false
279
+ let r = combinator.parse(input, pos, ctx)
280
+ if (r.ok && trailingTrivia && ctx.trivia) {
281
+ const end = consumeTrivia(input, r.span.end, ctx)
282
+ r = { ...r, span: { start: r.span.start, end } }
283
+ }
284
+ const fields = effFields ? buildFieldMap(ctx._fields) : undefined
285
+ ctx._fields = savedFields
286
+ ctx._triviaCaptureMask = savedMask
287
+ const { children, rawChildren, triviaLog } = endCstNodeCapture(ctx, saved)
288
+
289
+ if (!r.ok) return r
290
+
291
+ // unwrap/collapse: a single captured child IS the value — skip build.
292
+ const st = clonesState && ctx.state !== undefined ? Object.assign({}, ctx.state as Record<string, unknown>) : undefined
293
+ const nodeType = def.type ?? missingInferredType()
294
+ const cstOutput = cstOutputHost(ctx.build)
295
+ const span = ctx.trackLines ? annotateSpanFromLineContext(r.span, ctx) : r.span
296
+ // A direct builder that never declared `state` still owes the host its snapshot.
297
+ // The clone happens AFTER the body, so unlike trivia it needs no gate up front —
298
+ // build it here, on a branch the eval-AST path never takes. Matches what
299
+ // `hostMode: 'cst'` does in the compiled engine.
300
+ const hostState = clonesState
301
+ ? st
302
+ : ctx.state !== undefined ? Object.assign({}, ctx.state as Record<string, unknown>) : undefined
303
+ const built: unknown = unwrap && children.length === 1
304
+ ? unwrapChild(children[0])
305
+ : collapse && children.length === 1
306
+ ? children[0]
307
+ // Collapse applies wherever the node's VALUE comes from the host: a structural
308
+ // node, or ANY node under a positioned-CST host, where the direct builder is
309
+ // bypassed and the produced thing is a host-owned CST node either way. Gating on
310
+ // `!build` alone made `cstBuildHost({ collapse })` a documented no-op for every
311
+ // grammar whose rules carry reducers — silently, in both engines.
312
+ : (cstOutput || (!build && project === undefined))
313
+ && ctx.build?._parsemanCstCollapse
314
+ && children.length === 1
315
+ && rawChildren.length === 1
316
+ && ctx.build._parsemanCstCollapse(nodeType, children[0], children, rawChildren)
317
+ ? children[0]
318
+ : project !== undefined
319
+ ? cstOutput && ctx.build
320
+ ? ctx.build(nodeType, children, fields, span, rawChildren, triviaLog, hostState, tags)
321
+ : projectChild(children, project, nodeType)
322
+ : build
323
+ // A direct builder normally owns its result. The positioned-CST host is
324
+ // the one exception: it must never receive an arbitrary AST object as a
325
+ // child of a CST node, so build this grammar node through that host.
326
+ ? cstOutput && ctx.build
327
+ ? ctx.build(nodeType, children, fields, span, rawChildren, triviaLog, hostState, tags)
328
+ : build(children, fields, span, rawChildren, triviaLog, st)
329
+ // Structural node: a `ctx.build` host if present, else a default CST.
330
+ : ctx.build
331
+ ? ctx.build(nodeType, children, fields, span, rawChildren, triviaLog, st, tags)
332
+ : { _tag: 'node', type: nodeType, span: ctx.trackLines ? { ...span } : { start: r.span.start, end: r.span.end }, state: st ?? null, children }
333
+ const rawEntry = isCstChild(built)
334
+ ? built
335
+ // A direct semantic object is opaque to the raw CST, but its source is
336
+ // not. Preserve the exact matched span so legacy/structural parents can
337
+ // retain text and trivia without fabricating an empty token.
338
+ : { _tag: 'leaf', value: typeof built === 'string' ? built : typeof built === 'object' && built !== null ? input.slice(r.span.start, r.span.end) : '', span }
339
+ if (saved.buf !== undefined || saved.ch !== undefined) {
340
+ pushCstChild(ctx, built, rawEntry)
341
+ }
342
+ return { ok: true, value: built as N, span }
343
+ },
344
+ }
345
+ }
@@ -0,0 +1,55 @@
1
+ import type { Combinator, ParseContext, ParseResult, ParserMeta } from '../types.ts'
2
+ import { any } from './first-set.ts'
3
+ import { saveLookaheadMark, rollbackLookahead } from './trivia-skip.ts'
4
+
5
+ /**
6
+ * Negative lookahead. Succeeds (consuming nothing) when `combinator` fails;
7
+ * fails when `combinator` succeeds.
8
+ *
9
+ * The standard way to match a keyword without also matching the prefix
10
+ * of a longer identifier:
11
+ *
12
+ * const kwTrue = sequence(literal('true'), not(regex(/\w/)))
13
+ * // matches "true" in "true && x" but NOT in "trueish" or "trueness"
14
+ */
15
+ export function not(combinator: Combinator<unknown>): Combinator<null> {
16
+ const meta: ParserMeta = {
17
+ firstSet: any(), // we don't know what NOT matches
18
+ canMatchNewline: false,
19
+ isTrivia: false,
20
+ }
21
+
22
+ return {
23
+ _tag: 'not',
24
+ _meta: meta,
25
+ _def: { tag: 'not', parser: combinator },
26
+ parse(input: string, pos: number, ctx: ParseContext): ParseResult<null> {
27
+ // Negative lookahead is a pure predicate: whatever the inner attempt
28
+ // captured (CST leaves/rawChildren/per-node trivia/fields), recovered (a
29
+ // ParseError pushed to ctx._errors + embedded via captureError), or committed
30
+ // to the GLOBAL `_triviaLog` must be rolled back on BOTH outcomes — not()
31
+ // consumes nothing and so may leave no side effect.
32
+ //
33
+ // `rollbackLookahead`, not `rollbackCstCapture`: the latter's mark omits two
34
+ // further sinks a probe can write.
35
+ // - `_triviaLog`, which `scanTrivia().commit()` writes when the probed body
36
+ // skips ambient trivia between terms. Missing it duplicates every probed
37
+ // span once the region is parsed for real — `_triviaLog` has no dedup
38
+ // anywhere (`triviaEntries()` is a positional view over the flat array),
39
+ // so the duplicate reaches output.
40
+ // - `_probe.best`, the completions tracker, for the same reason `peek()`
41
+ // restores it: expectations raised INSIDE a zero-width probe are not
42
+ // reachable from the enclosing grammar at the cursor, so leaving them
43
+ // offers unreachable tokens as completions.
44
+ // Using the shared lookahead helper means neither can silently regress the
45
+ // next time a sink is added.
46
+ const mark = saveLookaheadMark(ctx)
47
+ const result = combinator.parse(input, pos, ctx)
48
+ rollbackLookahead(ctx, mark)
49
+ if (result.ok) {
50
+ return { ok: false, expected: [`not(${combinator._tag})`], span: { start: pos, end: pos } }
51
+ }
52
+ return { ok: true, value: null, span: { start: pos, end: pos } }
53
+ },
54
+ }
55
+ }
@@ -0,0 +1,266 @@
1
+ import type { Combinator } from '../types.ts'
2
+ import type { HostMode } from '../cst/host-mode.ts'
3
+ import { ref } from './ref.ts'
4
+ import { markUnusedValues } from '../compiler/value-usage.ts'
5
+ import { parser as grammarParser } from './grammar.ts'
6
+ import { collectGrammarReflection, NODE_TAG, NODE_TYPE, type GrammarWithReflection } from '../cst/reflection.ts'
7
+ import { attachGrammarReflection } from '../cst/reflection-attach.ts'
8
+
9
+ /**
10
+ * Non-enumerable key on a `rules()` result holding the factory's declaration
11
+ * order (the returned object's key order), which differs from the result's own
12
+ * reference-creation key order. Read by `parseman/spec` for source-order output.
13
+ */
14
+ export const RULE_ORDER = '__parsemanRuleOrder'
15
+
16
+ function tagRule(r: Combinator<unknown>, key: string): void {
17
+ ;(r as unknown as { _ruleName?: string })._ruleName = key
18
+ if (r._def.tag === 'node' && r._def.type === undefined) r._def.type = key
19
+ }
20
+
21
+ type DefinableRef = Combinator<unknown> & { define(p: Combinator<unknown>): void }
22
+
23
+ function isDefinableRef(v: unknown): v is DefinableRef {
24
+ return !!v
25
+ && typeof v === 'object'
26
+ && '_def' in v
27
+ && (v as { _def: { tag?: string } })._def.tag === 'lazy'
28
+ && typeof (v as { define?: unknown }).define === 'function'
29
+ }
30
+
31
+ function ruleNameOf(r: Combinator<unknown>): string | undefined {
32
+ return (r as unknown as { _ruleName?: string })._ruleName
33
+ }
34
+
35
+ function isNamedRuleRefForAnotherRule(r: Combinator<unknown>, key: string): boolean {
36
+ const name = r._def.tag === 'lazy' ? ruleNameOf(r) : undefined
37
+ return name !== undefined && name !== key
38
+ }
39
+
40
+ /**
41
+ * Define named grammar rules without forward declarations.
42
+ *
43
+ * Pass a factory that receives all rule names as references (via a Proxy)
44
+ * and returns a record of combinators. rules() handles creating ref()
45
+ * placeholders and wiring them up — the user never sees ref() at all.
46
+ *
47
+ * const { value } = rules(g => ({
48
+ * value: choice(g.object, g.array, str, num, bool, nil),
49
+ * object: transform(sequence('{', sepBy(g.pair, ','), '}'), Object.fromEntries),
50
+ * array: transform(sequence('[', sepBy(g.value, ','), ']'), ([, items]) => items),
51
+ * pair: transform(sequence(g.key, literal(':'), g.value), ([k,, v]) => [k, v]),
52
+ * }))
53
+ *
54
+ * Not every name in the factory must appear in the returned object — local helpers
55
+ * (like `comma`, `key`) can be plain const inside the factory and composed normally.
56
+ * Only names that OTHER rules reference via `g.xxx` need to be in the returned record.
57
+ *
58
+ * TypeScript: use an explicit type parameter for full type safety on `g`:
59
+ * rules<{ value: Combinator<JSONValue>; array: Combinator<JSONValue[]> }>(g => ({ ... }))
60
+ * Without it, `g.*` accesses are typed as `any` but the return is still inferred.
61
+ */
62
+ /**
63
+ * Grammar-level options for `rules()`. Parity with `parser({...})`, but declared
64
+ * ONCE for the whole grammar instead of wrapped around a scope. `trivia` becomes
65
+ * the ambient trivia for every rule (installed at the parse entry, inherited
66
+ * everywhere, incremental parse included); `parser({trivia})` / `noTrivia` still
67
+ * override it locally for a sub-region.
68
+ */
69
+ export type RulesOptions = {
70
+ /** Ambient trivia for the whole grammar. See `parser({ trivia })` for the shape
71
+ * (`null` clears it — equivalent to omitting it at the grammar level). */
72
+ trivia?: Combinator<unknown> | null
73
+ /**
74
+ * Ambient scan-skip for the whole grammar: opaque non-trivia units (strings,
75
+ * balanced brackets, …) that a `scanTo`/`balanced` with no explicit `skip`
76
+ * consults so a sentinel hidden inside one is never matched. Declared ONCE here
77
+ * and inherited everywhere, mirroring `trivia`. `null`/absent = none.
78
+ */
79
+ scanSkip?: Combinator<unknown>[] | null
80
+ /**
81
+ * Compile-time host mode for this grammar, same meaning as `compile(g, { hostMode })`
82
+ * and `compose(items, { hostMode })`: `'ast'` (default) emits each direct builder's
83
+ * own result and NO positioned-CST branch; `'cst'` builds every node through the
84
+ * `ctx.build` host and captures unconditionally.
85
+ *
86
+ * Declaring it HERE is what lets ONE grammar source serve both consumers under the
87
+ * macro, which cannot take a compile option any other way:
88
+ *
89
+ * ```ts
90
+ * const factory = (g: any) => ({ … })
91
+ * export const grammar = rules({ trivia: rw }, factory)
92
+ * export const cstGrammar = rules({ trivia: rw, hostMode: 'cst' }, factory)
93
+ * ```
94
+ *
95
+ * Two call sites over one factory, so the macro emits two independent top-level
96
+ * artifacts and each bundle tree-shakes away the one it does not import. Neither
97
+ * pays the other's cost — which is the whole reason host mode is a compile-time
98
+ * decision rather than a per-node runtime read.
99
+ *
100
+ * The INTERPRETER routes dynamically off the host and does not need this; it is
101
+ * still recorded, so `run()` can refuse a mismatched host once per parse instead of
102
+ * quietly producing the wrong tree shape.
103
+ */
104
+ hostMode?: HostMode
105
+ /**
106
+ * Compile-time line tracking for this grammar. Under the macro this emits a
107
+ * separate line-aware artifact from the same factory; the default artifact
108
+ * remains free of line-tracking helpers and branches.
109
+ */
110
+ trackLines?: boolean
111
+ }
112
+
113
+ // Options-first, mirroring `parser({ opts }, combinator)` — set once on the grammar
114
+ // vs scope it locally, same options in the same position. The bare `rules(factory)`
115
+ // form is unchanged. The impl also tolerates the legacy `rules(factory, opts)` order.
116
+ type RuleNodeType<K extends string, C> =
117
+ C extends { readonly [NODE_TYPE]?: infer T extends string }
118
+ ? [T] extends [never] ? K : T
119
+ : K
120
+
121
+ type RuleNodeTag<C> =
122
+ C extends { readonly [NODE_TAG]?: infer Tag extends string } ? Tag : never
123
+
124
+ type RuleMapNodeType<T extends Record<string, Combinator<unknown>>> = {
125
+ [K in keyof T & string]: RuleNodeType<K, T[K]>
126
+ }[keyof T & string]
127
+
128
+ type RuleMapNodeTag<T extends Record<string, Combinator<unknown>>> = {
129
+ [K in keyof T & string]: RuleNodeTag<T[K]>
130
+ }[keyof T & string]
131
+
132
+ type RulesResult<T extends Record<string, Combinator<unknown>>> =
133
+ T & GrammarWithReflection<RuleMapNodeType<T>, RuleMapNodeTag<T>>
134
+
135
+ export function rules<T extends Record<string, Combinator<unknown>>>(factory: (self: any) => T): RulesResult<T>
136
+ export function rules<T extends Record<string, Combinator<unknown>>>(options: RulesOptions, factory: (self: any) => T): RulesResult<T>
137
+ export function rules<T extends Record<string, Combinator<unknown>>>(
138
+ a: ((self: any) => T) | RulesOptions,
139
+ b?: (self: any) => T,
140
+ ): RulesResult<T> {
141
+ const factory = (typeof a === 'function' ? a : b) as (self: any) => T
142
+ const options = (typeof a === 'function' ? b : a) as RulesOptions | undefined
143
+ const cache: Partial<T> = {}
144
+
145
+ // Proxy: accessing any property creates a ref() placeholder on first touch.
146
+ const proxy = new Proxy(cache, {
147
+ get(target, key) {
148
+ if (typeof key !== 'string') return undefined
149
+ const record = target as Record<string, Combinator<unknown>>
150
+ if (!(key in record)) {
151
+ const r = ref()
152
+ // Tag the placeholder with its rule name so the linkable compiler can
153
+ // emit a by-name `_r_<key>` call for a reference to a rule defined in
154
+ // ANOTHER artifact (resolved at fuse time) — see compileLinkable.
155
+ tagRule(r, key)
156
+ record[key] = r
157
+ }
158
+ return record[key]
159
+ },
160
+ })
161
+
162
+ // Evaluate all rule definitions. JavaScript evaluates object-literal values
163
+ // left-to-right, so any `g.ruleName` access inside triggers placeholder creation
164
+ // before the rule's own parser is built — enabling forward references.
165
+ const definitions = factory(proxy)
166
+
167
+ // Fill each ref with its actual definition, or store directly if never accessed via proxy.
168
+ for (const key of Object.keys(definitions)) {
169
+ const placeholder = (cache as Record<string, Combinator<unknown>>)[key]
170
+ const parser = (definitions as Record<string, Combinator<unknown>>)[key]!
171
+ if (placeholder === parser) {
172
+ throw new Error(`rules(): rule "${key}" cannot be a direct alias to itself`)
173
+ }
174
+ if (isDefinableRef(placeholder)) {
175
+ if (!isNamedRuleRefForAnotherRule(parser, key)) tagRule(parser, key)
176
+ placeholder.define(parser)
177
+ // Propagate actual first-set so later choices wrapping this ref get correct dispatch.
178
+ placeholder._meta.firstSet = parser._meta.firstSet
179
+ placeholder._meta.canMatchNewline = parser._meta.canMatchNewline
180
+ } else if (isNamedRuleRefForAnotherRule(parser, key)) {
181
+ const alias = ref()
182
+ tagRule(alias, key)
183
+ alias.define(parser)
184
+ alias._meta.firstSet = parser._meta.firstSet
185
+ alias._meta.canMatchNewline = parser._meta.canMatchNewline
186
+ ;(cache as Record<string, Combinator<unknown>>)[key] = alias
187
+ } else {
188
+ tagRule(parser, key)
189
+ ;(cache as Record<string, Combinator<unknown>>)[key] = parser
190
+ }
191
+ }
192
+
193
+ // Declare the grammar-level ambient trivia on every rule, so parsing ANY rule
194
+ // as an entry installs it (run()/parse() read this), and the macro can seed the
195
+ // compiled map. `parser({trivia})` / `noTrivia` still override it locally.
196
+ // `!= null`: a `trivia: null` grammar clears trivia — same as omitting it at the
197
+ // grammar level — so store nothing (and never write null, which has no `_meta`).
198
+ if (options?.trivia != null) {
199
+ for (const key of Object.keys(definitions)) {
200
+ const rule = (cache as Record<string, Combinator<unknown>>)[key]
201
+ // Skip trivia rules (e.g. the grammar's `rw`, returned so the driver can
202
+ // reach it as `g.rw`): a trivia rule must never carry ambient trivia, or it
203
+ // would recursively skip trivia within itself. Mirrors the codegen guard.
204
+ if (rule && !rule._meta.isTrivia) (rule._meta as { grammarTrivia?: Combinator<unknown> }).grammarTrivia = options.trivia
205
+ }
206
+ }
207
+
208
+ // Grammar-level ambient scan-skip, mirroring the trivia stamp above: every
209
+ // non-trivia rule carries it so any parse entry installs `ctx.scanSkip` and the
210
+ // compiled map can seed it. `!= null` so `scanSkip: null` clears (stores nothing).
211
+ if (options?.scanSkip != null) {
212
+ for (const key of Object.keys(definitions)) {
213
+ const rule = (cache as Record<string, Combinator<unknown>>)[key]
214
+ if (rule && !rule._meta.isTrivia) (rule._meta as { grammarScanSkip?: Combinator<unknown>[] }).grammarScanSkip = options.scanSkip
215
+ }
216
+ }
217
+
218
+ // Grammar-level host mode, mirroring the two stamps above. Only `'cst'` is recorded:
219
+ // `'ast'` is the default everywhere, so stamping it would put a field on every rule of
220
+ // every grammar to say "unchanged". Trivia rules are skipped for the same reason as
221
+ // above — they build no nodes, so the mode is meaningless on them.
222
+ if (options?.hostMode === 'cst') {
223
+ for (const key of Object.keys(definitions)) {
224
+ const rule = (cache as Record<string, Combinator<unknown>>)[key]
225
+ if (rule && !rule._meta.isTrivia) (rule._meta as { grammarHostMode?: HostMode }).grammarHostMode = 'cst'
226
+ }
227
+ }
228
+
229
+ if (options?.trackLines === true) {
230
+ for (const key of Object.keys(definitions)) {
231
+ const rule = (cache as Record<string, Combinator<unknown>>)[key]
232
+ if (rule && !rule._meta.isTrivia) (rule._meta as { grammarTrackLines?: true }).grammarTrackLines = true
233
+ }
234
+ for (const key of Object.keys(definitions)) {
235
+ const rule = (cache as Record<string, Combinator<unknown>>)[key]
236
+ if (rule && !rule._meta.isTrivia && rule._def.tag !== 'grammar') {
237
+ const wrapped = grammarParser({ trackLines: true }, rule)
238
+ tagRule(wrapped, key)
239
+ ;(wrapped._meta as { grammarTrackLines?: true }).grammarTrackLines = true
240
+ ;(cache as Record<string, Combinator<unknown>>)[key] = wrapped
241
+ }
242
+ }
243
+ }
244
+
245
+ // Dead-value analysis: mark container aggregates that only feed a node()'s
246
+ // capture so the interpreter (and, via the same flag, the compiled output) skips
247
+ // building them. Each rule is its own root — refs are boundaries (see value-usage).
248
+ for (const key of Object.keys(definitions)) {
249
+ markUnusedValues((cache as Record<string, Combinator<unknown>>)[key]!)
250
+ }
251
+
252
+ // Record the factory's DECLARATION order (the returned object's key order).
253
+ // `cache`'s own key order is reference-creation order — a Proxy artifact — so
254
+ // it can lead with an internal rule instead of the entry rule. Consumers that
255
+ // want the order the author actually wrote (e.g. `parseman/spec`) read this.
256
+ // Non-enumerable, so Object.keys / spread / for-in over the grammar are
257
+ // unaffected and every existing consumer sees exactly the rules it did before.
258
+ Object.defineProperty(cache, RULE_ORDER, {
259
+ value: Object.keys(definitions),
260
+ enumerable: false,
261
+ configurable: true,
262
+ })
263
+ attachGrammarReflection(cache, collectGrammarReflection(Object.keys(definitions).map(key => [key, (cache as Record<string, Combinator<unknown>>)[key]!])))
264
+
265
+ return cache as RulesResult<T>
266
+ }