parseman 0.46.0 → 0.47.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (316) hide show
  1. package/README.md +8 -3
  2. package/dist/analysis/choice-cost-render.d.ts +12 -4
  3. package/dist/analysis/choice-cost-render.d.ts.map +1 -1
  4. package/dist/analysis/choice-cost.d.ts +2 -2
  5. package/dist/analysis/choice-cost.d.ts.map +1 -1
  6. package/dist/analysis/commitment.d.ts +131 -0
  7. package/dist/analysis/commitment.d.ts.map +1 -0
  8. package/dist/analysis/diagnostics.cjs +10339 -7787
  9. package/dist/analysis/diagnostics.cjs.map +3 -4
  10. package/dist/analysis/diagnostics.d.ts +2 -2
  11. package/dist/analysis/diagnostics.js +10339 -7787
  12. package/dist/analysis/diagnostics.js.map +3 -4
  13. package/dist/analysis/duplication.d.ts.map +1 -1
  14. package/dist/analysis/fix.d.ts +3 -3
  15. package/dist/analysis/fix.d.ts.map +1 -1
  16. package/dist/analysis/format-number.d.ts +23 -0
  17. package/dist/analysis/format-number.d.ts.map +1 -0
  18. package/dist/analysis/gating.d.ts +39 -2
  19. package/dist/analysis/gating.d.ts.map +1 -1
  20. package/dist/analysis/rebuild.d.ts.map +1 -1
  21. package/dist/analysis/terminal.d.ts +5 -2
  22. package/dist/analysis/terminal.d.ts.map +1 -1
  23. package/dist/cli/index.cjs +10924 -8506
  24. package/dist/cli/index.cjs.map +3 -4
  25. package/dist/cli/index.js +10924 -8506
  26. package/dist/cli/index.js.map +3 -4
  27. package/dist/combinators/adjacency.d.ts +100 -0
  28. package/dist/combinators/adjacency.d.ts.map +1 -0
  29. package/dist/combinators/choice.d.ts.map +1 -1
  30. package/dist/combinators/completions.d.ts.map +1 -1
  31. package/dist/combinators/dispatch.d.ts +8 -1
  32. package/dist/combinators/dispatch.d.ts.map +1 -1
  33. package/dist/combinators/expect.d.ts.map +1 -1
  34. package/dist/combinators/first-set.d.ts.map +1 -1
  35. package/dist/combinators/gate.d.ts.map +1 -1
  36. package/dist/combinators/grammar.d.ts +22 -0
  37. package/dist/combinators/grammar.d.ts.map +1 -1
  38. package/dist/combinators/literal.d.ts +8 -0
  39. package/dist/combinators/literal.d.ts.map +1 -1
  40. package/dist/combinators/map.d.ts +0 -1
  41. package/dist/combinators/map.d.ts.map +1 -1
  42. package/dist/combinators/node.d.ts +13 -3
  43. package/dist/combinators/node.d.ts.map +1 -1
  44. package/dist/combinators/parser.d.ts +1 -1
  45. package/dist/combinators/parser.d.ts.map +1 -1
  46. package/dist/combinators/ref.d.ts.map +1 -1
  47. package/dist/combinators/regex.d.ts +1 -16
  48. package/dist/combinators/regex.d.ts.map +1 -1
  49. package/dist/combinators/repeat.d.ts +30 -2
  50. package/dist/combinators/repeat.d.ts.map +1 -1
  51. package/dist/combinators/scanTo.d.ts +71 -1
  52. package/dist/combinators/scanTo.d.ts.map +1 -1
  53. package/dist/combinators/sequence.d.ts.map +1 -1
  54. package/dist/combinators/token.d.ts.map +1 -1
  55. package/dist/combinators/trivia-boundary.d.ts +26 -0
  56. package/dist/combinators/trivia-boundary.d.ts.map +1 -0
  57. package/dist/combinators/trivia-skip.d.ts +45 -1
  58. package/dist/combinators/trivia-skip.d.ts.map +1 -1
  59. package/dist/combinators/withCtx.d.ts.map +1 -1
  60. package/dist/compiler/compile-linkable-table.d.ts +104 -0
  61. package/dist/compiler/compile-linkable-table.d.ts.map +1 -0
  62. package/dist/compiler/degradation.d.ts +0 -2
  63. package/dist/compiler/degradation.d.ts.map +1 -1
  64. package/dist/compiler/fields.d.ts +13 -0
  65. package/dist/compiler/fields.d.ts.map +1 -1
  66. package/dist/compiler/grammar-coverage-ids.d.ts +5 -5
  67. package/dist/compiler/grammar-coverage-ids.d.ts.map +1 -1
  68. package/dist/compiler/ir-serialize.d.ts.map +1 -1
  69. package/dist/compiler/linker.d.ts +47 -53
  70. package/dist/compiler/linker.d.ts.map +1 -1
  71. package/dist/compiler/token-alphabet.d.ts +81 -0
  72. package/dist/compiler/token-alphabet.d.ts.map +1 -0
  73. package/dist/compiler/token-dispatch.d.ts +95 -0
  74. package/dist/compiler/token-dispatch.d.ts.map +1 -0
  75. package/dist/compiler/token-scanner.d.ts +53 -0
  76. package/dist/compiler/token-scanner.d.ts.map +1 -0
  77. package/dist/compiler/value-usage.d.ts.map +1 -1
  78. package/dist/coverage.d.ts +1 -1
  79. package/dist/coverage.d.ts.map +1 -1
  80. package/dist/cst/capture-buffer.d.ts +35 -0
  81. package/dist/cst/capture-buffer.d.ts.map +1 -1
  82. package/dist/cst/reflection-attach.d.ts +8 -0
  83. package/dist/cst/reflection-attach.d.ts.map +1 -0
  84. package/dist/cst/reflection-symbols.d.ts +10 -0
  85. package/dist/cst/reflection-symbols.d.ts.map +1 -0
  86. package/dist/cst/reflection.d.ts +2 -4
  87. package/dist/cst/reflection.d.ts.map +1 -1
  88. package/dist/cst/root-trivia-scope.d.ts +26 -0
  89. package/dist/cst/root-trivia-scope.d.ts.map +1 -0
  90. package/dist/cst/trivia-charscan.d.ts +18 -0
  91. package/dist/cst/trivia-charscan.d.ts.map +1 -0
  92. package/dist/cst/trivia-kinds.d.ts +19 -0
  93. package/dist/cst/trivia-kinds.d.ts.map +1 -1
  94. package/dist/functional/doc.d.ts.map +1 -1
  95. package/dist/functional/run-tabled.d.ts +12 -0
  96. package/dist/functional/run-tabled.d.ts.map +1 -0
  97. package/dist/functional/run.d.ts +25 -42
  98. package/dist/functional/run.d.ts.map +1 -1
  99. package/dist/grammar-metadata.d.ts +3 -0
  100. package/dist/grammar-metadata.d.ts.map +1 -0
  101. package/dist/index.cjs +9290 -7181
  102. package/dist/index.cjs.map +3 -4
  103. package/dist/index.d.ts +18 -9
  104. package/dist/index.d.ts.map +1 -1
  105. package/dist/index.js +9300 -7191
  106. package/dist/index.js.map +3 -4
  107. package/dist/language-service/index.cjs +313 -121
  108. package/dist/language-service/index.cjs.map +3 -4
  109. package/dist/language-service/index.js +313 -121
  110. package/dist/language-service/index.js.map +3 -4
  111. package/dist/oracle/index.cjs.map +0 -1
  112. package/dist/oracle/index.js.map +0 -1
  113. package/dist/parse-context.d.ts +49 -0
  114. package/dist/parse-context.d.ts.map +1 -0
  115. package/dist/plugin/evaluator.d.ts +40 -1
  116. package/dist/plugin/evaluator.d.ts.map +1 -1
  117. package/dist/plugin/index.cjs +11175 -9534
  118. package/dist/plugin/index.cjs.map +3 -4
  119. package/dist/plugin/index.d.ts.map +1 -1
  120. package/dist/plugin/index.js +11175 -9534
  121. package/dist/plugin/index.js.map +3 -4
  122. package/dist/regex/classes.d.ts +28 -0
  123. package/dist/regex/classes.d.ts.map +1 -1
  124. package/dist/regex/first-set.d.ts +14 -0
  125. package/dist/regex/first-set.d.ts.map +1 -1
  126. package/dist/run/index.cjs +136 -51
  127. package/dist/run/index.cjs.map +2 -3
  128. package/dist/run/index.js +136 -51
  129. package/dist/run/index.js.map +2 -3
  130. package/dist/spec/index.cjs +2934 -1611
  131. package/dist/spec/index.cjs.map +3 -4
  132. package/dist/spec/index.js +2934 -1611
  133. package/dist/spec/index.js.map +3 -4
  134. package/dist/spec/model.d.ts +8 -0
  135. package/dist/spec/model.d.ts.map +1 -1
  136. package/dist/table/assemble.d.ts +307 -0
  137. package/dist/table/assemble.d.ts.map +1 -0
  138. package/dist/table/cell.d.ts +42 -0
  139. package/dist/table/cell.d.ts.map +1 -0
  140. package/dist/table/child-slots.d.ts +30 -0
  141. package/dist/table/child-slots.d.ts.map +1 -0
  142. package/dist/table/compile-rule-map.d.ts +136 -0
  143. package/dist/table/compile-rule-map.d.ts.map +1 -0
  144. package/dist/table/compile.d.ts +56 -0
  145. package/dist/table/compile.d.ts.map +1 -0
  146. package/dist/table/duplication-hook.d.ts +32 -0
  147. package/dist/table/duplication-hook.d.ts.map +1 -0
  148. package/dist/table/emit-assembly.d.ts +139 -0
  149. package/dist/table/emit-assembly.d.ts.map +1 -0
  150. package/dist/table/emit.d.ts +113 -0
  151. package/dist/table/emit.d.ts.map +1 -0
  152. package/dist/table/encode-baseline.d.ts +16 -0
  153. package/dist/table/encode-baseline.d.ts.map +1 -0
  154. package/dist/table/encode.d.ts +71 -0
  155. package/dist/table/encode.d.ts.map +1 -0
  156. package/dist/table/exec-baseline.d.ts +9 -0
  157. package/dist/table/exec-baseline.d.ts.map +1 -0
  158. package/dist/table/exec.d.ts +72 -0
  159. package/dist/table/exec.d.ts.map +1 -0
  160. package/dist/table/fold.d.ts +25 -0
  161. package/dist/table/fold.d.ts.map +1 -0
  162. package/dist/table/index.cjs +11463 -0
  163. package/dist/table/index.cjs.map +6 -0
  164. package/dist/table/index.d.ts +100 -0
  165. package/dist/table/index.d.ts.map +1 -0
  166. package/dist/table/index.js +11440 -0
  167. package/dist/table/index.js.map +6 -0
  168. package/dist/table/inspect.d.ts +17 -0
  169. package/dist/table/inspect.d.ts.map +1 -0
  170. package/dist/table/ops.d.ts +453 -0
  171. package/dist/table/ops.d.ts.map +1 -0
  172. package/dist/table/program.d.ts +496 -0
  173. package/dist/table/program.d.ts.map +1 -0
  174. package/dist/table/run-support.d.ts +38 -0
  175. package/dist/table/run-support.d.ts.map +1 -0
  176. package/dist/{compiler/scannable-run.d.ts → table/scan-shapes.d.ts} +14 -13
  177. package/dist/table/scan-shapes.d.ts.map +1 -0
  178. package/dist/table/site-labels.d.ts +58 -0
  179. package/dist/table/site-labels.d.ts.map +1 -0
  180. package/dist/table/stamp.d.ts +70 -0
  181. package/dist/table/stamp.d.ts.map +1 -0
  182. package/dist/types.d.ts +80 -24
  183. package/dist/types.d.ts.map +1 -1
  184. package/dist/version.d.ts +1 -1
  185. package/package.json +26 -2
  186. package/src/analysis/choice-cost-gate.ts +359 -0
  187. package/src/analysis/choice-cost-render.ts +293 -0
  188. package/src/analysis/choice-cost.ts +1008 -0
  189. package/src/analysis/commitment.ts +557 -0
  190. package/src/analysis/corpus.ts +130 -0
  191. package/src/analysis/diagnose-render.ts +625 -0
  192. package/src/analysis/diagnose.ts +317 -0
  193. package/src/analysis/diagnostics.ts +33 -0
  194. package/src/analysis/duplication.ts +1955 -0
  195. package/src/analysis/fix-render.ts +217 -0
  196. package/src/analysis/fix.ts +669 -0
  197. package/src/analysis/format-number.ts +31 -0
  198. package/src/analysis/gating.ts +792 -0
  199. package/src/analysis/grammar.ts +72 -0
  200. package/src/analysis/rebuild.ts +287 -0
  201. package/src/analysis/terminal.ts +336 -0
  202. package/src/cli/index.ts +413 -0
  203. package/src/combinators/adjacency.ts +171 -0
  204. package/src/combinators/attempt.ts +41 -0
  205. package/src/combinators/case-fold.ts +90 -0
  206. package/src/combinators/choice.ts +466 -0
  207. package/src/combinators/completions.ts +64 -0
  208. package/src/combinators/dispatch.ts +358 -0
  209. package/src/combinators/expect.ts +159 -0
  210. package/src/combinators/first-set.ts +348 -0
  211. package/src/combinators/gate.ts +50 -0
  212. package/src/combinators/grammar.ts +272 -0
  213. package/src/combinators/keywords.ts +145 -0
  214. package/src/combinators/literal.ts +102 -0
  215. package/src/combinators/map.ts +106 -0
  216. package/src/combinators/node.ts +345 -0
  217. package/src/combinators/not.ts +55 -0
  218. package/src/combinators/parser.ts +266 -0
  219. package/src/combinators/peek.ts +63 -0
  220. package/src/combinators/probe.ts +25 -0
  221. package/src/combinators/ref.ts +109 -0
  222. package/src/combinators/regex.ts +170 -0
  223. package/src/combinators/repeat.ts +561 -0
  224. package/src/combinators/scanTo.ts +393 -0
  225. package/src/combinators/sequence.ts +237 -0
  226. package/src/combinators/token.ts +120 -0
  227. package/src/combinators/trivia-boundary.ts +48 -0
  228. package/src/combinators/trivia-skip.ts +852 -0
  229. package/src/combinators/withCtx.ts +45 -0
  230. package/src/compiler/build-arity.ts +330 -0
  231. package/src/compiler/compile-linkable-table.ts +209 -0
  232. package/src/compiler/degradation.ts +266 -0
  233. package/src/compiler/fields.ts +191 -0
  234. package/src/compiler/grammar-coverage-ids.ts +129 -0
  235. package/src/compiler/ir-serialize.ts +615 -0
  236. package/src/compiler/linker.ts +954 -0
  237. package/src/compiler/token-alphabet.ts +209 -0
  238. package/src/compiler/token-dispatch.ts +353 -0
  239. package/src/compiler/token-scanner.ts +318 -0
  240. package/src/compiler/value-usage.ts +147 -0
  241. package/src/coverage.ts +383 -0
  242. package/src/cst/capture-buffer.ts +297 -0
  243. package/src/cst/host-mode.ts +69 -0
  244. package/src/cst/reflection-attach.ts +11 -0
  245. package/src/cst/reflection-symbols.ts +9 -0
  246. package/src/cst/reflection.ts +149 -0
  247. package/src/cst/relative-spans.ts +207 -0
  248. package/src/cst/root-trivia-scope.ts +30 -0
  249. package/src/cst/trivia-charscan.ts +312 -0
  250. package/src/cst/trivia-entries.ts +285 -0
  251. package/src/cst/trivia-index.ts +131 -0
  252. package/src/cst/trivia-kinds.ts +358 -0
  253. package/src/cst/types.ts +76 -0
  254. package/src/cst/walk.ts +101 -0
  255. package/src/functional/doc.ts +959 -0
  256. package/src/functional/run-tabled.ts +153 -0
  257. package/src/functional/run.ts +558 -0
  258. package/src/grammar-metadata.ts +2 -0
  259. package/src/index.ts +189 -0
  260. package/src/language-service/index.ts +221 -0
  261. package/src/line-index.ts +131 -0
  262. package/src/oracle/digest.ts +620 -0
  263. package/src/oracle/index.ts +55 -0
  264. package/src/parse-context.ts +91 -0
  265. package/src/plugin/direct-builder-static.ts +102 -0
  266. package/src/plugin/evaluator.ts +1505 -0
  267. package/src/plugin/free-identifiers.ts +338 -0
  268. package/src/plugin/index.ts +2281 -0
  269. package/src/plugin/reducer-resolver.ts +606 -0
  270. package/src/recovery/scan.ts +155 -0
  271. package/src/regex/classes.ts +183 -0
  272. package/src/regex/first-set.ts +316 -0
  273. package/src/run/index.ts +31 -0
  274. package/src/spec/ebnf.ts +128 -0
  275. package/src/spec/index.ts +54 -0
  276. package/src/spec/model.ts +636 -0
  277. package/src/spec/railroad-lib.ts +559 -0
  278. package/src/spec/railroad.ts +256 -0
  279. package/src/table/assemble.ts +3006 -0
  280. package/src/table/cell.ts +42 -0
  281. package/src/table/child-slots.ts +113 -0
  282. package/src/table/compile-rule-map.ts +336 -0
  283. package/src/table/compile.ts +244 -0
  284. package/src/table/duplication-hook.ts +89 -0
  285. package/src/table/emit-assembly.ts +1777 -0
  286. package/src/table/emit.ts +429 -0
  287. package/src/table/encode-baseline.ts +340 -0
  288. package/src/table/encode.ts +1569 -0
  289. package/src/table/exec-baseline.ts +537 -0
  290. package/src/table/exec.ts +1680 -0
  291. package/src/table/fold.ts +38 -0
  292. package/src/table/index.ts +108 -0
  293. package/src/table/inspect.ts +52 -0
  294. package/src/table/ops.ts +467 -0
  295. package/src/table/program.ts +876 -0
  296. package/src/table/run-support.ts +66 -0
  297. package/src/table/scan-shapes.ts +1578 -0
  298. package/src/table/site-labels.ts +228 -0
  299. package/src/table/stamp.ts +229 -0
  300. package/src/types.ts +563 -0
  301. package/src/version.ts +16 -0
  302. package/dist/compiler/codegen.d.ts +0 -334
  303. package/dist/compiler/codegen.d.ts.map +0 -1
  304. package/dist/compiler/inline-build.d.ts +0 -11
  305. package/dist/compiler/inline-build.d.ts.map +0 -1
  306. package/dist/compiler/inline-callback.d.ts +0 -21
  307. package/dist/compiler/inline-callback.d.ts.map +0 -1
  308. package/dist/compiler/line-index.d.ts +0 -2
  309. package/dist/compiler/line-index.d.ts.map +0 -1
  310. package/dist/compiler/module-hoist.d.ts +0 -106
  311. package/dist/compiler/module-hoist.d.ts.map +0 -1
  312. package/dist/compiler/scannable-run.d.ts.map +0 -1
  313. package/dist/compiler/scannable-terminal.d.ts +0 -23
  314. package/dist/compiler/scannable-terminal.d.ts.map +0 -1
  315. package/dist/compiler/trivia-fast-path.d.ts +0 -38
  316. package/dist/compiler/trivia-fast-path.d.ts.map +0 -1
@@ -0,0 +1,312 @@
1
+ /**
2
+ * THE LABELLED-TRIVIA SCANNER, AT CHARACTER LEVEL.
3
+ *
4
+ * `fastTriviaScanner` (`../combinators/trivia-skip.ts`) already lowers UNLABELLED
5
+ * trivia to a char loop. Labelled trivia had no equivalent, so every gap between
6
+ * two sequence terms in a `classifiedTrivia()` grammar ran the arms as actual
7
+ * COMBINATORS — `matchArmAt` → `startsFirstSet` → a detached `ParseContext` → the
8
+ * regex combinator's `ParseResult`. On `benchmark.less`, one parse takes that path
9
+ * 31,758 times, tries 113,091 arms, and enters 13,696 of them, to classify 11,963
10
+ * chunks covering 33,981 characters — about 2.8 characters per chunk, for which the
11
+ * combinator machinery is pure overhead.
12
+ *
13
+ * The arms are static grammar data (`encode.ts`'s `triviaSpecOf` already lowers
14
+ * them to `[label, source, flags]`), so they can be classified ONCE, per spec, into
15
+ * per-arm character matchers. Anything that does not classify yields `null` and the
16
+ * combinator path stands — there is no fast path that is only usually right.
17
+ *
18
+ * A matcher returns the END of its match, or `pos` for "no match". That is exactly
19
+ * `matchArmAt`'s contract (`!r.ok || r.span.end <= pos` → null), so a lowered arm
20
+ * and a combinator arm are interchangeable inside the scan loops.
21
+ *
22
+ * The parser `state` `matchArmAt` threads into its detached context is not carried
23
+ * here, and does not need to be: only a plain `regex` arm lowers, and a regex
24
+ * terminal never reads state. An arm that could is exactly an arm that fails to
25
+ * classify.
26
+ */
27
+ import type { Combinator } from '../types.ts'
28
+ import { parseClassRanges } from '../regex/classes.ts'
29
+ import type { LabeledTriviaSpec } from './trivia-kinds.ts'
30
+
31
+ export type TriviaArmMatcher = (input: string, pos: number) => number
32
+
33
+ type Ranges = Array<[number, number]>
34
+
35
+ function inRanges(cp: number, ranges: Ranges): boolean {
36
+ for (let i = 0; i < ranges.length; i++) {
37
+ const r = ranges[i]!
38
+ if (cp >= r[0] && cp <= r[1]) return true
39
+ }
40
+ return false
41
+ }
42
+
43
+ /**
44
+ * A run of LITERAL characters as code points, or null.
45
+ *
46
+ * Only punctuation may be backslash-escaped: `\n`/`\d`/`\w` are not literals, and
47
+ * decoding them as one would silently accept a different language.
48
+ */
49
+ function literalCodes(src: string): number[] | null {
50
+ const codes: number[] = []
51
+ for (let i = 0; i < src.length; i++) {
52
+ let c = src[i]!
53
+ if (c === '\\') {
54
+ c = src[i + 1] ?? ''
55
+ if (c === '' || /[A-Za-z0-9]/.test(c)) return null
56
+ i++
57
+ } else if (/[[\]()|*+?{}^$.]/.test(c)) {
58
+ return null
59
+ }
60
+ codes.push(c.charCodeAt(0))
61
+ }
62
+ return codes.length > 0 ? codes : null
63
+ }
64
+
65
+ /**
66
+ * ONE character as it appears inside a character class, where `*` and friends are
67
+ * ordinary characters. `\x`-style escapes still have to be punctuation.
68
+ */
69
+ function classLiteralCode(src: string): number | null {
70
+ if (src.length === 1) return src.charCodeAt(0)
71
+ if (src.length === 2 && src[0] === '\\' && !/[A-Za-z0-9]/.test(src[1]!)) return src.charCodeAt(1)
72
+ return null
73
+ }
74
+
75
+ /** `[cls]+` / `[cls]*` — a positive character-class run. */
76
+ function classifyClassRun(src: string): TriviaArmMatcher | null {
77
+ const m = /^\[([^\]^](?:[^\]]|\\.)*)\][*+]$/.exec(src)
78
+ if (!m) return null
79
+ const ranges = parseClassRanges(m[1]!)
80
+ if (!ranges) return null
81
+ // `*` can match empty, but an empty match is "no match" to every caller here
82
+ // (`matchArmAt` rejects `end <= pos`), so both quantifiers scan the same.
83
+ return (input, pos) => {
84
+ let p = pos
85
+ while (p < input.length && inRanges(input.charCodeAt(p), ranges)) p++
86
+ return p
87
+ }
88
+ }
89
+
90
+ /** `//[^\n\r]*` — a literal leader run to (not including) the line break. */
91
+ function classifyLineRun(src: string): TriviaArmMatcher | null {
92
+ const m = /^(.*?)\[\^(?:\\n\\r|\\r\\n)\]\*$/.exec(src)
93
+ if (!m) return null
94
+ const codes = literalCodes(m[1]!)
95
+ if (!codes) return null
96
+ const n = codes.length
97
+ return (input, pos) => {
98
+ for (let i = 0; i < n; i++) if (input.charCodeAt(pos + i) !== codes[i]) return pos
99
+ let p = pos + n
100
+ while (p < input.length) {
101
+ const c = input.charCodeAt(p)
102
+ if (c === 10 || c === 13) break
103
+ p++
104
+ }
105
+ return p
106
+ }
107
+ }
108
+
109
+ /**
110
+ * `/*(?:[^*]|\*(?!\/))*\*\/` — a literal-delimited run, i.e. a block comment.
111
+ *
112
+ * The body alternation can never consume the closing pair, so the greedy regex
113
+ * stops at the FIRST close: an `indexOf` is not an approximation of it, it is the
114
+ * same position. An unterminated open fails in both, since the close is required.
115
+ */
116
+ function classifyDelimited(src: string): TriviaArmMatcher | null {
117
+ const m = /^(\\?.)(\\?.)\(\?:\[\^(\\?.)\]\|(\\?.)\(\?!(\\?.)\)\)\*(\\?.)(\\?.)$/.exec(src)
118
+ if (!m) return null
119
+ const open = literalCodes(m[1]! + m[2]!)
120
+ const close = literalCodes(m[6]! + m[7]!)
121
+ // `m[3]` sits inside `[^…]`, where a regex metacharacter stands for itself and
122
+ // needs no backslash — so it is read with the class-body rule, not the strict one.
123
+ const bodyExcluded = classLiteralCode(m[3]!)
124
+ const bodyGuard = literalCodes(m[4]!)
125
+ const guardNext = literalCodes(m[5]!)
126
+ if (!open || !close || bodyExcluded === null || !bodyGuard || !guardNext) return null
127
+ // The body must exclude exactly the close pair and nothing else — the shape
128
+ // `(?:[^C0]|C0(?!C1))*` with `C0C1` the close.
129
+ if (bodyExcluded !== close[0] || bodyGuard[0] !== close[0] || guardNext[0] !== close[1]) return null
130
+ const closeStr = String.fromCharCode(...close)
131
+ const openLen = open.length
132
+ const closeLen = closeStr.length
133
+ return (input, pos) => {
134
+ for (let i = 0; i < openLen; i++) if (input.charCodeAt(pos + i) !== open[i]) return pos
135
+ const hit = input.indexOf(closeStr, pos + openLen)
136
+ return hit < 0 ? pos : hit + closeLen
137
+ }
138
+ }
139
+
140
+ /** Split a regex body on top-level `|`, tracking group depth and classes. */
141
+ function splitAlts(body: string): string[] {
142
+ const out: string[] = []
143
+ let depth = 0
144
+ let inClass = false
145
+ let start = 0
146
+ for (let i = 0; i < body.length; i++) {
147
+ const c = body[i]
148
+ if (c === '\\') { i++; continue }
149
+ if (inClass) { if (c === ']') inClass = false; continue }
150
+ if (c === '[') inClass = true
151
+ else if (c === '(') depth++
152
+ else if (c === ')') depth--
153
+ else if (c === '|' && depth === 0) { out.push(body.slice(start, i)); start = i + 1 }
154
+ }
155
+ out.push(body.slice(start))
156
+ return out
157
+ }
158
+
159
+ /** Strip one full-span `(?:…)` wrapper. */
160
+ function unwrapGroup(src: string): string {
161
+ if (!src.startsWith('(?:') || !src.endsWith(')')) return src
162
+ let depth = 0
163
+ for (let i = 0; i < src.length; i++) {
164
+ const c = src[i]
165
+ if (c === '\\') { i++; continue }
166
+ if (c === '(') depth++
167
+ else if (c === ')') { depth--; if (depth === 0) return i === src.length - 1 ? src.slice(3, -1) : src }
168
+ }
169
+ return src
170
+ }
171
+
172
+ /**
173
+ * `(?:A|B|C)+` — one arm that is itself a whole trivia alternation (the shape
174
+ * `classifiedTrivia({ whitespace: /…|…|…/ })` produces when a grammar gives every
175
+ * form the SAME label).
176
+ *
177
+ * ORDERED-GREEDY IS THE REGEX RESULT, not an approximation of it. A greedy `+`
178
+ * only reconsiders an alternative when the CONTINUATION fails, and here the
179
+ * continuation is "more repetitions, then end of pattern", which cannot fail once
180
+ * one repetition has matched. So the engine keeps the first alternative that
181
+ * matches at each position, exactly as this loop does — no disjointness needed
182
+ * (`//` and `/*` share a leading `/` and still agree). An alternative that matches
183
+ * empty terminates both, as `end > p` requires progress.
184
+ */
185
+ function classifyAltRun(src: string): TriviaArmMatcher | null {
186
+ const m = /^\((?:\?:)?(.*)\)[*+]$/.exec(src)
187
+ if (!m) return null
188
+ const parts = splitAlts(m[1]!)
189
+ if (parts.length < 2) return null
190
+ const matchers: TriviaArmMatcher[] = []
191
+ for (const part of parts) {
192
+ const sub = classifyArm(unwrapGroup(part))
193
+ if (!sub) return null
194
+ matchers.push(sub)
195
+ }
196
+ const n = matchers.length
197
+ return (input, pos) => {
198
+ let p = pos
199
+ scan: while (p < input.length) {
200
+ for (let i = 0; i < n; i++) {
201
+ const end = matchers[i]!(input, p)
202
+ if (end > p) { p = end; continue scan }
203
+ }
204
+ break
205
+ }
206
+ return p
207
+ }
208
+ }
209
+
210
+ function classifyArm(src: string): TriviaArmMatcher | null {
211
+ return classifyClassRun(src)
212
+ ?? classifyLineRun(src)
213
+ ?? classifyDelimited(src)
214
+ ?? classifyAltRun(src)
215
+ }
216
+
217
+ function classifyParser(p: Combinator<unknown>): TriviaArmMatcher | null {
218
+ const d = p._def
219
+ if (d.tag !== 'regex' || d.flags) return null
220
+ return classifyArm(d.source)
221
+ }
222
+
223
+ let armSpec0: LabeledTriviaSpec | undefined
224
+ let armSpec1: LabeledTriviaSpec | undefined
225
+ let armSpec2: LabeledTriviaSpec | undefined
226
+ let armSpec3: LabeledTriviaSpec | undefined
227
+ let armValue0: readonly TriviaArmMatcher[] | null | undefined
228
+ let armValue1: readonly TriviaArmMatcher[] | null | undefined
229
+ let armValue2: readonly TriviaArmMatcher[] | null | undefined
230
+ let armValue3: readonly TriviaArmMatcher[] | null | undefined
231
+
232
+ /**
233
+ * The character-level matchers for a spec's arms — index-parallel to `spec.arms`,
234
+ * so a matcher's index IS its kind index — or `null` when any arm has no lowering.
235
+ *
236
+ * `spec.minRepeats > 1` is excluded: the loops below report only an END, and a run
237
+ * that fails a minimum of two must report NOTHING. `classifiedTrivia()` never
238
+ * produces one, and the combinator path still handles it if a host writes one.
239
+ */
240
+ export function charArmsFor(spec: LabeledTriviaSpec): readonly TriviaArmMatcher[] | null {
241
+ if (spec === armSpec0) return armValue0!
242
+ if (spec === armSpec1) return armValue1!
243
+ if (spec === armSpec2) return armValue2!
244
+ if (spec === armSpec3) return armValue3!
245
+ let arms: TriviaArmMatcher[] | null = []
246
+ if (spec.minRepeats > 1) {
247
+ arms = null
248
+ } else {
249
+ for (const arm of spec.arms) {
250
+ const c = classifyParser(arm.parser)
251
+ if (!c) { arms = null; break }
252
+ arms.push(c)
253
+ }
254
+ }
255
+ armSpec3 = armSpec2
256
+ armValue3 = armValue2
257
+ armSpec2 = armSpec1
258
+ armValue2 = armValue1
259
+ armSpec1 = armSpec0
260
+ armValue1 = armValue0
261
+ armSpec0 = spec
262
+ armValue0 = arms
263
+ return arms
264
+ }
265
+
266
+ /** End of the trivia run at `cur`, or `cur` when nothing matched. */
267
+ export function charTriviaEnd(input: string, cur: number, arms: readonly TriviaArmMatcher[]): number {
268
+ const n = arms.length
269
+ let pos = cur
270
+ scan: while (pos < input.length) {
271
+ for (let i = 0; i < n; i++) {
272
+ const end = arms[i]!(input, pos)
273
+ if (end > pos) { pos = end; continue scan }
274
+ }
275
+ break
276
+ }
277
+ return pos
278
+ }
279
+
280
+ /** As `charTriviaEnd`, reporting each chunk with the kind index that matched it. */
281
+ export function charTriviaVisit(
282
+ input: string,
283
+ cur: number,
284
+ arms: readonly TriviaArmMatcher[],
285
+ visit: (start: number, end: number, kindIndex: number) => void,
286
+ ): number {
287
+ const n = arms.length
288
+ let pos = cur
289
+ scan: while (pos < input.length) {
290
+ for (let i = 0; i < n; i++) {
291
+ const end = arms[i]!(input, pos)
292
+ if (end > pos) { visit(pos, end, i); pos = end; continue scan }
293
+ }
294
+ break
295
+ }
296
+ return pos
297
+ }
298
+
299
+ /** The `1 << kindIndex` mask of the categories in the run at `cur`. */
300
+ export function charTriviaKindMask(input: string, cur: number, arms: readonly TriviaArmMatcher[]): number {
301
+ const n = arms.length
302
+ let pos = cur
303
+ let mask = 0
304
+ scan: while (pos < input.length) {
305
+ for (let i = 0; i < n; i++) {
306
+ const end = arms[i]!(input, pos)
307
+ if (end > pos) { mask |= 1 << i; pos = end; continue scan }
308
+ }
309
+ break
310
+ }
311
+ return mask
312
+ }
@@ -0,0 +1,285 @@
1
+ /** View over a flat trivia log (`_triviaLog` or per-node `triviaLog`). */
2
+ export type TriviaEntriesView = {
3
+ readonly length: number
4
+ readonly labels: readonly string[] | undefined
5
+ readonly stride: number
6
+ start(i: number): number
7
+ end(i: number): number
8
+ /** Raw-child insertion boundary; defined only for a per-node trivia log. */
9
+ insertIndex(i: number): number | undefined
10
+ kindIndex(i: number): number | undefined
11
+ kind(i: number): string | undefined
12
+ text(i: number, input: string): string
13
+ }
14
+
15
+ export type RootTriviaGap = {
16
+ /** Start offset of the contiguous trivia gap. */
17
+ readonly start: number
18
+ /** End offset of the contiguous trivia gap. */
19
+ readonly end: number
20
+ /** Entry indices into `entries` that make up this gap. */
21
+ readonly entryIndices: readonly number[]
22
+ /** True when any entry in the gap has the requested labeled trivia kind. */
23
+ hasKind(kind: string): boolean
24
+ /** Slice the full contiguous gap text from the original input. */
25
+ text(input: string): string
26
+ }
27
+
28
+ export type RootTriviaIndex = {
29
+ /** Lazy entry-level view over sparse selected-root rows. */
30
+ readonly entries: TriviaEntriesView
31
+ /** Selected-root label table; entry kind indices refer to this array. */
32
+ readonly labels: readonly string[]
33
+ /**
34
+ * Contiguous trivia gaps keyed by the following content offset. Values are
35
+ * entry indices into `entries`, not materialized `{ value, span }` objects.
36
+ */
37
+ readonly before: ReadonlyMap<number, readonly number[]>
38
+ /**
39
+ * Contiguous trivia gaps keyed by the preceding content offset. Values are
40
+ * entry indices into `entries`, not materialized `{ value, span }` objects.
41
+ */
42
+ readonly after: ReadonlyMap<number, readonly number[]>
43
+ entryIndicesBefore(offset: number): readonly number[]
44
+ entryIndicesAfter(offset: number): readonly number[]
45
+ /**
46
+ * Contiguous trivia gap immediately before `offset`, keyed by the following
47
+ * content offset. Returns undefined when no gap sits at that boundary.
48
+ */
49
+ gapBefore(offset: number): RootTriviaGap | undefined
50
+ /**
51
+ * Contiguous trivia gap immediately after `offset`, keyed by the preceding
52
+ * content offset. Returns undefined when no gap sits at that boundary.
53
+ */
54
+ gapAfter(offset: number): RootTriviaGap | undefined
55
+ /** All contiguous trivia gaps in source order. */
56
+ gaps(): readonly RootTriviaGap[]
57
+ /** Source-ordered gaps containing at least one entry with the given label. */
58
+ gapsWithKind(kind: string | readonly string[]): readonly RootTriviaGap[]
59
+ }
60
+
61
+ const EMPTY_INDICES: readonly number[] = Object.freeze([])
62
+ const EMPTY_GAPS: readonly RootTriviaGap[] = Object.freeze([])
63
+ /** Number of packed fields in one selected-root trivia row. */
64
+ export const SELECTED_ROOT_STRIDE = 5
65
+
66
+ type RootTriviaMaps = {
67
+ before: Map<number, number[]>
68
+ after: Map<number, number[]>
69
+ beforeGaps: Map<number, RootTriviaGap>
70
+ afterGaps: Map<number, RootTriviaGap>
71
+ gaps: RootTriviaGap[]
72
+ }
73
+
74
+ function entryOffset(i: number, stride: number): number {
75
+ return i * stride
76
+ }
77
+
78
+ /**
79
+ * Wrap a flat trivia log. Stride is inferred from `labels`:
80
+ * - root `_triviaLog`: 2 (start, end) or 3 (+ kindIndex)
81
+ * - node `triviaLog`: 3 (start, end, insertIdx) or 4 (+ kindIndex)
82
+ */
83
+ export function triviaEntries(
84
+ log: readonly number[],
85
+ labels?: readonly string[],
86
+ opts?: { nodeLog?: boolean },
87
+ ): TriviaEntriesView {
88
+ const baseStride = opts?.nodeLog ? 3 : 2
89
+ const stride = labels ? baseStride + 1 : baseStride
90
+ const length = Math.floor(log.length / stride)
91
+
92
+ return {
93
+ length,
94
+ labels,
95
+ stride,
96
+ start(i) {
97
+ return log[entryOffset(i, stride)]!
98
+ },
99
+ end(i) {
100
+ return log[entryOffset(i, stride) + 1]!
101
+ },
102
+ insertIndex(i) {
103
+ return opts?.nodeLog ? log[entryOffset(i, stride) + 2] : undefined
104
+ },
105
+ kindIndex(i) {
106
+ if (!labels) return undefined
107
+ return log[entryOffset(i, stride) + baseStride]
108
+ },
109
+ kind(i) {
110
+ const ki = labels ? log[entryOffset(i, stride) + baseStride] : undefined
111
+ return ki !== undefined ? labels![ki] : undefined
112
+ },
113
+ text(i, input) {
114
+ const s = log[entryOffset(i, stride)]!
115
+ const e = log[entryOffset(i, stride) + 1]!
116
+ return input.slice(s, e)
117
+ },
118
+ }
119
+ }
120
+
121
+ /**
122
+ * Build a root view over the sparse selected-label rows captured as
123
+ * `[ownedRangeStart, ownedRangeEnd, markerStart, markerEnd, kindIndex]`.
124
+ *
125
+ * This is intentionally a different input shape from `triviaLog`: each row is
126
+ * one selected marker, while the first pair names the complete committed trivia
127
+ * range around it. Thus a mixed trivia run costs one five-number row, not three
128
+ * legacy trivia entries. Rows are source ordered and equal gap pairs are
129
+ * contiguous, which lets singleton boundary lookups binary-search the sparse
130
+ * rows without constructing document-wide maps.
131
+ */
132
+ export function buildRootTriviaIndex(
133
+ log: readonly number[],
134
+ labels: readonly string[],
135
+ ): RootTriviaIndex {
136
+ const length = Math.floor(log.length / SELECTED_ROOT_STRIDE)
137
+ const markerOffset = (i: number) => i * SELECTED_ROOT_STRIDE
138
+ const ownedRangeStart = (i: number) => log[markerOffset(i)]!
139
+ const ownedRangeEnd = (i: number) => log[markerOffset(i) + 1]!
140
+ const markerStart = (i: number) => log[markerOffset(i) + 2]!
141
+ const markerEnd = (i: number) => log[markerOffset(i) + 3]!
142
+ const markerKind = (i: number) => log[markerOffset(i) + 4]!
143
+
144
+ const entries: TriviaEntriesView = {
145
+ length,
146
+ labels,
147
+ stride: SELECTED_ROOT_STRIDE,
148
+ start: markerStart,
149
+ end: markerEnd,
150
+ insertIndex: () => undefined,
151
+ kindIndex: markerKind,
152
+ kind(i) {
153
+ return labels[markerKind(i)]
154
+ },
155
+ text(i, input) {
156
+ return input.slice(markerStart(i), markerEnd(i))
157
+ },
158
+ }
159
+
160
+ const lowerBound = (offset: number, by: (i: number) => number): number => {
161
+ let lo = 0
162
+ let hi = length
163
+ while (lo < hi) {
164
+ const mid = (lo + hi) >>> 1
165
+ if (by(mid) < offset) lo = mid + 1
166
+ else hi = mid
167
+ }
168
+ return lo
169
+ }
170
+
171
+ const gapRowsAt = (offset: number, direction: 'before' | 'after'): [number, number] | undefined => {
172
+ const edge = direction === 'before' ? ownedRangeEnd : ownedRangeStart
173
+ let first = lowerBound(offset, edge)
174
+ if (first === length || edge(first) !== offset) return undefined
175
+ const start = ownedRangeStart(first)
176
+ const end = ownedRangeEnd(first)
177
+ while (first > 0 && ownedRangeStart(first - 1) === start && ownedRangeEnd(first - 1) === end) first--
178
+ let last = first + 1
179
+ while (last < length && ownedRangeStart(last) === start && ownedRangeEnd(last) === end) last++
180
+ return [first, last]
181
+ }
182
+
183
+ const indices = (first: number, last: number): readonly number[] => {
184
+ const out: number[] = []
185
+ for (let i = first; i < last; i++) out.push(i)
186
+ return out
187
+ }
188
+
189
+ const makeGap = (first: number, last: number): RootTriviaGap => {
190
+ const start = ownedRangeStart(first)
191
+ const end = ownedRangeEnd(first)
192
+ let entryIndices: readonly number[] | undefined
193
+ return {
194
+ start,
195
+ end,
196
+ get entryIndices() {
197
+ return entryIndices ??= indices(first, last)
198
+ },
199
+ hasKind(kind) {
200
+ for (let i = first; i < last; i++) {
201
+ if (labels[markerKind(i)] === kind) return true
202
+ }
203
+ return false
204
+ },
205
+ text(input) {
206
+ return input.slice(start, end)
207
+ },
208
+ }
209
+ }
210
+
211
+ let maps: RootTriviaMaps | undefined
212
+ const getMaps = () => {
213
+ if (maps !== undefined) return maps
214
+ const before = new Map<number, number[]>()
215
+ const after = new Map<number, number[]>()
216
+ const beforeGaps = new Map<number, RootTriviaGap>()
217
+ const afterGaps = new Map<number, RootTriviaGap>()
218
+ const gaps: RootTriviaGap[] = []
219
+ for (let first = 0; first < length;) {
220
+ let last = first + 1
221
+ while (last < length && ownedRangeStart(last) === ownedRangeStart(first) && ownedRangeEnd(last) === ownedRangeEnd(first)) last++
222
+ const gap = makeGap(first, last)
223
+ gaps.push(gap)
224
+ afterGaps.set(gap.start, gap)
225
+ beforeGaps.set(gap.end, gap)
226
+ after.set(gap.start, [...gap.entryIndices])
227
+ before.set(gap.end, [...gap.entryIndices])
228
+ first = last
229
+ }
230
+ maps = { before, after, beforeGaps, afterGaps, gaps }
231
+ return maps
232
+ }
233
+
234
+ return {
235
+ entries,
236
+ labels,
237
+ get before() { return getMaps().before },
238
+ get after() { return getMaps().after },
239
+ entryIndicesBefore(offset) {
240
+ const rows = gapRowsAt(offset, 'before')
241
+ return rows ? indices(rows[0], rows[1]) : EMPTY_INDICES
242
+ },
243
+ entryIndicesAfter(offset) {
244
+ const rows = gapRowsAt(offset, 'after')
245
+ return rows ? indices(rows[0], rows[1]) : EMPTY_INDICES
246
+ },
247
+ gapBefore(offset) {
248
+ const rows = gapRowsAt(offset, 'before')
249
+ return rows ? makeGap(rows[0], rows[1]) : undefined
250
+ },
251
+ gapAfter(offset) {
252
+ const rows = gapRowsAt(offset, 'after')
253
+ return rows ? makeGap(rows[0], rows[1]) : undefined
254
+ },
255
+ gaps() {
256
+ return getMaps().gaps
257
+ },
258
+ gapsWithKind(kind) {
259
+ const kinds = typeof kind === 'string' ? [kind] : kind
260
+ const matches: RootTriviaGap[] = []
261
+ // Do not materialize the document-wide boundary maps for a sparse query.
262
+ // The primary consumer selects comments: it needs only comment-bearing gaps,
263
+ // not an object/Map for every whitespace-owned boundary in the document.
264
+ for (let first = 0; first < length;) {
265
+ let last = first + 1
266
+ while (last < length
267
+ && ownedRangeStart(last) === ownedRangeStart(first)
268
+ && ownedRangeEnd(last) === ownedRangeEnd(first)) last++
269
+ let matched = false
270
+ for (let i = first; i < last && !matched; i++) {
271
+ const label = labels[markerKind(i)]
272
+ for (const kind of kinds) {
273
+ if (label === kind) {
274
+ matched = true
275
+ break
276
+ }
277
+ }
278
+ }
279
+ if (matched) matches.push(makeGap(first, last))
280
+ first = last
281
+ }
282
+ return matches.length === 0 ? EMPTY_GAPS : matches
283
+ },
284
+ }
285
+ }