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,209 @@
1
+ /**
2
+ * Derived tokenization, part 1: the terminal ALPHABET.
3
+ *
4
+ * Parseman knows every terminal in the composed grammar at macro time — every
5
+ * `literal`, `keywords`, and `regex`. This module walks the combinator graph
6
+ * and assigns each distinct terminal a small integer TOKEN ID.
7
+ *
8
+ * Two things are deliberately separated, because conflating them is what makes
9
+ * a naive derived scanner useless:
10
+ *
11
+ * - The token ID SPACE is GLOBAL. One id per distinct terminal across the whole
12
+ * grammar, so an id can be compared, switched on, and indexed with.
13
+ * - The CANDIDATE SET is LOCAL, per decision point. Global maximal munch over
14
+ * the whole alphabet is wrong: a css alphabet contains construct-local
15
+ * long-run terminals (`[^()]+`, `[^{}/]+`, the `scanTo` raw-prelude runs)
16
+ * that swallow the document. Measured: global munch yields SEVEN tokens for a
17
+ * 123 KB stylesheet. A decision point consults only the terminals that can
18
+ * actually start one of its arms.
19
+ *
20
+ * Nothing here decides HOW a token is recognized (see `token-scanner.ts`); this
21
+ * module only decides WHAT the tokens are and WHICH ones each site may see.
22
+ */
23
+ import type { Combinator, ParserDef } from '../types.ts'
24
+
25
+ /** A terminal in the derived alphabet, with its globally assigned id. */
26
+ export type TokenTerminal =
27
+ | { kind: 'literal'; id: number; value: string; caseInsensitive: boolean }
28
+ | { kind: 'keywords'; id: number; words: readonly string[]; caseInsensitive: boolean; boundary: string | undefined }
29
+ | { kind: 'regex'; id: number; source: string; flags: string }
30
+
31
+ /** Reserved ids. Real terminals start at `FIRST_TERMINAL_ID`. */
32
+ export const TOK_EOF = 0
33
+ export const TOK_UNKNOWN = 1
34
+ export const TOK_WS = 2
35
+ export const FIRST_TERMINAL_ID = 3
36
+
37
+ export type Alphabet = {
38
+ terminals: TokenTerminal[]
39
+ /** Dedup key → id, so the same terminal spelled twice gets ONE id. */
40
+ byKey: Map<string, number>
41
+ /** The combinator that contributed each id (first one wins), for candidate sets. */
42
+ originOf: Map<number, Combinator<unknown>>
43
+ }
44
+
45
+ function keyOf(def: ParserDef): string | undefined {
46
+ switch (def.tag) {
47
+ case 'literal': return `L\u0000${def.value}\u0000${def.caseInsensitive ? 'i' : ''}`
48
+ case 'keywords': return `K\u0000${def.words.join('\u0001')}\u0000${def.caseInsensitive ? 'i' : ''}\u0000${def.boundary ?? ''}`
49
+ case 'regex': return `R\u0000${def.source}\u0000${def.flags}`
50
+ default: return undefined
51
+ }
52
+ }
53
+
54
+ function terminalOf(def: ParserDef, id: number): TokenTerminal | undefined {
55
+ switch (def.tag) {
56
+ case 'literal': return { kind: 'literal', id, value: def.value, caseInsensitive: def.caseInsensitive }
57
+ case 'keywords': return { kind: 'keywords', id, words: def.words, caseInsensitive: def.caseInsensitive, boundary: def.boundary }
58
+ case 'regex': return { kind: 'regex', id, source: def.source, flags: def.flags }
59
+ default: return undefined
60
+ }
61
+ }
62
+
63
+ /** Direct sub-parsers of a def, resolving a lazy through `resolve` when its own thunk is a hole. */
64
+ export function tokenChildren(
65
+ p: Combinator<unknown>,
66
+ resolve?: (name: string) => Combinator<unknown> | undefined,
67
+ ): Combinator<unknown>[] {
68
+ const d = p._def
69
+ const out: Combinator<unknown>[] = []
70
+ const push = (v: Combinator<unknown> | undefined): void => { if (v !== undefined) out.push(v) }
71
+ const rec = d as unknown as Record<string, unknown>
72
+ for (const k of ['parser', 'main', 'skipped', 'separator', 'selector', 'sentinel', 'fallback', 'otherwise', 'triviaParser']) {
73
+ const v = rec[k]
74
+ if (v !== null && typeof v === 'object' && '_def' in (v as object)) push(v as Combinator<unknown>)
75
+ }
76
+ if (Array.isArray(rec.parsers)) for (const c of rec.parsers as Combinator<unknown>[]) push(c)
77
+ if (Array.isArray(rec.skip)) for (const c of rec.skip as Combinator<unknown>[]) push(c)
78
+ if (Array.isArray(rec.cases)) for (const c of rec.cases as Array<{ parser: Combinator<unknown> }>) push(c.parser)
79
+ if (Array.isArray(rec.matchers)) for (const c of rec.matchers as Array<{ parser: Combinator<unknown> }>) push(c.parser)
80
+ if (d.tag === 'lazy') {
81
+ let target: Combinator<unknown> | undefined
82
+ try { target = d.thunk() } catch { target = undefined }
83
+ if (target === undefined && resolve !== undefined) {
84
+ const name = (p as unknown as { _ruleName?: string })._ruleName
85
+ if (name !== undefined) target = resolve(name)
86
+ }
87
+ push(target)
88
+ }
89
+ return out
90
+ }
91
+
92
+ /**
93
+ * Collect the alphabet over `roots`. Ids are assigned in first-encounter order,
94
+ * which is stable for a given grammar and therefore safe to switch on.
95
+ */
96
+ export function collectAlphabet(
97
+ roots: ReadonlyArray<Combinator<unknown>>,
98
+ resolve?: (name: string) => Combinator<unknown> | undefined,
99
+ ): Alphabet {
100
+ const terminals: TokenTerminal[] = []
101
+ const byKey = new Map<string, number>()
102
+ const originOf = new Map<number, Combinator<unknown>>()
103
+ const seen = new Set<Combinator<unknown>>()
104
+
105
+ const walk = (p: Combinator<unknown>): void => {
106
+ if (seen.has(p)) return
107
+ seen.add(p)
108
+ const key = keyOf(p._def)
109
+ if (key !== undefined && !byKey.has(key)) {
110
+ const id = FIRST_TERMINAL_ID + terminals.length
111
+ const t = terminalOf(p._def, id)
112
+ if (t !== undefined) {
113
+ byKey.set(key, id)
114
+ terminals.push(t)
115
+ originOf.set(id, p)
116
+ }
117
+ }
118
+ for (const c of tokenChildren(p, resolve)) walk(c)
119
+ }
120
+ for (const r of roots) walk(r)
121
+ return { terminals, byKey, originOf }
122
+ }
123
+
124
+ /** The id already assigned to this terminal, if it is one. */
125
+ export function terminalId(alphabet: Alphabet, p: Combinator<unknown>): number | undefined {
126
+ const key = keyOf(p._def)
127
+ return key === undefined ? undefined : alphabet.byKey.get(key)
128
+ }
129
+
130
+ /**
131
+ * The LEADING terminal of an arm — the one a decision point would consult. Walks
132
+ * through the wrappers that do not consume input, and through a sequence's
133
+ * nullable/zero-width prefix. Returns undefined when the lead is not a single
134
+ * derived terminal, which is the signal to stay scannerless at that site.
135
+ */
136
+ export function leadTerminal(
137
+ p: Combinator<unknown>,
138
+ alphabet: Alphabet,
139
+ resolve?: (name: string) => Combinator<unknown> | undefined,
140
+ depth = 0,
141
+ ): number | undefined {
142
+ if (depth > 24) return undefined
143
+ const direct = terminalId(alphabet, p)
144
+ if (direct !== undefined) return direct
145
+ const d = p._def
146
+ switch (d.tag) {
147
+ case 'sequence': {
148
+ for (const c of d.parsers) {
149
+ // CLASSIFY FIRST. The old order asked for the inner terminal and returned
150
+ // it before checking whether the term could consume anything, so:
151
+ //
152
+ // sequence(optional(X), Y) yielded X's terminal as THE lead -- but the
153
+ // input may legally start with Y's instead
154
+ // sequence(not(X), Y) yielded X's terminal -- from a NEGATIVE
155
+ // lookahead, which never consumes
156
+ //
157
+ // A single lead terminal cannot express "X's first set OR Y's", so a
158
+ // nullable prefix has no direct lead and the site must be REJECTED --
159
+ // scannerless gating handles it correctly. Zero-width prefixes are
160
+ // different: they consume nothing, so the next term genuinely leads.
161
+ const cd = c._def.tag
162
+ if (cd === 'not' || cd === 'peek' || cd === 'guard') continue
163
+ if (cd === 'optional' || cd === 'many') return undefined
164
+ return leadTerminal(c, alphabet, resolve, depth + 1)
165
+ }
166
+ return undefined
167
+ }
168
+ case 'sepBy':
169
+ return leadTerminal(d.parser, alphabet, resolve, depth + 1)
170
+ case 'lazy': {
171
+ let target: Combinator<unknown> | undefined
172
+ try { target = d.thunk() } catch { target = undefined }
173
+ if (target === undefined && resolve !== undefined) {
174
+ const name = (p as unknown as { _ruleName?: string })._ruleName
175
+ if (name !== undefined) target = resolve(name)
176
+ }
177
+ return target === undefined ? undefined : leadTerminal(target, alphabet, resolve, depth + 1)
178
+ }
179
+ case 'node': case 'field': case 'label': case 'transform': case 'leaf':
180
+ case 'token': case 'grammar': case 'attempt': case 'expect': case 'withCtx':
181
+ case 'oneOrMore': case 'trivia': case 'recover':
182
+ return leadTerminal(d.parser, alphabet, resolve, depth + 1)
183
+ default:
184
+ return undefined
185
+ }
186
+ }
187
+
188
+ /**
189
+ * The CANDIDATE SET for one decision point: the leading terminals of its arms.
190
+ * `complete` is false when any arm's lead is not a derived terminal — that site
191
+ * must stay scannerless, per-construct, exactly as `scanSkip` and first-set
192
+ * gating already apply per region rather than globally.
193
+ */
194
+ export type CandidateSet = { ids: number[]; complete: boolean }
195
+
196
+ export function candidateSet(
197
+ arms: ReadonlyArray<Combinator<unknown>>,
198
+ alphabet: Alphabet,
199
+ resolve?: (name: string) => Combinator<unknown> | undefined,
200
+ ): CandidateSet {
201
+ const ids: number[] = []
202
+ let complete = true
203
+ for (const a of arms) {
204
+ const t = leadTerminal(a, alphabet, resolve)
205
+ if (t === undefined) { complete = false; continue }
206
+ if (!ids.includes(t)) ids.push(t)
207
+ }
208
+ return { ids, complete }
209
+ }
@@ -0,0 +1,353 @@
1
+ /**
2
+ * Derived tokenization, part 3: token-KEYED dispatch.
3
+ *
4
+ * A `dispatch()` used to emit, per case, a chained condition that re-derived the
5
+ * key from the selector's STRING one character at a time:
6
+ *
7
+ * if (_dkey.length === 6 && _dkey.charCodeAt(0) === 64 && (_dkey.charCodeAt(1) | 32) === 108 && ...)
8
+ * else if (_dkey.length === 15 && ...)
9
+ *
10
+ * Measured on the built css artifact: 40,269 B of 3,336,650 B was exactly these
11
+ * chains, 10,499 B of it inside `_r_StylesheetAtRule` alone.
12
+ *
13
+ * Instead the selector's matched span is walked to a small integer case id, and
14
+ * the cases select on the integer. Every id strategy below is BUILT, not
15
+ * reasoned about — `PARSEMAN_DISPATCH=<id>:<sel>` picks one so they can be
16
+ * measured on the shipped artifact rather than in a microbenchmark, which
17
+ * already misled us once: a standalone "table" looked slow because that
18
+ * implementation sliced and lowercased and hashed, not because table dispatch
19
+ * is slow.
20
+ *
21
+ * Every strategy is "compute a candidate case cheaply, then VERIFY against the
22
+ * packed key text", except `trie`, whose walk is already exact.
23
+ */
24
+
25
+ /** One dispatch case's keys, in emission order. Case ids are 1-based; 0 = no match. */
26
+ export type DispatchKeySet = { keys: readonly string[]; caseInsensitive: boolean }
27
+
28
+ export type IdStrategy = 'trie' | 'lenswitch' | 'firstchar' | 'phash'
29
+ export type ArmSelection = 'ifchain' | 'switch'
30
+
31
+ export type DispatchConfig = { id: IdStrategy | 'auto'; sel: ArmSelection }
32
+
33
+ /**
34
+ * `PARSEMAN_DISPATCH=<id>:<sel>`. The default is the MEASURED winner on the
35
+ * shipped css artifact, not a preference. All five configurations were built
36
+ * into this emitter and compared on the real artifact with the trees diffed for
37
+ * equality, interleaved in one process (medians of 31 rounds):
38
+ *
39
+ * config ms/parse rel raw B gzip B
40
+ * chain 6.092 1.000 3,336,650 426,247
41
+ * trie:ifchain 5.967 0.979 3,311,657 424,465 <- default
42
+ * trie:switch 5.945 0.976 3,333,421 424,895
43
+ * phash:switch 6.011 0.987 3,331,361 424,637
44
+ *
45
+ * `firstchar` and `lenswitch` BUILD but are inapplicable to css's at-keyword
46
+ * sets — every key starts with `@`, and the lengths collide — so they fall back
47
+ * and emit the chain. `phash` finds an injective hash (the search works) but
48
+ * costs more table bytes than the trie saves. `switch` arm selection is within
49
+ * noise of the chain of integer compares at 3 sites x 8 cases; its larger raw
50
+ * size is the downstream formatter indenting case bodies, which is why gzip is
51
+ * the metric that decides here.
52
+ *
53
+ * The speed spread across ALL configurations is 2.4% — dispatch keying is not
54
+ * where css parse time goes.
55
+ */
56
+ export function dispatchConfigFromEnv(env: Record<string, string | undefined>): DispatchConfig {
57
+ const raw = env.PARSEMAN_DISPATCH
58
+ if (raw === undefined || raw === '') return { id: 'trie', sel: 'ifchain' }
59
+ const [idPart, selPart] = raw.split(':')
60
+ const id = (['trie', 'lenswitch', 'firstchar', 'phash', 'auto'] as const).find(s => s === idPart) ?? 'auto'
61
+ const sel: ArmSelection = selPart === 'ifchain' ? 'ifchain' : 'switch'
62
+ return { id, sel }
63
+ }
64
+
65
+ /** Flattened (key, caseIndex) pairs, folded when the case is case-insensitive. */
66
+ type FlatKey = { text: string; caseIndex: number }
67
+
68
+ function flatten(cases: ReadonlyArray<DispatchKeySet>): FlatKey[] | null {
69
+ const out: FlatKey[] = []
70
+ for (const [i, c] of cases.entries()) {
71
+ for (const key of c.keys) {
72
+ if (key.length === 0) return null
73
+ // A case-SENSITIVE key containing an ASCII letter cannot share a folded
74
+ // comparison with the rest; refuse rather than accept the wrong case.
75
+ if (!c.caseInsensitive && /[a-zA-Z]/.test(key)) return null
76
+ out.push({ text: foldText(key), caseIndex: i + 1 })
77
+ }
78
+ }
79
+ return out.length === 0 ? null : out
80
+ }
81
+
82
+ /**
83
+ * ASCII letter-only case fold. Used IDENTICALLY when building the tables and
84
+ * when reading input — the two must never disagree. The cheaper `c | 32` is
85
+ * wrong here: it maps '@'(64) to '`'(96), which silently stops every '@'-led
86
+ * key from ever matching, and maps '_'(95) to DEL(127), which would accept a
87
+ * character the key does not contain.
88
+ */
89
+ export const foldExpr = (c: string): string => `(${c} >= 65 && ${c} <= 90 ? ${c} + 32 : ${c})`
90
+ export function foldCode(c: number): number { return c >= 65 && c <= 90 ? c + 32 : c }
91
+ function foldText(t: string): string {
92
+ let out = ''
93
+ for (let i = 0; i < t.length; i++) out += String.fromCharCode(foldCode(t.charCodeAt(i)))
94
+ return out
95
+ }
96
+
97
+ /**
98
+ * Pack a non-negative int array into a two-chars-per-value string literal.
99
+ *
100
+ * Two chars at six bits each is TWELVE BITS: the representable range is 0..4095.
101
+ * The mask made anything larger wrap SILENTLY, and `unpack` then decoded a wrong
102
+ * index — a table that looks fine and routes to the wrong arm. Bounded here, and
103
+ * this is the ONLY implementation: a second copy in token-scanner.ts had the same
104
+ * encoding and the same missing check, which is how one defect became two.
105
+ */
106
+ export const PACK_MAX = 4095
107
+ export function packInts(values: readonly number[]): string {
108
+ let s = ''
109
+ for (const v of values) {
110
+ if (!Number.isInteger(v) || v < 0 || v > PACK_MAX) {
111
+ throw new RangeError(
112
+ `packInts: ${v} is outside the 12-bit range 0..${PACK_MAX} this encoding can represent. ` +
113
+ 'Widen the encoding rather than letting the value wrap.',
114
+ )
115
+ }
116
+ s += String.fromCharCode(35 + (v & 63), 35 + ((v >> 6) & 63))
117
+ }
118
+ return JSON.stringify(s)
119
+ }
120
+
121
+ /* ------------------------------------------------------------------ *
122
+ * Shared runtime helpers, emitted ONCE per artifact however many sites
123
+ * use them. That is what lets tables shrink the artifact instead of
124
+ * growing it: only the small per-site data is duplicated.
125
+ * ------------------------------------------------------------------ */
126
+
127
+ export type SharedHelper = 'unpack' | 'verify' | 'trie' | 'lenswitch' | 'firstchar' | 'phash'
128
+
129
+ export function sharedHelperDecl(kind: SharedHelper, n: (h: SharedHelper) => string): string {
130
+ switch (kind) {
131
+ case 'unpack':
132
+ return `const ${n('unpack')} = s => { const o = new Int32Array(s.length >> 1); for (let i = 0; i < o.length; i++) o[i] = (s.charCodeAt(i * 2) - 35) | ((s.charCodeAt(i * 2 + 1) - 35) << 6); return o }`
133
+ case 'verify':
134
+ return [
135
+ `function ${n('verify')}(input, from, to, keys, ko, kl) {`,
136
+ ` if (to - from !== kl) return false`,
137
+ ` for (let i = 0; i < kl; i++) { const c = input.charCodeAt(from + i); if (${foldExpr('c')} !== keys.charCodeAt(ko + i)) return false }`,
138
+ ` return true`,
139
+ `}`,
140
+ ].join('\n')
141
+ case 'trie':
142
+ return [
143
+ `function ${n('trie')}(input, from, to, ch, st, nx, ac) {`,
144
+ ` let node = 0`,
145
+ ` for (let i = from; i < to; i++) {`,
146
+ ` const c0 = input.charCodeAt(i), c = ${foldExpr('c0')}`,
147
+ ` let t = -1`,
148
+ ` for (let e = st[node], z = st[node + 1]; e < z; e++) if (ch.charCodeAt(e) === c) { t = nx[e]; break }`,
149
+ ` if (t < 0) return 0`,
150
+ ` node = t`,
151
+ ` }`,
152
+ ` return ac[node]`,
153
+ `}`,
154
+ ].join('\n')
155
+ case 'lenswitch':
156
+ return [
157
+ `function ${n('lenswitch')}(input, from, to, lens, cix, keys, kos, kls) {`,
158
+ ` const len = to - from`,
159
+ ` for (let i = 0; i < lens.length; i++) {`,
160
+ ` if (lens[i] !== len) continue`,
161
+ ` if (${n('verify')}(input, from, to, keys, kos[i], kls[i])) return cix[i]`,
162
+ ` }`,
163
+ ` return 0`,
164
+ `}`,
165
+ ].join('\n')
166
+ case 'firstchar':
167
+ return [
168
+ `function ${n('firstchar')}(input, from, to, tab, cix, keys, kos, kls) {`,
169
+ ` const c0 = input.charCodeAt(from), c = ${foldExpr('c0')}`,
170
+ ` if (c > 127) return 0`,
171
+ ` let i = tab[c]`,
172
+ ` if (i === 0) return 0`,
173
+ ` i--`,
174
+ ` return ${n('verify')}(input, from, to, keys, kos[i], kls[i]) ? cix[i] : 0`,
175
+ `}`,
176
+ ].join('\n')
177
+ case 'phash':
178
+ return [
179
+ `function ${n('phash')}(input, from, to, p1, p2, mul, mod, tab, cix, keys, kos, kls) {`,
180
+ ` const len = to - from`,
181
+ ` let a = 0, b = 0`,
182
+ ` if (p1 < len) { const c = input.charCodeAt(from + p1); a = ${foldExpr('c')} }`,
183
+ ` if (p2 < len) { const c = input.charCodeAt(from + p2); b = ${foldExpr('c')} }`,
184
+ ` let i = tab[(len * mul + a * 31 + b) % mod]`,
185
+ ` if (i === 0) return 0`,
186
+ ` i--`,
187
+ ` return ${n('verify')}(input, from, to, keys, kos[i], kls[i]) ? cix[i] : 0`,
188
+ `}`,
189
+ ].join('\n')
190
+ }
191
+ }
192
+
193
+ /** Per-site emission: declarations to hoist plus the call expression. */
194
+ export type SiteEmission = {
195
+ strategy: IdStrategy
196
+ decls: string[]
197
+ callExpr: string
198
+ helpers: SharedHelper[]
199
+ }
200
+
201
+ /** Packed key text plus offset/length/case tables, shared by every verify strategy. */
202
+ function packKeys(prefix: string, unpack: string, flat: FlatKey[]): {
203
+ decls: string[]; kos: string; kls: string; cix: string; keys: string
204
+ } {
205
+ let text = ''
206
+ const kos: number[] = []
207
+ const kls: number[] = []
208
+ const cix: number[] = []
209
+ for (const f of flat) {
210
+ kos.push(text.length)
211
+ kls.push(f.text.length)
212
+ cix.push(f.caseIndex)
213
+ text += f.text
214
+ }
215
+ return {
216
+ decls: [
217
+ `const ${prefix}k = ${JSON.stringify(text)}`,
218
+ `const ${prefix}o = ${unpack}(${packInts(kos)})`,
219
+ `const ${prefix}l = ${unpack}(${packInts(kls)})`,
220
+ `const ${prefix}x = ${unpack}(${packInts(cix)})`,
221
+ ],
222
+ keys: `${prefix}k`, kos: `${prefix}o`, kls: `${prefix}l`, cix: `${prefix}x`,
223
+ }
224
+ }
225
+
226
+ function emitTrie(prefix: string, unpack: string, helper: string, flat: FlatKey[], pos: string, end: string): SiteEmission | null {
227
+ type Node = { edges: Map<number, number>; accept: number }
228
+ const nodes: Node[] = [{ edges: new Map(), accept: 0 }]
229
+ for (const f of flat) {
230
+ let n = 0
231
+ for (let i = 0; i < f.text.length; i++) {
232
+ const code = f.text.charCodeAt(i)
233
+ let t = nodes[n]!.edges.get(code)
234
+ if (t === undefined) { t = nodes.length; nodes.push({ edges: new Map(), accept: 0 }); nodes[n]!.edges.set(code, t) }
235
+ n = t
236
+ }
237
+ if (nodes[n]!.accept !== 0 && nodes[n]!.accept !== f.caseIndex) return null
238
+ nodes[n]!.accept = f.caseIndex
239
+ }
240
+ const start: number[] = [0]; const char: number[] = []; const next: number[] = []; const accept: number[] = []
241
+ for (const nd of nodes) {
242
+ for (const [c, t] of [...nd.edges.entries()].sort((a, b) => a[0] - b[0])) { char.push(c); next.push(t) }
243
+ start.push(char.length); accept.push(nd.accept)
244
+ }
245
+ return {
246
+ strategy: 'trie',
247
+ decls: [
248
+ `const ${prefix}c = ${JSON.stringify(char.map(c => String.fromCharCode(c)).join(''))}`,
249
+ `const ${prefix}s = ${unpack}(${packInts(start)})`,
250
+ `const ${prefix}n = ${unpack}(${packInts(next)})`,
251
+ `const ${prefix}a = ${unpack}(${packInts(accept)})`,
252
+ ],
253
+ callExpr: `${helper}(input, ${pos}, ${end}, ${prefix}c, ${prefix}s, ${prefix}n, ${prefix}a)`,
254
+ helpers: ['unpack', 'trie'],
255
+ }
256
+ }
257
+
258
+ function emitLenSwitch(prefix: string, unpack: string, helper: string, flat: FlatKey[], pos: string, end: string): SiteEmission | null {
259
+ // Only worth it when length is close to decisive: at most one collision bucket.
260
+ const byLen = new Map<number, number>()
261
+ for (const f of flat) byLen.set(f.text.length, (byLen.get(f.text.length) ?? 0) + 1)
262
+ if ([...byLen.values()].filter(v => v > 1).length > 1) return null
263
+ const packed = packKeys(prefix, unpack, flat)
264
+ return {
265
+ strategy: 'lenswitch',
266
+ decls: [...packed.decls, `const ${prefix}n = ${unpack}(${packInts(flat.map(f => f.text.length))})`],
267
+ callExpr: `${helper}(input, ${pos}, ${end}, ${prefix}n, ${packed.cix}, ${packed.keys}, ${packed.kos}, ${packed.kls})`,
268
+ helpers: ['unpack', 'verify', 'lenswitch'],
269
+ }
270
+ }
271
+
272
+ function emitFirstChar(prefix: string, unpack: string, helper: string, flat: FlatKey[], pos: string, end: string): SiteEmission | null {
273
+ const tab = new Array<number>(128).fill(0)
274
+ for (const [i, f] of flat.entries()) {
275
+ const c = f.text.charCodeAt(0)
276
+ // Requires the key set to SEPARATE on char 0 — that is the whole premise.
277
+ if (c > 127 || tab[c] !== 0) return null
278
+ tab[c] = i + 1
279
+ }
280
+ const packed = packKeys(prefix, unpack, flat)
281
+ return {
282
+ strategy: 'firstchar',
283
+ decls: [...packed.decls, `const ${prefix}t = ${unpack}(${packInts(tab)})`],
284
+ callExpr: `${helper}(input, ${pos}, ${end}, ${prefix}t, ${packed.cix}, ${packed.keys}, ${packed.kos}, ${packed.kls})`,
285
+ helpers: ['unpack', 'verify', 'firstchar'],
286
+ }
287
+ }
288
+
289
+ /**
290
+ * gperf-style SEARCH for an injective hash over the ACTUAL key set. A FIXED
291
+ * discriminator provably fails — `(len,c1,c2)` collides on
292
+ * `@counter-style`/`@color-profile` and `@font-feature-values`/
293
+ * `@font-palette-values` — but that says nothing about a SEARCHED one, which is
294
+ * what this does: try every character-position pair against a set of
295
+ * multipliers and moduli and keep the first combination with no collisions.
296
+ */
297
+ function emitPHash(prefix: string, unpack: string, helper: string, flat: FlatKey[], pos: string, end: string): SiteEmission | null {
298
+ const maxLen = Math.max(...flat.map(f => f.text.length))
299
+ const at = (f: FlatKey, p: number): number => (p < f.text.length ? f.text.charCodeAt(p) : 0)
300
+ for (let p1 = 0; p1 < maxLen; p1++) {
301
+ for (let p2 = p1; p2 < maxLen; p2++) {
302
+ for (const mul of [1, 3, 7, 11, 17, 31, 53]) {
303
+ for (let mod = flat.length; mod <= flat.length * 6 + 16; mod++) {
304
+ const slots = new Array<number>(mod).fill(0)
305
+ let ok = true
306
+ for (const [i, f] of flat.entries()) {
307
+ const h = (f.text.length * mul + at(f, p1) * 31 + at(f, p2)) % mod
308
+ if (slots[h] !== 0) { ok = false; break }
309
+ slots[h] = i + 1
310
+ }
311
+ if (!ok) continue
312
+ const packed = packKeys(prefix, unpack, flat)
313
+ return {
314
+ strategy: 'phash',
315
+ decls: [...packed.decls, `const ${prefix}t = ${unpack}(${packInts(slots)})`],
316
+ callExpr: `${helper}(input, ${pos}, ${end}, ${p1}, ${p2}, ${mul}, ${mod}, ${prefix}t, ${packed.cix}, ${packed.keys}, ${packed.kos}, ${packed.kls})`,
317
+ helpers: ['unpack', 'verify', 'phash'],
318
+ }
319
+ }
320
+ }
321
+ }
322
+ }
323
+ return null
324
+ }
325
+
326
+ /**
327
+ * Emit the id computation for one site. `auto` picks by KEY-SET SHAPE measured
328
+ * at macro time rather than applying one strategy everywhere: separation on
329
+ * char 0 is the cheapest test there is, a searched perfect hash next, then
330
+ * length, with the trie as the always-applicable fallback.
331
+ */
332
+ export function emitDispatchId(
333
+ cases: ReadonlyArray<DispatchKeySet>,
334
+ cfg: DispatchConfig,
335
+ prefix: string,
336
+ helperName: (h: SharedHelper) => string,
337
+ pos: string,
338
+ end: string,
339
+ ): SiteEmission | null {
340
+ const flat = flatten(cases)
341
+ if (flat === null) return null
342
+ const unpack = helperName('unpack')
343
+ const order: IdStrategy[] = cfg.id === 'auto' ? ['firstchar', 'phash', 'lenswitch', 'trie'] : [cfg.id]
344
+ for (const s of order) {
345
+ const built =
346
+ s === 'trie' ? emitTrie(prefix, unpack, helperName('trie'), flat, pos, end)
347
+ : s === 'lenswitch' ? emitLenSwitch(prefix, unpack, helperName('lenswitch'), flat, pos, end)
348
+ : s === 'firstchar' ? emitFirstChar(prefix, unpack, helperName('firstchar'), flat, pos, end)
349
+ : emitPHash(prefix, unpack, helperName('phash'), flat, pos, end)
350
+ if (built !== null) return built
351
+ }
352
+ return null
353
+ }