solve-engine 1.2.0 → 2.1.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 (385) hide show
  1. package/README.md +12 -17
  2. package/dist/{BytecodeBuilder-Bp9xeTmX.d.cts → BytecodeBuilder-DGGeByAP.d.cts} +18 -0
  3. package/dist/{BytecodeBuilder-Bp9xeTmX.d.ts → BytecodeBuilder-DGGeByAP.d.ts} +18 -0
  4. package/dist/{Configuration-C9W8tJv_.d.cts → Configuration-B6W0-T67.d.cts} +17 -4
  5. package/dist/{Configuration-C9W8tJv_.d.ts → Configuration-B6W0-T67.d.ts} +17 -4
  6. package/dist/{EngineError-CNA_rHY-.d.cts → EngineError-Ce1lPkjZ.d.cts} +2 -0
  7. package/dist/{EngineError-CNA_rHY-.d.ts → EngineError-Ce1lPkjZ.d.ts} +2 -0
  8. package/dist/{PackageCompatibility-B6NX9IaN.d.cts → PackageCompatibility-Bn5jnBtA.d.cts} +2 -2
  9. package/dist/{PackageCompatibility-Ceg5XdAp.d.ts → PackageCompatibility-CIvq2MP5.d.ts} +2 -2
  10. package/dist/{PackageRegistry-DrCPQNyy.d.ts → PackageRegistry-DzeiWcv4.d.ts} +93 -172
  11. package/dist/{PackageRegistry-DnJYVw9G.d.cts → PackageRegistry-dpdurBVO.d.cts} +93 -172
  12. package/dist/{Parselet-Bhf7d0_W.d.ts → Parselet-B-zoPrTa.d.ts} +8 -5
  13. package/dist/{Parselet-Cv7S7KRt.d.cts → Parselet-C4Wu055_.d.cts} +8 -5
  14. package/dist/{ScopeManager-Bzq87GfA.d.ts → ScopeManager-C2LDOrDx.d.ts} +7 -16
  15. package/dist/{ScopeManager-DRDBVfan.d.cts → ScopeManager-D7uMXbhS.d.cts} +7 -16
  16. package/dist/{VMCheckpoints-C6c17Hbo.d.ts → VMCheckpoints-CGyFhivM.d.ts} +3 -3
  17. package/dist/{VMCheckpoints-dZxSMGHG.d.cts → VMCheckpoints-I9jg3jWR.d.cts} +3 -3
  18. package/dist/{Value-DZNQ_PU7.d.cts → Value-CCi_Eecs.d.cts} +40 -0
  19. package/dist/{Value-DZNQ_PU7.d.ts → Value-CCi_Eecs.d.ts} +40 -0
  20. package/dist/{WorkerError-Djd7GGGa.d.cts → WorkerError-B4p6PNfx.d.cts} +1 -1
  21. package/dist/{WorkerError-B7t7R2-m.d.ts → WorkerError-Dhj9BQ_A.d.ts} +1 -1
  22. package/dist/chunk-2JMFUQZH.js +2 -0
  23. package/dist/chunk-2JMFUQZH.js.map +1 -0
  24. package/dist/chunk-2LFLR3VZ.cjs +2 -0
  25. package/dist/chunk-2LFLR3VZ.cjs.map +1 -0
  26. package/dist/chunk-2RWAXT6O.js +0 -1
  27. package/dist/chunk-32GC2FHC.cjs +2 -0
  28. package/dist/chunk-32GC2FHC.cjs.map +1 -0
  29. package/dist/chunk-4BZIYU7S.cjs +3 -0
  30. package/dist/chunk-4BZIYU7S.cjs.map +1 -0
  31. package/dist/chunk-4E4YVQKK.js +5 -0
  32. package/dist/chunk-4E4YVQKK.js.map +1 -0
  33. package/dist/chunk-4QVBSG6B.cjs +2 -0
  34. package/dist/chunk-4QVBSG6B.cjs.map +1 -0
  35. package/dist/chunk-5FO4PDIG.js +2 -0
  36. package/dist/chunk-5FO4PDIG.js.map +1 -0
  37. package/dist/chunk-5IJD5L3O.cjs +2 -0
  38. package/dist/chunk-5IJD5L3O.cjs.map +1 -0
  39. package/dist/chunk-62LMTO4F.js +2 -0
  40. package/dist/chunk-62LMTO4F.js.map +1 -0
  41. package/dist/chunk-6G3PMEPA.js +2 -0
  42. package/dist/chunk-6G3PMEPA.js.map +1 -0
  43. package/dist/chunk-6INX773F.cjs +2 -0
  44. package/dist/chunk-6INX773F.cjs.map +1 -0
  45. package/dist/chunk-76F3LCIL.js +4 -0
  46. package/dist/chunk-76F3LCIL.js.map +1 -0
  47. package/dist/chunk-7BXPCWKM.cjs +2 -0
  48. package/dist/chunk-7BXPCWKM.cjs.map +1 -0
  49. package/dist/chunk-7GK3XEZF.js +2 -0
  50. package/dist/chunk-7GK3XEZF.js.map +1 -0
  51. package/dist/chunk-AXMQG5TS.cjs +2 -0
  52. package/dist/chunk-AXMQG5TS.cjs.map +1 -0
  53. package/dist/chunk-CIQOK7Q4.cjs +2 -0
  54. package/dist/chunk-CIQOK7Q4.cjs.map +1 -0
  55. package/dist/chunk-E2Q3PQ2G.js +3 -0
  56. package/dist/chunk-E2Q3PQ2G.js.map +1 -0
  57. package/dist/chunk-E6Z4NQAS.js +2 -0
  58. package/dist/chunk-E6Z4NQAS.js.map +1 -0
  59. package/dist/chunk-EFJCC6FJ.cjs +2 -0
  60. package/dist/chunk-EFJCC6FJ.cjs.map +1 -0
  61. package/dist/chunk-ESUN2THE.cjs +2 -0
  62. package/dist/chunk-ESUN2THE.cjs.map +1 -0
  63. package/dist/chunk-EVAOPEO4.js +2 -0
  64. package/dist/chunk-EVAOPEO4.js.map +1 -0
  65. package/dist/chunk-EVI6OHQH.cjs +2 -0
  66. package/dist/chunk-EVI6OHQH.cjs.map +1 -0
  67. package/dist/chunk-FDE5HWDI.cjs +2 -0
  68. package/dist/chunk-FDE5HWDI.cjs.map +1 -0
  69. package/dist/chunk-FWW3M36J.cjs +2 -0
  70. package/dist/chunk-FWW3M36J.cjs.map +1 -0
  71. package/dist/chunk-G7TBEQRQ.js +2 -0
  72. package/dist/chunk-G7TBEQRQ.js.map +1 -0
  73. package/dist/chunk-GK2OHWRF.cjs +5 -0
  74. package/dist/chunk-GK2OHWRF.cjs.map +1 -0
  75. package/dist/chunk-HFKDWQUL.js +2 -0
  76. package/dist/chunk-HFKDWQUL.js.map +1 -0
  77. package/dist/chunk-HLBYHXQD.cjs +3 -0
  78. package/dist/chunk-HLBYHXQD.cjs.map +1 -0
  79. package/dist/chunk-I52MYCVI.js +3 -0
  80. package/dist/chunk-I52MYCVI.js.map +1 -0
  81. package/dist/chunk-IANAMBJQ.cjs +2 -0
  82. package/dist/chunk-IANAMBJQ.cjs.map +1 -0
  83. package/dist/chunk-IJMNVBIS.js +2 -0
  84. package/dist/chunk-IJMNVBIS.js.map +1 -0
  85. package/dist/chunk-J4LZD4ZY.js +3 -0
  86. package/dist/chunk-J4LZD4ZY.js.map +1 -0
  87. package/dist/chunk-JAK3RAFA.cjs +2 -0
  88. package/dist/chunk-JAK3RAFA.cjs.map +1 -0
  89. package/dist/chunk-JFAJN3C2.cjs +2 -0
  90. package/dist/chunk-JFAJN3C2.cjs.map +1 -0
  91. package/dist/chunk-JPFCXLJV.js +2 -0
  92. package/dist/chunk-JPFCXLJV.js.map +1 -0
  93. package/dist/chunk-JUIJ6NRD.cjs +3 -0
  94. package/dist/chunk-JUIJ6NRD.cjs.map +1 -0
  95. package/dist/chunk-MIQBJGGR.js +2 -0
  96. package/dist/chunk-MIQBJGGR.js.map +1 -0
  97. package/dist/chunk-MPB7KUN5.js +3 -0
  98. package/dist/chunk-MPB7KUN5.js.map +1 -0
  99. package/dist/chunk-MSYX32PF.cjs +2 -0
  100. package/dist/chunk-MSYX32PF.cjs.map +1 -0
  101. package/dist/chunk-MVZUTETT.js +2 -0
  102. package/dist/chunk-MVZUTETT.js.map +1 -0
  103. package/dist/chunk-N7N7MPMK.cjs +2 -0
  104. package/dist/chunk-N7N7MPMK.cjs.map +1 -0
  105. package/dist/chunk-NGBBNL4K.cjs +1 -3
  106. package/dist/chunk-OVKDDTXO.cjs +3 -0
  107. package/dist/chunk-OVKDDTXO.cjs.map +1 -0
  108. package/dist/chunk-P6WPEUAJ.cjs +2 -0
  109. package/dist/chunk-P6WPEUAJ.cjs.map +1 -0
  110. package/dist/chunk-PS272DZJ.cjs +2 -0
  111. package/dist/chunk-PS272DZJ.cjs.map +1 -0
  112. package/dist/chunk-R2ENWQ76.js +2 -0
  113. package/dist/chunk-R2ENWQ76.js.map +1 -0
  114. package/dist/chunk-R2SZFJTT.cjs +2 -0
  115. package/dist/chunk-R2SZFJTT.cjs.map +1 -0
  116. package/dist/chunk-RAF57WYO.js +2 -0
  117. package/dist/chunk-RAF57WYO.js.map +1 -0
  118. package/dist/chunk-RSF25YSJ.js +2 -0
  119. package/dist/chunk-RSF25YSJ.js.map +1 -0
  120. package/dist/chunk-SOMGSIO4.cjs +3 -0
  121. package/dist/chunk-SOMGSIO4.cjs.map +1 -0
  122. package/dist/chunk-SYYE3QAX.cjs +2 -0
  123. package/dist/chunk-SYYE3QAX.cjs.map +1 -0
  124. package/dist/chunk-TCZSPH4A.js +2 -0
  125. package/dist/chunk-TCZSPH4A.js.map +1 -0
  126. package/dist/chunk-TLG7VZRX.cjs +2 -0
  127. package/dist/chunk-TLG7VZRX.cjs.map +1 -0
  128. package/dist/chunk-TN5E6JU6.js +2 -0
  129. package/dist/chunk-TN5E6JU6.js.map +1 -0
  130. package/dist/chunk-TRBXW6HW.js +2 -0
  131. package/dist/{chunk-AQNHHJMJ.cjs.map → chunk-TRBXW6HW.js.map} +1 -1
  132. package/dist/chunk-TTFO4YDP.cjs +2 -0
  133. package/dist/chunk-TTFO4YDP.cjs.map +1 -0
  134. package/dist/chunk-TWC7K3ZX.js +2 -0
  135. package/dist/chunk-TWC7K3ZX.js.map +1 -0
  136. package/dist/chunk-U4AEVL5Z.js +2 -0
  137. package/dist/chunk-U4AEVL5Z.js.map +1 -0
  138. package/dist/chunk-UURM7P3P.cjs +4 -0
  139. package/dist/chunk-UURM7P3P.cjs.map +1 -0
  140. package/dist/chunk-VAGS5JS7.cjs +2 -0
  141. package/dist/chunk-VAGS5JS7.cjs.map +1 -0
  142. package/dist/chunk-W2JAR233.js +2 -0
  143. package/dist/chunk-W2JAR233.js.map +1 -0
  144. package/dist/chunk-WGZ3FNHI.js +2 -0
  145. package/dist/chunk-WGZ3FNHI.js.map +1 -0
  146. package/dist/chunk-XEIDV5JP.js +2 -0
  147. package/dist/chunk-XEIDV5JP.js.map +1 -0
  148. package/dist/chunk-XNIWQHXY.js +2 -0
  149. package/dist/chunk-XNIWQHXY.js.map +1 -0
  150. package/dist/chunk-Y7O4D3BJ.cjs +2 -0
  151. package/dist/chunk-Y7O4D3BJ.cjs.map +1 -0
  152. package/dist/chunk-YAHGSBMC.js +2 -0
  153. package/dist/chunk-YAHGSBMC.js.map +1 -0
  154. package/dist/chunk-YAYBVVG2.js +3 -0
  155. package/dist/chunk-YAYBVVG2.js.map +1 -0
  156. package/dist/chunk-YJ2OPULK.js +2 -0
  157. package/dist/chunk-YJ2OPULK.js.map +1 -0
  158. package/dist/chunk-YVKHEAFT.js +2 -0
  159. package/dist/chunk-YVKHEAFT.js.map +1 -0
  160. package/dist/chunk-ZCNBT44X.cjs +2 -0
  161. package/dist/chunk-ZCNBT44X.cjs.map +1 -0
  162. package/dist/chunk-ZLRJ7TDP.cjs +2 -0
  163. package/dist/chunk-ZLRJ7TDP.cjs.map +1 -0
  164. package/dist/constants.cjs +1 -21
  165. package/dist/constants.d.cts +1 -1
  166. package/dist/constants.d.ts +1 -1
  167. package/dist/constants.js +1 -4
  168. package/dist/engine.cjs +1 -94
  169. package/dist/engine.d.cts +10 -11
  170. package/dist/engine.d.ts +10 -11
  171. package/dist/engine.js +1 -37
  172. package/dist/errors.cjs +1 -105
  173. package/dist/errors.d.cts +3 -3
  174. package/dist/errors.d.ts +3 -3
  175. package/dist/errors.js +1 -4
  176. package/dist/format.cjs +1 -26
  177. package/dist/format.d.cts +2 -2
  178. package/dist/format.d.ts +2 -2
  179. package/dist/format.js +1 -9
  180. package/dist/index.cjs +1 -314
  181. package/dist/index.cjs.map +1 -1
  182. package/dist/index.d.cts +52 -20
  183. package/dist/index.d.ts +52 -20
  184. package/dist/index.js +1 -278
  185. package/dist/index.js.map +1 -1
  186. package/dist/language.cjs +1 -369
  187. package/dist/language.cjs.map +1 -1
  188. package/dist/language.d.cts +9 -10
  189. package/dist/language.d.ts +9 -10
  190. package/dist/language.js +1 -348
  191. package/dist/language.js.map +1 -1
  192. package/dist/lexer.cjs +1 -63
  193. package/dist/lexer.js +1 -10
  194. package/dist/normalizer.cjs +1 -39
  195. package/dist/normalizer.js +1 -10
  196. package/dist/packages.cjs +1 -132
  197. package/dist/packages.d.cts +8 -9
  198. package/dist/packages.d.ts +8 -9
  199. package/dist/packages.js +1 -23
  200. package/dist/parser.cjs +1 -54
  201. package/dist/parser.d.cts +8 -8
  202. package/dist/parser.d.ts +8 -8
  203. package/dist/parser.js +1 -9
  204. package/dist/{pipeline-D-PS9gVw.d.ts → pipeline-72QmQlGx.d.ts} +1 -1
  205. package/dist/{pipeline-DtSBJ8TS.d.cts → pipeline-DCQicwWX.d.cts} +1 -1
  206. package/dist/resolvers.cjs +1 -12
  207. package/dist/resolvers.d.cts +2 -2
  208. package/dist/resolvers.d.ts +2 -2
  209. package/dist/resolvers.js +1 -3
  210. package/dist/services.cjs +1 -20
  211. package/dist/services.js +1 -3
  212. package/dist/testing.cjs +3 -477
  213. package/dist/testing.cjs.map +1 -1
  214. package/dist/testing.d.cts +9 -10
  215. package/dist/testing.d.ts +9 -10
  216. package/dist/testing.js +3 -469
  217. package/dist/testing.js.map +1 -1
  218. package/dist/uom.cjs +1 -55
  219. package/dist/uom.d.cts +2 -2
  220. package/dist/uom.d.ts +2 -2
  221. package/dist/uom.js +1 -10
  222. package/dist/utilities.cjs +1 -36
  223. package/dist/utilities.js +1 -7
  224. package/dist/vm.cjs +1 -295
  225. package/dist/vm.cjs.map +1 -1
  226. package/dist/vm.d.cts +22 -10
  227. package/dist/vm.d.ts +22 -10
  228. package/dist/vm.js +1 -193
  229. package/dist/vm.js.map +1 -1
  230. package/dist/worker.cjs +2 -492
  231. package/dist/worker.cjs.map +1 -1
  232. package/dist/worker.d.cts +16 -17
  233. package/dist/worker.d.ts +16 -17
  234. package/dist/worker.js +2 -483
  235. package/dist/worker.js.map +1 -1
  236. package/package.json +1 -13
  237. package/dist/chunk-3BOK2LDK.js +0 -17
  238. package/dist/chunk-3BOK2LDK.js.map +0 -1
  239. package/dist/chunk-3VGDNEE3.js +0 -84
  240. package/dist/chunk-3VGDNEE3.js.map +0 -1
  241. package/dist/chunk-4K5W7EH2.cjs +0 -112
  242. package/dist/chunk-4K5W7EH2.cjs.map +0 -1
  243. package/dist/chunk-5EFKSKMA.js +0 -413
  244. package/dist/chunk-5EFKSKMA.js.map +0 -1
  245. package/dist/chunk-5HRB36DK.js +0 -405
  246. package/dist/chunk-5HRB36DK.js.map +0 -1
  247. package/dist/chunk-5ON7PUAZ.js +0 -12
  248. package/dist/chunk-5ON7PUAZ.js.map +0 -1
  249. package/dist/chunk-7AXJNQMF.cjs +0 -297
  250. package/dist/chunk-7AXJNQMF.cjs.map +0 -1
  251. package/dist/chunk-7JM3F4KL.js +0 -1795
  252. package/dist/chunk-7JM3F4KL.js.map +0 -1
  253. package/dist/chunk-7USV76HV.cjs +0 -341
  254. package/dist/chunk-7USV76HV.cjs.map +0 -1
  255. package/dist/chunk-A2DZ5BEJ.cjs +0 -1930
  256. package/dist/chunk-A2DZ5BEJ.cjs.map +0 -1
  257. package/dist/chunk-ADBA3XBE.cjs +0 -20
  258. package/dist/chunk-ADBA3XBE.cjs.map +0 -1
  259. package/dist/chunk-AKNGX4TZ.cjs +0 -236
  260. package/dist/chunk-AKNGX4TZ.cjs.map +0 -1
  261. package/dist/chunk-ALYRJ72W.cjs +0 -434
  262. package/dist/chunk-ALYRJ72W.cjs.map +0 -1
  263. package/dist/chunk-AMOJYLG2.cjs +0 -1692
  264. package/dist/chunk-AMOJYLG2.cjs.map +0 -1
  265. package/dist/chunk-AOH6UR2L.js +0 -825
  266. package/dist/chunk-AOH6UR2L.js.map +0 -1
  267. package/dist/chunk-AQNHHJMJ.cjs +0 -261
  268. package/dist/chunk-B7LXJ6LC.cjs +0 -36
  269. package/dist/chunk-B7LXJ6LC.cjs.map +0 -1
  270. package/dist/chunk-BESTLPRK.cjs +0 -6919
  271. package/dist/chunk-BESTLPRK.cjs.map +0 -1
  272. package/dist/chunk-BLI4NIQY.js +0 -98
  273. package/dist/chunk-BLI4NIQY.js.map +0 -1
  274. package/dist/chunk-BSPXMGIS.js +0 -455
  275. package/dist/chunk-BSPXMGIS.js.map +0 -1
  276. package/dist/chunk-C2OMO3WN.cjs +0 -2806
  277. package/dist/chunk-C2OMO3WN.cjs.map +0 -1
  278. package/dist/chunk-CSDQMDU2.js +0 -204
  279. package/dist/chunk-CSDQMDU2.js.map +0 -1
  280. package/dist/chunk-EJ3ILXX6.cjs +0 -223
  281. package/dist/chunk-EJ3ILXX6.cjs.map +0 -1
  282. package/dist/chunk-ENRIK36Q.cjs +0 -94
  283. package/dist/chunk-ENRIK36Q.cjs.map +0 -1
  284. package/dist/chunk-F4UB4XIL.js +0 -109
  285. package/dist/chunk-F4UB4XIL.js.map +0 -1
  286. package/dist/chunk-FD5ZZHEU.js +0 -92
  287. package/dist/chunk-FD5ZZHEU.js.map +0 -1
  288. package/dist/chunk-FE4RC37D.js +0 -2801
  289. package/dist/chunk-FE4RC37D.js.map +0 -1
  290. package/dist/chunk-FEAIOR7N.cjs +0 -91
  291. package/dist/chunk-FEAIOR7N.cjs.map +0 -1
  292. package/dist/chunk-FR7RNOPT.cjs +0 -3303
  293. package/dist/chunk-FR7RNOPT.cjs.map +0 -1
  294. package/dist/chunk-G2YPPLGX.cjs +0 -484
  295. package/dist/chunk-G2YPPLGX.cjs.map +0 -1
  296. package/dist/chunk-GCM5JHMX.js +0 -88
  297. package/dist/chunk-GCM5JHMX.js.map +0 -1
  298. package/dist/chunk-GDEU72RG.cjs +0 -829
  299. package/dist/chunk-GDEU72RG.cjs.map +0 -1
  300. package/dist/chunk-HKTQCQ4Z.cjs +0 -8383
  301. package/dist/chunk-HKTQCQ4Z.cjs.map +0 -1
  302. package/dist/chunk-HWHU642X.cjs +0 -316
  303. package/dist/chunk-HWHU642X.cjs.map +0 -1
  304. package/dist/chunk-ICEKS6YC.cjs +0 -507
  305. package/dist/chunk-ICEKS6YC.cjs.map +0 -1
  306. package/dist/chunk-IJGOFP2J.js +0 -6904
  307. package/dist/chunk-IJGOFP2J.js.map +0 -1
  308. package/dist/chunk-IQJGDZOP.js +0 -333
  309. package/dist/chunk-IQJGDZOP.js.map +0 -1
  310. package/dist/chunk-IT74O4BE.cjs +0 -456
  311. package/dist/chunk-IT74O4BE.cjs.map +0 -1
  312. package/dist/chunk-KXDQKVNX.cjs +0 -459
  313. package/dist/chunk-KXDQKVNX.cjs.map +0 -1
  314. package/dist/chunk-L2TE7PMO.cjs +0 -14
  315. package/dist/chunk-L2TE7PMO.cjs.map +0 -1
  316. package/dist/chunk-L5YHV6BQ.js +0 -499
  317. package/dist/chunk-L5YHV6BQ.js.map +0 -1
  318. package/dist/chunk-LO7HU5WB.cjs +0 -209
  319. package/dist/chunk-LO7HU5WB.cjs.map +0 -1
  320. package/dist/chunk-LSALB3IN.js +0 -292
  321. package/dist/chunk-LSALB3IN.js.map +0 -1
  322. package/dist/chunk-MI7OTUHL.cjs +0 -339
  323. package/dist/chunk-MI7OTUHL.cjs.map +0 -1
  324. package/dist/chunk-MIGXCBXT.cjs +0 -381
  325. package/dist/chunk-MIGXCBXT.cjs.map +0 -1
  326. package/dist/chunk-MVTOCRV2.js +0 -213
  327. package/dist/chunk-MVTOCRV2.js.map +0 -1
  328. package/dist/chunk-N7FNVFXW.cjs +0 -32
  329. package/dist/chunk-N7FNVFXW.cjs.map +0 -1
  330. package/dist/chunk-N7OA4WPJ.js +0 -362
  331. package/dist/chunk-N7OA4WPJ.js.map +0 -1
  332. package/dist/chunk-NAWZATKT.js +0 -478
  333. package/dist/chunk-NAWZATKT.js.map +0 -1
  334. package/dist/chunk-NLNDMWZC.js +0 -31
  335. package/dist/chunk-NLNDMWZC.js.map +0 -1
  336. package/dist/chunk-ORM7CHYZ.js +0 -1531
  337. package/dist/chunk-ORM7CHYZ.js.map +0 -1
  338. package/dist/chunk-OUHT5Z36.cjs +0 -393
  339. package/dist/chunk-OUHT5Z36.cjs.map +0 -1
  340. package/dist/chunk-PCZVMMWU.cjs +0 -74
  341. package/dist/chunk-PCZVMMWU.cjs.map +0 -1
  342. package/dist/chunk-RNKDIXDV.js +0 -336
  343. package/dist/chunk-RNKDIXDV.js.map +0 -1
  344. package/dist/chunk-SBUFNJVE.js +0 -258
  345. package/dist/chunk-SBUFNJVE.js.map +0 -1
  346. package/dist/chunk-TC6BTUSU.cjs +0 -1799
  347. package/dist/chunk-TC6BTUSU.cjs.map +0 -1
  348. package/dist/chunk-TPOK3UNI.js +0 -8355
  349. package/dist/chunk-TPOK3UNI.js.map +0 -1
  350. package/dist/chunk-UF6JG5SG.js +0 -312
  351. package/dist/chunk-UF6JG5SG.js.map +0 -1
  352. package/dist/chunk-UKQHXPGW.js +0 -30
  353. package/dist/chunk-UKQHXPGW.js.map +0 -1
  354. package/dist/chunk-V4XG5QF6.js +0 -1685
  355. package/dist/chunk-V4XG5QF6.js.map +0 -1
  356. package/dist/chunk-VEKIWQ3F.cjs +0 -39
  357. package/dist/chunk-VEKIWQ3F.cjs.map +0 -1
  358. package/dist/chunk-WMQRJTAO.js +0 -3266
  359. package/dist/chunk-WMQRJTAO.js.map +0 -1
  360. package/dist/chunk-X6K3HUOY.cjs +0 -86
  361. package/dist/chunk-X6K3HUOY.cjs.map +0 -1
  362. package/dist/chunk-XBTEO4OB.js +0 -391
  363. package/dist/chunk-XBTEO4OB.js.map +0 -1
  364. package/dist/chunk-XIYYHA65.cjs +0 -101
  365. package/dist/chunk-XIYYHA65.cjs.map +0 -1
  366. package/dist/chunk-YJHY7CMN.cjs +0 -1534
  367. package/dist/chunk-YJHY7CMN.cjs.map +0 -1
  368. package/dist/chunk-YLJ4XMA6.js +0 -33
  369. package/dist/chunk-YLJ4XMA6.js.map +0 -1
  370. package/dist/chunk-YNIWOYW6.js +0 -232
  371. package/dist/chunk-YNIWOYW6.js.map +0 -1
  372. package/dist/chunk-Z2XP767T.cjs +0 -35
  373. package/dist/chunk-Z2XP767T.cjs.map +0 -1
  374. package/dist/chunk-Z4X2RWOF.js +0 -37
  375. package/dist/chunk-Z4X2RWOF.js.map +0 -1
  376. package/dist/chunk-ZN6YYPIW.js +0 -67
  377. package/dist/chunk-ZN6YYPIW.js.map +0 -1
  378. package/dist/chunk-ZNFFTYWH.js +0 -1854
  379. package/dist/chunk-ZNFFTYWH.js.map +0 -1
  380. package/dist/variables.cjs +0 -17
  381. package/dist/variables.cjs.map +0 -1
  382. package/dist/variables.d.cts +0 -87
  383. package/dist/variables.d.ts +0 -87
  384. package/dist/variables.js +0 -4
  385. package/dist/variables.js.map +0 -1
package/dist/language.js CHANGED
@@ -1,349 +1,2 @@
1
- import { getMeasure } from './chunk-MVTOCRV2.js';
2
- import { getTokenCategory } from './chunk-LSALB3IN.js';
3
- export { UNCATEGORIZED_TOKEN_TYPES, getTokenCategory, registerTokenCategory, unregisterTokenCategory } from './chunk-LSALB3IN.js';
4
- import { knownUnits } from './chunk-SBUFNJVE.js';
5
- import './chunk-V4XG5QF6.js';
6
- import './chunk-YLJ4XMA6.js';
7
-
8
- // src/language/LanguageService.ts
9
- var MAX_COMPLETIONS = 50;
10
- var CATEGORY_TIER = {
11
- variable: 0,
12
- function: 1,
13
- keyword: 1,
14
- operator: 1,
15
- comparison: 1,
16
- bitwise: 1,
17
- datetime: 1,
18
- vector: 1,
19
- unit: 2
20
- };
21
- var MAX_CACHED_LINES = 2e3;
22
- var LanguageService = class {
23
- constructor(engine, options) {
24
- // Bounded cache keyed by line number ALONE, not `${lineNumber}:${lineText}`
25
- // as an earlier version of this class did. A line's previous text state is
26
- // never useful once it changes, so keying on text too was pure waste:
27
- // every keystroke on a line minted a brand-new, never-reclaimed cache
28
- // entry (an effective per-keystroke memory leak over a long editing
29
- // session). Keying on line number alone makes "same line, new text" a
30
- // cheap overwrite instead.
31
- //
32
- // Eviction is oldest-inserted (Map iteration order) when at capacity
33
- // mirroring the same bounded-cache pattern ExpressionEngine's own
34
- // bytecodeCache already uses elsewhere in this codebase. Deliberately
35
- // NOT an LFU (least-frequently-used) policy: LFU would keep resisting
36
- // eviction of old, once-popular lines while punishing a line that just
37
- // scrolled into view (frequency 1), the opposite of what a "currently
38
- // visible" cache should prioritize.
39
- this.cache = /* @__PURE__ */ new Map();
40
- // Keyword/unit/package-contributed completion candidates don't depend
41
- // on any particular line, built lazily on first getCompletions() call
42
- // and reused after that, since a package registration is the only thing
43
- // that could ever change this list mid-session (see invalidateCache()).
44
- // Variable-name candidates are NOT part of this, they're read fresh on
45
- // every call from variableNameSource(), since those genuinely change on
46
- // every edit.
47
- this.staticCompletionCandidates = null;
48
- // The same candidates, bucketed by their lowercased first character with the
49
- // lowercased label precomputed. getCompletions() runs on every keystroke and
50
- // only ever wants candidates sharing the prefix's first character, so
51
- // scanning the whole list and lowercasing each label per call was doing two
52
- // avoidable things: touching entries that could not possibly match, and
53
- // allocating a string per candidate per keystroke. That was affordable when
54
- // the vocabulary was a few hundred entries. Deriving the unit list from the
55
- // conversion tables took it past a thousand, and the warm completion
56
- // benchmarks regressed roughly 2.9x until this was added.
57
- this.staticCompletionIndex = null;
58
- this.engine = engine ?? null;
59
- this.variableNameSource = options?.variableNameSource ?? (() => this.defaultVariableNames());
60
- this.normalizeForHighlighting = options?.normalizeForHighlighting ?? false;
61
- }
62
- defaultVariableNames() {
63
- if (!this.engine) return [];
64
- const snapshot = this.engine.getDag().getSnapshot();
65
- const names = new Set(Object.keys(snapshot.consumers));
66
- for (const written of Object.values(snapshot.writes)) {
67
- for (const name of written) names.add(name);
68
- }
69
- return names;
70
- }
71
- /**
72
- * Lex a line, normalize it, and place every resulting token back in the
73
- * source text.
74
- *
75
- * ## Why placing them back is the hard part
76
- *
77
- * Normalization produces three kinds of token and only one of them can be
78
- * highlighted the obvious way.
79
- *
80
- * A token the normalizer left alone still describes its own text, so its
81
- * span is `offset` to `offset + text.length`, exactly as before.
82
- *
83
- * A FUSED token does not. `10 frames` becomes a FRAME_COUNT whose value is
84
- * `10`, and a timecode becomes a token whose value is a comma-separated
85
- * tuple appearing nowhere on the line. Those carry `sourceEnd`, set by
86
- * `createFusedToken`, which is the only place that knows where the fusion
87
- * ended.
88
- *
89
- * An INSERTED token has no text at all. Implicit multiplication puts a STAR
90
- * at the following token's offset, so `5(3)` gains a `*` sitting exactly
91
- * where the `(` is. Painting it would colour a character the reader never
92
- * typed as an operator, and would overlap the token that really is there.
93
- * These are dropped, detected by the one test that needs no cooperation
94
- * from any rule: a token with no recorded fusion span must match the text
95
- * at its own offset, and an inserted one does not.
96
- *
97
- * @param lineText - The raw line.
98
- * @returns Spans in the same shape `Lexer.getHighlightTokens` returns.
99
- */
100
- classifyNormalized(lineText) {
101
- const lexer = this.engine.getLexer();
102
- const raw = lexer.getHighlightTokenObjects(lineText);
103
- if (raw.length === 0) return [];
104
- const source = lineText.startsWith("> ") && lexer.classifyLine(lineText).skip ? lineText.slice(2) : lineText;
105
- let normalized;
106
- try {
107
- normalized = this.engine.getNormalizer().normalize(raw);
108
- } catch {
109
- normalized = raw;
110
- }
111
- const out = [];
112
- for (const token of normalized) {
113
- let end;
114
- if (token.sourceEnd !== void 0) {
115
- end = token.sourceEnd;
116
- } else if (source.startsWith(token.text, token.offset)) {
117
- end = token.offset + token.text.length;
118
- } else {
119
- continue;
120
- }
121
- out.push({
122
- type: token.type,
123
- value: token.value,
124
- offset: token.offset,
125
- col: token.col,
126
- length: end - token.offset,
127
- category: getTokenCategory(token.type)
128
- });
129
- }
130
- return out;
131
- }
132
- /**
133
- * Classify every recognized token on one line.
134
- *
135
- * @param lineText - The raw line text (may be a markdown-structural line
136
- * the engine's classifier skips, that's handled by the underlying
137
- * lexer, which returns no tokens for those).
138
- * @param lineNumber - 1-based line number, used purely as a cache key.
139
- */
140
- getSemanticTokens(lineText, lineNumber) {
141
- const cached = this.cache.get(lineNumber);
142
- if (cached && cached.text === lineText) {
143
- if (cached.bareWordCandidate !== void 0) {
144
- return this.isKnownVariable(cached.bareWordCandidate) ? cached.tokens : [];
145
- }
146
- return cached.tokens;
147
- }
148
- if (!this.engine) {
149
- return [];
150
- }
151
- const lexer = this.engine.getLexer();
152
- const lexed = this.normalizeForHighlighting ? this.classifyNormalized(lineText) : lexer.getHighlightTokens(lineText);
153
- if (lexed.length === 0) {
154
- this.putCache(lineNumber, lineText, []);
155
- return [];
156
- }
157
- const classification = lexer.classifyLine(lineText);
158
- const tokens = [];
159
- if (classification.hasInlineSolve) {
160
- const validSpans = lexer.findInlineSolves(lineText).filter((span) => this.parsesAsExpression(span.expression)).map((span) => ({ from: span.start, to: span.end }));
161
- for (const token of lexed) {
162
- if (!token.category) continue;
163
- const from = token.offset;
164
- const to = token.offset + token.length;
165
- if (!validSpans.some((s) => from >= s.from && to <= s.to)) continue;
166
- tokens.push({ from, to, category: token.category });
167
- }
168
- } else {
169
- const text = lineText.startsWith("> ") && classification.skip ? lineText.slice(2) : lineText;
170
- if (this.parsesAsExpression(text)) {
171
- for (const token of lexed) {
172
- if (!token.category) continue;
173
- tokens.push({ from: token.offset, to: token.offset + token.length, category: token.category });
174
- }
175
- }
176
- }
177
- if (tokens.length === 1 && tokens[0].category === "variable") {
178
- const name = lineText.slice(tokens[0].from, tokens[0].to);
179
- this.putCache(lineNumber, lineText, tokens, name);
180
- return this.isKnownVariable(name) ? tokens : [];
181
- }
182
- this.putCache(lineNumber, lineText, tokens);
183
- return tokens;
184
- }
185
- /**
186
- * Completion candidates for the identifier prefix immediately before
187
- * `cursorOffset` on `lineText`. Deliberately simple prefix matching, not
188
- * parser-driven "what's grammatically valid here" prediction, a
189
- * half-typed expression almost never parses, so gating on parse
190
- * validity (the way `getSemanticTokens` does) would suppress
191
- * completions almost always. This is the safest, fastest option that
192
- * still delivers real value.
193
- *
194
- * Candidates come from three sources: keywords (which already include
195
- * function names. See `ExpressionLexer.getKeywords()`'s doc comment)
196
- * and units, both static per engine configuration and cached lazily;
197
- * package-contributed items (`IEnginePackage.completionItems`), same
198
- * cache; and variable names, read fresh from `variableNameSource()` on
199
- * every call since those change on every edit.
200
- */
201
- getCompletions(lineText, cursorOffset) {
202
- const prefixMatch = /[A-Za-z0-9_]+$/.exec(lineText.slice(0, cursorOffset));
203
- if (!prefixMatch) return [];
204
- const prefix = prefixMatch[0].toLowerCase();
205
- if (!this.engine) return [];
206
- const matches = [];
207
- for (const name of this.variableNameSource()) {
208
- if (name.toLowerCase().startsWith(prefix)) {
209
- matches.push({ label: name, category: "variable" });
210
- }
211
- }
212
- const bucket = this.getStaticCompletionIndex().get(prefix[0]);
213
- if (bucket) {
214
- for (const candidate of bucket) {
215
- if (candidate.lowerLabel.startsWith(prefix)) matches.push(candidate.item);
216
- }
217
- }
218
- matches.sort((a, b) => {
219
- const tierDiff = (CATEGORY_TIER[a.category] ?? 3) - (CATEGORY_TIER[b.category] ?? 3);
220
- if (tierDiff !== 0) return tierDiff;
221
- return a.label.localeCompare(b.label);
222
- });
223
- return matches.slice(0, MAX_COMPLETIONS);
224
- }
225
- /** Lazily builds and caches the keyword/unit/package-item candidate list. See `staticCompletionCandidates`. */
226
- getStaticCompletionCandidates() {
227
- if (this.staticCompletionCandidates) return this.staticCompletionCandidates;
228
- const items = [];
229
- for (const [word, tokenType] of Object.entries(this.engine.getLexer().getKeywords())) {
230
- const category = getTokenCategory(tokenType);
231
- if (!category) continue;
232
- items.push({ label: word, category });
233
- }
234
- for (const unit of knownUnits) {
235
- items.push({ label: unit, category: "unit", detail: getMeasure(unit) });
236
- }
237
- items.push(...this.engine.getPackageCompletionItems());
238
- this.staticCompletionCandidates = items;
239
- return items;
240
- }
241
- /**
242
- * The static candidates bucketed by lowercased first character, built once
243
- * from {@link getStaticCompletionCandidates} and invalidated alongside it.
244
- */
245
- getStaticCompletionIndex() {
246
- if (this.staticCompletionIndex) return this.staticCompletionIndex;
247
- const index = /* @__PURE__ */ new Map();
248
- for (const item of this.getStaticCompletionCandidates()) {
249
- const lowerLabel = item.label.toLowerCase();
250
- const firstCharacter = lowerLabel[0];
251
- if (firstCharacter === void 0) continue;
252
- let bucket = index.get(firstCharacter);
253
- if (bucket === void 0) {
254
- bucket = [];
255
- index.set(firstCharacter, bucket);
256
- }
257
- bucket.push({ item, lowerLabel });
258
- }
259
- this.staticCompletionIndex = index;
260
- return index;
261
- }
262
- isKnownVariable(name) {
263
- for (const known of this.variableNameSource()) {
264
- if (known === name) return true;
265
- }
266
- return false;
267
- }
268
- /**
269
- * Whether the engine's parser actually accepts a piece of text as a
270
- * well-formed expression, not merely whether it lexes into individually
271
- * recognized token types. See the class doc comment's prose example.
272
- * `tryCompileExpression` is compile-only (lex → normalize → parse →
273
- * cache bytecode, no VM execution, no network/async side effects) and
274
- * reuses the engine's existing bytecode cache, so text that's already
275
- * been evaluated (or previously highlight-checked) is a cache hit here
276
- * too.
277
- *
278
- * Deliberately calls the non-throwing `tryCompileExpression` rather than
279
- * try/catching `compileExpression`. This runs on every visible line on
280
- * every keystroke, and the common case for a real markdown document is
281
- * lines that DON'T parse (prose), not lines that do. Throwing there would
282
- * mean constructing a EngineError (with V8 stack-trace capture) for the
283
- * common case instead of the rare one.
284
- */
285
- parsesAsExpression(text) {
286
- return this.engine.tryCompileExpression(text);
287
- }
288
- putCache(lineNumber, text, tokens, bareWordCandidate) {
289
- if (!this.cache.has(lineNumber) && this.cache.size >= MAX_CACHED_LINES) {
290
- const oldestKey = this.cache.keys().next().value;
291
- if (oldestKey !== void 0) this.cache.delete(oldestKey);
292
- }
293
- this.cache.set(lineNumber, { text, tokens, bareWordCandidate });
294
- }
295
- /**
296
- * Evict specific lines (e.g. the lines actually touched by a CodeMirror
297
- * change set) instead of the whole cache, the surgical counterpart to
298
- * {@link invalidateCache}, letting a single-line edit stay cheap even in
299
- * a large document: every other cached line is untouched and still hits
300
- * on the next call.
301
- */
302
- invalidateLines(lineNumbers) {
303
- for (const lineNumber of lineNumbers) {
304
- this.cache.delete(lineNumber);
305
- }
306
- }
307
- /**
308
- * Full cache clear. Reserved for cases with no meaningful "which lines
309
- * changed" (e.g. the document was swapped wholesale, or a package was
310
- * registered/unregistered mid-session, changing what categories exist).
311
- * Prefer {@link invalidateLines} for ordinary edits. Also rebuilds the
312
- * lazily-cached keyword/unit/package-item completion candidates on next
313
- * use, the only thing that can change that list mid-session.
314
- */
315
- invalidateCache() {
316
- this.cache.clear();
317
- this.staticCompletionCandidates = null;
318
- this.staticCompletionIndex = null;
319
- }
320
- };
321
-
322
- // src/language/tokenClassName.ts
323
- var DEFAULT_TOKEN_CLASS_PREFIX = "solve-";
324
- function tokenClassName(category, prefix = DEFAULT_TOKEN_CLASS_PREFIX) {
325
- return `${prefix}${category}`;
326
- }
327
- function createTokenClassName(prefix) {
328
- return (category) => `${prefix}${category}`;
329
- }
330
-
331
- // src/language/adapters/codemirror.ts
332
- var CATEGORY_TO_COMPLETION_TYPE = {
333
- keyword: "keyword",
334
- operator: "keyword",
335
- comparison: "keyword",
336
- bitwise: "keyword",
337
- function: "function",
338
- variable: "variable",
339
- unit: "type",
340
- datetime: "keyword",
341
- vector: "type"
342
- };
343
- function completionItemToOption(item) {
344
- return { label: item.label, type: CATEGORY_TO_COMPLETION_TYPE[item.category] ?? "text", detail: item.detail };
345
- }
346
-
347
- export { DEFAULT_TOKEN_CLASS_PREFIX, LanguageService, completionItemToOption, createTokenClassName, tokenClassName };
348
- //# sourceMappingURL=language.js.map
1
+ import {c}from'./chunk-WGZ3FNHI.js';import {d}from'./chunk-JPFCXLJV.js';export{a as UNCATEGORIZED_TOKEN_TYPES,d as getTokenCategory,b as registerTokenCategory,c as unregisterTokenCategory}from'./chunk-JPFCXLJV.js';import {a}from'./chunk-TRBXW6HW.js';import'./chunk-2JMFUQZH.js';var I=50,C={variable:0,function:1,keyword:1,operator:1,comparison:1,bitwise:1,datetime:1,vector:1,unit:2},T=2e3,p=class{constructor(e,o){this.cache=new Map;this.staticCompletionCandidates=null;this.staticCompletionIndex=null;this.engine=e??null,this.variableNameSource=o?.variableNameSource??(()=>this.defaultVariableNames()),this.normalizeForHighlighting=o?.normalizeForHighlighting??false;}defaultVariableNames(){if(!this.engine)return [];let e=this.engine.getDag().getSnapshot(),o=new Set(Object.keys(e.consumers));for(let n of Object.values(e.writes))for(let i of n)o.add(i);return o}classifyNormalized(e){let o=this.engine.getLexer(),n=o.getHighlightTokenObjects(e);if(n.length===0)return [];let i=e.startsWith("> ")&&o.classifyLine(e).skip?e.slice(2):e,r;try{r=this.engine.getNormalizer().normalize(n);}catch{r=n;}let l=[];for(let t of r){let s;if(t.sourceEnd!==void 0)s=t.sourceEnd;else if(i.startsWith(t.text,t.offset))s=t.offset+t.text.length;else continue;l.push({type:t.type,value:t.value,offset:t.offset,col:t.col,length:s-t.offset,category:d(t.type)});}return l}getSemanticTokens(e,o){let n=this.cache.get(o);if(n&&n.text===e)return n.bareWordCandidate!==void 0?this.isKnownVariable(n.bareWordCandidate)?n.tokens:[]:n.tokens;if(!this.engine)return [];let i=this.engine.getLexer(),r=this.normalizeForHighlighting?this.classifyNormalized(e):i.getHighlightTokens(e);if(r.length===0)return this.putCache(o,e,[]),[];let l=i.classifyLine(e),t=[];if(l.hasInlineSolve){let s=i.findInlineSolves(e).filter(a=>this.parsesAsExpression(a.expression)).map(a=>({from:a.start,to:a.end}));for(let a of r){if(!a.category)continue;let m=a.offset,f=a.offset+a.length;s.some(u=>m>=u.from&&f<=u.to)&&t.push({from:m,to:f,category:a.category});}}else {let s=e.startsWith("> ")&&l.skip?e.slice(2):e;if(this.parsesAsExpression(s))for(let a of r)a.category&&t.push({from:a.offset,to:a.offset+a.length,category:a.category});}if(t.length===1&&t[0].category==="variable"){let s=e.slice(t[0].from,t[0].to);return this.putCache(o,e,t,s),this.isKnownVariable(s)?t:[]}return this.putCache(o,e,t),t}getCompletions(e,o){let n=/[A-Za-z0-9_]+$/.exec(e.slice(0,o));if(!n)return [];let i=n[0].toLowerCase();if(!this.engine)return [];let r=[];for(let t of this.variableNameSource())t.toLowerCase().startsWith(i)&&r.push({label:t,category:"variable"});let l=this.getStaticCompletionIndex().get(i[0]);if(l)for(let t of l)t.lowerLabel.startsWith(i)&&r.push(t.item);return r.sort((t,s)=>{let a=(C[t.category]??3)-(C[s.category]??3);return a!==0?a:t.label.localeCompare(s.label)}),r.slice(0,I)}getStaticCompletionCandidates(){if(this.staticCompletionCandidates)return this.staticCompletionCandidates;let e=[];for(let[o,n]of Object.entries(this.engine.getLexer().getKeywords())){let i=d(n);i&&e.push({label:o,category:i});}for(let o of a)e.push({label:o,category:"unit",detail:c(o)});return e.push(...this.engine.getPackageCompletionItems()),this.staticCompletionCandidates=e,e}getStaticCompletionIndex(){if(this.staticCompletionIndex)return this.staticCompletionIndex;let e=new Map;for(let o of this.getStaticCompletionCandidates()){let n=o.label.toLowerCase(),i=n[0];if(i===void 0)continue;let r=e.get(i);r===void 0&&(r=[],e.set(i,r)),r.push({item:o,lowerLabel:n});}return this.staticCompletionIndex=e,e}isKnownVariable(e){for(let o of this.variableNameSource())if(o===e)return true;return false}parsesAsExpression(e){return this.engine.tryCompileExpression(e)}putCache(e,o,n,i){if(!this.cache.has(e)&&this.cache.size>=T){let r=this.cache.keys().next().value;r!==void 0&&this.cache.delete(r);}this.cache.set(e,{text:o,tokens:n,bareWordCandidate:i});}invalidateLines(e){for(let o of e)this.cache.delete(o);}invalidateCache(){this.cache.clear(),this.staticCompletionCandidates=null,this.staticCompletionIndex=null;}};var y="solve-";function x(c,e=y){return `${e}${c}`}function E(c){return e=>`${c}${e}`}var S={keyword:"keyword",operator:"keyword",comparison:"keyword",bitwise:"keyword",function:"function",variable:"variable",unit:"type",datetime:"keyword",vector:"type"};function w(c){return {label:c.label,type:S[c.category]??"text",detail:c.detail}}export{y as DEFAULT_TOKEN_CLASS_PREFIX,p as LanguageService,w as completionItemToOption,E as createTokenClassName,x as tokenClassName};//# sourceMappingURL=language.js.map
349
2
  //# sourceMappingURL=language.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/language/LanguageService.ts","../src/language/tokenClassName.ts","../src/language/adapters/codemirror.ts"],"names":[],"mappings":"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","file":"language.js","sourcesContent":["import type { ExpressionEngine } from \"@solve-js/engine/ExpressionEngine\";\nimport type { TokenCategory } from \"@solve-js/language/TokenCategory\";\nimport { getTokenCategory } from \"@solve-js/language/TokenCategoryMap\";\nimport type { Token } from \"@solve-js/lexer/Token\";\nimport { knownUnits } from \"@solve-js/lexer/units\";\nimport { getMeasure } from \"@solve-js/uom/UomConverter\";\n\n/** A single classified span within a line, the entire output contract of the language service. */\nexport interface SemanticToken {\n\tfrom: number;\n\tto: number;\n\tcategory: TokenCategory;\n}\n\n/** A single completion candidate, the entire output contract of `getCompletions()`. */\nexport interface CompletionItem {\n\tlabel: string;\n\t/** Reuses the highlighting taxonomy, one adapter can serve both features. */\n\tcategory: TokenCategory;\n\t/** e.g. a unit's measure (\"length\"), or the category name for keywords/functions. */\n\tdetail?: string;\n}\n\n/**\n * A completion candidate with its lowercased label precomputed.\n *\n * Internal to the prefix index; callers only ever see the {@link CompletionItem}.\n */\ninterface IndexedCompletionCandidate {\n\titem: CompletionItem;\n\tlowerLabel: string;\n}\n\n/** Completion results are capped, a document-wide candidate pool has no reason to return more than this. */\nconst MAX_COMPLETIONS = 50;\n\n/** Tier ordering for completion results: user-authored variables first, then grammar, then units. */\nconst CATEGORY_TIER: Partial<Record<TokenCategory, number>> = {\n\tvariable: 0,\n\tfunction: 1,\n\tkeyword: 1,\n\toperator: 1,\n\tcomparison: 1,\n\tbitwise: 1,\n\tdatetime: 1,\n\tvector: 1,\n\tunit: 2,\n};\n\n/** Bounded cache size. See the eviction-policy note on `LanguageService.cache`. */\nconst MAX_CACHED_LINES = 2000;\n\ninterface CacheEntry {\n\ttext: string;\n\ttokens: SemanticToken[];\n\t// When set, `tokens` is a single bare-identifier token whose validity\n\t// depends on document-wide DAG state (see the bare-word gate in\n\t// getSemanticTokens), not just this line's own text, so it can't be\n\t// cached as a plain pass/fail result the way every other line can. The\n\t// lex+parse work that produced `tokens` is still cached normally; only\n\t// the DAG membership check is re-run on every lookup (cache hit or\n\t// miss alike), since it's cheap (a Set lookup) and the alternative\n\t// caching the gated result, would go stale the moment some OTHER\n\t// line's edit changes what variables exist, with nothing to trigger a\n\t// re-check of this untouched line.\n\tbareWordCandidate?: string;\n}\n\n/** Options for the editor-facing language service: completions and highlighting. */\nexport interface LanguageServiceOptions {\n\t/**\n\t * Overrides how the service discovers \"variable names known in this\n\t * document\", used to legitimize a lone bare identifier line (see\n\t * `getSemanticTokens`'s single-token gate) and variable-name\n\t * completions (`getCompletions`). Defaults to reading\n\t * `engine.getDag().getSnapshot()`, which works for any consumer\n\t * sharing one `ExpressionEngine` between evaluation and the language\n\t * service (the real Obsidian editor).\n\t *\n\t * Required for consumers whose language service is backed by a\n\t * *different*, non-evaluating engine than the one that actually runs\n\t * the document (the playground's dedicated lexing-only engine, whose\n\t * own DAG is always empty), pass a function reading the real\n\t * evaluation engine's DAG snapshot instead.\n\t */\n\tvariableNameSource?: () => Iterable<string>;\n\n\t/**\n\t * Run the normalizer on the highlighting path, so phrase-fused tokens are\n\t * classified as the thing the parser will actually see.\n\t *\n\t * Off by default, and the default is a judgement rather than an oversight.\n\t * Highlighting runs per keystroke, normalization is real work, and a host\n\t * that is happy with lexer-level categories should not start paying for\n\t * fusion because a new version shipped. Turn it on and `12/09/2026` is one\n\t * `datetime` span instead of five number and operator spans; leave it off\n\t * and nothing about this class changes.\n\t *\n\t * See `benchmarks/languageServiceBenchmarks.spec.ts` for what it costs.\n\t */\n\tnormalizeForHighlighting?: boolean;\n}\n\n/**\n * Editor-agnostic \"language server\" for solve expressions: turns a line of\n * text into semantic token ranges, using the exact same lexer real\n * evaluation uses (so it only ever classifies what the engine's grammar\n * actually recognizes, never a separate/duplicated tokenizer). No\n * knowledge of CSS, CodeMirror, VS Code, or any other rendering concept\n * lives here. See `language/adapters/` for that.\n *\n * Classification happens at the LEXER stage, before the normalizer runs\n * (normalization, phrase fusion, implicit multiply, and package-specific\n * rules, happens later, only on the real evaluation path). A package's\n * lexer-level custom token types (e.g. a custom keyword) are recognized\n * here exactly as evaluation would see them. A package's *normalizer*-fused\n * synthetic tokens (e.g. OSRS's GAME_ITEM, built by fusing several\n * consecutive IDENT tokens against an item-name trie) are NOT. This\n * service still shows the pre-fusion IDENT tokens individually for those.\n * `IEnginePackage.tokenCategories` entries for normalizer-only token types\n * are still valid, correct registrations (queryable via getTokenCategory)\n *, they just won't currently be reachable through this lexer-only\n * classification path. Folding normalization in would require running it\n * per keystroke on the highlighting path too, which needs its own careful\n * design (span recomputation for fused multi-token ranges, in particular)\n * rather than a quick addition here.\n *\n * Lexing alone is NOT sufficient to decide \"recognized\", though: a run of\n * plain-English words (\"My name is ron\") lexes into a sequence of\n * individually-valid IDENT tokens with no grammar tying them together\n * every word \"recognized\" at the token level, but the line as a whole is\n * not something the engine would ever accept as an expression. Surfacing\n * per-token colors for that case looks like the editor mistook prose for\n * code. So a line's tokens are only surfaced once the line as a whole\n * parses successfully (via `ExpressionEngine.compileExpression`, the same\n * parse pipeline, and the same bytecode cache, real evaluation uses; no\n * separate/duplicated grammar check). A single bare word (\"hello\", a valid\n * variable reference) or a keyword-only line (\"pi\") still parses and still\n * highlights, only genuinely ungrammatical text is suppressed, unless it's\n * a known variable elsewhere in the document (see `variableNameSource`).\n *\n * `getCompletions()` is the other half of this \"language server\": unlike\n * `getSemanticTokens()`, it's explicitly for *incomplete*, mid-typing text\n *, it deliberately does NOT gate on parse validity (a half-typed\n * expression almost never parses), using simple prefix matching instead.\n *\n * Must be constructed with an already-configured `ExpressionEngine` (one\n * with all currently-relevant packages registered) rather than a bare\n * lexer, reusing an existing engine is both the fast path (no throwaway\n * lexer construction) and the *correct* one: a highlighting-only lexer\n * built independently of the evaluation engine would silently fail to\n * recognize plugin-contributed tokens (e.g. a package's custom keywords)\n * unless it happened to have the identical packages registered.\n */\nexport class LanguageService {\n\tprivate engine: ExpressionEngine | null;\n\tprivate variableNameSource: () => Iterable<string>;\n\tprivate readonly normalizeForHighlighting: boolean;\n\n\t// Bounded cache keyed by line number ALONE, not `${lineNumber}:${lineText}`\n\t// as an earlier version of this class did. A line's previous text state is\n\t// never useful once it changes, so keying on text too was pure waste:\n\t// every keystroke on a line minted a brand-new, never-reclaimed cache\n\t// entry (an effective per-keystroke memory leak over a long editing\n\t// session). Keying on line number alone makes \"same line, new text\" a\n\t// cheap overwrite instead.\n\t//\n\t// Eviction is oldest-inserted (Map iteration order) when at capacity\n\t// mirroring the same bounded-cache pattern ExpressionEngine's own\n\t// bytecodeCache already uses elsewhere in this codebase. Deliberately\n\t// NOT an LFU (least-frequently-used) policy: LFU would keep resisting\n\t// eviction of old, once-popular lines while punishing a line that just\n\t// scrolled into view (frequency 1), the opposite of what a \"currently\n\t// visible\" cache should prioritize.\n\tprivate cache = new Map<number, CacheEntry>();\n\n\t// Keyword/unit/package-contributed completion candidates don't depend\n\t// on any particular line, built lazily on first getCompletions() call\n\t// and reused after that, since a package registration is the only thing\n\t// that could ever change this list mid-session (see invalidateCache()).\n\t// Variable-name candidates are NOT part of this, they're read fresh on\n\t// every call from variableNameSource(), since those genuinely change on\n\t// every edit.\n\tprivate staticCompletionCandidates: CompletionItem[] | null = null;\n\n\t// The same candidates, bucketed by their lowercased first character with the\n\t// lowercased label precomputed. getCompletions() runs on every keystroke and\n\t// only ever wants candidates sharing the prefix's first character, so\n\t// scanning the whole list and lowercasing each label per call was doing two\n\t// avoidable things: touching entries that could not possibly match, and\n\t// allocating a string per candidate per keystroke. That was affordable when\n\t// the vocabulary was a few hundred entries. Deriving the unit list from the\n\t// conversion tables took it past a thousand, and the warm completion\n\t// benchmarks regressed roughly 2.9x until this was added.\n\tprivate staticCompletionIndex: Map<string, IndexedCompletionCandidate[]> | null = null;\n\n\tconstructor(engine?: ExpressionEngine | null, options?: LanguageServiceOptions) {\n\t\tthis.engine = engine ?? null;\n\t\tthis.variableNameSource = options?.variableNameSource ?? (() => this.defaultVariableNames());\n\t\tthis.normalizeForHighlighting = options?.normalizeForHighlighting ?? false;\n\t}\n\n\tprivate defaultVariableNames(): Iterable<string> {\n\t\tif (!this.engine) return [];\n\t\tconst snapshot = this.engine.getDag().getSnapshot();\n\t\tconst names = new Set<string>(Object.keys(snapshot.consumers));\n\t\tfor (const written of Object.values(snapshot.writes)) {\n\t\t\tfor (const name of written) names.add(name);\n\t\t}\n\t\treturn names;\n\t}\n\n\t/**\n\t * Lex a line, normalize it, and place every resulting token back in the\n\t * source text.\n\t *\n\t * ## Why placing them back is the hard part\n\t *\n\t * Normalization produces three kinds of token and only one of them can be\n\t * highlighted the obvious way.\n\t *\n\t * A token the normalizer left alone still describes its own text, so its\n\t * span is `offset` to `offset + text.length`, exactly as before.\n\t *\n\t * A FUSED token does not. `10 frames` becomes a FRAME_COUNT whose value is\n\t * `10`, and a timecode becomes a token whose value is a comma-separated\n\t * tuple appearing nowhere on the line. Those carry `sourceEnd`, set by\n\t * `createFusedToken`, which is the only place that knows where the fusion\n\t * ended.\n\t *\n\t * An INSERTED token has no text at all. Implicit multiplication puts a STAR\n\t * at the following token's offset, so `5(3)` gains a `*` sitting exactly\n\t * where the `(` is. Painting it would colour a character the reader never\n\t * typed as an operator, and would overlap the token that really is there.\n\t * These are dropped, detected by the one test that needs no cooperation\n\t * from any rule: a token with no recorded fusion span must match the text\n\t * at its own offset, and an inserted one does not.\n\t *\n\t * @param lineText - The raw line.\n\t * @returns Spans in the same shape `Lexer.getHighlightTokens` returns.\n\t */\n\tprivate classifyNormalized(\n\t\tlineText: string,\n\t): { type: string; value: string; offset: number; col: number; length: number; category: TokenCategory | undefined }[] {\n\t\tconst lexer = this.engine!.getLexer();\n\t\tconst raw = lexer.getHighlightTokenObjects(lineText);\n\t\tif (raw.length === 0) return [];\n\n\t\t// Offsets from a blockquote line are relative to the stripped text, so\n\t\t// the text this checks against has to be stripped the same way.\n\t\tconst source =\n\t\t\tlineText.startsWith(\"> \") && lexer.classifyLine(lineText).skip ? lineText.slice(2) : lineText;\n\n\t\tlet normalized: Token[];\n\t\ttry {\n\t\t\tnormalized = this.engine!.getNormalizer().normalize(raw);\n\t\t} catch {\n\t\t\t// Normalization is an enhancement here, not a requirement. A rule\n\t\t\t// that throws on a half-typed line should cost the reader phrase\n\t\t\t// colouring, not all colouring.\n\t\t\tnormalized = raw;\n\t\t}\n\n\t\tconst out: {\n\t\t\ttype: string;\n\t\t\tvalue: string;\n\t\t\toffset: number;\n\t\t\tcol: number;\n\t\t\tlength: number;\n\t\t\tcategory: TokenCategory | undefined;\n\t\t}[] = [];\n\n\t\tfor (const token of normalized) {\n\t\t\tlet end: number;\n\t\t\tif (token.sourceEnd !== undefined) {\n\t\t\t\tend = token.sourceEnd;\n\t\t\t} else if (source.startsWith(token.text, token.offset)) {\n\t\t\t\t// `text`, not `value`. They are the same string for almost every\n\t\t\t\t// token, and differ for a string literal, whose value is the\n\t\t\t\t// payload while its text is the quoted source slice. Matching on\n\t\t\t\t// value dropped every string literal from the painted line,\n\t\t\t\t// since `gbp` does not appear at the offset where `\"gbp\"` does.\n\t\t\t\tend = token.offset + token.text.length;\n\t\t\t} else {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tout.push({\n\t\t\t\ttype: token.type,\n\t\t\t\tvalue: token.value,\n\t\t\t\toffset: token.offset,\n\t\t\t\tcol: token.col,\n\t\t\t\tlength: end - token.offset,\n\t\t\t\tcategory: getTokenCategory(token.type),\n\t\t\t});\n\t\t}\n\n\t\treturn out;\n\t}\n\n\t/**\n\t * Classify every recognized token on one line.\n\t *\n\t * @param lineText - The raw line text (may be a markdown-structural line\n\t * the engine's classifier skips, that's handled by the underlying\n\t * lexer, which returns no tokens for those).\n\t * @param lineNumber - 1-based line number, used purely as a cache key.\n\t */\n\tgetSemanticTokens(lineText: string, lineNumber: number): SemanticToken[] {\n\t\tconst cached = this.cache.get(lineNumber);\n\t\tif (cached && cached.text === lineText) {\n\t\t\tif (cached.bareWordCandidate !== undefined) {\n\t\t\t\treturn this.isKnownVariable(cached.bareWordCandidate) ? cached.tokens : [];\n\t\t\t}\n\t\t\treturn cached.tokens;\n\t\t}\n\n\t\tif (!this.engine) {\n\t\t\t// No engine available (e.g. a consumer that hasn't wired one up yet)\n\t\t\t//, no highlighting, not an error.\n\t\t\treturn [];\n\t\t}\n\n\t\tconst lexer = this.engine.getLexer();\n\t\tconst lexed = this.normalizeForHighlighting\n\t\t\t? this.classifyNormalized(lineText)\n\t\t\t: lexer.getHighlightTokens(lineText);\n\t\tif (lexed.length === 0) {\n\t\t\tthis.putCache(lineNumber, lineText, []);\n\t\t\treturn [];\n\t\t}\n\n\t\tconst classification = lexer.classifyLine(lineText);\n\t\tconst tokens: SemanticToken[] = [];\n\n\t\tif (classification.hasInlineSolve) {\n\t\t\t// A line can mix markdown prose with one or more embedded\n\t\t\t// `s`...`` expressions. Only the text actually inside a\n\t\t\t// well-formed marker is a recognized expression, surrounding\n\t\t\t// prose lexes into individually-valid tokens too (see the class\n\t\t\t// doc comment) but is never something the engine would parse,\n\t\t\t// so it's excluded token-by-token via span membership rather\n\t\t\t// than gating the whole line pass/fail.\n\t\t\tconst validSpans = lexer\n\t\t\t\t.findInlineSolves(lineText)\n\t\t\t\t.filter(span => this.parsesAsExpression(span.expression))\n\t\t\t\t.map(span => ({ from: span.start, to: span.end }));\n\t\t\tfor (const token of lexed) {\n\t\t\t\tif (!token.category) continue;\n\t\t\t\tconst from = token.offset;\n\t\t\t\tconst to = token.offset + token.length;\n\t\t\t\tif (!validSpans.some(s => from >= s.from && to <= s.to)) continue;\n\t\t\t\ttokens.push({ from, to, category: token.category });\n\t\t\t}\n\t\t} else {\n\t\t\t// Blockquote content is stripped of its \"> \" prefix before being\n\t\t\t// tokenized (see Lexer.getHighlightTokens), token offsets are\n\t\t\t// already relative to the stripped text, so the parse check must\n\t\t\t// run against that same substring to match.\n\t\t\tconst text = lineText.startsWith(\"> \") && classification.skip\n\t\t\t\t? lineText.slice(2)\n\t\t\t\t: lineText;\n\t\t\tif (this.parsesAsExpression(text)) {\n\t\t\t\tfor (const token of lexed) {\n\t\t\t\t\tif (!token.category) continue;\n\t\t\t\t\ttokens.push({ from: token.offset, to: token.offset + token.length, category: token.category });\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// A lone bare word (\"hello\") is exactly as ambiguous as a run of\n\t\t// prose (\"My name is dave\"), it happens to parse as a\n\t\t// single-identifier variable-reference expression, but that's true\n\t\t// of literally any English word, so on its own it isn't \"recognized\"\n\t\t// in any meaningful sense. Sigil-marked variables (\":x\", \"$x\") are\n\t\t// unaffected, those lex to TWO tokens (sigil + ident), never\n\t\t// hitting this single-token check. Keywords (\"pi\") are unaffected\n\t\t// too, their category is \"keyword\", not \"variable\". Only surface\n\t\t// it once it's an actual known variable elsewhere in the document\n\t\t// checked live (see the `bareWordCandidate` cache field), not baked\n\t\t// into the cached result, since another line's edit can make this\n\t\t// check flip without this line's own text ever changing.\n\t\tif (tokens.length === 1 && tokens[0].category === \"variable\") {\n\t\t\tconst name = lineText.slice(tokens[0].from, tokens[0].to);\n\t\t\tthis.putCache(lineNumber, lineText, tokens, name);\n\t\t\treturn this.isKnownVariable(name) ? tokens : [];\n\t\t}\n\n\t\tthis.putCache(lineNumber, lineText, tokens);\n\t\treturn tokens;\n\t}\n\n\t/**\n\t * Completion candidates for the identifier prefix immediately before\n\t * `cursorOffset` on `lineText`. Deliberately simple prefix matching, not\n\t * parser-driven \"what's grammatically valid here\" prediction, a\n\t * half-typed expression almost never parses, so gating on parse\n\t * validity (the way `getSemanticTokens` does) would suppress\n\t * completions almost always. This is the safest, fastest option that\n\t * still delivers real value.\n\t *\n\t * Candidates come from three sources: keywords (which already include\n\t * function names. See `ExpressionLexer.getKeywords()`'s doc comment)\n\t * and units, both static per engine configuration and cached lazily;\n\t * package-contributed items (`IEnginePackage.completionItems`), same\n\t * cache; and variable names, read fresh from `variableNameSource()` on\n\t * every call since those change on every edit.\n\t */\n\tgetCompletions(lineText: string, cursorOffset: number): CompletionItem[] {\n\t\tconst prefixMatch = /[A-Za-z0-9_]+$/.exec(lineText.slice(0, cursorOffset));\n\t\tif (!prefixMatch) return [];\n\t\tconst prefix = prefixMatch[0].toLowerCase();\n\n\t\tif (!this.engine) return [];\n\n\t\tconst matches: CompletionItem[] = [];\n\n\t\t// Variables are read fresh every call and there are few of them, so they\n\t\t// stay a linear scan.\n\t\tfor (const name of this.variableNameSource()) {\n\t\t\tif (name.toLowerCase().startsWith(prefix)) {\n\t\t\t\tmatches.push({ label: name, category: \"variable\" });\n\t\t\t}\n\t\t}\n\n\t\t// Static candidates only ever match if they share the prefix's first\n\t\t// character, so consult that bucket alone.\n\t\tconst bucket = this.getStaticCompletionIndex().get(prefix[0]);\n\t\tif (bucket) {\n\t\t\tfor (const candidate of bucket) {\n\t\t\t\tif (candidate.lowerLabel.startsWith(prefix)) matches.push(candidate.item);\n\t\t\t}\n\t\t}\n\n\t\tmatches.sort((a, b) => {\n\t\t\tconst tierDiff = (CATEGORY_TIER[a.category] ?? 3) - (CATEGORY_TIER[b.category] ?? 3);\n\t\t\tif (tierDiff !== 0) return tierDiff;\n\t\t\treturn a.label.localeCompare(b.label);\n\t\t});\n\n\t\treturn matches.slice(0, MAX_COMPLETIONS);\n\t}\n\n\t/** Lazily builds and caches the keyword/unit/package-item candidate list. See `staticCompletionCandidates`. */\n\tprivate getStaticCompletionCandidates(): CompletionItem[] {\n\t\tif (this.staticCompletionCandidates) return this.staticCompletionCandidates;\n\n\t\tconst items: CompletionItem[] = [];\n\t\tfor (const [word, tokenType] of Object.entries(this.engine!.getLexer().getKeywords())) {\n\t\t\tconst category = getTokenCategory(tokenType);\n\t\t\tif (!category) continue;\n\t\t\titems.push({ label: word, category });\n\t\t}\n\t\tfor (const unit of knownUnits) {\n\t\t\titems.push({ label: unit, category: \"unit\", detail: getMeasure(unit) });\n\t\t}\n\t\titems.push(...this.engine!.getPackageCompletionItems());\n\n\t\tthis.staticCompletionCandidates = items;\n\t\treturn items;\n\t}\n\n\t/**\n\t * The static candidates bucketed by lowercased first character, built once\n\t * from {@link getStaticCompletionCandidates} and invalidated alongside it.\n\t */\n\tprivate getStaticCompletionIndex(): Map<string, IndexedCompletionCandidate[]> {\n\t\tif (this.staticCompletionIndex) return this.staticCompletionIndex;\n\n\t\tconst index = new Map<string, IndexedCompletionCandidate[]>();\n\t\tfor (const item of this.getStaticCompletionCandidates()) {\n\t\t\tconst lowerLabel = item.label.toLowerCase();\n\t\t\tconst firstCharacter = lowerLabel[0];\n\t\t\t// A label cannot match any prefix if it is empty, and the prefix\n\t\t\t// regex guarantees at least one character on the query side.\n\t\t\tif (firstCharacter === undefined) continue;\n\t\t\tlet bucket = index.get(firstCharacter);\n\t\t\tif (bucket === undefined) {\n\t\t\t\tbucket = [];\n\t\t\t\tindex.set(firstCharacter, bucket);\n\t\t\t}\n\t\t\tbucket.push({ item, lowerLabel });\n\t\t}\n\n\t\tthis.staticCompletionIndex = index;\n\t\treturn index;\n\t}\n\n\tprivate isKnownVariable(name: string): boolean {\n\t\tfor (const known of this.variableNameSource()) {\n\t\t\tif (known === name) return true;\n\t\t}\n\t\treturn false;\n\t}\n\n\t/**\n\t * Whether the engine's parser actually accepts a piece of text as a\n\t * well-formed expression, not merely whether it lexes into individually\n\t * recognized token types. See the class doc comment's prose example.\n\t * `tryCompileExpression` is compile-only (lex → normalize → parse →\n\t * cache bytecode, no VM execution, no network/async side effects) and\n\t * reuses the engine's existing bytecode cache, so text that's already\n\t * been evaluated (or previously highlight-checked) is a cache hit here\n\t * too.\n\t *\n\t * Deliberately calls the non-throwing `tryCompileExpression` rather than\n\t * try/catching `compileExpression`. This runs on every visible line on\n\t * every keystroke, and the common case for a real markdown document is\n\t * lines that DON'T parse (prose), not lines that do. Throwing there would\n\t * mean constructing a EngineError (with V8 stack-trace capture) for the\n\t * common case instead of the rare one.\n\t */\n\tprivate parsesAsExpression(text: string): boolean {\n\t\treturn this.engine!.tryCompileExpression(text);\n\t}\n\n\tprivate putCache(lineNumber: number, text: string, tokens: SemanticToken[], bareWordCandidate?: string): void {\n\t\tif (!this.cache.has(lineNumber) && this.cache.size >= MAX_CACHED_LINES) {\n\t\t\tconst oldestKey = this.cache.keys().next().value;\n\t\t\tif (oldestKey !== undefined) this.cache.delete(oldestKey);\n\t\t}\n\t\tthis.cache.set(lineNumber, { text, tokens, bareWordCandidate });\n\t}\n\n\t/**\n\t * Evict specific lines (e.g. the lines actually touched by a CodeMirror\n\t * change set) instead of the whole cache, the surgical counterpart to\n\t * {@link invalidateCache}, letting a single-line edit stay cheap even in\n\t * a large document: every other cached line is untouched and still hits\n\t * on the next call.\n\t */\n\tinvalidateLines(lineNumbers: Iterable<number>): void {\n\t\tfor (const lineNumber of lineNumbers) {\n\t\t\tthis.cache.delete(lineNumber);\n\t\t}\n\t}\n\n\t/**\n\t * Full cache clear. Reserved for cases with no meaningful \"which lines\n\t * changed\" (e.g. the document was swapped wholesale, or a package was\n\t * registered/unregistered mid-session, changing what categories exist).\n\t * Prefer {@link invalidateLines} for ordinary edits. Also rebuilds the\n\t * lazily-cached keyword/unit/package-item completion candidates on next\n\t * use, the only thing that can change that list mid-session.\n\t */\n\tinvalidateCache(): void {\n\t\tthis.cache.clear();\n\t\tthis.staticCompletionCandidates = null;\n\t\tthis.staticCompletionIndex = null;\n\t}\n}\n","import type { TokenCategory } from \"@solve-js/language/TokenCategory\";\n\n/**\n * Namespace applied to a semantic category when no other one is asked for.\n *\n * A prefix is needed because a bare category name like `number` or `error`\n * would collide with almost any host stylesheet. `solve-` is the narrowest\n * namespace that still says where the class came from, and it deliberately\n * names this library rather than any particular editor.\n */\nexport const DEFAULT_TOKEN_CLASS_PREFIX = \"solve-\";\n\n/**\n * Maps a semantic category to a predictable, stable CSS class name.\n *\n * Deliberately trivial: the category name IS the class-name key\n * (`\"number\"` → `solve-number`), so there is no separate table to keep in\n * sync as categories grow. A brand-new category, including one contributed\n * by a solve-js package at runtime, gets a matching class name with no\n * changes here.\n *\n * This is editor-agnostic on purpose. It produces a class-name string and\n * nothing else, so it works the same whether the host is CodeMirror\n * (`Decoration.mark({ class: tokenClassName(token.category) })`), Slate,\n * ProseMirror, Monaco or a plain `<span>`. Actual colors are pure CSS,\n * resolved from `--solve-hl-{category}` custom properties by each consumer.\n *\n * @param category - Semantic category from `getSemanticTokens()`.\n * @param prefix - Namespace to apply. Defaults to `solve-`.\n * @returns The class name for that category.\n */\nexport function tokenClassName(category: TokenCategory, prefix: string = DEFAULT_TOKEN_CLASS_PREFIX): string {\n\treturn `${prefix}${category}`;\n}\n\n/**\n * Builds a `tokenClassName` bound to a fixed prefix.\n *\n * The prefix is configurable because a host may already own a namespace it\n * wants these classes to sit inside: a CodeMirror theme that keys off `cm-`,\n * a design system that scopes everything under one token, or an app that\n * embeds two editors and wants to style them apart. Passing the prefix once\n * here is cheaper than threading it through every call site.\n *\n * ```ts\n * const className = createTokenClassName(\"cm-solve-\");\n * className(\"number\"); // \"cm-solve-number\"\n * ```\n *\n * @param prefix - Namespace to apply to every category.\n * @returns A function mapping a category to a prefixed class name.\n */\nexport function createTokenClassName(prefix: string): (category: TokenCategory) => string {\n\treturn (category: TokenCategory) => `${prefix}${category}`;\n}\n","import type { TokenCategory } from \"@solve-js/language/TokenCategory\";\nimport type { CompletionItem } from \"@solve-js/language/LanguageService\";\n\n/**\n * Maps a semantic category to one of `@codemirror/autocomplete`'s built-in\n * completion \"type\" strings (which drive its default gutter icon), the one\n * genuinely CodeMirror-specific thing this feature needs. Falls back to\n * \"text\" for anything unmapped, including plugin-contributed categories\n * (e.g. OSRS's \"osrs-item\"), a reasonable neutral default rather than a\n * hard failure for a category this adapter doesn't know about yet.\n *\n * No `@codemirror/autocomplete` import here, the returned object shape is\n * structurally compatible with CM6's `Completion` type by duck typing, so\n * `solve-js` gains no new dependency; the actual `CompletionSource`\n * function (reading `CompletionContext`, building a `CompletionResult`)\n * lives in each consumer (src/app, playground), same tier as\n * `buildDecorations()` already is for highlighting.\n */\nconst CATEGORY_TO_COMPLETION_TYPE: Partial<Record<TokenCategory, string>> = {\n\tkeyword: \"keyword\",\n\toperator: \"keyword\",\n\tcomparison: \"keyword\",\n\tbitwise: \"keyword\",\n\tfunction: \"function\",\n\tvariable: \"variable\",\n\tunit: \"type\",\n\tdatetime: \"keyword\",\n\tvector: \"type\",\n};\n\n/**\n * Convert a completion into CodeMirror's option shape.\n *\n * Kept in an adapter so the language service itself stays editor-agnostic.\n *\n * @param item - Completion produced by the language service.\n * @returns The equivalent CodeMirror option.\n */\nexport function completionItemToOption(item: CompletionItem): { label: string; type: string; detail?: string } {\n\treturn { label: item.label, type: CATEGORY_TO_COMPLETION_TYPE[item.category] ?? \"text\", detail: item.detail };\n}\n"]}
1
+ {"version":3,"sources":["../src/language/LanguageService.ts","../src/language/tokenClassName.ts","../src/language/adapters/codemirror.ts"],"names":["MAX_COMPLETIONS","CATEGORY_TIER","MAX_CACHED_LINES","LanguageService","engine","options","snapshot","names","written","name","lineText","lexer","raw","source","normalized","out","token","end","getTokenCategory","lineNumber","cached","lexed","classification","tokens","validSpans","span","from","to","s","text","cursorOffset","prefixMatch","prefix","matches","bucket","candidate","a","b","tierDiff","items","word","tokenType","category","unit","knownUnits","getMeasure","index","item","lowerLabel","firstCharacter","known","bareWordCandidate","oldestKey","lineNumbers","DEFAULT_TOKEN_CLASS_PREFIX","tokenClassName","createTokenClassName","CATEGORY_TO_COMPLETION_TYPE","completionItemToOption"],"mappings":"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","file":"language.js","sourcesContent":["import type { ExpressionEngine } from \"@solve-js/engine/ExpressionEngine\";\nimport type { TokenCategory } from \"@solve-js/language/TokenCategory\";\nimport { getTokenCategory } from \"@solve-js/language/TokenCategoryMap\";\nimport type { Token } from \"@solve-js/lexer/Token\";\nimport { knownUnits } from \"@solve-js/lexer/units\";\nimport { getMeasure } from \"@solve-js/uom/UomConverter\";\n\n/** A single classified span within a line, the entire output contract of the language service. */\nexport interface SemanticToken {\n\tfrom: number;\n\tto: number;\n\tcategory: TokenCategory;\n}\n\n/** A single completion candidate, the entire output contract of `getCompletions()`. */\nexport interface CompletionItem {\n\tlabel: string;\n\t/** Reuses the highlighting taxonomy, one adapter can serve both features. */\n\tcategory: TokenCategory;\n\t/** e.g. a unit's measure (\"length\"), or the category name for keywords/functions. */\n\tdetail?: string;\n}\n\n/**\n * A completion candidate with its lowercased label precomputed.\n *\n * Internal to the prefix index; callers only ever see the {@link CompletionItem}.\n */\ninterface IndexedCompletionCandidate {\n\titem: CompletionItem;\n\tlowerLabel: string;\n}\n\n/** Completion results are capped, a document-wide candidate pool has no reason to return more than this. */\nconst MAX_COMPLETIONS = 50;\n\n/** Tier ordering for completion results: user-authored variables first, then grammar, then units. */\nconst CATEGORY_TIER: Partial<Record<TokenCategory, number>> = {\n\tvariable: 0,\n\tfunction: 1,\n\tkeyword: 1,\n\toperator: 1,\n\tcomparison: 1,\n\tbitwise: 1,\n\tdatetime: 1,\n\tvector: 1,\n\tunit: 2,\n};\n\n/** Bounded cache size. See the eviction-policy note on `LanguageService.cache`. */\nconst MAX_CACHED_LINES = 2000;\n\ninterface CacheEntry {\n\ttext: string;\n\ttokens: SemanticToken[];\n\t// When set, `tokens` is a single bare-identifier token whose validity\n\t// depends on document-wide DAG state (see the bare-word gate in\n\t// getSemanticTokens), not just this line's own text, so it can't be\n\t// cached as a plain pass/fail result the way every other line can. The\n\t// lex+parse work that produced `tokens` is still cached normally; only\n\t// the DAG membership check is re-run on every lookup (cache hit or\n\t// miss alike), since it's cheap (a Set lookup) and the alternative\n\t// caching the gated result, would go stale the moment some OTHER\n\t// line's edit changes what variables exist, with nothing to trigger a\n\t// re-check of this untouched line.\n\tbareWordCandidate?: string;\n}\n\n/** Options for the editor-facing language service: completions and highlighting. */\nexport interface LanguageServiceOptions {\n\t/**\n\t * Overrides how the service discovers \"variable names known in this\n\t * document\", used to legitimize a lone bare identifier line (see\n\t * `getSemanticTokens`'s single-token gate) and variable-name\n\t * completions (`getCompletions`). Defaults to reading\n\t * `engine.getDag().getSnapshot()`, which works for any consumer\n\t * sharing one `ExpressionEngine` between evaluation and the language\n\t * service (the real Obsidian editor).\n\t *\n\t * Required for consumers whose language service is backed by a\n\t * *different*, non-evaluating engine than the one that actually runs\n\t * the document (the playground's dedicated lexing-only engine, whose\n\t * own DAG is always empty), pass a function reading the real\n\t * evaluation engine's DAG snapshot instead.\n\t */\n\tvariableNameSource?: () => Iterable<string>;\n\n\t/**\n\t * Run the normalizer on the highlighting path, so phrase-fused tokens are\n\t * classified as the thing the parser will actually see.\n\t *\n\t * Off by default, and the default is a judgement rather than an oversight.\n\t * Highlighting runs per keystroke, normalization is real work, and a host\n\t * that is happy with lexer-level categories should not start paying for\n\t * fusion because a new version shipped. Turn it on and `12/09/2026` is one\n\t * `datetime` span instead of five number and operator spans; leave it off\n\t * and nothing about this class changes.\n\t *\n\t * See `benchmarks/languageServiceBenchmarks.spec.ts` for what it costs.\n\t */\n\tnormalizeForHighlighting?: boolean;\n}\n\n/**\n * Editor-agnostic \"language server\" for solve expressions: turns a line of\n * text into semantic token ranges, using the exact same lexer real\n * evaluation uses (so it only ever classifies what the engine's grammar\n * actually recognizes, never a separate/duplicated tokenizer). No\n * knowledge of CSS, CodeMirror, VS Code, or any other rendering concept\n * lives here. See `language/adapters/` for that.\n *\n * Classification happens at the LEXER stage, before the normalizer runs\n * (normalization, phrase fusion, implicit multiply, and package-specific\n * rules, happens later, only on the real evaluation path). A package's\n * lexer-level custom token types (e.g. a custom keyword) are recognized\n * here exactly as evaluation would see them. A package's *normalizer*-fused\n * synthetic tokens (e.g. OSRS's GAME_ITEM, built by fusing several\n * consecutive IDENT tokens against an item-name trie) are NOT. This\n * service still shows the pre-fusion IDENT tokens individually for those.\n * `IEnginePackage.tokenCategories` entries for normalizer-only token types\n * are still valid, correct registrations (queryable via getTokenCategory)\n *, they just won't currently be reachable through this lexer-only\n * classification path. Folding normalization in would require running it\n * per keystroke on the highlighting path too, which needs its own careful\n * design (span recomputation for fused multi-token ranges, in particular)\n * rather than a quick addition here.\n *\n * Lexing alone is NOT sufficient to decide \"recognized\", though: a run of\n * plain-English words (\"My name is ron\") lexes into a sequence of\n * individually-valid IDENT tokens with no grammar tying them together\n * every word \"recognized\" at the token level, but the line as a whole is\n * not something the engine would ever accept as an expression. Surfacing\n * per-token colors for that case looks like the editor mistook prose for\n * code. So a line's tokens are only surfaced once the line as a whole\n * parses successfully (via `ExpressionEngine.compileExpression`, the same\n * parse pipeline, and the same bytecode cache, real evaluation uses; no\n * separate/duplicated grammar check). A single bare word (\"hello\", a valid\n * variable reference) or a keyword-only line (\"pi\") still parses and still\n * highlights, only genuinely ungrammatical text is suppressed, unless it's\n * a known variable elsewhere in the document (see `variableNameSource`).\n *\n * `getCompletions()` is the other half of this \"language server\": unlike\n * `getSemanticTokens()`, it's explicitly for *incomplete*, mid-typing text\n *, it deliberately does NOT gate on parse validity (a half-typed\n * expression almost never parses), using simple prefix matching instead.\n *\n * Must be constructed with an already-configured `ExpressionEngine` (one\n * with all currently-relevant packages registered) rather than a bare\n * lexer, reusing an existing engine is both the fast path (no throwaway\n * lexer construction) and the *correct* one: a highlighting-only lexer\n * built independently of the evaluation engine would silently fail to\n * recognize plugin-contributed tokens (e.g. a package's custom keywords)\n * unless it happened to have the identical packages registered.\n */\nexport class LanguageService {\n\tprivate engine: ExpressionEngine | null;\n\tprivate variableNameSource: () => Iterable<string>;\n\tprivate readonly normalizeForHighlighting: boolean;\n\n\t// Bounded cache keyed by line number ALONE, not `${lineNumber}:${lineText}`\n\t// as an earlier version of this class did. A line's previous text state is\n\t// never useful once it changes, so keying on text too was pure waste:\n\t// every keystroke on a line minted a brand-new, never-reclaimed cache\n\t// entry (an effective per-keystroke memory leak over a long editing\n\t// session). Keying on line number alone makes \"same line, new text\" a\n\t// cheap overwrite instead.\n\t//\n\t// Eviction is oldest-inserted (Map iteration order) when at capacity\n\t// mirroring the same bounded-cache pattern ExpressionEngine's own\n\t// bytecodeCache already uses elsewhere in this codebase. Deliberately\n\t// NOT an LFU (least-frequently-used) policy: LFU would keep resisting\n\t// eviction of old, once-popular lines while punishing a line that just\n\t// scrolled into view (frequency 1), the opposite of what a \"currently\n\t// visible\" cache should prioritize.\n\tprivate cache = new Map<number, CacheEntry>();\n\n\t// Keyword/unit/package-contributed completion candidates don't depend\n\t// on any particular line, built lazily on first getCompletions() call\n\t// and reused after that, since a package registration is the only thing\n\t// that could ever change this list mid-session (see invalidateCache()).\n\t// Variable-name candidates are NOT part of this, they're read fresh on\n\t// every call from variableNameSource(), since those genuinely change on\n\t// every edit.\n\tprivate staticCompletionCandidates: CompletionItem[] | null = null;\n\n\t// The same candidates, bucketed by their lowercased first character with the\n\t// lowercased label precomputed. getCompletions() runs on every keystroke and\n\t// only ever wants candidates sharing the prefix's first character, so\n\t// scanning the whole list and lowercasing each label per call was doing two\n\t// avoidable things: touching entries that could not possibly match, and\n\t// allocating a string per candidate per keystroke. That was affordable when\n\t// the vocabulary was a few hundred entries. Deriving the unit list from the\n\t// conversion tables took it past a thousand, and the warm completion\n\t// benchmarks regressed roughly 2.9x until this was added.\n\tprivate staticCompletionIndex: Map<string, IndexedCompletionCandidate[]> | null = null;\n\n\tconstructor(engine?: ExpressionEngine | null, options?: LanguageServiceOptions) {\n\t\tthis.engine = engine ?? null;\n\t\tthis.variableNameSource = options?.variableNameSource ?? (() => this.defaultVariableNames());\n\t\tthis.normalizeForHighlighting = options?.normalizeForHighlighting ?? false;\n\t}\n\n\tprivate defaultVariableNames(): Iterable<string> {\n\t\tif (!this.engine) return [];\n\t\tconst snapshot = this.engine.getDag().getSnapshot();\n\t\tconst names = new Set<string>(Object.keys(snapshot.consumers));\n\t\tfor (const written of Object.values(snapshot.writes)) {\n\t\t\tfor (const name of written) names.add(name);\n\t\t}\n\t\treturn names;\n\t}\n\n\t/**\n\t * Lex a line, normalize it, and place every resulting token back in the\n\t * source text.\n\t *\n\t * ## Why placing them back is the hard part\n\t *\n\t * Normalization produces three kinds of token and only one of them can be\n\t * highlighted the obvious way.\n\t *\n\t * A token the normalizer left alone still describes its own text, so its\n\t * span is `offset` to `offset + text.length`, exactly as before.\n\t *\n\t * A FUSED token does not. `10 frames` becomes a FRAME_COUNT whose value is\n\t * `10`, and a timecode becomes a token whose value is a comma-separated\n\t * tuple appearing nowhere on the line. Those carry `sourceEnd`, set by\n\t * `createFusedToken`, which is the only place that knows where the fusion\n\t * ended.\n\t *\n\t * An INSERTED token has no text at all. Implicit multiplication puts a STAR\n\t * at the following token's offset, so `5(3)` gains a `*` sitting exactly\n\t * where the `(` is. Painting it would colour a character the reader never\n\t * typed as an operator, and would overlap the token that really is there.\n\t * These are dropped, detected by the one test that needs no cooperation\n\t * from any rule: a token with no recorded fusion span must match the text\n\t * at its own offset, and an inserted one does not.\n\t *\n\t * @param lineText - The raw line.\n\t * @returns Spans in the same shape `Lexer.getHighlightTokens` returns.\n\t */\n\tprivate classifyNormalized(\n\t\tlineText: string,\n\t): { type: string; value: string; offset: number; col: number; length: number; category: TokenCategory | undefined }[] {\n\t\tconst lexer = this.engine!.getLexer();\n\t\tconst raw = lexer.getHighlightTokenObjects(lineText);\n\t\tif (raw.length === 0) return [];\n\n\t\t// Offsets from a blockquote line are relative to the stripped text, so\n\t\t// the text this checks against has to be stripped the same way.\n\t\tconst source =\n\t\t\tlineText.startsWith(\"> \") && lexer.classifyLine(lineText).skip ? lineText.slice(2) : lineText;\n\n\t\tlet normalized: Token[];\n\t\ttry {\n\t\t\tnormalized = this.engine!.getNormalizer().normalize(raw);\n\t\t} catch {\n\t\t\t// Normalization is an enhancement here, not a requirement. A rule\n\t\t\t// that throws on a half-typed line should cost the reader phrase\n\t\t\t// colouring, not all colouring.\n\t\t\tnormalized = raw;\n\t\t}\n\n\t\tconst out: {\n\t\t\ttype: string;\n\t\t\tvalue: string;\n\t\t\toffset: number;\n\t\t\tcol: number;\n\t\t\tlength: number;\n\t\t\tcategory: TokenCategory | undefined;\n\t\t}[] = [];\n\n\t\tfor (const token of normalized) {\n\t\t\tlet end: number;\n\t\t\tif (token.sourceEnd !== undefined) {\n\t\t\t\tend = token.sourceEnd;\n\t\t\t} else if (source.startsWith(token.text, token.offset)) {\n\t\t\t\t// `text`, not `value`. They are the same string for almost every\n\t\t\t\t// token, and differ for a string literal, whose value is the\n\t\t\t\t// payload while its text is the quoted source slice. Matching on\n\t\t\t\t// value dropped every string literal from the painted line,\n\t\t\t\t// since `gbp` does not appear at the offset where `\"gbp\"` does.\n\t\t\t\tend = token.offset + token.text.length;\n\t\t\t} else {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tout.push({\n\t\t\t\ttype: token.type,\n\t\t\t\tvalue: token.value,\n\t\t\t\toffset: token.offset,\n\t\t\t\tcol: token.col,\n\t\t\t\tlength: end - token.offset,\n\t\t\t\tcategory: getTokenCategory(token.type),\n\t\t\t});\n\t\t}\n\n\t\treturn out;\n\t}\n\n\t/**\n\t * Classify every recognized token on one line.\n\t *\n\t * @param lineText - The raw line text (may be a markdown-structural line\n\t * the engine's classifier skips, that's handled by the underlying\n\t * lexer, which returns no tokens for those).\n\t * @param lineNumber - 1-based line number, used purely as a cache key.\n\t */\n\tgetSemanticTokens(lineText: string, lineNumber: number): SemanticToken[] {\n\t\tconst cached = this.cache.get(lineNumber);\n\t\tif (cached && cached.text === lineText) {\n\t\t\tif (cached.bareWordCandidate !== undefined) {\n\t\t\t\treturn this.isKnownVariable(cached.bareWordCandidate) ? cached.tokens : [];\n\t\t\t}\n\t\t\treturn cached.tokens;\n\t\t}\n\n\t\tif (!this.engine) {\n\t\t\t// No engine available (e.g. a consumer that hasn't wired one up yet)\n\t\t\t//, no highlighting, not an error.\n\t\t\treturn [];\n\t\t}\n\n\t\tconst lexer = this.engine.getLexer();\n\t\tconst lexed = this.normalizeForHighlighting\n\t\t\t? this.classifyNormalized(lineText)\n\t\t\t: lexer.getHighlightTokens(lineText);\n\t\tif (lexed.length === 0) {\n\t\t\tthis.putCache(lineNumber, lineText, []);\n\t\t\treturn [];\n\t\t}\n\n\t\tconst classification = lexer.classifyLine(lineText);\n\t\tconst tokens: SemanticToken[] = [];\n\n\t\tif (classification.hasInlineSolve) {\n\t\t\t// A line can mix markdown prose with one or more embedded\n\t\t\t// `s`...`` expressions. Only the text actually inside a\n\t\t\t// well-formed marker is a recognized expression, surrounding\n\t\t\t// prose lexes into individually-valid tokens too (see the class\n\t\t\t// doc comment) but is never something the engine would parse,\n\t\t\t// so it's excluded token-by-token via span membership rather\n\t\t\t// than gating the whole line pass/fail.\n\t\t\tconst validSpans = lexer\n\t\t\t\t.findInlineSolves(lineText)\n\t\t\t\t.filter(span => this.parsesAsExpression(span.expression))\n\t\t\t\t.map(span => ({ from: span.start, to: span.end }));\n\t\t\tfor (const token of lexed) {\n\t\t\t\tif (!token.category) continue;\n\t\t\t\tconst from = token.offset;\n\t\t\t\tconst to = token.offset + token.length;\n\t\t\t\tif (!validSpans.some(s => from >= s.from && to <= s.to)) continue;\n\t\t\t\ttokens.push({ from, to, category: token.category });\n\t\t\t}\n\t\t} else {\n\t\t\t// Blockquote content is stripped of its \"> \" prefix before being\n\t\t\t// tokenized (see Lexer.getHighlightTokens), token offsets are\n\t\t\t// already relative to the stripped text, so the parse check must\n\t\t\t// run against that same substring to match.\n\t\t\tconst text = lineText.startsWith(\"> \") && classification.skip\n\t\t\t\t? lineText.slice(2)\n\t\t\t\t: lineText;\n\t\t\tif (this.parsesAsExpression(text)) {\n\t\t\t\tfor (const token of lexed) {\n\t\t\t\t\tif (!token.category) continue;\n\t\t\t\t\ttokens.push({ from: token.offset, to: token.offset + token.length, category: token.category });\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// A lone bare word (\"hello\") is exactly as ambiguous as a run of\n\t\t// prose (\"My name is dave\"), it happens to parse as a\n\t\t// single-identifier variable-reference expression, but that's true\n\t\t// of literally any English word, so on its own it isn't \"recognized\"\n\t\t// in any meaningful sense. Sigil-marked variables (\":x\", \"$x\") are\n\t\t// unaffected, those lex to TWO tokens (sigil + ident), never\n\t\t// hitting this single-token check. Keywords (\"pi\") are unaffected\n\t\t// too, their category is \"keyword\", not \"variable\". Only surface\n\t\t// it once it's an actual known variable elsewhere in the document\n\t\t// checked live (see the `bareWordCandidate` cache field), not baked\n\t\t// into the cached result, since another line's edit can make this\n\t\t// check flip without this line's own text ever changing.\n\t\tif (tokens.length === 1 && tokens[0].category === \"variable\") {\n\t\t\tconst name = lineText.slice(tokens[0].from, tokens[0].to);\n\t\t\tthis.putCache(lineNumber, lineText, tokens, name);\n\t\t\treturn this.isKnownVariable(name) ? tokens : [];\n\t\t}\n\n\t\tthis.putCache(lineNumber, lineText, tokens);\n\t\treturn tokens;\n\t}\n\n\t/**\n\t * Completion candidates for the identifier prefix immediately before\n\t * `cursorOffset` on `lineText`. Deliberately simple prefix matching, not\n\t * parser-driven \"what's grammatically valid here\" prediction, a\n\t * half-typed expression almost never parses, so gating on parse\n\t * validity (the way `getSemanticTokens` does) would suppress\n\t * completions almost always. This is the safest, fastest option that\n\t * still delivers real value.\n\t *\n\t * Candidates come from three sources: keywords (which already include\n\t * function names. See `ExpressionLexer.getKeywords()`'s doc comment)\n\t * and units, both static per engine configuration and cached lazily;\n\t * package-contributed items (`IEnginePackage.completionItems`), same\n\t * cache; and variable names, read fresh from `variableNameSource()` on\n\t * every call since those change on every edit.\n\t */\n\tgetCompletions(lineText: string, cursorOffset: number): CompletionItem[] {\n\t\tconst prefixMatch = /[A-Za-z0-9_]+$/.exec(lineText.slice(0, cursorOffset));\n\t\tif (!prefixMatch) return [];\n\t\tconst prefix = prefixMatch[0].toLowerCase();\n\n\t\tif (!this.engine) return [];\n\n\t\tconst matches: CompletionItem[] = [];\n\n\t\t// Variables are read fresh every call and there are few of them, so they\n\t\t// stay a linear scan.\n\t\tfor (const name of this.variableNameSource()) {\n\t\t\tif (name.toLowerCase().startsWith(prefix)) {\n\t\t\t\tmatches.push({ label: name, category: \"variable\" });\n\t\t\t}\n\t\t}\n\n\t\t// Static candidates only ever match if they share the prefix's first\n\t\t// character, so consult that bucket alone.\n\t\tconst bucket = this.getStaticCompletionIndex().get(prefix[0]);\n\t\tif (bucket) {\n\t\t\tfor (const candidate of bucket) {\n\t\t\t\tif (candidate.lowerLabel.startsWith(prefix)) matches.push(candidate.item);\n\t\t\t}\n\t\t}\n\n\t\tmatches.sort((a, b) => {\n\t\t\tconst tierDiff = (CATEGORY_TIER[a.category] ?? 3) - (CATEGORY_TIER[b.category] ?? 3);\n\t\t\tif (tierDiff !== 0) return tierDiff;\n\t\t\treturn a.label.localeCompare(b.label);\n\t\t});\n\n\t\treturn matches.slice(0, MAX_COMPLETIONS);\n\t}\n\n\t/** Lazily builds and caches the keyword/unit/package-item candidate list. See `staticCompletionCandidates`. */\n\tprivate getStaticCompletionCandidates(): CompletionItem[] {\n\t\tif (this.staticCompletionCandidates) return this.staticCompletionCandidates;\n\n\t\tconst items: CompletionItem[] = [];\n\t\tfor (const [word, tokenType] of Object.entries(this.engine!.getLexer().getKeywords())) {\n\t\t\tconst category = getTokenCategory(tokenType);\n\t\t\tif (!category) continue;\n\t\t\titems.push({ label: word, category });\n\t\t}\n\t\tfor (const unit of knownUnits) {\n\t\t\titems.push({ label: unit, category: \"unit\", detail: getMeasure(unit) });\n\t\t}\n\t\titems.push(...this.engine!.getPackageCompletionItems());\n\n\t\tthis.staticCompletionCandidates = items;\n\t\treturn items;\n\t}\n\n\t/**\n\t * The static candidates bucketed by lowercased first character, built once\n\t * from {@link getStaticCompletionCandidates} and invalidated alongside it.\n\t */\n\tprivate getStaticCompletionIndex(): Map<string, IndexedCompletionCandidate[]> {\n\t\tif (this.staticCompletionIndex) return this.staticCompletionIndex;\n\n\t\tconst index = new Map<string, IndexedCompletionCandidate[]>();\n\t\tfor (const item of this.getStaticCompletionCandidates()) {\n\t\t\tconst lowerLabel = item.label.toLowerCase();\n\t\t\tconst firstCharacter = lowerLabel[0];\n\t\t\t// A label cannot match any prefix if it is empty, and the prefix\n\t\t\t// regex guarantees at least one character on the query side.\n\t\t\tif (firstCharacter === undefined) continue;\n\t\t\tlet bucket = index.get(firstCharacter);\n\t\t\tif (bucket === undefined) {\n\t\t\t\tbucket = [];\n\t\t\t\tindex.set(firstCharacter, bucket);\n\t\t\t}\n\t\t\tbucket.push({ item, lowerLabel });\n\t\t}\n\n\t\tthis.staticCompletionIndex = index;\n\t\treturn index;\n\t}\n\n\tprivate isKnownVariable(name: string): boolean {\n\t\tfor (const known of this.variableNameSource()) {\n\t\t\tif (known === name) return true;\n\t\t}\n\t\treturn false;\n\t}\n\n\t/**\n\t * Whether the engine's parser actually accepts a piece of text as a\n\t * well-formed expression, not merely whether it lexes into individually\n\t * recognized token types. See the class doc comment's prose example.\n\t * `tryCompileExpression` is compile-only (lex → normalize → parse →\n\t * cache bytecode, no VM execution, no network/async side effects) and\n\t * reuses the engine's existing bytecode cache, so text that's already\n\t * been evaluated (or previously highlight-checked) is a cache hit here\n\t * too.\n\t *\n\t * Deliberately calls the non-throwing `tryCompileExpression` rather than\n\t * try/catching `compileExpression`. This runs on every visible line on\n\t * every keystroke, and the common case for a real markdown document is\n\t * lines that DON'T parse (prose), not lines that do. Throwing there would\n\t * mean constructing a EngineError (with V8 stack-trace capture) for the\n\t * common case instead of the rare one.\n\t */\n\tprivate parsesAsExpression(text: string): boolean {\n\t\treturn this.engine!.tryCompileExpression(text);\n\t}\n\n\tprivate putCache(lineNumber: number, text: string, tokens: SemanticToken[], bareWordCandidate?: string): void {\n\t\tif (!this.cache.has(lineNumber) && this.cache.size >= MAX_CACHED_LINES) {\n\t\t\tconst oldestKey = this.cache.keys().next().value;\n\t\t\tif (oldestKey !== undefined) this.cache.delete(oldestKey);\n\t\t}\n\t\tthis.cache.set(lineNumber, { text, tokens, bareWordCandidate });\n\t}\n\n\t/**\n\t * Evict specific lines (e.g. the lines actually touched by a CodeMirror\n\t * change set) instead of the whole cache, the surgical counterpart to\n\t * {@link invalidateCache}, letting a single-line edit stay cheap even in\n\t * a large document: every other cached line is untouched and still hits\n\t * on the next call.\n\t */\n\tinvalidateLines(lineNumbers: Iterable<number>): void {\n\t\tfor (const lineNumber of lineNumbers) {\n\t\t\tthis.cache.delete(lineNumber);\n\t\t}\n\t}\n\n\t/**\n\t * Full cache clear. Reserved for cases with no meaningful \"which lines\n\t * changed\" (e.g. the document was swapped wholesale, or a package was\n\t * registered/unregistered mid-session, changing what categories exist).\n\t * Prefer {@link invalidateLines} for ordinary edits. Also rebuilds the\n\t * lazily-cached keyword/unit/package-item completion candidates on next\n\t * use, the only thing that can change that list mid-session.\n\t */\n\tinvalidateCache(): void {\n\t\tthis.cache.clear();\n\t\tthis.staticCompletionCandidates = null;\n\t\tthis.staticCompletionIndex = null;\n\t}\n}\n","import type { TokenCategory } from \"@solve-js/language/TokenCategory\";\n\n/**\n * Namespace applied to a semantic category when no other one is asked for.\n *\n * A prefix is needed because a bare category name like `number` or `error`\n * would collide with almost any host stylesheet. `solve-` is the narrowest\n * namespace that still says where the class came from, and it deliberately\n * names this library rather than any particular editor.\n */\nexport const DEFAULT_TOKEN_CLASS_PREFIX = \"solve-\";\n\n/**\n * Maps a semantic category to a predictable, stable CSS class name.\n *\n * Deliberately trivial: the category name IS the class-name key\n * (`\"number\"` → `solve-number`), so there is no separate table to keep in\n * sync as categories grow. A brand-new category, including one contributed\n * by a solve-js package at runtime, gets a matching class name with no\n * changes here.\n *\n * This is editor-agnostic on purpose. It produces a class-name string and\n * nothing else, so it works the same whether the host is CodeMirror\n * (`Decoration.mark({ class: tokenClassName(token.category) })`), Slate,\n * ProseMirror, Monaco or a plain `<span>`. Actual colors are pure CSS,\n * resolved from `--solve-hl-{category}` custom properties by each consumer.\n *\n * @param category - Semantic category from `getSemanticTokens()`.\n * @param prefix - Namespace to apply. Defaults to `solve-`.\n * @returns The class name for that category.\n */\nexport function tokenClassName(category: TokenCategory, prefix: string = DEFAULT_TOKEN_CLASS_PREFIX): string {\n\treturn `${prefix}${category}`;\n}\n\n/**\n * Builds a `tokenClassName` bound to a fixed prefix.\n *\n * The prefix is configurable because a host may already own a namespace it\n * wants these classes to sit inside: a CodeMirror theme that keys off `cm-`,\n * a design system that scopes everything under one token, or an app that\n * embeds two editors and wants to style them apart. Passing the prefix once\n * here is cheaper than threading it through every call site.\n *\n * ```ts\n * const className = createTokenClassName(\"cm-solve-\");\n * className(\"number\"); // \"cm-solve-number\"\n * ```\n *\n * @param prefix - Namespace to apply to every category.\n * @returns A function mapping a category to a prefixed class name.\n */\nexport function createTokenClassName(prefix: string): (category: TokenCategory) => string {\n\treturn (category: TokenCategory) => `${prefix}${category}`;\n}\n","import type { TokenCategory } from \"@solve-js/language/TokenCategory\";\nimport type { CompletionItem } from \"@solve-js/language/LanguageService\";\n\n/**\n * Maps a semantic category to one of `@codemirror/autocomplete`'s built-in\n * completion \"type\" strings (which drive its default gutter icon), the one\n * genuinely CodeMirror-specific thing this feature needs. Falls back to\n * \"text\" for anything unmapped, including plugin-contributed categories\n * (e.g. OSRS's \"osrs-item\"), a reasonable neutral default rather than a\n * hard failure for a category this adapter doesn't know about yet.\n *\n * No `@codemirror/autocomplete` import here, the returned object shape is\n * structurally compatible with CM6's `Completion` type by duck typing, so\n * `solve-js` gains no new dependency; the actual `CompletionSource`\n * function (reading `CompletionContext`, building a `CompletionResult`)\n * lives in each consumer (src/app, playground), same tier as\n * `buildDecorations()` already is for highlighting.\n */\nconst CATEGORY_TO_COMPLETION_TYPE: Partial<Record<TokenCategory, string>> = {\n\tkeyword: \"keyword\",\n\toperator: \"keyword\",\n\tcomparison: \"keyword\",\n\tbitwise: \"keyword\",\n\tfunction: \"function\",\n\tvariable: \"variable\",\n\tunit: \"type\",\n\tdatetime: \"keyword\",\n\tvector: \"type\",\n};\n\n/**\n * Convert a completion into CodeMirror's option shape.\n *\n * Kept in an adapter so the language service itself stays editor-agnostic.\n *\n * @param item - Completion produced by the language service.\n * @returns The equivalent CodeMirror option.\n */\nexport function completionItemToOption(item: CompletionItem): { label: string; type: string; detail?: string } {\n\treturn { label: item.label, type: CATEGORY_TO_COMPLETION_TYPE[item.category] ?? \"text\", detail: item.detail };\n}\n"]}
package/dist/lexer.cjs CHANGED
@@ -1,64 +1,2 @@
1
- 'use strict';
2
-
3
- var chunk7USV76HV_cjs = require('./chunk-7USV76HV.cjs');
4
- require('./chunk-7AXJNQMF.cjs');
5
- var chunkYJHY7CMN_cjs = require('./chunk-YJHY7CMN.cjs');
6
- var chunkG2YPPLGX_cjs = require('./chunk-G2YPPLGX.cjs');
7
- require('./chunk-OUHT5Z36.cjs');
8
- var chunkAQNHHJMJ_cjs = require('./chunk-AQNHHJMJ.cjs');
9
- require('./chunk-AMOJYLG2.cjs');
10
- require('./chunk-MIGXCBXT.cjs');
11
- require('./chunk-B7LXJ6LC.cjs');
12
-
13
-
14
-
15
- Object.defineProperty(exports, "Lexer", {
16
- enumerable: true,
17
- get: function () { return chunk7USV76HV_cjs.Lexer; }
18
- });
19
- Object.defineProperty(exports, "LexerState", {
20
- enumerable: true,
21
- get: function () { return chunk7USV76HV_cjs.LexerState; }
22
- });
23
- Object.defineProperty(exports, "buildTokenLookup", {
24
- enumerable: true,
25
- get: function () { return chunk7USV76HV_cjs.buildTokenLookup; }
26
- });
27
- Object.defineProperty(exports, "sharedLexer", {
28
- enumerable: true,
29
- get: function () { return chunk7USV76HV_cjs.sharedLexer; }
30
- });
31
- Object.defineProperty(exports, "ExpressionLexer", {
32
- enumerable: true,
33
- get: function () { return chunkYJHY7CMN_cjs.ExpressionLexer; }
34
- });
35
- Object.defineProperty(exports, "LexerToken", {
36
- enumerable: true,
37
- get: function () { return chunkYJHY7CMN_cjs.LexerToken; }
38
- });
39
- Object.defineProperty(exports, "TokenTypes", {
40
- enumerable: true,
41
- get: function () { return chunkG2YPPLGX_cjs.TokenTypes; }
42
- });
43
- Object.defineProperty(exports, "registerAllTokenTypes", {
44
- enumerable: true,
45
- get: function () { return chunkG2YPPLGX_cjs.registerAllTokenTypes; }
46
- });
47
- Object.defineProperty(exports, "registerTokenType", {
48
- enumerable: true,
49
- get: function () { return chunkG2YPPLGX_cjs.registerTokenType; }
50
- });
51
- Object.defineProperty(exports, "tokenTypeId", {
52
- enumerable: true,
53
- get: function () { return chunkG2YPPLGX_cjs.tokenTypeId; }
54
- });
55
- Object.defineProperty(exports, "tokenTypeName", {
56
- enumerable: true,
57
- get: function () { return chunkG2YPPLGX_cjs.tokenTypeName; }
58
- });
59
- Object.defineProperty(exports, "knownUnits", {
60
- enumerable: true,
61
- get: function () { return chunkAQNHHJMJ_cjs.knownUnits; }
62
- });
63
- //# sourceMappingURL=lexer.cjs.map
1
+ 'use strict';var chunk32GC2FHC_cjs=require('./chunk-32GC2FHC.cjs');require('./chunk-Y7O4D3BJ.cjs');var chunk2LFLR3VZ_cjs=require('./chunk-2LFLR3VZ.cjs'),chunk5IJD5L3O_cjs=require('./chunk-5IJD5L3O.cjs');require('./chunk-MSYX32PF.cjs');var chunkFWW3M36J_cjs=require('./chunk-FWW3M36J.cjs');require('./chunk-CIQOK7Q4.cjs'),require('./chunk-SOMGSIO4.cjs');Object.defineProperty(exports,"Lexer",{enumerable:true,get:function(){return chunk32GC2FHC_cjs.b}});Object.defineProperty(exports,"LexerState",{enumerable:true,get:function(){return chunk32GC2FHC_cjs.a}});Object.defineProperty(exports,"buildTokenLookup",{enumerable:true,get:function(){return chunk32GC2FHC_cjs.d}});Object.defineProperty(exports,"sharedLexer",{enumerable:true,get:function(){return chunk32GC2FHC_cjs.c}});Object.defineProperty(exports,"ExpressionLexer",{enumerable:true,get:function(){return chunk2LFLR3VZ_cjs.b}});Object.defineProperty(exports,"LexerToken",{enumerable:true,get:function(){return chunk2LFLR3VZ_cjs.a}});Object.defineProperty(exports,"TokenTypes",{enumerable:true,get:function(){return chunk5IJD5L3O_cjs.a}});Object.defineProperty(exports,"registerAllTokenTypes",{enumerable:true,get:function(){return chunk5IJD5L3O_cjs.e}});Object.defineProperty(exports,"registerTokenType",{enumerable:true,get:function(){return chunk5IJD5L3O_cjs.b}});Object.defineProperty(exports,"tokenTypeId",{enumerable:true,get:function(){return chunk5IJD5L3O_cjs.c}});Object.defineProperty(exports,"tokenTypeName",{enumerable:true,get:function(){return chunk5IJD5L3O_cjs.d}});Object.defineProperty(exports,"knownUnits",{enumerable:true,get:function(){return chunkFWW3M36J_cjs.a}});//# sourceMappingURL=lexer.cjs.map
64
2
  //# sourceMappingURL=lexer.cjs.map
package/dist/lexer.js CHANGED
@@ -1,11 +1,2 @@
1
- export { Lexer, LexerState, buildTokenLookup, sharedLexer } from './chunk-RNKDIXDV.js';
2
- import './chunk-LSALB3IN.js';
3
- export { ExpressionLexer, LexerToken } from './chunk-ORM7CHYZ.js';
4
- export { TokenTypes, registerAllTokenTypes, registerTokenType, tokenTypeId, tokenTypeName } from './chunk-NAWZATKT.js';
5
- import './chunk-XBTEO4OB.js';
6
- export { knownUnits } from './chunk-SBUFNJVE.js';
7
- import './chunk-V4XG5QF6.js';
8
- import './chunk-N7OA4WPJ.js';
9
- import './chunk-YLJ4XMA6.js';
10
- //# sourceMappingURL=lexer.js.map
1
+ export{b as Lexer,a as LexerState,d as buildTokenLookup,c as sharedLexer}from'./chunk-YAHGSBMC.js';import'./chunk-JPFCXLJV.js';export{b as ExpressionLexer,a as LexerToken}from'./chunk-G7TBEQRQ.js';export{a as TokenTypes,e as registerAllTokenTypes,b as registerTokenType,c as tokenTypeId,d as tokenTypeName}from'./chunk-IJMNVBIS.js';import'./chunk-62LMTO4F.js';export{a as knownUnits}from'./chunk-TRBXW6HW.js';import'./chunk-2JMFUQZH.js';import'./chunk-MPB7KUN5.js';//# sourceMappingURL=lexer.js.map
11
2
  //# sourceMappingURL=lexer.js.map