parseman 0.46.0 → 0.47.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (316) hide show
  1. package/README.md +8 -3
  2. package/dist/analysis/choice-cost-render.d.ts +12 -4
  3. package/dist/analysis/choice-cost-render.d.ts.map +1 -1
  4. package/dist/analysis/choice-cost.d.ts +2 -2
  5. package/dist/analysis/choice-cost.d.ts.map +1 -1
  6. package/dist/analysis/commitment.d.ts +131 -0
  7. package/dist/analysis/commitment.d.ts.map +1 -0
  8. package/dist/analysis/diagnostics.cjs +9929 -7465
  9. package/dist/analysis/diagnostics.cjs.map +3 -4
  10. package/dist/analysis/diagnostics.d.ts +2 -2
  11. package/dist/analysis/diagnostics.js +9929 -7465
  12. package/dist/analysis/diagnostics.js.map +3 -4
  13. package/dist/analysis/duplication.d.ts.map +1 -1
  14. package/dist/analysis/fix.d.ts +3 -3
  15. package/dist/analysis/fix.d.ts.map +1 -1
  16. package/dist/analysis/format-number.d.ts +23 -0
  17. package/dist/analysis/format-number.d.ts.map +1 -0
  18. package/dist/analysis/gating.d.ts +39 -2
  19. package/dist/analysis/gating.d.ts.map +1 -1
  20. package/dist/analysis/rebuild.d.ts.map +1 -1
  21. package/dist/analysis/terminal.d.ts +5 -2
  22. package/dist/analysis/terminal.d.ts.map +1 -1
  23. package/dist/cli/index.cjs +10865 -8535
  24. package/dist/cli/index.cjs.map +3 -4
  25. package/dist/cli/index.js +10865 -8535
  26. package/dist/cli/index.js.map +3 -4
  27. package/dist/combinators/adjacency.d.ts +100 -0
  28. package/dist/combinators/adjacency.d.ts.map +1 -0
  29. package/dist/combinators/choice.d.ts.map +1 -1
  30. package/dist/combinators/completions.d.ts.map +1 -1
  31. package/dist/combinators/dispatch.d.ts +8 -1
  32. package/dist/combinators/dispatch.d.ts.map +1 -1
  33. package/dist/combinators/expect.d.ts.map +1 -1
  34. package/dist/combinators/first-set.d.ts.map +1 -1
  35. package/dist/combinators/gate.d.ts.map +1 -1
  36. package/dist/combinators/grammar.d.ts +22 -0
  37. package/dist/combinators/grammar.d.ts.map +1 -1
  38. package/dist/combinators/literal.d.ts +8 -0
  39. package/dist/combinators/literal.d.ts.map +1 -1
  40. package/dist/combinators/map.d.ts +0 -1
  41. package/dist/combinators/map.d.ts.map +1 -1
  42. package/dist/combinators/node.d.ts +13 -3
  43. package/dist/combinators/node.d.ts.map +1 -1
  44. package/dist/combinators/parser.d.ts +1 -1
  45. package/dist/combinators/parser.d.ts.map +1 -1
  46. package/dist/combinators/ref.d.ts.map +1 -1
  47. package/dist/combinators/regex.d.ts +1 -16
  48. package/dist/combinators/regex.d.ts.map +1 -1
  49. package/dist/combinators/repeat.d.ts +30 -2
  50. package/dist/combinators/repeat.d.ts.map +1 -1
  51. package/dist/combinators/scanTo.d.ts +71 -1
  52. package/dist/combinators/scanTo.d.ts.map +1 -1
  53. package/dist/combinators/sequence.d.ts.map +1 -1
  54. package/dist/combinators/token.d.ts.map +1 -1
  55. package/dist/combinators/trivia-boundary.d.ts +26 -0
  56. package/dist/combinators/trivia-boundary.d.ts.map +1 -0
  57. package/dist/combinators/trivia-skip.d.ts +40 -1
  58. package/dist/combinators/trivia-skip.d.ts.map +1 -1
  59. package/dist/combinators/withCtx.d.ts.map +1 -1
  60. package/dist/compiler/compile-linkable-table.d.ts +104 -0
  61. package/dist/compiler/compile-linkable-table.d.ts.map +1 -0
  62. package/dist/compiler/degradation.d.ts +0 -2
  63. package/dist/compiler/degradation.d.ts.map +1 -1
  64. package/dist/compiler/fields.d.ts +13 -0
  65. package/dist/compiler/fields.d.ts.map +1 -1
  66. package/dist/compiler/grammar-coverage-ids.d.ts +5 -5
  67. package/dist/compiler/grammar-coverage-ids.d.ts.map +1 -1
  68. package/dist/compiler/ir-serialize.d.ts.map +1 -1
  69. package/dist/compiler/linker.d.ts +47 -53
  70. package/dist/compiler/linker.d.ts.map +1 -1
  71. package/dist/compiler/token-alphabet.d.ts +81 -0
  72. package/dist/compiler/token-alphabet.d.ts.map +1 -0
  73. package/dist/compiler/token-dispatch.d.ts +95 -0
  74. package/dist/compiler/token-dispatch.d.ts.map +1 -0
  75. package/dist/compiler/token-scanner.d.ts +53 -0
  76. package/dist/compiler/token-scanner.d.ts.map +1 -0
  77. package/dist/compiler/value-usage.d.ts.map +1 -1
  78. package/dist/coverage.d.ts +1 -1
  79. package/dist/coverage.d.ts.map +1 -1
  80. package/dist/cst/capture-buffer.d.ts +35 -0
  81. package/dist/cst/capture-buffer.d.ts.map +1 -1
  82. package/dist/cst/reflection-attach.d.ts +8 -0
  83. package/dist/cst/reflection-attach.d.ts.map +1 -0
  84. package/dist/cst/reflection-symbols.d.ts +10 -0
  85. package/dist/cst/reflection-symbols.d.ts.map +1 -0
  86. package/dist/cst/reflection.d.ts +2 -4
  87. package/dist/cst/reflection.d.ts.map +1 -1
  88. package/dist/cst/root-trivia-scope.d.ts +26 -0
  89. package/dist/cst/root-trivia-scope.d.ts.map +1 -0
  90. package/dist/cst/trivia-charscan.d.ts +18 -0
  91. package/dist/cst/trivia-charscan.d.ts.map +1 -0
  92. package/dist/cst/trivia-kinds.d.ts +19 -0
  93. package/dist/cst/trivia-kinds.d.ts.map +1 -1
  94. package/dist/functional/doc.d.ts.map +1 -1
  95. package/dist/functional/run-tabled.d.ts +12 -0
  96. package/dist/functional/run-tabled.d.ts.map +1 -0
  97. package/dist/functional/run.d.ts +25 -42
  98. package/dist/functional/run.d.ts.map +1 -1
  99. package/dist/grammar-metadata.d.ts +3 -0
  100. package/dist/grammar-metadata.d.ts.map +1 -0
  101. package/dist/index.cjs +9224 -7203
  102. package/dist/index.cjs.map +3 -4
  103. package/dist/index.d.ts +18 -9
  104. package/dist/index.d.ts.map +1 -1
  105. package/dist/index.js +9219 -7198
  106. package/dist/index.js.map +3 -4
  107. package/dist/language-service/index.cjs +311 -121
  108. package/dist/language-service/index.cjs.map +3 -4
  109. package/dist/language-service/index.js +311 -121
  110. package/dist/language-service/index.js.map +3 -4
  111. package/dist/oracle/index.cjs.map +0 -1
  112. package/dist/oracle/index.js.map +0 -1
  113. package/dist/parse-context.d.ts +49 -0
  114. package/dist/parse-context.d.ts.map +1 -0
  115. package/dist/plugin/evaluator.d.ts +40 -1
  116. package/dist/plugin/evaluator.d.ts.map +1 -1
  117. package/dist/plugin/index.cjs +11146 -9593
  118. package/dist/plugin/index.cjs.map +3 -4
  119. package/dist/plugin/index.d.ts.map +1 -1
  120. package/dist/plugin/index.js +11146 -9593
  121. package/dist/plugin/index.js.map +3 -4
  122. package/dist/regex/classes.d.ts +28 -0
  123. package/dist/regex/classes.d.ts.map +1 -1
  124. package/dist/regex/first-set.d.ts +14 -0
  125. package/dist/regex/first-set.d.ts.map +1 -1
  126. package/dist/run/index.cjs +136 -51
  127. package/dist/run/index.cjs.map +2 -3
  128. package/dist/run/index.js +136 -51
  129. package/dist/run/index.js.map +2 -3
  130. package/dist/spec/index.cjs +2898 -1623
  131. package/dist/spec/index.cjs.map +3 -4
  132. package/dist/spec/index.js +2898 -1623
  133. package/dist/spec/index.js.map +3 -4
  134. package/dist/spec/model.d.ts +8 -0
  135. package/dist/spec/model.d.ts.map +1 -1
  136. package/dist/table/assemble.d.ts +307 -0
  137. package/dist/table/assemble.d.ts.map +1 -0
  138. package/dist/table/cell.d.ts +42 -0
  139. package/dist/table/cell.d.ts.map +1 -0
  140. package/dist/table/child-slots.d.ts +30 -0
  141. package/dist/table/child-slots.d.ts.map +1 -0
  142. package/dist/table/compile-rule-map.d.ts +136 -0
  143. package/dist/table/compile-rule-map.d.ts.map +1 -0
  144. package/dist/table/compile.d.ts +56 -0
  145. package/dist/table/compile.d.ts.map +1 -0
  146. package/dist/table/duplication-hook.d.ts +32 -0
  147. package/dist/table/duplication-hook.d.ts.map +1 -0
  148. package/dist/table/emit-assembly.d.ts +139 -0
  149. package/dist/table/emit-assembly.d.ts.map +1 -0
  150. package/dist/table/emit.d.ts +113 -0
  151. package/dist/table/emit.d.ts.map +1 -0
  152. package/dist/table/encode-baseline.d.ts +16 -0
  153. package/dist/table/encode-baseline.d.ts.map +1 -0
  154. package/dist/table/encode.d.ts +71 -0
  155. package/dist/table/encode.d.ts.map +1 -0
  156. package/dist/table/exec-baseline.d.ts +9 -0
  157. package/dist/table/exec-baseline.d.ts.map +1 -0
  158. package/dist/table/exec.d.ts +72 -0
  159. package/dist/table/exec.d.ts.map +1 -0
  160. package/dist/table/fold.d.ts +25 -0
  161. package/dist/table/fold.d.ts.map +1 -0
  162. package/dist/table/index.cjs +11375 -0
  163. package/dist/table/index.cjs.map +6 -0
  164. package/dist/table/index.d.ts +100 -0
  165. package/dist/table/index.d.ts.map +1 -0
  166. package/dist/table/index.js +11352 -0
  167. package/dist/table/index.js.map +6 -0
  168. package/dist/table/inspect.d.ts +17 -0
  169. package/dist/table/inspect.d.ts.map +1 -0
  170. package/dist/table/ops.d.ts +446 -0
  171. package/dist/table/ops.d.ts.map +1 -0
  172. package/dist/table/program.d.ts +493 -0
  173. package/dist/table/program.d.ts.map +1 -0
  174. package/dist/table/run-support.d.ts +38 -0
  175. package/dist/table/run-support.d.ts.map +1 -0
  176. package/dist/{compiler/scannable-run.d.ts → table/scan-shapes.d.ts} +14 -13
  177. package/dist/table/scan-shapes.d.ts.map +1 -0
  178. package/dist/table/site-labels.d.ts +58 -0
  179. package/dist/table/site-labels.d.ts.map +1 -0
  180. package/dist/table/stamp.d.ts +70 -0
  181. package/dist/table/stamp.d.ts.map +1 -0
  182. package/dist/types.d.ts +80 -24
  183. package/dist/types.d.ts.map +1 -1
  184. package/dist/version.d.ts +1 -1
  185. package/package.json +25 -2
  186. package/src/analysis/choice-cost-gate.ts +359 -0
  187. package/src/analysis/choice-cost-render.ts +293 -0
  188. package/src/analysis/choice-cost.ts +1008 -0
  189. package/src/analysis/commitment.ts +557 -0
  190. package/src/analysis/corpus.ts +130 -0
  191. package/src/analysis/diagnose-render.ts +625 -0
  192. package/src/analysis/diagnose.ts +317 -0
  193. package/src/analysis/diagnostics.ts +33 -0
  194. package/src/analysis/duplication.ts +1955 -0
  195. package/src/analysis/fix-render.ts +217 -0
  196. package/src/analysis/fix.ts +669 -0
  197. package/src/analysis/format-number.ts +31 -0
  198. package/src/analysis/gating.ts +792 -0
  199. package/src/analysis/grammar.ts +72 -0
  200. package/src/analysis/rebuild.ts +287 -0
  201. package/src/analysis/terminal.ts +336 -0
  202. package/src/cli/index.ts +413 -0
  203. package/src/combinators/adjacency.ts +171 -0
  204. package/src/combinators/attempt.ts +41 -0
  205. package/src/combinators/case-fold.ts +90 -0
  206. package/src/combinators/choice.ts +466 -0
  207. package/src/combinators/completions.ts +64 -0
  208. package/src/combinators/dispatch.ts +358 -0
  209. package/src/combinators/expect.ts +159 -0
  210. package/src/combinators/first-set.ts +322 -0
  211. package/src/combinators/gate.ts +50 -0
  212. package/src/combinators/grammar.ts +272 -0
  213. package/src/combinators/keywords.ts +145 -0
  214. package/src/combinators/literal.ts +102 -0
  215. package/src/combinators/map.ts +106 -0
  216. package/src/combinators/node.ts +345 -0
  217. package/src/combinators/not.ts +55 -0
  218. package/src/combinators/parser.ts +266 -0
  219. package/src/combinators/peek.ts +63 -0
  220. package/src/combinators/probe.ts +25 -0
  221. package/src/combinators/ref.ts +109 -0
  222. package/src/combinators/regex.ts +170 -0
  223. package/src/combinators/repeat.ts +561 -0
  224. package/src/combinators/scanTo.ts +393 -0
  225. package/src/combinators/sequence.ts +221 -0
  226. package/src/combinators/token.ts +120 -0
  227. package/src/combinators/trivia-boundary.ts +48 -0
  228. package/src/combinators/trivia-skip.ts +821 -0
  229. package/src/combinators/withCtx.ts +45 -0
  230. package/src/compiler/build-arity.ts +330 -0
  231. package/src/compiler/compile-linkable-table.ts +209 -0
  232. package/src/compiler/degradation.ts +266 -0
  233. package/src/compiler/fields.ts +191 -0
  234. package/src/compiler/grammar-coverage-ids.ts +129 -0
  235. package/src/compiler/ir-serialize.ts +615 -0
  236. package/src/compiler/linker.ts +954 -0
  237. package/src/compiler/token-alphabet.ts +209 -0
  238. package/src/compiler/token-dispatch.ts +353 -0
  239. package/src/compiler/token-scanner.ts +318 -0
  240. package/src/compiler/value-usage.ts +147 -0
  241. package/src/coverage.ts +383 -0
  242. package/src/cst/capture-buffer.ts +297 -0
  243. package/src/cst/host-mode.ts +69 -0
  244. package/src/cst/reflection-attach.ts +11 -0
  245. package/src/cst/reflection-symbols.ts +9 -0
  246. package/src/cst/reflection.ts +149 -0
  247. package/src/cst/relative-spans.ts +207 -0
  248. package/src/cst/root-trivia-scope.ts +30 -0
  249. package/src/cst/trivia-charscan.ts +312 -0
  250. package/src/cst/trivia-entries.ts +285 -0
  251. package/src/cst/trivia-index.ts +131 -0
  252. package/src/cst/trivia-kinds.ts +358 -0
  253. package/src/cst/types.ts +76 -0
  254. package/src/cst/walk.ts +101 -0
  255. package/src/functional/doc.ts +959 -0
  256. package/src/functional/run-tabled.ts +153 -0
  257. package/src/functional/run.ts +558 -0
  258. package/src/grammar-metadata.ts +2 -0
  259. package/src/index.ts +189 -0
  260. package/src/language-service/index.ts +221 -0
  261. package/src/line-index.ts +131 -0
  262. package/src/oracle/digest.ts +620 -0
  263. package/src/oracle/index.ts +55 -0
  264. package/src/parse-context.ts +91 -0
  265. package/src/plugin/direct-builder-static.ts +102 -0
  266. package/src/plugin/evaluator.ts +1505 -0
  267. package/src/plugin/free-identifiers.ts +338 -0
  268. package/src/plugin/index.ts +2281 -0
  269. package/src/plugin/reducer-resolver.ts +606 -0
  270. package/src/recovery/scan.ts +155 -0
  271. package/src/regex/classes.ts +183 -0
  272. package/src/regex/first-set.ts +316 -0
  273. package/src/run/index.ts +31 -0
  274. package/src/spec/ebnf.ts +128 -0
  275. package/src/spec/index.ts +54 -0
  276. package/src/spec/model.ts +636 -0
  277. package/src/spec/railroad-lib.ts +559 -0
  278. package/src/spec/railroad.ts +256 -0
  279. package/src/table/assemble.ts +2994 -0
  280. package/src/table/cell.ts +42 -0
  281. package/src/table/child-slots.ts +113 -0
  282. package/src/table/compile-rule-map.ts +336 -0
  283. package/src/table/compile.ts +244 -0
  284. package/src/table/duplication-hook.ts +89 -0
  285. package/src/table/emit-assembly.ts +1766 -0
  286. package/src/table/emit.ts +418 -0
  287. package/src/table/encode-baseline.ts +340 -0
  288. package/src/table/encode.ts +1569 -0
  289. package/src/table/exec-baseline.ts +537 -0
  290. package/src/table/exec.ts +1672 -0
  291. package/src/table/fold.ts +38 -0
  292. package/src/table/index.ts +108 -0
  293. package/src/table/inspect.ts +52 -0
  294. package/src/table/ops.ts +459 -0
  295. package/src/table/program.ts +855 -0
  296. package/src/table/run-support.ts +66 -0
  297. package/src/table/scan-shapes.ts +1578 -0
  298. package/src/table/site-labels.ts +228 -0
  299. package/src/table/stamp.ts +229 -0
  300. package/src/types.ts +563 -0
  301. package/src/version.ts +16 -0
  302. package/dist/compiler/codegen.d.ts +0 -334
  303. package/dist/compiler/codegen.d.ts.map +0 -1
  304. package/dist/compiler/inline-build.d.ts +0 -11
  305. package/dist/compiler/inline-build.d.ts.map +0 -1
  306. package/dist/compiler/inline-callback.d.ts +0 -21
  307. package/dist/compiler/inline-callback.d.ts.map +0 -1
  308. package/dist/compiler/line-index.d.ts +0 -2
  309. package/dist/compiler/line-index.d.ts.map +0 -1
  310. package/dist/compiler/module-hoist.d.ts +0 -106
  311. package/dist/compiler/module-hoist.d.ts.map +0 -1
  312. package/dist/compiler/scannable-run.d.ts.map +0 -1
  313. package/dist/compiler/scannable-terminal.d.ts +0 -23
  314. package/dist/compiler/scannable-terminal.d.ts.map +0 -1
  315. package/dist/compiler/trivia-fast-path.d.ts +0 -38
  316. package/dist/compiler/trivia-fast-path.d.ts.map +0 -1
package/src/index.ts ADDED
@@ -0,0 +1,189 @@
1
+ export type { Combinator, CompiledParser, ParseResult, ParseOk, ParseFail, ParseContext, ParseError, Span, ParserMeta, FirstSet, CharRange, ParserDef, ChoiceStrategy, AutoNotCheck, GatedArm, DispatchCase, DispatchMatcherCase, BuildHost, CstCollapsePredicate, FieldCapture, FieldMap } from './types.ts'
2
+
3
+ export { literal } from './combinators/literal.ts'
4
+ export type { LiteralOptions } from './combinators/literal.ts'
5
+
6
+ // `regex()` derives its own first-set: the analyzer (`./regex/first-set.ts`) is
7
+ // imported directly by `./combinators/regex.ts`, NOT registered here. It used to
8
+ // be wired in from this entry, which made `regex()`'s first-set depend on whether
9
+ // the library entry was in the bundle's module graph — and `parseman/table` is a
10
+ // separate graph, so its `regex()` fell back to the permissive `any()` set and
11
+ // `classifiedTrivia()` rejected every table-lowered grammar. See the comment on
12
+ // the import in `./combinators/regex.ts`.
13
+ export { regex } from './combinators/regex.ts'
14
+ export { keywords, word, makeWord } from './combinators/keywords.ts'
15
+ export type { KeywordsOptions, WordOptions } from './combinators/keywords.ts'
16
+
17
+ export { sequence } from './combinators/sequence.ts'
18
+ export { choice } from './combinators/choice.ts'
19
+ export { dispatch, endsWith, makeWhen, matches, otherwise, routed, startsWith, when } from './combinators/dispatch.ts'
20
+ export type { DispatchArm, DispatchOtherwise, DispatchStringMatcher, DispatchWhen, DispatchWhenFactory, DispatchWhenOptions, DispatchWhenMatcher } from './combinators/dispatch.ts'
21
+ export { attempt } from './combinators/attempt.ts'
22
+ export { many, oneOrMore, optional, sepBy, oneOrMoreSep, keepSeparator } from './combinators/repeat.ts'
23
+ export type { RepeatOptions, SepByOptions, TrailingSeparator, KeptSeparator } from './combinators/repeat.ts'
24
+ export { rules } from './combinators/parser.ts'
25
+ export { ref } from './combinators/ref.ts'
26
+ export { not } from './combinators/not.ts'
27
+ export { peek } from './combinators/peek.ts'
28
+ export { node } from './combinators/node.ts'
29
+ export type { BuildNode, NodeCombinator, NodeOptions, NodeProjectOptions } from './combinators/node.ts'
30
+ // lazy() is intentionally NOT exported.
31
+ export { transform, trivia, classifiedTrivia, label, field } from './combinators/map.ts'
32
+ export { parse, parser, noTrivia } from './combinators/grammar.ts'
33
+ export type { ParseOptions, ParserOptions, ParsemanParser } from './combinators/grammar.ts'
34
+ export { token, leaf } from './combinators/token.ts'
35
+
36
+ export { compile } from './table/compile.ts'
37
+ /*
38
+ * `CompiledParser` is exported above, from `./types.ts` — it is the contract
39
+ * `compile()` answers to, so it lives with the library's types.
40
+ *
41
+ * TWO NAMES USED TO SIT HERE and no longer do. Neither was ever documented and
42
+ * nothing outside the source lowering referenced either; they were in the barrel
43
+ * because the barrel re-exported the engine, not because anything asked for them.
44
+ *
45
+ * `LinkablePieces` — the SOURCE-LOWERING linkable IR. Every field of it is
46
+ * source text or an input to resolving source text (see
47
+ * `compile-linkable-table.ts`), so it has no meaning for a
48
+ * table artifact and dies with the source lowering. The
49
+ * public composable-piece type is `LinkableTable`, below.
50
+ * `DuplicationOption` — an option bag for the source lowering's duplication
51
+ * diagnostic, used nowhere else. The public diagnostic
52
+ * surface is `analyzeDuplication` /
53
+ * `AnalyzeDuplicationOptions`, exported further down.
54
+ *
55
+ * `HostMode` stays: it names the `hostMode` option on `rules()` and `compile()`,
56
+ * which is public. It now comes from the artifact↔host contract module that has
57
+ * owned the definition all along.
58
+ */
59
+ export type { HostMode } from './cst/host-mode.ts'
60
+ /**
61
+ * `linkable()` produces TABLES too — the table counterpart of `compileLinkable`.
62
+ * A piece is a table when it is self-contained, and always carries its IR, which
63
+ * is what makes table-to-table composition a rule-map merge and one encode rather
64
+ * than a merge of two already-encoded programs.
65
+ */
66
+ export { compileLinkableTable, type LinkableTable, type LinkableTableOptions } from './compiler/compile-linkable-table.ts'
67
+ export type { DuplicationOption } from './table/duplication-hook.ts'
68
+
69
+ // THE diagnostic entry point. `compile()` never reports anything: diagnostics are a
70
+ // deliberate act, not a side effect of producing an artifact. `diagnoseGrammar` accepts
71
+ // any grammar shape, returns a deterministic machine-readable object that fails CLOSED,
72
+ // and `formatGrammarDiagnosis` renders it for a human.
73
+ //
74
+ // const d = diagnoseGrammar(myGrammar)
75
+ // if (!d.ok) { console.error(formatGrammarDiagnosis(d).join('\n')); process.exit(1) }
76
+ //
77
+ // `examinedNothing(d)` separates "measured, and it is bad" from "could not measure":
78
+ // `ok` is false for both, and a gate that treats them the same reports a confident
79
+ // finding count over a grammar it never opened.
80
+ //
81
+ // The `analyze*` functions below remain the lower-level surface it is built on.
82
+ export { diagnoseGrammar, examinedNothing, formatGrammarDiagnosis } from './analysis/diagnose.ts'
83
+ export type {
84
+ GrammarDiagnosis, DiagnosisFinding, DiagnosisCode, DiagnosisSeverity,
85
+ DiagnosableGrammar, DiagnoseOptions,
86
+ } from './analysis/diagnose.ts'
87
+
88
+ export { analyzeGating, analyzeGatingRules, formatGatingWarnings, firstSetToString } from './analysis/gating.ts'
89
+ export type {
90
+ GatingReport, ChoiceGating, AnyArm, Overlap, AntiPattern, Unanalysable,
91
+ FirstSetCause, ChoiceStrategyTag, AnalyzeGatingOptions,
92
+ } from './analysis/gating.ts'
93
+ // Analyze a WHOLE grammar, including a `compose()` result — whose fused map holds
94
+ // rule FUNCTIONS, not combinators, and so cannot be walked by `analyzeGatingRules`
95
+ // directly. This is the entry point a composed grammar's author wants.
96
+ export { analyzeGrammarGating } from './analysis/grammar.ts'
97
+
98
+ // Choice-cost diagnostics: the static shared-prefix inventory, the interpreted
99
+ // corpus wasted-work profile, and the gate policy over them. Quiet by default —
100
+ // nothing here runs or prints unless it is called.
101
+ export { analyzeChoiceInventory, profileWastedWork, choiceSiteKey, armLabel } from './analysis/choice-cost.ts'
102
+ export type {
103
+ ChoiceSite, ChoiceInventoryEntry, ChoiceInventoryReport, PrefixGroup,
104
+ ArmDeclineReason, SiteDeclineReason, WastedWorkCorpusEntry, ProfileWastedWorkOptions,
105
+ WastedWorkArm, WastedWorkSite, WastedWorkReport,
106
+ } from './analysis/choice-cost.ts'
107
+ export { renderChoiceInventory, renderWastedWork, leftFactorPreview } from './analysis/choice-cost-render.ts'
108
+ export type { RenderOptions } from './analysis/choice-cost-render.ts'
109
+ export { checkWastedWork, buildWastedWorkBaseline } from './analysis/choice-cost-gate.ts'
110
+ export type { WastedWorkBaseline, WastedWorkPolicy, GateBreach, GateVerdict } from './analysis/choice-cost-gate.ts'
111
+ export type { AnalysableGrammar } from './analysis/grammar.ts'
112
+
113
+ export {
114
+ analyzeDuplication, analyzeDuplicationRules, formatDuplicationFindings,
115
+ duplicationFindingCount, siteToString, alternationGroups, keywordRegexShape,
116
+ extractCharClasses, charClassMembers, keywordAlternationHazards,
117
+ } from './analysis/duplication.ts'
118
+ export type {
119
+ DuplicationReport, AnalyzeDuplicationOptions, DuplicationWarnLevel, Site,
120
+ DuplicateFinding, NearDuplicateFinding, RegexFragmentFinding,
121
+ RegexClassFinding, RegexClassVariant,
122
+ ArmOverlapFinding, RewriteFinding, RewriteKind, SepByVerdict, KeywordRegexFinding, DivergentNodeFinding,
123
+ StructureLossFinding,
124
+ } from './analysis/duplication.ts'
125
+ // `composeLeaf()` is terminal by design; ordinary reusable grammar composition is
126
+ // `compose()`. There is no à-la-carte rule selection: compose small pieces instead.
127
+ //
128
+ // `fuseInterpreted()` / `isInterpretedFuse()` are deliberately NOT re-exported. The
129
+ // interpreted fuse is a DIAGNOSTIC engine — it is how the differential harnesses and
130
+ // bench legs run a grammar without reaching codegen — not a supported way to ship a
131
+ // parser. Publishing it advertised a second engine with the same grammar and different
132
+ // runtime characteristics, and `isInterpretedFuse` existed as an escape hatch that not
133
+ // even Parseman called. Both stay internal (./compiler/linker.ts).
134
+ //
135
+ // Withdrawing the discriminator is only safe because `composeLeaf()` no longer LIES
136
+ // about which engine it returned: it is typed `Record<string, Runnable>`, true on both
137
+ // paths, and `Runnable` is what `run()`/`parseDoc()` take. Were that type ever narrowed
138
+ // back to `FusedRule`, `isInterpretedFuse` would have to be re-exported in the same
139
+ // change — a dual shape with no way to detect it is the worse of the two.
140
+ export { compose, composeLeaf, cstBuildHost } from './compiler/linker.ts'
141
+ export type { CstBuildHostOptions, FusedRule } from './compiler/linker.ts'
142
+
143
+ export { buildLineIndex, createLineIndex, recordLineRange, normalizeLineIndex, offsetToLineCol, annotateSpan, annotateTreeSpans } from './line-index.ts'
144
+ export type { LineIndex } from './line-index.ts'
145
+
146
+ export { gate } from './combinators/gate.ts'
147
+ export { adjacent, notAdjacent } from './combinators/adjacency.ts'
148
+ export { withCtx } from './combinators/withCtx.ts'
149
+ export { isParseError } from './combinators/expect.ts'
150
+ export { expect } from './combinators/expect.ts'
151
+ export { completionsAt } from './combinators/completions.ts'
152
+ export { scanTo, balanced } from './combinators/scanTo.ts'
153
+ export type { ScanToOptions } from './combinators/scanTo.ts'
154
+
155
+ export type { CSTNode, CSTLeaf, CSTError, CSTTrivia, CSTChild, CSTRawChild, NodeLike } from './cst/types.ts'
156
+
157
+ export { parseDoc } from './functional/doc.ts'
158
+ export type { ParseDoc, ParseDocOptions, Registry, RuleFn } from './functional/doc.ts'
159
+ /* THE PUBLIC PARSE PATH IS THE TABLE. `functional/run.ts` is the driver and
160
+ * still walks a combinator directly when handed one — that branch is the
161
+ * interpreter, and it stays reachable from `test/` and the identity sweep so a
162
+ * table/codegen divergence has something to be bisected against. It is not what
163
+ * the main entry hands out. See `functional/run-tabled.ts` for why the encode
164
+ * cannot live inside the driver (`parseman/run`'s closure) and why the cache is
165
+ * load-bearing rather than an optimisation. */
166
+ export { run } from './functional/run-tabled.ts'
167
+ export type { RootTriviaCapture, RunResult, RunOptions, Runnable } from './functional/run.ts'
168
+ export { GRAMMAR_COVERAGE_DEFINITIONS, compiledGrammarCoverageDefinitions, createGrammarCoverageCollector, createGrammarInstrumentationContext, createGrammarTraceSink, grammarCoverageDefinitions, composedGrammarCoverageDefinitions, runWithGrammarCoverage } from './coverage.ts'
169
+ export type { GrammarCoverageCollector, GrammarCoverageDefinition, GrammarCoverageSnapshot, GrammarInstrumentationContext, GrammarTraceEvent, GrammarTracePhase, GrammarTraceSink, GrammarTraceSnapshot } from './coverage.ts'
170
+ export { buildTriviaIndex } from './cst/trivia-index.ts'
171
+ export type { TriviaIndex, TriviaToken, TriviaIndexOptions } from './cst/trivia-index.ts'
172
+ export { createVisitor } from './cst/walk.ts'
173
+ export type { Walkable, VisitorHandler, VisitorSpec } from './cst/walk.ts'
174
+ export type { GrammarReflection, GrammarNodeReflection, GrammarWithReflection } from './cst/reflection.ts'
175
+ export { buildRootTriviaIndex, triviaEntries } from './cst/trivia-entries.ts'
176
+ export type { RootTriviaGap, RootTriviaIndex, TriviaEntriesView } from './cst/trivia-entries.ts'
177
+ export { triviaKindMask } from './cst/trivia-kinds.ts'
178
+
179
+ export {
180
+ relativize,
181
+ absolutize,
182
+ absoluteSpanAt,
183
+ shiftAbsolute,
184
+ applyEdit,
185
+ relativizeCST,
186
+ absolutizeCST,
187
+ absoluteSpanCST,
188
+ } from './cst/relative-spans.ts'
189
+ export type { AbsNode, RelNode } from './cst/relative-spans.ts'
@@ -0,0 +1,221 @@
1
+ /**
2
+ * `parseman/language-service` — the external, grammar-agnostic IDE layer.
3
+ *
4
+ * A grammar is pure structure (it carries no recovery/completion/lint config).
5
+ * `languageService(grammar, config)` layers editor behaviour ON TOP, keyed by rule
6
+ * name = node type, without ever modifying the grammar:
7
+ *
8
+ * - `parse(src, { tolerant })` → CST + `ParseError[]` (recovers on the compiled
9
+ * fast path when `grammar` is a `compile(g, { recovery: true })` grammar).
10
+ * - `diagnostics(src)` → structural parse errors + your per-node-type lint rules.
11
+ * - `completionsAt(src, offset)` → the grammar's expected-token set at the cursor,
12
+ * mapped through your per-rule semantic completion handlers.
13
+ *
14
+ * The grammar can be the interpreter combinator OR a compiled grammar — the domain
15
+ * knowledge (what to suggest after `color:`, which nodes to lint) lives here, in the
16
+ * consumer's config, never in the grammar.
17
+ */
18
+ import type { Combinator, ParseContext, ParseResult, Span, ParseError } from '../types.ts'
19
+ import { run } from '../functional/run.ts'
20
+ import { completionsAt as coreCompletionsAt } from '../combinators/completions.ts'
21
+ import { cstBuildHost } from '../compiler/linker.ts'
22
+ import type { Walkable } from '../cst/walk.ts'
23
+ import { parseDoc, type Registry } from '../functional/doc.ts'
24
+ import { absolutizeCST } from '../cst/relative-spans.ts'
25
+ import type { NodeLike } from '../cst/types.ts'
26
+
27
+ export type Severity = 'error' | 'warning' | 'info'
28
+ export type Diagnostic = { severity: Severity; message: string; span: Span }
29
+ export type CompletionItem = { label: string; detail?: string }
30
+
31
+ /** A CST node/leaf as a diagnostics handler sees it: the walk shape plus span/value. */
32
+ export type LsNode = Walkable & { readonly span: Span; readonly value?: string }
33
+
34
+ /** Context handed to a completion handler: which rule the cursor sits in and the
35
+ * grammar's raw expected-token labels there. */
36
+ export type CompletionContext = { rule: string | null; expected: string[]; offset: number }
37
+
38
+ export type LanguageServiceConfig = {
39
+ /** Lint/diagnostic rules keyed by node type (rule name). Return zero or more
40
+ * Diagnostics for each matching node. */
41
+ diagnostics?: Record<string, (node: LsNode) => Diagnostic | Diagnostic[] | null | undefined | void>
42
+ /** Semantic completion handlers keyed by the rule the cursor is in. Turn the
43
+ * grammar's structural expectation into domain suggestions. */
44
+ complete?: Record<string, (ctx: CompletionContext) => CompletionItem[]>
45
+ }
46
+
47
+ /** Grammar the service drives: an interpreter combinator or a compiled entry fn. */
48
+ type Grammar = Combinator<unknown> | ((input: string, pos: number, ctx: ParseContext) => ParseResult<unknown>)
49
+
50
+ /**
51
+ * What you hand `languageService`:
52
+ * - a single entry (combinator or `compile()`d fn) — stateless methods only, or
53
+ * - `{ rules, root }` (a `rules()` registry + its root rule name) — also unlocks
54
+ * `openDocument`, the live incremental editor document.
55
+ */
56
+ export type GrammarInput = Grammar | { rules: Registry<NodeLike>; root: string }
57
+
58
+ /**
59
+ * A live incremental document — the fused editor backend. Holds a tolerant
60
+ * `parseDoc` whose tree survives broken input (recovery) and re-parses only the
61
+ * edited span on `edit()`. Diagnostics walk the maintained tree (recovered errors
62
+ * ride inside it as `parseError` nodes), so they stay complete across edits without
63
+ * re-parsing the whole document.
64
+ */
65
+ export type LsDocument = {
66
+ /** The current CST with ABSOLUTE spans (recovered errors embedded as parseError nodes). */
67
+ readonly tree: LsNode | null
68
+ /** Apply an editor change (byte offsets in the CURRENT text); returns the next document. */
69
+ edit(from: number, to: number, replacement: string): LsDocument
70
+ /** Structural parse errors (from the tree) + your per-node-type lint rules. */
71
+ diagnostics(): Diagnostic[]
72
+ /** Completions at a cursor offset in the current text. */
73
+ completionsAt(offset: number): CompletionItem[]
74
+ }
75
+
76
+ export type LanguageService = {
77
+ parse(src: string, opts?: { tolerant?: boolean }): ParseResult<unknown> & { errors: ParseError[] }
78
+ diagnostics(src: string): Diagnostic[]
79
+ completionsAt(src: string, offset: number): CompletionItem[]
80
+ /** Open a live incremental document. Requires `languageService({ rules, root }, …)`. */
81
+ openDocument(src: string): LsDocument
82
+ }
83
+
84
+ const errorMessage = (expected: readonly string[]): string =>
85
+ expected.length ? `Unexpected input; expected ${expected.join(' or ')}` : 'Unexpected input'
86
+
87
+ function visitTree(root: LsNode, enter: (node: LsNode) => void): void {
88
+ const go = (node: LsNode): void => {
89
+ enter(node)
90
+ const children = node.children as ReadonlyArray<LsNode> | undefined
91
+ if (Array.isArray(children)) for (const child of children) go(child)
92
+ }
93
+ go(root)
94
+ }
95
+
96
+ /**
97
+ * Merge every syntax-error source into one deduped diagnostic list:
98
+ * (a) recovered `parseError` nodes embedded in the tree + per-node lint rules
99
+ * (via {@link diagnoseTree}),
100
+ * (b) residual FLAT recovery/failure errors from the run/doc (present when the
101
+ * parse produced no tree, or as the flat mirror of embedded errors), and
102
+ * (c) a sentinel over any trailing `unconsumedFrom..end` junk the grammar left.
103
+ * Deduped by span+message so an embedded error and its flat twin collapse to one,
104
+ * while distinct diagnostics at the same offset survive. With the reporting fixes
105
+ * (expect() embeds, speculative rollback is clean) (a) is already complete; the
106
+ * merge is what guarantees missing-closers and trailing junk are never invisible.
107
+ */
108
+ function mergeDiagnostics(
109
+ tree: LsNode | null,
110
+ flat: ReadonlyArray<{ expected: readonly string[]; span: Span }>,
111
+ unconsumedFrom: number | null,
112
+ inputLength: number,
113
+ config: LanguageServiceConfig,
114
+ ): Diagnostic[] {
115
+ const out: Diagnostic[] = []
116
+ const seen = new Set<string>()
117
+ const add = (d: Diagnostic): void => {
118
+ const key = `${d.severity}|${d.span.start}|${d.span.end}|${d.message}`
119
+ if (seen.has(key)) return
120
+ seen.add(key)
121
+ out.push(d)
122
+ }
123
+ if (tree) for (const d of diagnoseTree(tree, config)) add(d)
124
+ for (const e of flat) add({ severity: 'error', message: errorMessage(e.expected), span: e.span })
125
+ if (unconsumedFrom !== null) {
126
+ add({ severity: 'error', message: 'Unexpected input', span: { start: unconsumedFrom, end: inputLength } })
127
+ }
128
+ return out
129
+ }
130
+
131
+ /** One walk over a CST: recovered `parseError` nodes → error diagnostics, and each
132
+ * node dispatched to its `config.diagnostics[type]` lint handler. Errors live in the
133
+ * tree, so this is the single source for both — no separate flat channel to merge. */
134
+ function diagnoseTree(root: LsNode, config: LanguageServiceConfig): Diagnostic[] {
135
+ const out: Diagnostic[] = []
136
+ const rules = config.diagnostics
137
+ visitTree(root, node => {
138
+ if (node._tag === 'parseError') {
139
+ out.push({ severity: 'error', message: errorMessage((node as { expected?: string[] }).expected ?? []), span: node.span })
140
+ return
141
+ }
142
+ const h = rules && node.type !== undefined ? rules[node.type] : undefined
143
+ if (h) {
144
+ const d = h(node)
145
+ if (d) out.push(...(Array.isArray(d) ? d : [d]))
146
+ }
147
+ })
148
+ return out
149
+ }
150
+
151
+ export function languageService(grammar: GrammarInput, config: LanguageServiceConfig = {}): LanguageService {
152
+ const build = cstBuildHost()
153
+ const asReg = typeof grammar === 'object' && grammar !== null && 'rules' in grammar
154
+ const registry = asReg ? grammar.rules : undefined
155
+ const root = asReg ? grammar.root : undefined
156
+ const entry: Grammar = asReg ? (registry![root!] as Grammar) : grammar
157
+
158
+ // The grammar's ambient trivia (interpreter combinators carry it in _meta) —
159
+ // handed to run() so it skips a trailing whitespace/comment tail before
160
+ // reporting unconsumedFrom, matching parseDoc/run semantics.
161
+ const trivia = typeof entry !== 'function' ? (entry as Combinator<unknown>)._meta.grammarTrivia : undefined
162
+ const parseTolerant = (src: string) =>
163
+ run(entry as never, src, { tolerant: true, build, ...(trivia !== undefined ? { trivia } : {}) })
164
+
165
+ const complete = (input: string, offset: number, treeForCursor: unknown): CompletionItem[] => {
166
+ const expected = coreCompletionsAt(entry as never, input, offset, { tolerant: true })
167
+ const rule = ruleAtCursor(treeForCursor, offset)
168
+ const handler = rule !== null ? config.complete?.[rule] : undefined
169
+ if (handler) return handler({ rule, expected, offset })
170
+ return expected.map(label => ({ label }))
171
+ }
172
+
173
+ return {
174
+ parse(src, opts) {
175
+ return run(entry as never, src, { tolerant: opts?.tolerant ?? true, build }) as ParseResult<unknown> & { errors: ParseError[] }
176
+ },
177
+
178
+ diagnostics(src) {
179
+ const r = parseTolerant(src)
180
+ const tree = r.value && typeof r.value === 'object' ? (r.value as LsNode) : null
181
+ return mergeDiagnostics(tree, r.errors, r.unconsumedFrom, src.length, config)
182
+ },
183
+
184
+ completionsAt(src, offset) {
185
+ return complete(src, offset, parseTolerant(src.slice(0, offset)).value)
186
+ },
187
+
188
+ openDocument(src) {
189
+ if (!registry || root === undefined) {
190
+ throw new Error('openDocument requires languageService({ rules, root }, config) — a rules() registry, not a single entry')
191
+ }
192
+ const host = cstBuildHost()
193
+ const make = (doc: ReturnType<typeof parseDoc<NodeLike>>): LsDocument => {
194
+ // Absolutize once per document version (memoized in this closure).
195
+ const abs = doc.tree ? (absolutizeCST(doc.tree as never) as unknown as LsNode) : null
196
+ return {
197
+ get tree() { return abs },
198
+ edit: (from, to, replacement) => make(doc.edit(from, to, replacement)),
199
+ // Same unified merge as the stateless path: embedded parseError nodes +
200
+ // lint, the flat recovery errors the doc collected, and a trailing-junk
201
+ // sentinel — so a live editor document surfaces missing-closers and junk.
202
+ diagnostics: () => mergeDiagnostics(abs, doc.errors, doc.unconsumedFrom, doc.input.length, config),
203
+ completionsAt: (offset) => complete(doc.input, offset, abs),
204
+ }
205
+ }
206
+ return make(parseDoc<NodeLike>(registry, root, src, { tolerant: true, structuralReuse: true, build: host }))
207
+ },
208
+ }
209
+ }
210
+
211
+ /** Deepest node whose span brackets the cursor — "which rule is the cursor in".
212
+ * The grammar carries no cursor knowledge, so we reconstruct it from the CST. */
213
+ function ruleAtCursor(root: unknown, offset: number): string | null {
214
+ if (!root || typeof root !== 'object') return null
215
+ let best: string | null = null
216
+ visitTree(root as LsNode, node => {
217
+ const s = node.span
218
+ if (s && s.start <= offset && offset <= s.end && node.type !== undefined) best = node.type
219
+ })
220
+ return best
221
+ }
@@ -0,0 +1,131 @@
1
+ import type { Span } from './types.ts'
2
+
3
+ /**
4
+ * Precomputed index of newline positions for O(log n) offset->line/col lookup.
5
+ * lineStarts[i] is the byte offset of the first character on line i+1.
6
+ */
7
+ export type LineIndex = { lineStarts: number[] }
8
+ type LineTrackingContext = {
9
+ trackLines?: boolean | undefined
10
+ _lineIndex?: LineIndex | undefined
11
+ _lineStarts?: number[] | undefined
12
+ _lineScannedTo?: number | undefined
13
+ }
14
+
15
+ export function createLineIndex(): LineIndex {
16
+ return { lineStarts: [0] }
17
+ }
18
+
19
+ export function buildLineIndex(input: string): LineIndex {
20
+ const lineStarts = [0]
21
+ for (let i = 0; i < input.length; i++) {
22
+ if (input.charCodeAt(i) === 10) lineStarts.push(i + 1)
23
+ }
24
+ return { lineStarts }
25
+ }
26
+
27
+ /**
28
+ * Record newline offsets for a matched range. Intended for optional parsers that
29
+ * collect line starts while they consume terminals. The collector is deliberately
30
+ * append-only: speculative parses never roll it back. Normalize once at the
31
+ * driver boundary before doing offset->line/column lookup.
32
+ */
33
+ export function recordLineRange(index: LineIndex, input: string, start: number, end: number): void {
34
+ const lineStarts = index.lineStarts
35
+ for (let i = start; i < end; i++) {
36
+ if (input.charCodeAt(i) === 10) lineStarts.push(i + 1)
37
+ }
38
+ }
39
+
40
+ /**
41
+ * Records newlines from the context high-water mark through `end`. The start
42
+ * argument is only a caller hint: append-only tracking uses `_lineScannedTo` so
43
+ * overlapping calls can backfill gaps without double-scanning committed ranges.
44
+ */
45
+ export function recordLineRangeFromContext(
46
+ ctx: LineTrackingContext,
47
+ input: string,
48
+ _startHint: number,
49
+ end: number,
50
+ ): void {
51
+ if (!ctx.trackLines) return
52
+ const from = ctx._lineScannedTo ?? 0
53
+ if (end <= from) return
54
+ const index = ctx._lineIndex ?? (ctx._lineStarts ? { lineStarts: ctx._lineStarts } : undefined)
55
+ if (!index) return
56
+ recordLineRange(index, input, from, end)
57
+ ctx._lineScannedTo = end
58
+ }
59
+
60
+ export function normalizeLineIndex(index: LineIndex): LineIndex {
61
+ const starts = index.lineStarts
62
+ starts.sort((a, b) => a - b)
63
+ let write = 0
64
+ for (let read = 0; read < starts.length; read++) {
65
+ const value = starts[read]!
66
+ if (write === 0 || starts[write - 1] !== value) starts[write++] = value
67
+ }
68
+ starts.length = write
69
+ if (starts.length === 0 || starts[0] !== 0) starts.unshift(0)
70
+ return index
71
+ }
72
+
73
+ export function offsetToLineCol(
74
+ index: LineIndex,
75
+ offset: number
76
+ ): { line: number; col: number } {
77
+ const { lineStarts } = index
78
+ let lo = 0
79
+ let hi = lineStarts.length - 1
80
+ while (lo < hi) {
81
+ const mid = (lo + hi + 1) >> 1
82
+ if (lineStarts[mid]! <= offset) lo = mid
83
+ else hi = mid - 1
84
+ }
85
+ return { line: lo + 1, col: offset - lineStarts[lo]! + 1 }
86
+ }
87
+
88
+ /** Fills startLine/startColumn/endLine/endColumn on a span in-place. */
89
+ export function annotateSpan(span: Span, index: LineIndex): Span {
90
+ const s = offsetToLineCol(index, span.start)
91
+ const e = offsetToLineCol(index, span.end)
92
+ return {
93
+ ...span,
94
+ startLine: s.line,
95
+ startColumn: s.col,
96
+ endLine: e.line,
97
+ endColumn: e.col,
98
+ }
99
+ }
100
+
101
+ export function annotateSpanFromLineContext(
102
+ span: Span,
103
+ ctx: LineTrackingContext,
104
+ ): Span {
105
+ if (!ctx.trackLines) return span
106
+ const index = ctx._lineIndex ?? (ctx._lineStarts ? { lineStarts: ctx._lineStarts } : undefined)
107
+ return index ? annotateSpan(span, index) : span
108
+ }
109
+
110
+ function isSpan(value: unknown): value is Span {
111
+ return !!value
112
+ && typeof value === 'object'
113
+ && typeof (value as { start?: unknown }).start === 'number'
114
+ && typeof (value as { end?: unknown }).end === 'number'
115
+ }
116
+
117
+ /** Annotate every span-shaped object reachable from a parse result value. */
118
+ export function annotateTreeSpans<T>(value: T, index: LineIndex, seen: WeakSet<object> = new WeakSet()): T {
119
+ if (!value || typeof value !== 'object') return value
120
+ if (seen.has(value)) return value
121
+ seen.add(value)
122
+
123
+ const obj = value as Record<string, unknown>
124
+ if (isSpan(obj.span)) obj.span = annotateSpan(obj.span, index)
125
+
126
+ for (const key of Object.keys(obj)) {
127
+ const child = obj[key]
128
+ if (child && typeof child === 'object') annotateTreeSpans(child, index, seen)
129
+ }
130
+ return value
131
+ }