solve-engine 1.0.0-beta.0 → 1.0.0-beta.2

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 (253) hide show
  1. package/dist/BytecodeBuilder-3egMyuDi.d.cts +274 -0
  2. package/dist/BytecodeBuilder-3egMyuDi.d.ts +274 -0
  3. package/dist/Configuration-fVpd-HC-.d.cts +228 -0
  4. package/dist/Configuration-fVpd-HC-.d.ts +228 -0
  5. package/dist/EngineError-DvPSnhZi.d.cts +304 -0
  6. package/dist/EngineError-DvPSnhZi.d.ts +304 -0
  7. package/dist/Lexer-BJdhlnej.d.cts +531 -0
  8. package/dist/Lexer-D9l4Yrk2.d.ts +531 -0
  9. package/dist/NormalizerRule-BrVoVjmP.d.cts +163 -0
  10. package/dist/NormalizerRule-CEjf1FyD.d.ts +163 -0
  11. package/dist/PackageRegistry-D-Tv_7ca.d.cts +2572 -0
  12. package/dist/PackageRegistry-DaH4aIKP.d.ts +2572 -0
  13. package/dist/Parselet-CK8cQu2a.d.cts +299 -0
  14. package/dist/Parselet-ConOIdRO.d.ts +299 -0
  15. package/dist/ScopeManager-B5k0nk05.d.ts +559 -0
  16. package/dist/ScopeManager-CKfQtl7l.d.cts +559 -0
  17. package/dist/Token-0jpvUdPY.d.cts +245 -0
  18. package/dist/Token-0jpvUdPY.d.ts +245 -0
  19. package/dist/VMCheckpoints-BmDwIXgK.d.ts +171 -0
  20. package/dist/VMCheckpoints-CqFuLwrT.d.cts +171 -0
  21. package/dist/Value-KtAQNHur.d.cts +295 -0
  22. package/dist/Value-KtAQNHur.d.ts +295 -0
  23. package/dist/chunk-2RWAXT6O.js +3 -0
  24. package/dist/chunk-2RWAXT6O.js.map +1 -0
  25. package/dist/chunk-34RRD7PC.js +1492 -0
  26. package/dist/chunk-34RRD7PC.js.map +1 -0
  27. package/dist/chunk-3PPFLFH4.js +5097 -0
  28. package/dist/chunk-3PPFLFH4.js.map +1 -0
  29. package/dist/chunk-3VGDNEE3.js +84 -0
  30. package/dist/chunk-3VGDNEE3.js.map +1 -0
  31. package/dist/chunk-3YNVWKR2.cjs +370 -0
  32. package/dist/chunk-3YNVWKR2.cjs.map +1 -0
  33. package/dist/chunk-47LRVGOT.cjs +316 -0
  34. package/dist/chunk-47LRVGOT.cjs.map +1 -0
  35. package/dist/chunk-4MG4XKO2.js +1644 -0
  36. package/dist/chunk-4MG4XKO2.js.map +1 -0
  37. package/dist/chunk-4QADQTWS.js +2392 -0
  38. package/dist/chunk-4QADQTWS.js.map +1 -0
  39. package/dist/chunk-53B6KDDJ.cjs +1651 -0
  40. package/dist/chunk-53B6KDDJ.cjs.map +1 -0
  41. package/dist/chunk-5X2PTP6F.cjs +104 -0
  42. package/dist/chunk-5X2PTP6F.cjs.map +1 -0
  43. package/dist/chunk-5YEMOYSE.js +264 -0
  44. package/dist/chunk-5YEMOYSE.js.map +1 -0
  45. package/dist/chunk-64W6GLLZ.js +481 -0
  46. package/dist/chunk-64W6GLLZ.js.map +1 -0
  47. package/dist/chunk-6BKTCEUP.cjs +233 -0
  48. package/dist/chunk-6BKTCEUP.cjs.map +1 -0
  49. package/dist/chunk-6NTVRDQV.cjs +1642 -0
  50. package/dist/chunk-6NTVRDQV.cjs.map +1 -0
  51. package/dist/chunk-6RDSQURE.js +290 -0
  52. package/dist/chunk-6RDSQURE.js.map +1 -0
  53. package/dist/chunk-75JP4BZX.js +93 -0
  54. package/dist/chunk-75JP4BZX.js.map +1 -0
  55. package/dist/chunk-B7LXJ6LC.cjs +36 -0
  56. package/dist/chunk-B7LXJ6LC.cjs.map +1 -0
  57. package/dist/chunk-BVAR5J2F.js +257 -0
  58. package/dist/chunk-BVAR5J2F.js.map +1 -0
  59. package/dist/chunk-C4XZV6E7.cjs +269 -0
  60. package/dist/chunk-C4XZV6E7.cjs.map +1 -0
  61. package/dist/chunk-CCJPSHGI.cjs +40 -0
  62. package/dist/chunk-CCJPSHGI.cjs.map +1 -0
  63. package/dist/chunk-CLL7RUQV.cjs +316 -0
  64. package/dist/chunk-CLL7RUQV.cjs.map +1 -0
  65. package/dist/chunk-DM3LMRBC.js +329 -0
  66. package/dist/chunk-DM3LMRBC.js.map +1 -0
  67. package/dist/chunk-DUEZFTK2.js +38 -0
  68. package/dist/chunk-DUEZFTK2.js.map +1 -0
  69. package/dist/chunk-EBSPLUW4.cjs +489 -0
  70. package/dist/chunk-EBSPLUW4.cjs.map +1 -0
  71. package/dist/chunk-EHAHVROS.cjs +261 -0
  72. package/dist/chunk-EHAHVROS.cjs.map +1 -0
  73. package/dist/chunk-EIGTWK5N.js +167 -0
  74. package/dist/chunk-EIGTWK5N.js.map +1 -0
  75. package/dist/chunk-EPOXXJBK.js +96 -0
  76. package/dist/chunk-EPOXXJBK.js.map +1 -0
  77. package/dist/chunk-FEAIOR7N.cjs +91 -0
  78. package/dist/chunk-FEAIOR7N.cjs.map +1 -0
  79. package/dist/chunk-G535KJEG.js +312 -0
  80. package/dist/chunk-G535KJEG.js.map +1 -0
  81. package/dist/chunk-GCM5JHMX.js +88 -0
  82. package/dist/chunk-GCM5JHMX.js.map +1 -0
  83. package/dist/chunk-GOLDJNMZ.js +368 -0
  84. package/dist/chunk-GOLDJNMZ.js.map +1 -0
  85. package/dist/chunk-GW32KPCU.cjs +360 -0
  86. package/dist/chunk-GW32KPCU.cjs.map +1 -0
  87. package/dist/chunk-HTXVVJRA.cjs +1549 -0
  88. package/dist/chunk-HTXVVJRA.cjs.map +1 -0
  89. package/dist/chunk-HWSZJQCI.js +311 -0
  90. package/dist/chunk-HWSZJQCI.js.map +1 -0
  91. package/dist/chunk-IF532O7C.js +101 -0
  92. package/dist/chunk-IF532O7C.js.map +1 -0
  93. package/dist/chunk-J73SJHR3.cjs +2415 -0
  94. package/dist/chunk-J73SJHR3.cjs.map +1 -0
  95. package/dist/chunk-JBSYC7BB.cjs +7473 -0
  96. package/dist/chunk-JBSYC7BB.cjs.map +1 -0
  97. package/dist/chunk-KVILKGMS.js +228 -0
  98. package/dist/chunk-KVILKGMS.js.map +1 -0
  99. package/dist/chunk-LIPPNDBE.js +258 -0
  100. package/dist/chunk-LIPPNDBE.js.map +1 -0
  101. package/dist/chunk-LR7YASZF.cjs +172 -0
  102. package/dist/chunk-LR7YASZF.cjs.map +1 -0
  103. package/dist/chunk-M4N5GA5Z.cjs +309 -0
  104. package/dist/chunk-M4N5GA5Z.cjs.map +1 -0
  105. package/dist/chunk-M5LX5AOO.js +7459 -0
  106. package/dist/chunk-M5LX5AOO.js.map +1 -0
  107. package/dist/chunk-MZOVP3WI.cjs +96 -0
  108. package/dist/chunk-MZOVP3WI.cjs.map +1 -0
  109. package/dist/chunk-N5YFR4UP.cjs +10 -0
  110. package/dist/chunk-N5YFR4UP.cjs.map +1 -0
  111. package/dist/chunk-NGBBNL4K.cjs +4 -0
  112. package/dist/chunk-NGBBNL4K.cjs.map +1 -0
  113. package/dist/chunk-NH2O2AUR.js +146 -0
  114. package/dist/chunk-NH2O2AUR.js.map +1 -0
  115. package/dist/chunk-NLNDMWZC.js +31 -0
  116. package/dist/chunk-NLNDMWZC.js.map +1 -0
  117. package/dist/chunk-NMCRQP3Z.cjs +778 -0
  118. package/dist/chunk-NMCRQP3Z.cjs.map +1 -0
  119. package/dist/chunk-NMD5VRN4.cjs +1327 -0
  120. package/dist/chunk-NMD5VRN4.cjs.map +1 -0
  121. package/dist/chunk-OC6LTXYS.cjs +286 -0
  122. package/dist/chunk-OC6LTXYS.cjs.map +1 -0
  123. package/dist/chunk-OT6OJY7C.cjs +114 -0
  124. package/dist/chunk-OT6OJY7C.cjs.map +1 -0
  125. package/dist/chunk-PFSDGGYL.js +8 -0
  126. package/dist/chunk-PFSDGGYL.js.map +1 -0
  127. package/dist/chunk-QNJ4ACRT.cjs +155 -0
  128. package/dist/chunk-QNJ4ACRT.cjs.map +1 -0
  129. package/dist/chunk-QWY3VEZN.js +1637 -0
  130. package/dist/chunk-QWY3VEZN.js.map +1 -0
  131. package/dist/chunk-RFYD5TJE.js +111 -0
  132. package/dist/chunk-RFYD5TJE.js.map +1 -0
  133. package/dist/chunk-RIN643A3.js +354 -0
  134. package/dist/chunk-RIN643A3.js.map +1 -0
  135. package/dist/chunk-RZCWSXTA.cjs +5122 -0
  136. package/dist/chunk-RZCWSXTA.cjs.map +1 -0
  137. package/dist/chunk-SDGRK7EP.js +774 -0
  138. package/dist/chunk-SDGRK7EP.js.map +1 -0
  139. package/dist/chunk-VEKIWQ3F.cjs +39 -0
  140. package/dist/chunk-VEKIWQ3F.cjs.map +1 -0
  141. package/dist/chunk-WQTTOGXC.cjs +99 -0
  142. package/dist/chunk-WQTTOGXC.cjs.map +1 -0
  143. package/dist/chunk-X6K3HUOY.cjs +86 -0
  144. package/dist/chunk-X6K3HUOY.cjs.map +1 -0
  145. package/dist/chunk-XJCMXB2E.cjs +333 -0
  146. package/dist/chunk-XJCMXB2E.cjs.map +1 -0
  147. package/dist/chunk-XVWCOTR6.js +1324 -0
  148. package/dist/chunk-XVWCOTR6.js.map +1 -0
  149. package/dist/chunk-YLJ4XMA6.js +33 -0
  150. package/dist/chunk-YLJ4XMA6.js.map +1 -0
  151. package/dist/chunk-Z2XP767T.cjs +35 -0
  152. package/dist/chunk-Z2XP767T.cjs.map +1 -0
  153. package/dist/chunk-Z4X2RWOF.js +37 -0
  154. package/dist/chunk-Z4X2RWOF.js.map +1 -0
  155. package/dist/constants.cjs +22 -0
  156. package/dist/constants.cjs.map +1 -0
  157. package/dist/constants.d.cts +12 -0
  158. package/dist/constants.d.ts +12 -0
  159. package/dist/constants.js +5 -0
  160. package/dist/constants.js.map +1 -0
  161. package/dist/engine.cjs +79 -0
  162. package/dist/engine.cjs.map +1 -0
  163. package/dist/engine.d.cts +587 -0
  164. package/dist/engine.d.ts +587 -0
  165. package/dist/engine.js +34 -0
  166. package/dist/engine.js.map +1 -0
  167. package/dist/errors.cjs +81 -0
  168. package/dist/errors.cjs.map +1 -0
  169. package/dist/errors.d.cts +95 -0
  170. package/dist/errors.d.ts +95 -0
  171. package/dist/errors.js +4 -0
  172. package/dist/errors.js.map +1 -0
  173. package/dist/format.cjs +171 -0
  174. package/dist/format.cjs.map +1 -0
  175. package/dist/format.d.cts +51 -0
  176. package/dist/format.d.ts +51 -0
  177. package/dist/format.js +168 -0
  178. package/dist/format.js.map +1 -0
  179. package/dist/index.cjs +97 -0
  180. package/dist/index.cjs.map +1 -0
  181. package/dist/index.d.cts +152 -0
  182. package/dist/index.d.ts +152 -0
  183. package/dist/index.js +75 -0
  184. package/dist/index.js.map +1 -0
  185. package/dist/language.cjs +308 -0
  186. package/dist/language.cjs.map +1 -0
  187. package/dist/language.d.cts +110 -0
  188. package/dist/language.d.ts +110 -0
  189. package/dist/language.js +287 -0
  190. package/dist/language.js.map +1 -0
  191. package/dist/lexer.cjs +64 -0
  192. package/dist/lexer.cjs.map +1 -0
  193. package/dist/lexer.d.cts +56 -0
  194. package/dist/lexer.d.ts +56 -0
  195. package/dist/lexer.js +11 -0
  196. package/dist/lexer.js.map +1 -0
  197. package/dist/normalizer.cjs +40 -0
  198. package/dist/normalizer.cjs.map +1 -0
  199. package/dist/normalizer.d.cts +364 -0
  200. package/dist/normalizer.d.ts +364 -0
  201. package/dist/normalizer.js +11 -0
  202. package/dist/normalizer.js.map +1 -0
  203. package/dist/packages.cjs +121 -0
  204. package/dist/packages.cjs.map +1 -0
  205. package/dist/packages.d.cts +691 -0
  206. package/dist/packages.d.ts +691 -0
  207. package/dist/packages.js +24 -0
  208. package/dist/packages.js.map +1 -0
  209. package/dist/parser.cjs +55 -0
  210. package/dist/parser.cjs.map +1 -0
  211. package/dist/parser.d.cts +46 -0
  212. package/dist/parser.d.ts +46 -0
  213. package/dist/parser.js +10 -0
  214. package/dist/parser.js.map +1 -0
  215. package/dist/pipeline-CrsRPmsv.d.ts +285 -0
  216. package/dist/pipeline-DPUQt7yL.d.cts +285 -0
  217. package/dist/resolvers.cjs +13 -0
  218. package/dist/resolvers.cjs.map +1 -0
  219. package/dist/resolvers.d.cts +111 -0
  220. package/dist/resolvers.d.ts +111 -0
  221. package/dist/resolvers.js +4 -0
  222. package/dist/resolvers.js.map +1 -0
  223. package/dist/services.cjs +21 -0
  224. package/dist/services.cjs.map +1 -0
  225. package/dist/services.d.cts +30 -0
  226. package/dist/services.d.ts +30 -0
  227. package/dist/services.js +4 -0
  228. package/dist/services.js.map +1 -0
  229. package/dist/uom.cjs +56 -0
  230. package/dist/uom.cjs.map +1 -0
  231. package/dist/uom.d.cts +224 -0
  232. package/dist/uom.d.ts +224 -0
  233. package/dist/uom.js +11 -0
  234. package/dist/uom.js.map +1 -0
  235. package/dist/utilities.cjs +32 -0
  236. package/dist/utilities.cjs.map +1 -0
  237. package/dist/utilities.d.cts +113 -0
  238. package/dist/utilities.d.ts +113 -0
  239. package/dist/utilities.js +7 -0
  240. package/dist/utilities.js.map +1 -0
  241. package/dist/variables.cjs +17 -0
  242. package/dist/variables.cjs.map +1 -0
  243. package/dist/variables.d.cts +87 -0
  244. package/dist/variables.d.ts +87 -0
  245. package/dist/variables.js +4 -0
  246. package/dist/variables.js.map +1 -0
  247. package/dist/vm.cjs +284 -0
  248. package/dist/vm.cjs.map +1 -0
  249. package/dist/vm.d.cts +68 -0
  250. package/dist/vm.d.ts +68 -0
  251. package/dist/vm.js +194 -0
  252. package/dist/vm.js.map +1 -0
  253. package/package.json +4 -4
package/dist/index.js ADDED
@@ -0,0 +1,75 @@
1
+ import { assertEngineVersionCompatible } from './chunk-M5LX5AOO.js';
2
+ export { ExpressionEngine, assertEngineVersionCompatible, checkEngineVersionCompatibility, checkPackageCompatibility } from './chunk-M5LX5AOO.js';
3
+ import { sharedParseletRegistry } from './chunk-SDGRK7EP.js';
4
+ import './chunk-2RWAXT6O.js';
5
+ import './chunk-3VGDNEE3.js';
6
+ import './chunk-IF532O7C.js';
7
+ import './chunk-QWY3VEZN.js';
8
+ import './chunk-G535KJEG.js';
9
+ import './chunk-3PPFLFH4.js';
10
+ import './chunk-5YEMOYSE.js';
11
+ import './chunk-64W6GLLZ.js';
12
+ import './chunk-PFSDGGYL.js';
13
+ import './chunk-EPOXXJBK.js';
14
+ import './chunk-NLNDMWZC.js';
15
+ import './chunk-4QADQTWS.js';
16
+ import { sharedVariableResolver } from './chunk-GCM5JHMX.js';
17
+ import './chunk-DM3LMRBC.js';
18
+ import './chunk-75JP4BZX.js';
19
+ import './chunk-Z4X2RWOF.js';
20
+ import './chunk-RFYD5TJE.js';
21
+ import './chunk-34RRD7PC.js';
22
+ import { Value } from './chunk-BVAR5J2F.js';
23
+ import './chunk-NH2O2AUR.js';
24
+ export { ENGINE_VERSION } from './chunk-EIGTWK5N.js';
25
+ import { sharedLexer } from './chunk-HWSZJQCI.js';
26
+ import './chunk-KVILKGMS.js';
27
+ import './chunk-XVWCOTR6.js';
28
+ import './chunk-RIN643A3.js';
29
+ import './chunk-GOLDJNMZ.js';
30
+ import './chunk-LIPPNDBE.js';
31
+ import './chunk-4MG4XKO2.js';
32
+ import './chunk-6RDSQURE.js';
33
+ import './chunk-YLJ4XMA6.js';
34
+
35
+ // src/api/PackageRegistry.ts
36
+ var PackageRegistry = class {
37
+ constructor() {
38
+ this.Value = Value;
39
+ }
40
+ registerPrefixParselet(tokenType, parselet) {
41
+ sharedParseletRegistry.registerPrefix(tokenType, parselet);
42
+ }
43
+ registerInfixParselet(tokenType, parselet) {
44
+ sharedParseletRegistry.registerInfix(tokenType, parselet);
45
+ }
46
+ registerVariableSource(source) {
47
+ sharedVariableResolver.registerSource(source);
48
+ }
49
+ registerPackage(pkg) {
50
+ assertEngineVersionCompatible(pkg);
51
+ if (pkg.lexerVocabulary) {
52
+ sharedLexer.registerVocabulary(pkg.lexerVocabulary);
53
+ }
54
+ if (pkg.prefixParselets) {
55
+ for (const pp of pkg.prefixParselets) {
56
+ this.registerPrefixParselet(pp.tokenType, pp.parselet);
57
+ }
58
+ }
59
+ if (pkg.infixParselets) {
60
+ for (const ip of pkg.infixParselets) {
61
+ this.registerInfixParselet(ip.tokenType, ip.parselet);
62
+ }
63
+ }
64
+ if (pkg.variableSources) {
65
+ for (const vs of pkg.variableSources) {
66
+ this.registerVariableSource(vs);
67
+ }
68
+ }
69
+ }
70
+ };
71
+ var packageRegistry = new PackageRegistry();
72
+
73
+ export { PackageRegistry, packageRegistry };
74
+ //# sourceMappingURL=index.js.map
75
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/api/PackageRegistry.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA+PO,IAAM,kBAAN,MAAkD;AAAA,EAAlD,WAAA,GAAA;AACL,IAAA,IAAA,CAAA,KAAA,GAAQ,KAAA;AAAA,EAAA;AAAA,EAER,sBAAA,CAAuB,WAAmB,QAAA,EAAgC;AACxE,IAAA,sBAAA,CAAuB,cAAA,CAAe,WAAW,QAAQ,CAAA;AAAA,EAC3D;AAAA,EAEA,qBAAA,CAAsB,WAAmB,QAAA,EAA+B;AACtE,IAAA,sBAAA,CAAuB,aAAA,CAAc,WAAW,QAAQ,CAAA;AAAA,EAC1D;AAAA,EAEA,uBAAuB,MAAA,EAA+B;AACpD,IAAA,sBAAA,CAAuB,eAAe,MAAM,CAAA;AAAA,EAC9C;AAAA,EAEA,gBAAgB,GAAA,EAA2B;AAMzC,IAAA,6BAAA,CAA8B,GAAG,CAAA;AAEjC,IAAA,IAAI,IAAI,eAAA,EAAiB;AACvB,MAAA,WAAA,CAAY,kBAAA,CAAmB,IAAI,eAAe,CAAA;AAAA,IACpD;AACA,IAAA,IAAI,IAAI,eAAA,EAAiB;AACvB,MAAA,KAAA,MAAW,EAAA,IAAM,IAAI,eAAA,EAAiB;AACpC,QAAA,IAAA,CAAK,sBAAA,CAAuB,EAAA,CAAG,SAAA,EAAW,EAAA,CAAG,QAAQ,CAAA;AAAA,MACvD;AAAA,IACF;AACA,IAAA,IAAI,IAAI,cAAA,EAAgB;AACtB,MAAA,KAAA,MAAW,EAAA,IAAM,IAAI,cAAA,EAAgB;AACnC,QAAA,IAAA,CAAK,qBAAA,CAAsB,EAAA,CAAG,SAAA,EAAW,EAAA,CAAG,QAAQ,CAAA;AAAA,MACtD;AAAA,IACF;AACA,IAAA,IAAI,IAAI,eAAA,EAAiB;AACvB,MAAA,KAAA,MAAW,EAAA,IAAM,IAAI,eAAA,EAAiB;AACpC,QAAA,IAAA,CAAK,uBAAuB,EAAE,CAAA;AAAA,MAChC;AAAA,IACF;AAAA,EAIF;AACF;AAQO,IAAM,eAAA,GAAkB,IAAI,eAAA","file":"index.js","sourcesContent":["import { sharedParseletRegistry } from \"@solve-js/parser/registry/ParseletRegistry\";\nimport { PrefixParselet, InfixParselet } from \"@solve-js/parser/Parselet\";\nimport { Value } from \"@solve-js/vm/Value\";\nimport { IVariableSource } from \"@solve-js/variables/IVariableSource\";\nimport { sharedVariableResolver } from \"@solve-js/variables/VariableResolver\";\nimport { sharedLexer } from \"@solve-js/lexer/Lexer\";\nimport type { LexerVocabulary } from \"@solve-js/lexer/ExpressionLexer\";\nimport type { IAsyncResolver } from \"@solve-js/resolvers/ResolverRegistry\";\nimport type { NormalizerRule } from \"@solve-js/normalizer/NormalizerRule\";\nimport type { TokenCategory } from \"@solve-js/language/TokenCategory\";\nimport type { CompletionItem } from \"@solve-js/language/LanguageService\";\nimport type { LineExecutionContext } from \"@solve-js/vm/VM\";\nimport { assertEngineVersionCompatible } from \"./EngineVersionCompatibility\";\n\n/**\n * Public API for registering plugins with the solve-js engine.\n *\n * All registration goes through this interface, parselets, variable\n * sources, and full packages. The default implementation is\n * {@link PackageRegistry} (singleton via {@link packageRegistry}).\n *\n * @example\n * ```typescript\n * import { packageRegistry } from \"solve-engine\";\n * packageRegistry.registerPackage(myCustomPackage);\n * ```\n */\nexport interface IPackageRegistry {\n /** Register a prefix parselet (e.g., `GE`, `NOW`, `floor`). */\n registerPrefixParselet(tokenType: string, parselet: PrefixParselet): void;\n /** Register an infix parselet (e.g., `+`, `in`, `to`). */\n registerInfixParselet(tokenType: string, parselet: InfixParselet): void;\n /** @deprecated Has no effect. See {@link IEnginePackage.variableSources}. */\n registerVariableSource(source: IVariableSource): void;\n /** Register a complete package (parselets + variable sources). */\n registerPackage(pkg: IEnginePackage): void;\n /** Convenience reference to the Value class for creating typed values. */\n Value: typeof Value;\n}\n\n/**\n * Package descriptor for registering a complete provider with the engine.\n *\n * A package bundles all the pieces needed for a domain-specific provider:\n * lexer plugins for custom token recognition, parselets for Pratt parsing,\n * plugin functions dispatched via CALL_PLUGIN bytecode, variable sources,\n * and optional async resolvers for data that loads asynchronously (e.g.,\n * exchange rates, game prices).\n *\n * @example\n * ```typescript\n * const myPackage: IEnginePackage = {\n * name: \"MyProvider\",\n * lexerVocabulary: myLexerVocabulary,\n * prefixParselets: [{ tokenType: \"MY_FUNC\", parselet: new MyParselet() }],\n * pluginFunctions: [{ index: MY_FN_IDX, handler: myHandler }],\n * asyncResolvers: [myAsyncResolver],\n * };\n * packageRegistry.registerPackage(myPackage);\n * ```\n */\nexport interface IEnginePackage {\n /** Human-readable name for debugging and error attribution. */\n name: string;\n /**\n * Semver range of `solve-engine` versions this package is compatible with\n * (e.g. `\"^0.1.0\"`, `\">=0.1.0 <0.3.0\"`), checked against the engine's own\n * running version ({@link ENGINE_VERSION}, `@solve-js/constants/version`)\n * via `checkEngineVersionCompatibility()`/`assertEngineVersionCompatible()`\n * (`@solve-js/api/EngineVersionCompatibility`) at registration time.\n *\n * Optional, omitted means \"no declared constraint,\" so every package\n * that predates this field (all built-ins, `examples/osrs`) keeps\n * registering exactly as before.\n *\n * Unlike every other compatibility signal in this codebase (e.g.\n * `checkPackageCompatibility()`'s sibling-package collision warnings,\n * which always log and proceed. See `api/PackageCompatibility.ts`), a\n * declared `engineVersion` range the running engine does NOT satisfy is\n * a deliberate, hard REJECTION: `registerPackage()` throws rather than\n * warning. See `ARCHITECTURE.md` §5.3.\n */\n engineVersion?: string;\n /** Optional lexer vocabulary (keywords/operators/units) for recognizing custom tokens (e.g., `GE`, `£`). */\n lexerVocabulary?: LexerVocabulary;\n /** Prefix parselets for this package's custom functions/operators. */\n prefixParselets?: Array<{ tokenType: string; parselet: PrefixParselet }>;\n /** Infix parselets for this package's custom binary operators. */\n infixParselets?: Array<{ tokenType: string; parselet: InfixParselet }>;\n /**\n * Functions dispatched via CALL_PLUGIN bytecode (emitted by this package's\n * parselets with `builder.emitIndex(index)`).\n *\n * Each entry's `index` MUST come from {@link allocatePluginFunctionIndex}\n * (`@solve-js/vm/VMBuiltins`), never hardcode a number. Two packages\n * independently picking the same index would silently overwrite each\n * other's handler in the shared registry.\n *\n * The handler's optional second parameter, `context`, carries the\n * current line's {@link LineExecutionContext} (line number, and, only\n * inside a real document, never `evaluateExpression()`'s single-shot\n * path, closures for reading another line's cached result). Every\n * handler that doesn't need cross-line data can ignore it entirely.\n *\n * @example\n * ```ts\n * const MY_FN_IDX = allocatePluginFunctionIndex();\n * // In a parselet's parse(): builder.emitOpcode(OpCode.CALL_PLUGIN); builder.emitIndex(MY_FN_IDX); builder.emitIndex(argCount);\n * pluginFunctions: [{ index: MY_FN_IDX, handler: myHandler }]\n * ```\n */\n pluginFunctions?: Array<{ index: number; handler: (args: Value[], context?: LineExecutionContext) => Value | Promise<Value> }>;\n /**\n * Named-variable sources.\n *\n * @deprecated Currently has no effect. Sources declared here are registered\n * into the engine's {@link EngineContext} and unregistered again on package\n * removal, but no evaluation path ever calls `VariableResolver.resolve()`, so\n * a variable a source provides is never found. Verified by searching every\n * use of `IVariableSource` outside its own declaration: they are all\n * registration bookkeeping.\n *\n * Declared here rather than deleted because removing a public field is a\n * breaking change and the intended behaviour is worth keeping. Documented as\n * dead so a package author does not spend an afternoon working out why their\n * variables resolve to nothing. To expose a value today, contribute a plugin\n * function through {@link IEnginePackage.pluginFunctions}.\n */\n variableSources?: IVariableSource[];\n /**\n * Async resolvers for this package's domain.\n * When set, the ExpressionEngine runs preflight() before VM execution\n * for each resolver. If async data is needed, a Pending result is\n * returned immediately and the line re-evaluates when the data resolves.\n *\n * Each resolver must have a unique `namespace`, the ResolverRegistry\n * is keyed by namespace. Multiple resolvers let a package handle\n * distinct async operations (e.g., `fetch`, `wait`, `poll`) in\n * separate, focused classes rather than one monolithic preflight().\n */\n asyncResolvers?: IAsyncResolver[];\n /**\n * Multi-word phrases to fuse into single compound tokens.\n * Each key is a space-separated phrase (e.g., \"to the power of\"),\n * each value is the target token type after fusion (e.g., \"CARET\").\n *\n * Registered into the engine's {@link PhraseTrie} for single-pass\n * O(depth) matching, no separate rule scanning per phrase.\n *\n * @example\n * ```ts\n * phrases: {\n * \"to the power of\": \"CARET\",\n * \"abyssal whip\": \"ITEM\",\n * }\n * ```\n */\n phrases?: Record<string, string>;\n /**\n * Normalizer rules for post-lexer token transformation.\n * Applied by the TokenNormalizer between lexing and parsing.\n * For phrase fusion, prefer the declarative {@link phrases} field\n * which uses the faster PhraseTrie. Use this for non-phrase rules\n * like implicit operator insertion.\n */\n normalizerRules?: NormalizerRule[];\n /**\n * Semantic highlight categories for this package's custom token types\n * (introduced via {@link lexerVocabulary} or {@link normalizerRules}), the\n * plugin-facing half of solve-js's editor-agnostic language service (see\n * `language/TokenCategoryMap.ts`). Without an entry here, a package's\n * custom tokens (e.g. a game-item name fused from several identifiers)\n * are still lexed and parsed correctly, but render with no highlight\n * category in any editor integration.\n *\n * @example\n * ```ts\n * tokenCategories: { MY_KEYWORD: \"keyword\", MY_ITEM: \"my-plugin-item\" }\n * ```\n */\n tokenCategories?: Record<string, TokenCategory>;\n /**\n * Completion candidates for this package, the plugin-facing half of\n * solve-js's editor-agnostic completions API\n * (`LanguageService.getCompletions()`). A package's single-word\n * keywords (via {@link lexerVocabulary}) already flow into completions\n * automatically; this field is for candidates that AREN'T lexer\n * keywords, such as a vocabulary of item/entity names. A plain,\n * pre-built list, not a callback, completion candidate lists are\n * meant to be cheap and static within one engine configuration.\n *\n * @example\n * ```ts\n * completionItems: [{ label: \"Abyssal whip\", category: \"my-plugin-item\", detail: \"Item\" }]\n * ```\n */\n completionItems?: CompletionItem[];\n /**\n * Custom `as <name>` converters, the extension point for the\n * Converters package's general `<expr> as <type>` grammar (e.g.\n * `50% as decimal`, `255 as hex`). The built-in converter names\n * (`percent`, `decimal`, `hex`, `fraction`, `multiplier`, `sci`,\n * `binary`, `octal`, ...) dispatch to dedicated fast opcodes; anything\n * else, including any name a third-party package registers here\n * resolves through `OpCode.CALL_AS_CONVERTER` against\n * `vm/VMBuiltins.ts`'s `asConverterRegistry` at runtime. No lexer\n * keyword registration is needed for a custom name: the AS parselet\n * accepts any bare-word token after \"as\" and reads its raw text.\n *\n * Each handler is a pure, synchronous `(value: Value) => Value`, for\n * async conversions (e.g. a live currency-style lookup), use\n * {@link asyncResolvers} instead.\n *\n * @example\n * ```ts\n * asConverters: { roman: (v) => stringValue(toRomanNumeral(v.toNumber())) }\n * ```\n */\n asConverters?: Record<string, (value: Value) => Value>;\n}\n\n/**\n * Default implementation of {@link IPackageRegistry}, the plugin registration API.\n *\n * All registrations delegate to shared singletons (parselet registry,\n * variable resolver, lexer). This ensures that packages registered through\n * any PackageRegistry instance are visible engine-wide.\n *\n * @example\n * ```typescript\n * import { packageRegistry } from \"solve-engine\";\n *\n * // Register a complete provider package\n * packageRegistry.registerPackage({\n * name: \"MyProvider\",\n * prefixParselets: [{ tokenType: \"MY_FUNC\", parselet: new MyParselet() }],\n * });\n * ```\n *\n * @deprecated Register on an engine instead:\n * `engine.registerPackage(pkg)`.\n *\n * This class writes into process-wide singletons, which is incompatible with an\n * engine owning its own registries. Since the introduction of\n * {@link EngineContext}, an engine reads plugin functions, opcode handlers and\n * variable sources from its own context, so a package registered here is not\n * visible to any engine. Parselets and lexer vocabulary registered here reach\n * the shared registries, which an engine also does not read: it builds its own\n * `ParseletRegistry` and its own `Lexer`.\n *\n * In other words this path registers into state nothing evaluates against. It\n * remains exported because removing it is a breaking change, and it is where\n * the singletons that survive are still written from, but it should not be used\n * in new code and will be removed before 1.0 proper.\n */\nexport class PackageRegistry implements IPackageRegistry {\n Value = Value;\n\n registerPrefixParselet(tokenType: string, parselet: PrefixParselet): void {\n sharedParseletRegistry.registerPrefix(tokenType, parselet);\n }\n\n registerInfixParselet(tokenType: string, parselet: InfixParselet): void {\n sharedParseletRegistry.registerInfix(tokenType, parselet);\n }\n\n registerVariableSource(source: IVariableSource): void {\n sharedVariableResolver.registerSource(source);\n }\n\n registerPackage(pkg: IEnginePackage): void {\n // Same hard engine-version gate ExpressionEngine.registerPackage() uses\n // (see its own comment and ARCHITECTURE.md §5.3). This weaker\n // shared-singleton path had no compatibility checking of any kind\n // before this, so without this call the version gate would be\n // trivially bypassable through this entry point.\n assertEngineVersionCompatible(pkg);\n\n if (pkg.lexerVocabulary) {\n sharedLexer.registerVocabulary(pkg.lexerVocabulary);\n }\n if (pkg.prefixParselets) {\n for (const pp of pkg.prefixParselets) {\n this.registerPrefixParselet(pp.tokenType, pp.parselet);\n }\n }\n if (pkg.infixParselets) {\n for (const ip of pkg.infixParselets) {\n this.registerInfixParselet(ip.tokenType, ip.parselet);\n }\n }\n if (pkg.variableSources) {\n for (const vs of pkg.variableSources) {\n this.registerVariableSource(vs);\n }\n }\n // Note: asyncResolvers are NOT registered here, the shared PackageRegistry singleton\n // doesn't have a ResolverRegistry (that lives inside ExpressionEngine).\n // Use ExpressionEngine.registerPackage() directly if you need async resolvers.\n }\n}\n\n/**\n * Singleton PackageRegistry instance, the default plugin registration API.\n *\n * All packages should register through this instance. The underlying registries\n * are shared singletons, so multiple PackageRegistry instances would be redundant.\n */\nexport const packageRegistry = new PackageRegistry();\n"]}
@@ -0,0 +1,308 @@
1
+ 'use strict';
2
+
3
+ var chunkQNJ4ACRT_cjs = require('./chunk-QNJ4ACRT.cjs');
4
+ var chunk6BKTCEUP_cjs = require('./chunk-6BKTCEUP.cjs');
5
+ var chunkEHAHVROS_cjs = require('./chunk-EHAHVROS.cjs');
6
+ require('./chunk-53B6KDDJ.cjs');
7
+ require('./chunk-B7LXJ6LC.cjs');
8
+
9
+ // src/language/LanguageService.ts
10
+ var MAX_COMPLETIONS = 50;
11
+ var CATEGORY_TIER = {
12
+ variable: 0,
13
+ function: 1,
14
+ keyword: 1,
15
+ operator: 1,
16
+ comparison: 1,
17
+ bitwise: 1,
18
+ datetime: 1,
19
+ vector: 1,
20
+ unit: 2
21
+ };
22
+ var MAX_CACHED_LINES = 2e3;
23
+ var LanguageService = class {
24
+ constructor(engine, options) {
25
+ // Bounded cache keyed by line number ALONE, not `${lineNumber}:${lineText}`
26
+ // as an earlier version of this class did. A line's previous text state is
27
+ // never useful once it changes, so keying on text too was pure waste:
28
+ // every keystroke on a line minted a brand-new, never-reclaimed cache
29
+ // entry (an effective per-keystroke memory leak over a long editing
30
+ // session). Keying on line number alone makes "same line, new text" a
31
+ // cheap overwrite instead.
32
+ //
33
+ // Eviction is oldest-inserted (Map iteration order) when at capacity
34
+ // mirroring the same bounded-cache pattern ExpressionEngine's own
35
+ // bytecodeCache already uses elsewhere in this codebase. Deliberately
36
+ // NOT an LFU (least-frequently-used) policy: LFU would keep resisting
37
+ // eviction of old, once-popular lines while punishing a line that just
38
+ // scrolled into view (frequency 1), the opposite of what a "currently
39
+ // visible" cache should prioritize.
40
+ this.cache = /* @__PURE__ */ new Map();
41
+ // Keyword/unit/package-contributed completion candidates don't depend
42
+ // on any particular line, built lazily on first getCompletions() call
43
+ // and reused after that, since a package registration is the only thing
44
+ // that could ever change this list mid-session (see invalidateCache()).
45
+ // Variable-name candidates are NOT part of this, they're read fresh on
46
+ // every call from variableNameSource(), since those genuinely change on
47
+ // every edit.
48
+ this.staticCompletionCandidates = null;
49
+ // The same candidates, bucketed by their lowercased first character with the
50
+ // lowercased label precomputed. getCompletions() runs on every keystroke and
51
+ // only ever wants candidates sharing the prefix's first character, so
52
+ // scanning the whole list and lowercasing each label per call was doing two
53
+ // avoidable things: touching entries that could not possibly match, and
54
+ // allocating a string per candidate per keystroke. That was affordable when
55
+ // the vocabulary was a few hundred entries. Deriving the unit list from the
56
+ // conversion tables took it past a thousand, and the warm completion
57
+ // benchmarks regressed roughly 2.9x until this was added.
58
+ this.staticCompletionIndex = null;
59
+ this.engine = engine ?? null;
60
+ this.variableNameSource = options?.variableNameSource ?? (() => this.defaultVariableNames());
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
+ * Classify every recognized token on one line.
73
+ *
74
+ * @param lineText - The raw line text (may be a markdown-structural line
75
+ * the engine's classifier skips, that's handled by the underlying
76
+ * lexer, which returns no tokens for those).
77
+ * @param lineNumber - 1-based line number, used purely as a cache key.
78
+ */
79
+ getSemanticTokens(lineText, lineNumber) {
80
+ const cached = this.cache.get(lineNumber);
81
+ if (cached && cached.text === lineText) {
82
+ if (cached.bareWordCandidate !== void 0) {
83
+ return this.isKnownVariable(cached.bareWordCandidate) ? cached.tokens : [];
84
+ }
85
+ return cached.tokens;
86
+ }
87
+ if (!this.engine) {
88
+ return [];
89
+ }
90
+ const lexer = this.engine.getLexer();
91
+ const lexed = lexer.getHighlightTokens(lineText);
92
+ if (lexed.length === 0) {
93
+ this.putCache(lineNumber, lineText, []);
94
+ return [];
95
+ }
96
+ const classification = lexer.classifyLine(lineText);
97
+ const tokens = [];
98
+ if (classification.hasInlineSolve) {
99
+ const validSpans = lexer.findInlineSolves(lineText).filter((span) => this.parsesAsExpression(span.expression)).map((span) => ({ from: span.start, to: span.end }));
100
+ for (const token of lexed) {
101
+ if (!token.category) continue;
102
+ const from = token.offset;
103
+ const to = token.offset + token.length;
104
+ if (!validSpans.some((s) => from >= s.from && to <= s.to)) continue;
105
+ tokens.push({ from, to, category: token.category });
106
+ }
107
+ } else {
108
+ const text = lineText.startsWith("> ") && classification.skip ? lineText.slice(2) : lineText;
109
+ if (this.parsesAsExpression(text)) {
110
+ for (const token of lexed) {
111
+ if (!token.category) continue;
112
+ tokens.push({ from: token.offset, to: token.offset + token.length, category: token.category });
113
+ }
114
+ }
115
+ }
116
+ if (tokens.length === 1 && tokens[0].category === "variable") {
117
+ const name = lineText.slice(tokens[0].from, tokens[0].to);
118
+ this.putCache(lineNumber, lineText, tokens, name);
119
+ return this.isKnownVariable(name) ? tokens : [];
120
+ }
121
+ this.putCache(lineNumber, lineText, tokens);
122
+ return tokens;
123
+ }
124
+ /**
125
+ * Completion candidates for the identifier prefix immediately before
126
+ * `cursorOffset` on `lineText`. Deliberately simple prefix matching, not
127
+ * parser-driven "what's grammatically valid here" prediction, a
128
+ * half-typed expression almost never parses, so gating on parse
129
+ * validity (the way `getSemanticTokens` does) would suppress
130
+ * completions almost always. This is the safest, fastest option that
131
+ * still delivers real value.
132
+ *
133
+ * Candidates come from three sources: keywords (which already include
134
+ * function names. See `ExpressionLexer.getKeywords()`'s doc comment)
135
+ * and units, both static per engine configuration and cached lazily;
136
+ * package-contributed items (`IEnginePackage.completionItems`), same
137
+ * cache; and variable names, read fresh from `variableNameSource()` on
138
+ * every call since those change on every edit.
139
+ */
140
+ getCompletions(lineText, cursorOffset) {
141
+ const prefixMatch = /[A-Za-z0-9_]+$/.exec(lineText.slice(0, cursorOffset));
142
+ if (!prefixMatch) return [];
143
+ const prefix = prefixMatch[0].toLowerCase();
144
+ if (!this.engine) return [];
145
+ const matches = [];
146
+ for (const name of this.variableNameSource()) {
147
+ if (name.toLowerCase().startsWith(prefix)) {
148
+ matches.push({ label: name, category: "variable" });
149
+ }
150
+ }
151
+ const bucket = this.getStaticCompletionIndex().get(prefix[0]);
152
+ if (bucket) {
153
+ for (const candidate of bucket) {
154
+ if (candidate.lowerLabel.startsWith(prefix)) matches.push(candidate.item);
155
+ }
156
+ }
157
+ matches.sort((a, b) => {
158
+ const tierDiff = (CATEGORY_TIER[a.category] ?? 3) - (CATEGORY_TIER[b.category] ?? 3);
159
+ if (tierDiff !== 0) return tierDiff;
160
+ return a.label.localeCompare(b.label);
161
+ });
162
+ return matches.slice(0, MAX_COMPLETIONS);
163
+ }
164
+ /** Lazily builds and caches the keyword/unit/package-item candidate list. See `staticCompletionCandidates`. */
165
+ getStaticCompletionCandidates() {
166
+ if (this.staticCompletionCandidates) return this.staticCompletionCandidates;
167
+ const items = [];
168
+ for (const [word, tokenType] of Object.entries(this.engine.getLexer().getKeywords())) {
169
+ const category = chunk6BKTCEUP_cjs.getTokenCategory(tokenType);
170
+ if (!category) continue;
171
+ items.push({ label: word, category });
172
+ }
173
+ for (const unit of chunkEHAHVROS_cjs.knownUnits) {
174
+ items.push({ label: unit, category: "unit", detail: chunkQNJ4ACRT_cjs.getMeasure(unit) });
175
+ }
176
+ items.push(...this.engine.getPackageCompletionItems());
177
+ this.staticCompletionCandidates = items;
178
+ return items;
179
+ }
180
+ /**
181
+ * The static candidates bucketed by lowercased first character, built once
182
+ * from {@link getStaticCompletionCandidates} and invalidated alongside it.
183
+ */
184
+ getStaticCompletionIndex() {
185
+ if (this.staticCompletionIndex) return this.staticCompletionIndex;
186
+ const index = /* @__PURE__ */ new Map();
187
+ for (const item of this.getStaticCompletionCandidates()) {
188
+ const lowerLabel = item.label.toLowerCase();
189
+ const firstCharacter = lowerLabel[0];
190
+ if (firstCharacter === void 0) continue;
191
+ let bucket = index.get(firstCharacter);
192
+ if (bucket === void 0) {
193
+ bucket = [];
194
+ index.set(firstCharacter, bucket);
195
+ }
196
+ bucket.push({ item, lowerLabel });
197
+ }
198
+ this.staticCompletionIndex = index;
199
+ return index;
200
+ }
201
+ isKnownVariable(name) {
202
+ for (const known of this.variableNameSource()) {
203
+ if (known === name) return true;
204
+ }
205
+ return false;
206
+ }
207
+ /**
208
+ * Whether the engine's parser actually accepts a piece of text as a
209
+ * well-formed expression, not merely whether it lexes into individually
210
+ * recognized token types. See the class doc comment's prose example.
211
+ * `tryCompileExpression` is compile-only (lex → normalize → parse →
212
+ * cache bytecode, no VM execution, no network/async side effects) and
213
+ * reuses the engine's existing bytecode cache, so text that's already
214
+ * been evaluated (or previously highlight-checked) is a cache hit here
215
+ * too.
216
+ *
217
+ * Deliberately calls the non-throwing `tryCompileExpression` rather than
218
+ * try/catching `compileExpression`. This runs on every visible line on
219
+ * every keystroke, and the common case for a real markdown document is
220
+ * lines that DON'T parse (prose), not lines that do. Throwing there would
221
+ * mean constructing a EngineError (with V8 stack-trace capture) for the
222
+ * common case instead of the rare one.
223
+ */
224
+ parsesAsExpression(text) {
225
+ return this.engine.tryCompileExpression(text);
226
+ }
227
+ putCache(lineNumber, text, tokens, bareWordCandidate) {
228
+ if (!this.cache.has(lineNumber) && this.cache.size >= MAX_CACHED_LINES) {
229
+ const oldestKey = this.cache.keys().next().value;
230
+ if (oldestKey !== void 0) this.cache.delete(oldestKey);
231
+ }
232
+ this.cache.set(lineNumber, { text, tokens, bareWordCandidate });
233
+ }
234
+ /**
235
+ * Evict specific lines (e.g. the lines actually touched by a CodeMirror
236
+ * change set) instead of the whole cache, the surgical counterpart to
237
+ * {@link invalidateCache}, letting a single-line edit stay cheap even in
238
+ * a large document: every other cached line is untouched and still hits
239
+ * on the next call.
240
+ */
241
+ invalidateLines(lineNumbers) {
242
+ for (const lineNumber of lineNumbers) {
243
+ this.cache.delete(lineNumber);
244
+ }
245
+ }
246
+ /**
247
+ * Full cache clear. Reserved for cases with no meaningful "which lines
248
+ * changed" (e.g. the document was swapped wholesale, or a package was
249
+ * registered/unregistered mid-session, changing what categories exist).
250
+ * Prefer {@link invalidateLines} for ordinary edits. Also rebuilds the
251
+ * lazily-cached keyword/unit/package-item completion candidates on next
252
+ * use, the only thing that can change that list mid-session.
253
+ */
254
+ invalidateCache() {
255
+ this.cache.clear();
256
+ this.staticCompletionCandidates = null;
257
+ this.staticCompletionIndex = null;
258
+ }
259
+ };
260
+
261
+ // src/language/tokenClassName.ts
262
+ var DEFAULT_TOKEN_CLASS_PREFIX = "solve-";
263
+ function tokenClassName(category, prefix = DEFAULT_TOKEN_CLASS_PREFIX) {
264
+ return `${prefix}${category}`;
265
+ }
266
+ function createTokenClassName(prefix) {
267
+ return (category) => `${prefix}${category}`;
268
+ }
269
+
270
+ // src/language/adapters/codemirror.ts
271
+ var CATEGORY_TO_COMPLETION_TYPE = {
272
+ keyword: "keyword",
273
+ operator: "keyword",
274
+ comparison: "keyword",
275
+ bitwise: "keyword",
276
+ function: "function",
277
+ variable: "variable",
278
+ unit: "type",
279
+ datetime: "keyword",
280
+ vector: "type"
281
+ };
282
+ function completionItemToOption(item) {
283
+ return { label: item.label, type: CATEGORY_TO_COMPLETION_TYPE[item.category] ?? "text", detail: item.detail };
284
+ }
285
+
286
+ Object.defineProperty(exports, "UNCATEGORIZED_TOKEN_TYPES", {
287
+ enumerable: true,
288
+ get: function () { return chunk6BKTCEUP_cjs.UNCATEGORIZED_TOKEN_TYPES; }
289
+ });
290
+ Object.defineProperty(exports, "getTokenCategory", {
291
+ enumerable: true,
292
+ get: function () { return chunk6BKTCEUP_cjs.getTokenCategory; }
293
+ });
294
+ Object.defineProperty(exports, "registerTokenCategory", {
295
+ enumerable: true,
296
+ get: function () { return chunk6BKTCEUP_cjs.registerTokenCategory; }
297
+ });
298
+ Object.defineProperty(exports, "unregisterTokenCategory", {
299
+ enumerable: true,
300
+ get: function () { return chunk6BKTCEUP_cjs.unregisterTokenCategory; }
301
+ });
302
+ exports.DEFAULT_TOKEN_CLASS_PREFIX = DEFAULT_TOKEN_CLASS_PREFIX;
303
+ exports.LanguageService = LanguageService;
304
+ exports.completionItemToOption = completionItemToOption;
305
+ exports.createTokenClassName = createTokenClassName;
306
+ exports.tokenClassName = tokenClassName;
307
+ //# sourceMappingURL=language.cjs.map
308
+ //# sourceMappingURL=language.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/language/LanguageService.ts","../src/language/tokenClassName.ts","../src/language/adapters/codemirror.ts"],"names":["getTokenCategory","knownUnits","getMeasure"],"mappings":"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","file":"language.cjs","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 { 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\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\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}\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 * 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 = 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"]}
@@ -0,0 +1,110 @@
1
+ import { n as CompletionItem } from './PackageRegistry-D-Tv_7ca.cjs';
2
+ export { o as LanguageService, q as LanguageServiceOptions, S as SemanticToken } from './PackageRegistry-D-Tv_7ca.cjs';
3
+ import { e as TokenCategory } from './Lexer-BJdhlnej.cjs';
4
+ import './Parselet-CK8cQu2a.cjs';
5
+ import './BytecodeBuilder-3egMyuDi.cjs';
6
+ import './Token-0jpvUdPY.cjs';
7
+ import './pipeline-DPUQt7yL.cjs';
8
+ import './Value-KtAQNHur.cjs';
9
+ import './variables.cjs';
10
+ import './resolvers.cjs';
11
+ import '@tanstack/query-core';
12
+ import './NormalizerRule-BrVoVjmP.cjs';
13
+ import './ScopeManager-CKfQtl7l.cjs';
14
+ import './EngineError-DvPSnhZi.cjs';
15
+ import './Configuration-fVpd-HC-.cjs';
16
+
17
+ /**
18
+ * Token types intentionally left uncategorized, either purely structural
19
+ * (never meant to be visibly styled) or already filtered out upstream
20
+ * before reaching a category lookup. Used only by the completeness test to
21
+ * distinguish "deliberately unstyled" from "forgotten."
22
+ */
23
+ declare const UNCATEGORIZED_TOKEN_TYPES: ReadonlySet<string>;
24
+ /**
25
+ * Map a token type to a highlighting category.
26
+ *
27
+ * @param tokenType - Token type a package registered.
28
+ * @param category - Category deciding how an editor colours it.
29
+ */
30
+ declare function registerTokenCategory(tokenType: string, category: TokenCategory): void;
31
+ /**
32
+ * Remove a token type's highlighting category.
33
+ *
34
+ * @param tokenType - Token type to forget. Unknown types are ignored.
35
+ */
36
+ declare function unregisterTokenCategory(tokenType: string): void;
37
+ /**
38
+ * Resolve a token type to its semantic category, checking plugin-contributed
39
+ * categories first (so a plugin could theoretically re-categorize a builtin
40
+ * token type, though in practice plugins only ever add categories for their
41
+ * own new types).
42
+ *
43
+ * @returns The category, or `undefined` if the token type has no category
44
+ * the signal to a UI adapter that this token should render unstyled.
45
+ */
46
+ declare function getTokenCategory(tokenType: string): TokenCategory | undefined;
47
+
48
+ /**
49
+ * Namespace applied to a semantic category when no other one is asked for.
50
+ *
51
+ * A prefix is needed because a bare category name like `number` or `error`
52
+ * would collide with almost any host stylesheet. `solve-` is the narrowest
53
+ * namespace that still says where the class came from, and it deliberately
54
+ * names this library rather than any particular editor.
55
+ */
56
+ declare const DEFAULT_TOKEN_CLASS_PREFIX = "solve-";
57
+ /**
58
+ * Maps a semantic category to a predictable, stable CSS class name.
59
+ *
60
+ * Deliberately trivial: the category name IS the class-name key
61
+ * (`"number"` → `solve-number`), so there is no separate table to keep in
62
+ * sync as categories grow. A brand-new category, including one contributed
63
+ * by a solve-js package at runtime, gets a matching class name with no
64
+ * changes here.
65
+ *
66
+ * This is editor-agnostic on purpose. It produces a class-name string and
67
+ * nothing else, so it works the same whether the host is CodeMirror
68
+ * (`Decoration.mark({ class: tokenClassName(token.category) })`), Slate,
69
+ * ProseMirror, Monaco or a plain `<span>`. Actual colors are pure CSS,
70
+ * resolved from `--solve-hl-{category}` custom properties by each consumer.
71
+ *
72
+ * @param category - Semantic category from `getSemanticTokens()`.
73
+ * @param prefix - Namespace to apply. Defaults to `solve-`.
74
+ * @returns The class name for that category.
75
+ */
76
+ declare function tokenClassName(category: TokenCategory, prefix?: string): string;
77
+ /**
78
+ * Builds a `tokenClassName` bound to a fixed prefix.
79
+ *
80
+ * The prefix is configurable because a host may already own a namespace it
81
+ * wants these classes to sit inside: a CodeMirror theme that keys off `cm-`,
82
+ * a design system that scopes everything under one token, or an app that
83
+ * embeds two editors and wants to style them apart. Passing the prefix once
84
+ * here is cheaper than threading it through every call site.
85
+ *
86
+ * ```ts
87
+ * const className = createTokenClassName("cm-solve-");
88
+ * className("number"); // "cm-solve-number"
89
+ * ```
90
+ *
91
+ * @param prefix - Namespace to apply to every category.
92
+ * @returns A function mapping a category to a prefixed class name.
93
+ */
94
+ declare function createTokenClassName(prefix: string): (category: TokenCategory) => string;
95
+
96
+ /**
97
+ * Convert a completion into CodeMirror's option shape.
98
+ *
99
+ * Kept in an adapter so the language service itself stays editor-agnostic.
100
+ *
101
+ * @param item - Completion produced by the language service.
102
+ * @returns The equivalent CodeMirror option.
103
+ */
104
+ declare function completionItemToOption(item: CompletionItem): {
105
+ label: string;
106
+ type: string;
107
+ detail?: string;
108
+ };
109
+
110
+ export { CompletionItem, DEFAULT_TOKEN_CLASS_PREFIX, TokenCategory, UNCATEGORIZED_TOKEN_TYPES, completionItemToOption, createTokenClassName, getTokenCategory, registerTokenCategory, tokenClassName, unregisterTokenCategory };