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

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 (171) hide show
  1. package/dist/{Lexer-D9l4Yrk2.d.ts → Lexer-Cfm79Dak.d.cts} +18 -2
  2. package/dist/{Lexer-BJdhlnej.d.cts → Lexer-W9MBOP0V.d.ts} +18 -2
  3. package/dist/{PackageRegistry-DaH4aIKP.d.ts → PackageRegistry-CjDt-Jy_.d.ts} +60 -4
  4. package/dist/{PackageRegistry-D-Tv_7ca.d.cts → PackageRegistry-pHtKythi.d.cts} +60 -4
  5. package/dist/{Parselet-CK8cQu2a.d.cts → Parselet-Cu0bLSis.d.cts} +1 -1
  6. package/dist/{Parselet-ConOIdRO.d.ts → Parselet-DEdF9I7n.d.ts} +1 -1
  7. package/dist/{Token-0jpvUdPY.d.cts → Token-BzG5G4ja.d.cts} +44 -0
  8. package/dist/{Token-0jpvUdPY.d.ts → Token-BzG5G4ja.d.ts} +44 -0
  9. package/dist/TokenNormalizer-DGVa24Q-.d.cts +377 -0
  10. package/dist/TokenNormalizer-t_GotBxr.d.ts +377 -0
  11. package/dist/{chunk-HWSZJQCI.js → chunk-2CS6OMZK.js} +37 -15
  12. package/dist/chunk-2CS6OMZK.js.map +1 -0
  13. package/dist/{chunk-XJCMXB2E.cjs → chunk-2MV4HBKC.cjs} +187 -61
  14. package/dist/chunk-2MV4HBKC.cjs.map +1 -0
  15. package/dist/{chunk-J73SJHR3.cjs → chunk-2NPS5DQ3.cjs} +558 -341
  16. package/dist/chunk-2NPS5DQ3.cjs.map +1 -0
  17. package/dist/{chunk-QWY3VEZN.js → chunk-3MNKQZ77.js} +65 -12
  18. package/dist/chunk-3MNKQZ77.js.map +1 -0
  19. package/dist/{chunk-RZCWSXTA.cjs → chunk-43G3JT2E.cjs} +1491 -218
  20. package/dist/chunk-43G3JT2E.cjs.map +1 -0
  21. package/dist/{chunk-WQTTOGXC.cjs → chunk-4AVD7NZW.cjs} +4 -4
  22. package/dist/{chunk-WQTTOGXC.cjs.map → chunk-4AVD7NZW.cjs.map} +1 -1
  23. package/dist/{chunk-HTXVVJRA.cjs → chunk-4CVLFLOB.cjs} +112 -2
  24. package/dist/chunk-4CVLFLOB.cjs.map +1 -0
  25. package/dist/{chunk-5YEMOYSE.js → chunk-6GCKCWLB.js} +9 -3
  26. package/dist/chunk-6GCKCWLB.js.map +1 -0
  27. package/dist/{chunk-EBSPLUW4.cjs → chunk-AHCLWAM5.cjs} +28 -10
  28. package/dist/chunk-AHCLWAM5.cjs.map +1 -0
  29. package/dist/{chunk-KVILKGMS.js → chunk-AJA6LUI7.js} +36 -2
  30. package/dist/chunk-AJA6LUI7.js.map +1 -0
  31. package/dist/{chunk-EHAHVROS.cjs → chunk-CLVQBF5C.cjs} +5 -5
  32. package/dist/{chunk-EHAHVROS.cjs.map → chunk-CLVQBF5C.cjs.map} +1 -1
  33. package/dist/{chunk-34RRD7PC.js → chunk-CYFK5SY2.js} +111 -3
  34. package/dist/chunk-CYFK5SY2.js.map +1 -0
  35. package/dist/{chunk-M5LX5AOO.js → chunk-DDQVZGLO.js} +25 -12
  36. package/dist/chunk-DDQVZGLO.js.map +1 -0
  37. package/dist/{chunk-LR7YASZF.cjs → chunk-EAAHVJ4P.cjs} +3 -3
  38. package/dist/chunk-EAAHVJ4P.cjs.map +1 -0
  39. package/dist/{chunk-3PPFLFH4.js → chunk-FIFQPKBA.js} +1417 -144
  40. package/dist/chunk-FIFQPKBA.js.map +1 -0
  41. package/dist/{chunk-RIN643A3.js → chunk-GPPLSM2Z.js} +61 -2
  42. package/dist/chunk-GPPLSM2Z.js.map +1 -0
  43. package/dist/{chunk-4QADQTWS.js → chunk-KHJUSHFU.js} +231 -14
  44. package/dist/chunk-KHJUSHFU.js.map +1 -0
  45. package/dist/{chunk-NMD5VRN4.cjs → chunk-M4F66R4O.cjs} +74 -73
  46. package/dist/chunk-M4F66R4O.cjs.map +1 -0
  47. package/dist/{chunk-6NTVRDQV.cjs → chunk-M4Q47KHF.cjs} +126 -73
  48. package/dist/chunk-M4Q47KHF.cjs.map +1 -0
  49. package/dist/{chunk-LIPPNDBE.js → chunk-M5E34VG5.js} +3 -3
  50. package/dist/{chunk-LIPPNDBE.js.map → chunk-M5E34VG5.js.map} +1 -1
  51. package/dist/{chunk-53B6KDDJ.cjs → chunk-MBNQVDVC.cjs} +38 -3
  52. package/dist/{chunk-53B6KDDJ.cjs.map → chunk-MBNQVDVC.cjs.map} +1 -1
  53. package/dist/{chunk-SDGRK7EP.js → chunk-MEOHSQEH.js} +6 -5
  54. package/dist/chunk-MEOHSQEH.js.map +1 -0
  55. package/dist/{chunk-NMCRQP3Z.cjs → chunk-MTX2KVU7.cjs} +71 -70
  56. package/dist/chunk-MTX2KVU7.cjs.map +1 -0
  57. package/dist/{chunk-QNJ4ACRT.cjs → chunk-NJTXJ5AG.cjs} +37 -15
  58. package/dist/chunk-NJTXJ5AG.cjs.map +1 -0
  59. package/dist/{chunk-C4XZV6E7.cjs → chunk-NZFKROS7.cjs} +26 -20
  60. package/dist/chunk-NZFKROS7.cjs.map +1 -0
  61. package/dist/{chunk-64W6GLLZ.js → chunk-OFXOTECC.js} +24 -6
  62. package/dist/chunk-OFXOTECC.js.map +1 -0
  63. package/dist/{chunk-3YNVWKR2.cjs → chunk-OUHT5Z36.cjs} +28 -5
  64. package/dist/chunk-OUHT5Z36.cjs.map +1 -0
  65. package/dist/{chunk-EIGTWK5N.js → chunk-OWRHDUJD.js} +3 -3
  66. package/dist/chunk-OWRHDUJD.js.map +1 -0
  67. package/dist/{chunk-4MG4XKO2.js → chunk-PCPX42KL.js} +38 -3
  68. package/dist/{chunk-4MG4XKO2.js.map → chunk-PCPX42KL.js.map} +1 -1
  69. package/dist/{chunk-GW32KPCU.cjs → chunk-R24DI24X.cjs} +61 -2
  70. package/dist/chunk-R24DI24X.cjs.map +1 -0
  71. package/dist/{chunk-XVWCOTR6.js → chunk-SFQWJMKT.js} +7 -6
  72. package/dist/chunk-SFQWJMKT.js.map +1 -0
  73. package/dist/{chunk-6BKTCEUP.cjs → chunk-TY3TLZAW.cjs} +36 -2
  74. package/dist/chunk-TY3TLZAW.cjs.map +1 -0
  75. package/dist/{chunk-DM3LMRBC.js → chunk-UKPGSAZW.js} +187 -61
  76. package/dist/chunk-UKPGSAZW.js.map +1 -0
  77. package/dist/{chunk-NH2O2AUR.js → chunk-WC5FFSHB.js} +26 -4
  78. package/dist/chunk-WC5FFSHB.js.map +1 -0
  79. package/dist/{chunk-CLL7RUQV.cjs → chunk-WWOLFXTX.cjs} +40 -18
  80. package/dist/chunk-WWOLFXTX.cjs.map +1 -0
  81. package/dist/{chunk-JBSYC7BB.cjs → chunk-X2BWAZAU.cjs} +66 -53
  82. package/dist/chunk-X2BWAZAU.cjs.map +1 -0
  83. package/dist/{chunk-GOLDJNMZ.js → chunk-XBTEO4OB.js} +28 -5
  84. package/dist/chunk-XBTEO4OB.js.map +1 -0
  85. package/dist/{chunk-EPOXXJBK.js → chunk-YU2CUNFO.js} +3 -3
  86. package/dist/{chunk-EPOXXJBK.js.map → chunk-YU2CUNFO.js.map} +1 -1
  87. package/dist/constants.cjs +4 -4
  88. package/dist/constants.js +1 -1
  89. package/dist/engine.cjs +29 -30
  90. package/dist/engine.d.cts +6 -6
  91. package/dist/engine.d.ts +6 -6
  92. package/dist/engine.js +19 -20
  93. package/dist/format.cjs +7 -8
  94. package/dist/format.cjs.map +1 -1
  95. package/dist/format.js +2 -3
  96. package/dist/format.js.map +1 -1
  97. package/dist/index.cjs +28 -29
  98. package/dist/index.cjs.map +1 -1
  99. package/dist/index.d.cts +6 -6
  100. package/dist/index.d.ts +6 -6
  101. package/dist/index.js +20 -21
  102. package/dist/index.js.map +1 -1
  103. package/dist/language.cjs +74 -12
  104. package/dist/language.cjs.map +1 -1
  105. package/dist/language.d.cts +6 -6
  106. package/dist/language.d.ts +6 -6
  107. package/dist/language.js +68 -6
  108. package/dist/language.js.map +1 -1
  109. package/dist/lexer.cjs +19 -19
  110. package/dist/lexer.d.cts +3 -3
  111. package/dist/lexer.d.ts +3 -3
  112. package/dist/lexer.js +7 -7
  113. package/dist/normalizer.cjs +12 -12
  114. package/dist/normalizer.d.cts +4 -210
  115. package/dist/normalizer.d.ts +4 -210
  116. package/dist/normalizer.js +6 -6
  117. package/dist/packages.cjs +36 -37
  118. package/dist/packages.d.cts +5 -47
  119. package/dist/packages.d.ts +5 -47
  120. package/dist/packages.js +13 -14
  121. package/dist/parser.cjs +12 -12
  122. package/dist/parser.d.cts +3 -2
  123. package/dist/parser.d.ts +3 -2
  124. package/dist/parser.js +4 -4
  125. package/dist/resolvers.d.cts +1 -1
  126. package/dist/resolvers.d.ts +1 -1
  127. package/dist/uom.cjs +14 -14
  128. package/dist/uom.d.cts +12 -4
  129. package/dist/uom.d.ts +12 -4
  130. package/dist/uom.js +4 -4
  131. package/dist/vm.cjs +17 -17
  132. package/dist/vm.js +6 -6
  133. package/package.json +2 -1
  134. package/dist/NormalizerRule-BrVoVjmP.d.cts +0 -163
  135. package/dist/NormalizerRule-CEjf1FyD.d.ts +0 -163
  136. package/dist/chunk-34RRD7PC.js.map +0 -1
  137. package/dist/chunk-3PPFLFH4.js.map +0 -1
  138. package/dist/chunk-3YNVWKR2.cjs.map +0 -1
  139. package/dist/chunk-4QADQTWS.js.map +0 -1
  140. package/dist/chunk-5YEMOYSE.js.map +0 -1
  141. package/dist/chunk-64W6GLLZ.js.map +0 -1
  142. package/dist/chunk-6BKTCEUP.cjs.map +0 -1
  143. package/dist/chunk-6NTVRDQV.cjs.map +0 -1
  144. package/dist/chunk-C4XZV6E7.cjs.map +0 -1
  145. package/dist/chunk-CLL7RUQV.cjs.map +0 -1
  146. package/dist/chunk-DM3LMRBC.js.map +0 -1
  147. package/dist/chunk-EBSPLUW4.cjs.map +0 -1
  148. package/dist/chunk-EIGTWK5N.js.map +0 -1
  149. package/dist/chunk-GOLDJNMZ.js.map +0 -1
  150. package/dist/chunk-GW32KPCU.cjs.map +0 -1
  151. package/dist/chunk-HTXVVJRA.cjs.map +0 -1
  152. package/dist/chunk-HWSZJQCI.js.map +0 -1
  153. package/dist/chunk-J73SJHR3.cjs.map +0 -1
  154. package/dist/chunk-JBSYC7BB.cjs.map +0 -1
  155. package/dist/chunk-KVILKGMS.js.map +0 -1
  156. package/dist/chunk-LR7YASZF.cjs.map +0 -1
  157. package/dist/chunk-M5LX5AOO.js.map +0 -1
  158. package/dist/chunk-NH2O2AUR.js.map +0 -1
  159. package/dist/chunk-NMCRQP3Z.cjs.map +0 -1
  160. package/dist/chunk-NMD5VRN4.cjs.map +0 -1
  161. package/dist/chunk-OT6OJY7C.cjs +0 -114
  162. package/dist/chunk-OT6OJY7C.cjs.map +0 -1
  163. package/dist/chunk-QNJ4ACRT.cjs.map +0 -1
  164. package/dist/chunk-QWY3VEZN.js.map +0 -1
  165. package/dist/chunk-RFYD5TJE.js +0 -111
  166. package/dist/chunk-RFYD5TJE.js.map +0 -1
  167. package/dist/chunk-RIN643A3.js.map +0 -1
  168. package/dist/chunk-RZCWSXTA.cjs.map +0 -1
  169. package/dist/chunk-SDGRK7EP.js.map +0 -1
  170. package/dist/chunk-XJCMXB2E.cjs.map +0 -1
  171. package/dist/chunk-XVWCOTR6.js.map +0 -1
package/dist/format.js CHANGED
@@ -1,8 +1,7 @@
1
1
  import { autoFormatIntegerOrFloat } from './chunk-DUEZFTK2.js';
2
- import { CURRENCY_DISPLAY } from './chunk-RFYD5TJE.js';
3
- import { formatSymbolic, columnMajorToRowMajor } from './chunk-34RRD7PC.js';
2
+ import { formatSymbolic, columnMajorToRowMajor, CURRENCY_DISPLAY } from './chunk-CYFK5SY2.js';
4
3
  import './chunk-BVAR5J2F.js';
5
- import { getLocale } from './chunk-GOLDJNMZ.js';
4
+ import { getLocale } from './chunk-XBTEO4OB.js';
6
5
  import './chunk-6RDSQURE.js';
7
6
  import './chunk-YLJ4XMA6.js';
8
7
 
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/format/FormattingSettings.ts","../src/format/FormatEngine.ts"],"names":[],"mappings":";;;;;;;;;AA0BO,IAAM,2BAAA,GAAkD;AAAA,EAC7D,WAAA,EAAa;AAAA,IACX,aAAA,EAAe,CAAA;AAAA,IACf,eAAA,EAAiB;AAAA,GACnB;AAAA,EACA,YAAA,EAAc;AAAA,IACZ,sBAAA,EAAwB;AAAA,GAC1B;AAAA,EACA,SAAA,EAAW;AAAA,IACT,aAAA,EAAe,KAAA;AAAA,IACf,YAAA,EAAc;AAAA,GAChB;AAAA,EACA,uBAAA,EAAyB;AAAA,IACvB,aAAA,EAAe,CAAA;AAAA,IACf,SAAA,EAAW;AAAA,GACb;AAAA,EACA,gBAAA,EAAkB;AAAA,IAChB,aAAA,EAAe;AAAA;AAEnB;;;ACrCA,SAAS,YAAA,CAAa,KAAA,EAAe,MAAA,EAAiB,QAAA,EAAsC;AAC1F,EAAA,MAAM,EAAA,GAAK,SAAS,WAAA,CAAY,aAAA;AAChC,EAAA,MAAM,GAAA,GAAM,SAAS,WAAA,CAAY,eAAA;AACjC,EAAA,MAAM,GAAA,GAAM,SAAS,YAAA,CAAa,sBAAA;AAClC,EAAA,MAAM,SAAA,GAAY,wBAAA,CAAyB,KAAA,EAAO,EAAA,EAAI,KAAK,GAAG,CAAA;AAC9D,EAAA,OAAO,CAAA,EAAG,MAAA,CAAO,OAAA,CAAQ,YAAY,GAAG,SAAS,CAAA,CAAA;AACnD;AAUA,SAAS,SAAA,CAAU,KAAA,EAAe,QAAA,EAA8B,IAAA,EAAuB;AAMrF,EAAA,MAAM,SAAA,GAAY,IAAA,CAAK,KAAA,CAAM,KAAK,CAAA;AAClC,EAAA,MAAM,IAAA,GAAO,SAAA,GAAY,CAAA,GAAI,GAAA,GAAM,EAAA;AACnC,EAAA,MAAM,SAAA,GAAY,IAAA,CAAK,GAAA,CAAI,SAAS,CAAA;AAEpC,EAAA,IAAI,IAAA,KAAS,OAAO,OAAO,CAAA,EAAA,EAAK,IAAI,CAAA,EAAA,EAAK,SAAA,CAAU,QAAA,CAAS,CAAC,CAAC,CAAA,CAAA;AAC9D,EAAA,IAAI,IAAA,KAAS,OAAO,OAAO,CAAA,EAAA,EAAK,IAAI,CAAA,EAAA,EAAK,SAAA,CAAU,QAAA,CAAS,CAAC,CAAC,CAAA,CAAA;AAC9D,EAAA,MAAM,UAAU,QAAA,CAAS,SAAA,CAAU,aAAA,GAAgB,QAAA,CAAS,UAAU,YAAA,GAAe,CAAA;AACrF,EAAA,MAAM,GAAA,GAAM,UAAU,QAAA,CAAS,EAAE,EAAE,WAAA,EAAY,CAAE,QAAA,CAAS,OAAA,EAAS,GAAG,CAAA;AACtE,EAAA,OAAO,CAAA,EAAA,EAAK,IAAI,CAAA,EAAA,EAAK,GAAG,CAAA,CAAA;AAC1B;AAEA,SAAS,aAAa,KAAA,EAAuB;AAC3C,EAAA,OAAO,KAAK,KAAK,CAAA,CAAA;AACnB;AAEA,SAAS,aAAa,KAAA,EAAuB;AAC3C,EAAA,OAAO,KAAK,KAAK,CAAA,CAAA;AACnB;AAEA,SAAS,cAAc,KAAA,EAAwB;AAC7C,EAAA,OAAO,KAAK,KAAK,CAAA,CAAA;AACnB;AAqBA,SAAS,cAAA,CAAe,OAAe,MAAA,EAAyB;AAC9D,EAAA,MAAM,CAAA,GAAI,IAAI,IAAA,CAAK,KAAK,CAAA;AACxB,EAAA,MAAM,OAAA,GAAU,CAAA,CAAE,kBAAA,CAAmB,MAAA,CAAO,MAAM,EAAE,OAAA,EAAS,MAAA,EAAQ,IAAA,EAAM,SAAA,EAAW,KAAA,EAAO,MAAA,EAAQ,GAAA,EAAK,WAAW,CAAA;AACrH,EAAA,MAAM,UAAA,GAAa,CAAA,CAAE,QAAA,EAAS,KAAM,KAAK,CAAA,CAAE,UAAA,EAAW,KAAM,CAAA,IAAK,EAAE,UAAA,EAAW,KAAM,CAAA,IAAK,CAAA,CAAE,iBAAgB,KAAM,CAAA;AACjH,EAAA,IAAI,UAAA,EAAY,OAAO,CAAA,EAAA,EAAK,OAAO,CAAA,CAAA;AACnC,EAAA,OAAO,KAAK,OAAO,CAAA,EAAA,EAAK,EAAE,kBAAA,CAAmB,MAAA,CAAO,IAAI,CAAC,CAAA,CAAA;AAC3D;AAUA,SAAS,iBAAiB,EAAA,EAAoB;AAC5C,EAAA,MAAM,IAAA,GAAO,EAAA,GAAK,CAAA,GAAI,GAAA,GAAM,EAAA;AAC5B,EAAA,MAAM,eAAe,IAAA,CAAK,KAAA,CAAM,KAAK,GAAA,CAAI,EAAE,IAAI,GAAI,CAAA;AACnD,EAAA,MAAM,KAAA,GAAQ,IAAA,CAAK,KAAA,CAAM,YAAA,GAAe,IAAI,CAAA;AAC5C,EAAA,MAAM,OAAA,GAAU,IAAA,CAAK,KAAA,CAAO,YAAA,GAAe,OAAQ,EAAE,CAAA;AACrD,EAAA,MAAM,UAAU,YAAA,GAAe,EAAA;AAC/B,EAAA,MAAM,KAAK,MAAA,CAAO,OAAO,CAAA,CAAE,QAAA,CAAS,GAAG,GAAG,CAAA;AAC1C,EAAA,IAAI,OAAA,KAAY,GAAG,OAAO,CAAA,EAAG,IAAI,CAAA,EAAG,KAAK,IAAI,EAAE,CAAA,CAAA;AAC/C,EAAA,MAAM,KAAK,MAAA,CAAO,OAAO,CAAA,CAAE,QAAA,CAAS,GAAG,GAAG,CAAA;AAC1C,EAAA,OAAO,GAAG,IAAI,CAAA,EAAG,KAAK,CAAA,CAAA,EAAI,EAAE,IAAI,EAAE,CAAA,CAAA;AACpC;AAEA,SAAS,SAAA,CAAU,KAAA,EAAe,IAAA,EAA0B,MAAA,EAAiB,QAAA,EAAsC;AACjH,EAAA,IAAI,SAAS,IAAA,EAAM,OAAO,CAAA,EAAA,EAAK,gBAAA,CAAiB,KAAK,CAAC,CAAA,CAAA;AAEtD,EAAA,MAAM,EAAA,GAAK,SAAS,uBAAA,CAAwB,aAAA;AAC5C,EAAA,MAAM,YAAA,GAAe,SAAS,uBAAA,CAAwB,SAAA;AAGtD,EAAA,MAAM,aAAA,GAAgB,CAAC,MAAA,EAAQ,OAAA,EAAS,OAAA,EAAS,SAAA,EAAW,SAAA,EAAW,KAAA,EAAO,MAAA,EAAQ,MAAA,EAAQ,QAAA,EAAU,QAAQ,CAAA;AAChH,EAAA,MAAM,UAAA,GAAa,IAAA,IAAQ,aAAA,CAAc,QAAA,CAAS,IAAI,CAAA;AAEtD,EAAA,IAAI,SAAA;AACJ,EAAA,IAAI,UAAA,IAAc,KAAA,KAAU,IAAA,CAAK,KAAA,CAAM,KAAK,CAAA,EAAG;AAE7C,IAAA,SAAA,GAAY,MAAM,QAAA,EAAS;AAAA,EAC7B,CAAA,MAAO;AAEL,IAAA,SAAA,GAAY,KAAA,CAAM,QAAQ,EAAE,CAAA;AAAA,EAC9B;AASA,EAAA,MAAM,kBAAkB,IAAA,GAAO,gBAAA,CAAiB,IAAA,CAAK,WAAA,EAAa,CAAA,GAAI,MAAA;AACtE,EAAA,IAAI,eAAA,EAAiB;AACnB,IAAA,MAAM,GAAA,GAAM,eAAA,CAAgB,MAAA,GAAS,GAAA,GAAM,EAAA;AAC3C,IAAA,MAAM,aAAa,eAAA,CAAgB,QAAA,KAAa,WAC5C,CAAA,EAAG,eAAA,CAAgB,MAAM,CAAA,EAAG,GAAG,CAAA,EAAG,SAAS,KAC3C,CAAA,EAAG,SAAS,GAAG,GAAG,CAAA,EAAG,gBAAgB,MAAM,CAAA,CAAA;AAC/C,IAAA,OAAO,KAAK,UAAU,CAAA,CAAA;AAAA,EACxB;AAEA,EAAA,MAAM,SAAA,GAAY,YAAA,GAAgB,IAAA,IAAQ,EAAA,GAAO,IAAA,IAAQ,EAAA;AACzD,EAAA,OAAO,CAAA,EAAA,EAAK,SAAS,CAAA,CAAA,EAAI,SAAS,GAAG,IAAA,EAAK;AAC5C;AAEA,SAAS,iBAAA,CAAkB,OAAoB,QAAA,EAAsC;AACnF,EAAA,IAAI,OAAO,KAAA,KAAU,SAAA,EAAW,OAAO,QAAQ,MAAA,GAAS,OAAA;AACxD,EAAA,IAAI,OAAO,KAAA,KAAU,QAAA,IAAY,UAAU,IAAA,EAAM,OAAO,eAAe,KAAK,CAAA;AAC5E,EAAA,MAAM,EAAA,GAAK,SAAS,WAAA,CAAY,aAAA;AAChC,EAAA,MAAM,GAAA,GAAM,SAAS,WAAA,CAAY,eAAA;AACjC,EAAA,MAAM,GAAA,GAAM,SAAS,YAAA,CAAa,sBAAA;AAClC,EAAA,OAAO,wBAAA,CAAyB,KAAA,EAAO,EAAA,EAAI,GAAA,EAAK,GAAG,CAAA;AACrD;AASA,SAAS,YAAA,CAAa,CAAA,EAAe,MAAA,EAAiB,QAAA,EAAsC;AAC1F,EAAA,MAAM,QAAA,GAAW,sBAAsB,CAAC,CAAA;AACxC,EAAA,MAAM,OAAiB,EAAC;AACxB,EAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,CAAA,CAAE,MAAM,CAAA,EAAA,EAAK;AAC/B,IAAA,MAAM,QAAkB,EAAC;AACzB,IAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,CAAA,CAAE,MAAM,CAAA,EAAA,EAAK;AAC/B,MAAA,KAAA,CAAM,IAAA,CAAK,kBAAkB,QAAA,CAAS,CAAA,GAAI,EAAE,IAAA,GAAO,CAAC,CAAA,EAAG,QAAQ,CAAC,CAAA;AAAA,IAClE;AACA,IAAA,IAAA,CAAK,IAAA,CAAK,KAAA,CAAM,IAAA,CAAK,IAAI,CAAC,CAAA;AAAA,EAC5B;AACA,EAAA,OAAO,CAAA,EAAG,OAAO,OAAA,CAAQ,YAAY,IAAI,IAAA,CAAK,IAAA,CAAK,IAAI,CAAC,CAAA,CAAA,CAAA;AAC1D;AAEA,SAAS,WAAA,CAAY,GAAA,EAAa,GAAA,EAAa,MAAA,EAAyB;AACtE,EAAA,OAAO,GAAG,MAAA,CAAO,OAAA,CAAQ,YAAY,CAAA,EAAG,GAAG,IAAI,GAAG,CAAA,CAAA;AACpD;AAEA,SAAS,gBAAA,CAAiB,KAAA,EAAe,MAAA,EAAiB,QAAA,EAAsC;AAM9F,EAAA,MAAM,EAAA,GAAK,SAAS,gBAAA,CAAiB,aAAA;AACrC,EAAA,MAAM,SAAA,GAAA,CAAa,KAAA,GAAQ,GAAA,EAAK,OAAA,CAAQ,EAAE,CAAA;AAC1C,EAAA,OAAO,CAAA,EAAA,EAAK,SAAS,CAAA,EAAG,MAAA,CAAO,QAAQ,gBAAgB,CAAA,CAAA;AACzD;AAEA,SAAS,UAAA,CAAW,OAAe,IAAA,EAAkC;AACnE,EAAA,OAAO,KAAK,KAAK,CAAA,CAAA,EAAI,IAAA,IAAQ,EAAE,GAAG,IAAA,EAAK;AACzC;AAqBO,SAAS,WAAA,CAAY,OAAc,QAAA,EAAuC;AAC/E,EAAA,MAAM,KAAK,QAAA,IAAY,2BAAA;AACvB,EAAA,MAAM,UAAA,GAAa,EAAA,CAAG,YAAA,CAAa,sBAAA,IAA0B,IAAA;AAC7D,EAAA,MAAM,MAAA,GAAS,UAAU,UAAU,CAAA;AAEnC,EAAA,QAAQ,MAAM,IAAA;AAAM,IAClB,KAAA,CAAA;AACE,MAAA,OAAO,YAAA,CAAa,KAAA,CAAM,KAAA,EAAiB,MAAA,EAAQ,EAAE,CAAA;AAAA,IACvD,KAAA,CAAA;AACE,MAAA,OAAO,SAAA,CAAU,KAAA,CAAM,KAAA,EAAiB,EAAA,EAAI,MAAM,IAAI,CAAA;AAAA,IACxD,KAAA,CAAA;AACE,MAAA,OAAO,YAAA,CAAa,MAAM,KAAe,CAAA;AAAA,IAC3C,KAAA,CAAA;AACE,MAAA,OAAO,YAAA,CAAa,MAAM,KAAe,CAAA;AAAA,IAC3C,KAAA,EAAA;AACE,MAAA,OAAO,aAAA,CAAc,MAAM,KAAgB,CAAA;AAAA,IAC7C,KAAA,CAAA;AACE,MAAA,OAAO,cAAA,CAAe,KAAA,CAAM,KAAA,EAAiB,MAAM,CAAA;AAAA,IACrD,KAAA,CAAA;AACE,MAAA,OAAO,UAAU,KAAA,CAAM,KAAA,EAAiB,KAAA,CAAM,IAAA,EAAM,QAAQ,EAAE,CAAA;AAAA,IAChE,KAAA,CAAA;AACE,MAAA,OAAO,YAAA,CAAa,KAAA,CAAM,KAAA,EAAqB,MAAA,EAAQ,EAAE,CAAA;AAAA,IAC3D,KAAA,CAAA,cAAsB;AACpB,MAAA,MAAM,IAAI,KAAA,CAAM,KAAA;AAChB,MAAA,OAAO,WAAA,CAAY,CAAA,CAAE,GAAA,EAAK,CAAA,CAAE,KAAK,MAAM,CAAA;AAAA,IACzC;AAAA,IACA,KAAA,CAAA;AACE,MAAA,OAAO,cAAA,CAAe,MAAM,KAAqB,CAAA;AAAA,IACnD,KAAA,CAAA;AACE,MAAA,OAAO,gBAAA,CAAiB,KAAA,CAAM,KAAA,EAAiB,MAAA,EAAQ,EAAE,CAAA;AAAA,IAC3D,KAAA,EAAA;AACE,MAAA,OAAO,UAAA,CAAW,KAAA,CAAM,KAAA,EAAiB,KAAA,CAAM,IAAI,CAAA;AAAA,IACrD,KAAA,EAAA;AAKE,MAAA,OAAO,KAAA,CAAM,IAAA,IAAQ,MAAA,CAAO,KAAA,CAAM,KAAK,CAAA;AAAA,IACzC;AACE,MAAA,OAAO,CAAA,EAAA,EAAK,MAAA,CAAO,KAAA,CAAM,KAAK,CAAC,CAAA,CAAA;AAAA;AAErC","file":"format.js","sourcesContent":["/**\n * Minimal formatting settings interface for the engine\n * This allows the engine to format values without depending on app-specific settings\n */\nexport interface FormattingSettings {\n floatResult: {\n decimalPlaces: number;\n enableSeperator: boolean;\n };\n numberResult: {\n decimalSeparatorLocale: string;\n };\n hexResult: {\n enablePadding: boolean;\n paddingZeros: number;\n };\n unitOfMeasurementResult: {\n decimalPlaces: number;\n unitNames: boolean;\n };\n percentageResult: {\n decimalPlaces: number;\n };\n}\n\n/** Formatting used when a host supplies none. */\nexport const DEFAULT_FORMATTING_SETTINGS: FormattingSettings = {\n floatResult: {\n decimalPlaces: 2,\n enableSeperator: true,\n },\n numberResult: {\n decimalSeparatorLocale: \"en-US\",\n },\n hexResult: {\n enablePadding: false,\n paddingZeros: 0,\n },\n unitOfMeasurementResult: {\n decimalPlaces: 2,\n unitNames: true,\n },\n percentageResult: {\n decimalPlaces: 2,\n },\n};\n","import { Value, ValueType, type MatrixData, type MatrixEntry, type RangeData } from \"@solve-js/vm/Value\";\nimport { getLocale, type ILocale } from \"@solve-js/constants/locales\";\nimport { autoFormatIntegerOrFloat } from \"@solve-js/utilities/Number\";\nimport { FormattingSettings, DEFAULT_FORMATTING_SETTINGS } from \"./FormattingSettings\";\nimport { CURRENCY_DISPLAY } from \"@solve-js/uom/CurrencyAliases\";\nimport { columnMajorToRowMajor } from \"@solve-js/vm/MatrixOps\";\nimport { formatSymbolic, type SymbolicNode } from \"@solve-js/symbolic\";\n\nfunction formatNumber(value: number, locale: ILocale, settings: FormattingSettings): string {\n const dp = settings.floatResult.decimalPlaces;\n const sep = settings.floatResult.enableSeperator;\n const loc = settings.numberResult.decimalSeparatorLocale;\n const formatted = autoFormatIntegerOrFloat(value, dp, sep, loc);\n return `${locale.display.resultPrefix}${formatted}`;\n}\n\n/**\n * Renders a number in whichever base it is tagged with.\n *\n * The zero padding is a hexadecimal setting and stays one. Applying it to a\n * binary rendering would pad `0b101` out to the same digit count as a hex\n * value, which is a different quantity of zeros and not what the setting asks\n * for.\n */\nfunction formatHex(value: number, settings: FormattingSettings, base?: string): string {\n // Truncate and take the sign off before converting. `Number.toString(radix)`\n // does neither: it renders -255 as \"-ff\", which lands the minus inside the\n // literal as `0x-FF`, and it renders 255.7 as \"ff.b3333333333\", inventing\n // fractional hex digits for a notation that has no use for them. Both were\n // visible on `as hex` until the builtins started sharing this path.\n const truncated = Math.trunc(value);\n const sign = truncated < 0 ? \"-\" : \"\";\n const magnitude = Math.abs(truncated);\n\n if (base === \"bin\") return `= ${sign}0b${magnitude.toString(2)}`;\n if (base === \"oct\") return `= ${sign}0o${magnitude.toString(8)}`;\n const padding = settings.hexResult.enablePadding ? settings.hexResult.paddingZeros : 0;\n const hex = magnitude.toString(16).toUpperCase().padStart(padding, \"0\");\n return `= ${sign}0x${hex}`;\n}\n\nfunction formatBigInt(value: bigint): string {\n return `= ${value}`;\n}\n\nfunction formatString(value: string): string {\n return `= ${value}`;\n}\n\nfunction formatBoolean(value: boolean): string {\n return `= ${value}`;\n}\n\n/**\n * Renders a Datetime value locale-aware, the previous implementation\n * called `d.toLocaleString()` with no arguments, which always uses the JS\n * runtime's own default locale and never actually consulted `locale.code`\n * despite receiving it as a parameter (both branches of its old\n * `dateFormat === \"default\"` check were byte-for-byte identical, dead\n * groundwork for a distinction that was never implemented). Concretely,\n * this meant every configured locale (including the shipped German one)\n * always displayed weekday/month names in English. See GitHub issue #77.\n *\n * Uses `weekday`/`month`: \"long\" for a spelled-out date (\"Monday,\n * November 17, 2025\" / \"lundi 17 novembre 2025\") since that's what a\n * literal weekday name is for; a bare numeric date doesn't need\n * localizing beyond the decimal/thousands separators `formatNumber()`\n * already handles. The time-of-day portion is only appended when it's\n * not exactly local midnight, bare date literals (\"today\", \"17/11/2025\")\n * always anchor to local midnight, and showing \"00:00:00\" on every one of\n * those would be noise, not information.\n */\nfunction formatDatetime(value: number, locale: ILocale): string {\n const d = new Date(value);\n const dateStr = d.toLocaleDateString(locale.code, { weekday: \"long\", year: \"numeric\", month: \"long\", day: \"numeric\" });\n const isMidnight = d.getHours() === 0 && d.getMinutes() === 0 && d.getSeconds() === 0 && d.getMilliseconds() === 0;\n if (isMidnight) return `= ${dateStr}`;\n return `= ${dateStr}, ${d.toLocaleTimeString(locale.code)}`;\n}\n\n/**\n * Renders a millisecond duration as clock-style `H:MM` (or `H:MM:SS` when\n * there's a non-zero seconds component). `ms` is never a user-typeable\n * unit (confirmed: it appears nowhere in `lexer/units.ts`), it's only\n * ever produced by subtracting two clock times/datetimes (`9:30 - 8:30`,\n * `VM.ts`'s Datetime SUB case), so this is a safe, narrow special case,\n * not a general change to how durations display.\n */\nfunction formatMsDuration(ms: number): string {\n const sign = ms < 0 ? \"-\" : \"\";\n const totalSeconds = Math.round(Math.abs(ms) / 1000);\n const hours = Math.floor(totalSeconds / 3600);\n const minutes = Math.floor((totalSeconds % 3600) / 60);\n const seconds = totalSeconds % 60;\n const mm = String(minutes).padStart(2, \"0\");\n if (seconds === 0) return `${sign}${hours}:${mm}`;\n const ss = String(seconds).padStart(2, \"0\");\n return `${sign}${hours}:${mm}:${ss}`;\n}\n\nfunction formatUom(value: number, unit: string | undefined, locale: ILocale, settings: FormattingSettings): string {\n if (unit === \"ms\") return `= ${formatMsDuration(value)}`;\n\n const dp = settings.unitOfMeasurementResult.decimalPlaces;\n const useUnitNames = settings.unitOfMeasurementResult.unitNames;\n\n // For TimeSpan values (days, weeks, hours, etc.), format as integer if the value is a whole number\n const timeSpanUnits = [\"days\", \"weeks\", \"hours\", \"minutes\", \"seconds\", \"day\", \"week\", \"hour\", \"minute\", \"second\"];\n const isTimeSpan = unit && timeSpanUnits.includes(unit);\n\n let formatted: string;\n if (isTimeSpan && value === Math.floor(value)) {\n // For whole number TimeSpan values, format as integer\n formatted = value.toString();\n } else {\n // For other values, use the configured decimal places\n formatted = value.toFixed(dp);\n }\n\n // Currency display: symbol + culturally-conventional placement (e.g.\n // \"$100.00\" prefix vs \"100.00 kr\" suffix) instead of the generic\n // \"amount CODE\" fallback below. See uom/CurrencyAliases.ts's\n // CURRENCY_DISPLAY table for the exact set covered and the reasoning\n // behind each placement choice. Any currency code NOT in that table\n // (most of the ~150 `CurrencyExchange.isCurrency()` recognizes) falls\n // through to the unchanged \"amount CODE\" format below.\n const currencyDisplay = unit ? CURRENCY_DISPLAY[unit.toUpperCase()] : undefined;\n if (currencyDisplay) {\n const sep = currencyDisplay.spaced ? \" \" : \"\";\n const withSymbol = currencyDisplay.position === \"prefix\"\n ? `${currencyDisplay.symbol}${sep}${formatted}`\n : `${formatted}${sep}${currencyDisplay.symbol}`;\n return `= ${withSymbol}`;\n }\n\n const unitLabel = useUnitNames ? (unit || \"\") : (unit || \"\");\n return `= ${formatted} ${unitLabel}`.trim();\n}\n\nfunction formatMatrixEntry(entry: MatrixEntry, settings: FormattingSettings): string {\n if (typeof entry === \"boolean\") return entry ? \"true\" : \"false\";\n if (typeof entry === \"object\" && entry !== null) return formatSymbolic(entry);\n const dp = settings.floatResult.decimalPlaces;\n const sep = settings.floatResult.enableSeperator;\n const loc = settings.numberResult.decimalSeparatorLocale;\n return autoFormatIntegerOrFloat(entry, dp, sep, loc);\n}\n\n/**\n * Renders a Matrix matching its own literal syntax: a single row (1xN,\n * including plain vectors) as `[a, b, c]`, a single column (Nx1) as\n * `[a; b; c]`, and a general shape as `[r0c0, r0c1; r1c0, r1c1]`, row-major\n * textual output read back out of the column-major storage\n * (`columnMajorToRowMajor()`), matching how `[1,2;3,4]` is written.\n */\nfunction formatMatrix(m: MatrixData, locale: ILocale, settings: FormattingSettings): string {\n const rowMajor = columnMajorToRowMajor(m);\n const rows: string[] = [];\n for (let r = 0; r < m.rows; r++) {\n const cells: string[] = [];\n for (let c = 0; c < m.cols; c++) {\n cells.push(formatMatrixEntry(rowMajor[r * m.cols + c], settings));\n }\n rows.push(cells.join(\", \"));\n }\n return `${locale.display.resultPrefix}[${rows.join(\"; \")}]`;\n}\n\nfunction formatRange(min: number, max: number, locale: ILocale): string {\n return `${locale.display.resultPrefix}${min}:${max}`;\n}\n\nfunction formatPercentage(value: number, locale: ILocale, settings: FormattingSettings): string {\n // ValueType.Percentage stores a fraction (0.25 for 25%). See Value.ts's\n // documented contract and the sole producer, VM.ts's TO_PERCENTAGE opcode\n // (`right/left - 1`, e.g. 0.25 for \"800 to 1000\"). Multiply by 100 before\n // formatting; without this every percentage-change result displayed as\n // e.g. \"0.25%\" instead of \"25.00%\".\n const dp = settings.percentageResult.decimalPlaces;\n const formatted = (value * 100).toFixed(dp);\n return `= ${formatted}${locale.display.percentageSuffix}`;\n}\n\nfunction formatUnit(value: number, unit: string | undefined): string {\n return `= ${value} ${unit || \"\"}`.trim();\n}\n\n/**\n * Render an evaluated {@link Value} as a display string, dispatching on\n * `value.type` to the type-specific formatter (number, hex, datetime, unit\n * of measurement, matrix, range, percentage, ...).\n *\n * Most branches produce a `\"= \"`-prefixed result string (matching the\n * plugin's inline-result convention); `ValueType.Error` is the one\n * exception, it returns the human-readable error message directly\n * (stored in `value.unit`), not an `\"= \"`-prefixed string.\n *\n * @param value - The evaluated value to format.\n * @param settings - Locale/precision/separator options; defaults to\n * {@link DEFAULT_FORMATTING_SETTINGS} when omitted.\n * @example\n * ```typescript\n * const [value] = engine.evaluateExpression(\"10 USD to GBP\");\n * formatValue(value); // \"= £7.85\" (exact output depends on live exchange rates)\n * ```\n */\nexport function formatValue(value: Value, settings?: FormattingSettings): string {\n const us = settings || DEFAULT_FORMATTING_SETTINGS;\n const localeCode = us.numberResult.decimalSeparatorLocale || \"en\";\n const locale = getLocale(localeCode);\n\n switch (value.type) {\n case ValueType.Number:\n return formatNumber(value.value as number, locale, us);\n case ValueType.Hex:\n return formatHex(value.value as number, us, value.unit);\n case ValueType.BigInt:\n return formatBigInt(value.value as bigint);\n case ValueType.String:\n return formatString(value.value as string);\n case ValueType.Boolean:\n return formatBoolean(value.value as boolean);\n case ValueType.Datetime:\n return formatDatetime(value.value as number, locale);\n case ValueType.Uom:\n return formatUom(value.value as number, value.unit, locale, us);\n case ValueType.Matrix:\n return formatMatrix(value.value as MatrixData, locale, us);\n case ValueType.Range: {\n const r = value.value as RangeData;\n return formatRange(r.min, r.max, locale);\n }\n case ValueType.Symbolic:\n return formatSymbolic(value.value as SymbolicNode);\n case ValueType.Percentage:\n return formatPercentage(value.value as number, locale, us);\n case ValueType.Unit:\n return formatUnit(value.value as number, value.unit);\n case ValueType.Error:\n // errorValue(code, message) stores the human-readable message in\n // `.unit` (code goes in `.value`), falling through to the default\n // case here previously displayed the raw code (e.g.\n // \"CURRENCY_RATE_UNAVAILABLE\") instead of the actual message.\n return value.unit ?? String(value.value);\n default:\n return `= ${String(value.value)}`;\n }\n}\n"]}
1
+ {"version":3,"sources":["../src/format/FormattingSettings.ts","../src/format/FormatEngine.ts"],"names":[],"mappings":"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","file":"format.js","sourcesContent":["/**\n * Minimal formatting settings interface for the engine\n * This allows the engine to format values without depending on app-specific settings\n */\nexport interface FormattingSettings {\n floatResult: {\n decimalPlaces: number;\n enableSeperator: boolean;\n };\n numberResult: {\n decimalSeparatorLocale: string;\n };\n hexResult: {\n enablePadding: boolean;\n paddingZeros: number;\n };\n unitOfMeasurementResult: {\n decimalPlaces: number;\n unitNames: boolean;\n };\n percentageResult: {\n decimalPlaces: number;\n };\n}\n\n/** Formatting used when a host supplies none. */\nexport const DEFAULT_FORMATTING_SETTINGS: FormattingSettings = {\n floatResult: {\n decimalPlaces: 2,\n enableSeperator: true,\n },\n numberResult: {\n decimalSeparatorLocale: \"en-US\",\n },\n hexResult: {\n enablePadding: false,\n paddingZeros: 0,\n },\n unitOfMeasurementResult: {\n decimalPlaces: 2,\n unitNames: true,\n },\n percentageResult: {\n decimalPlaces: 2,\n },\n};\n","import { Value, ValueType, type MatrixData, type MatrixEntry, type RangeData } from \"@solve-js/vm/Value\";\nimport { getLocale, type ILocale } from \"@solve-js/constants/locales\";\nimport { autoFormatIntegerOrFloat } from \"@solve-js/utilities/Number\";\nimport { FormattingSettings, DEFAULT_FORMATTING_SETTINGS } from \"./FormattingSettings\";\nimport { CURRENCY_DISPLAY } from \"@solve-js/uom/CurrencyAliases\";\nimport { columnMajorToRowMajor } from \"@solve-js/vm/MatrixOps\";\nimport { formatSymbolic, type SymbolicNode } from \"@solve-js/symbolic\";\n\nfunction formatNumber(value: number, locale: ILocale, settings: FormattingSettings): string {\n const dp = settings.floatResult.decimalPlaces;\n const sep = settings.floatResult.enableSeperator;\n const loc = settings.numberResult.decimalSeparatorLocale;\n const formatted = autoFormatIntegerOrFloat(value, dp, sep, loc);\n return `${locale.display.resultPrefix}${formatted}`;\n}\n\n/**\n * Renders a number in whichever base it is tagged with.\n *\n * The zero padding is a hexadecimal setting and stays one. Applying it to a\n * binary rendering would pad `0b101` out to the same digit count as a hex\n * value, which is a different quantity of zeros and not what the setting asks\n * for.\n */\nfunction formatHex(value: number, settings: FormattingSettings, base?: string): string {\n // Truncate and take the sign off before converting. `Number.toString(radix)`\n // does neither: it renders -255 as \"-ff\", which lands the minus inside the\n // literal as `0x-FF`, and it renders 255.7 as \"ff.b3333333333\", inventing\n // fractional hex digits for a notation that has no use for them. Both were\n // visible on `as hex` until the builtins started sharing this path.\n const truncated = Math.trunc(value);\n const sign = truncated < 0 ? \"-\" : \"\";\n const magnitude = Math.abs(truncated);\n\n if (base === \"bin\") return `= ${sign}0b${magnitude.toString(2)}`;\n if (base === \"oct\") return `= ${sign}0o${magnitude.toString(8)}`;\n const padding = settings.hexResult.enablePadding ? settings.hexResult.paddingZeros : 0;\n const hex = magnitude.toString(16).toUpperCase().padStart(padding, \"0\");\n return `= ${sign}0x${hex}`;\n}\n\nfunction formatBigInt(value: bigint): string {\n return `= ${value}`;\n}\n\nfunction formatString(value: string): string {\n return `= ${value}`;\n}\n\nfunction formatBoolean(value: boolean): string {\n return `= ${value}`;\n}\n\n/**\n * Renders a Datetime value locale-aware, the previous implementation\n * called `d.toLocaleString()` with no arguments, which always uses the JS\n * runtime's own default locale and never actually consulted `locale.code`\n * despite receiving it as a parameter (both branches of its old\n * `dateFormat === \"default\"` check were byte-for-byte identical, dead\n * groundwork for a distinction that was never implemented). Concretely,\n * this meant every configured locale (including the shipped German one)\n * always displayed weekday/month names in English. See GitHub issue #77.\n *\n * Uses `weekday`/`month`: \"long\" for a spelled-out date (\"Monday,\n * November 17, 2025\" / \"lundi 17 novembre 2025\") since that's what a\n * literal weekday name is for; a bare numeric date doesn't need\n * localizing beyond the decimal/thousands separators `formatNumber()`\n * already handles. The time-of-day portion is only appended when it's\n * not exactly local midnight, bare date literals (\"today\", \"17/11/2025\")\n * always anchor to local midnight, and showing \"00:00:00\" on every one of\n * those would be noise, not information.\n */\nfunction formatDatetime(value: number, locale: ILocale): string {\n const d = new Date(value);\n const dateStr = d.toLocaleDateString(locale.code, { weekday: \"long\", year: \"numeric\", month: \"long\", day: \"numeric\" });\n const isMidnight = d.getHours() === 0 && d.getMinutes() === 0 && d.getSeconds() === 0 && d.getMilliseconds() === 0;\n if (isMidnight) return `= ${dateStr}`;\n return `= ${dateStr}, ${d.toLocaleTimeString(locale.code)}`;\n}\n\n/**\n * Renders a millisecond duration as clock-style `H:MM` (or `H:MM:SS` when\n * there's a non-zero seconds component). `ms` is never a user-typeable\n * unit (confirmed: it appears nowhere in `lexer/units.ts`), it's only\n * ever produced by subtracting two clock times/datetimes (`9:30 - 8:30`,\n * `VM.ts`'s Datetime SUB case), so this is a safe, narrow special case,\n * not a general change to how durations display.\n */\nfunction formatMsDuration(ms: number): string {\n const sign = ms < 0 ? \"-\" : \"\";\n const totalSeconds = Math.round(Math.abs(ms) / 1000);\n const hours = Math.floor(totalSeconds / 3600);\n const minutes = Math.floor((totalSeconds % 3600) / 60);\n const seconds = totalSeconds % 60;\n const mm = String(minutes).padStart(2, \"0\");\n if (seconds === 0) return `${sign}${hours}:${mm}`;\n const ss = String(seconds).padStart(2, \"0\");\n return `${sign}${hours}:${mm}:${ss}`;\n}\n\nfunction formatUom(value: number, unit: string | undefined, locale: ILocale, settings: FormattingSettings): string {\n if (unit === \"ms\") return `= ${formatMsDuration(value)}`;\n\n const dp = settings.unitOfMeasurementResult.decimalPlaces;\n const useUnitNames = settings.unitOfMeasurementResult.unitNames;\n\n // For TimeSpan values (days, weeks, hours, etc.), format as integer if the value is a whole number\n const timeSpanUnits = [\"days\", \"weeks\", \"hours\", \"minutes\", \"seconds\", \"day\", \"week\", \"hour\", \"minute\", \"second\"];\n const isTimeSpan = unit && timeSpanUnits.includes(unit);\n\n let formatted: string;\n if (isTimeSpan && value === Math.floor(value)) {\n // For whole number TimeSpan values, format as integer\n formatted = value.toString();\n } else {\n // For other values, use the configured decimal places\n formatted = value.toFixed(dp);\n }\n\n // Currency display: symbol + culturally-conventional placement (e.g.\n // \"$100.00\" prefix vs \"100.00 kr\" suffix) instead of the generic\n // \"amount CODE\" fallback below. See uom/CurrencyAliases.ts's\n // CURRENCY_DISPLAY table for the exact set covered and the reasoning\n // behind each placement choice. Any currency code NOT in that table\n // (most of the ~150 `CurrencyExchange.isCurrency()` recognizes) falls\n // through to the unchanged \"amount CODE\" format below.\n const currencyDisplay = unit ? CURRENCY_DISPLAY[unit.toUpperCase()] : undefined;\n if (currencyDisplay) {\n const sep = currencyDisplay.spaced ? \" \" : \"\";\n const withSymbol = currencyDisplay.position === \"prefix\"\n ? `${currencyDisplay.symbol}${sep}${formatted}`\n : `${formatted}${sep}${currencyDisplay.symbol}`;\n return `= ${withSymbol}`;\n }\n\n const unitLabel = useUnitNames ? (unit || \"\") : (unit || \"\");\n return `= ${formatted} ${unitLabel}`.trim();\n}\n\nfunction formatMatrixEntry(entry: MatrixEntry, settings: FormattingSettings): string {\n if (typeof entry === \"boolean\") return entry ? \"true\" : \"false\";\n if (typeof entry === \"object\" && entry !== null) return formatSymbolic(entry);\n const dp = settings.floatResult.decimalPlaces;\n const sep = settings.floatResult.enableSeperator;\n const loc = settings.numberResult.decimalSeparatorLocale;\n return autoFormatIntegerOrFloat(entry, dp, sep, loc);\n}\n\n/**\n * Renders a Matrix matching its own literal syntax: a single row (1xN,\n * including plain vectors) as `[a, b, c]`, a single column (Nx1) as\n * `[a; b; c]`, and a general shape as `[r0c0, r0c1; r1c0, r1c1]`, row-major\n * textual output read back out of the column-major storage\n * (`columnMajorToRowMajor()`), matching how `[1,2;3,4]` is written.\n */\nfunction formatMatrix(m: MatrixData, locale: ILocale, settings: FormattingSettings): string {\n const rowMajor = columnMajorToRowMajor(m);\n const rows: string[] = [];\n for (let r = 0; r < m.rows; r++) {\n const cells: string[] = [];\n for (let c = 0; c < m.cols; c++) {\n cells.push(formatMatrixEntry(rowMajor[r * m.cols + c], settings));\n }\n rows.push(cells.join(\", \"));\n }\n return `${locale.display.resultPrefix}[${rows.join(\"; \")}]`;\n}\n\nfunction formatRange(min: number, max: number, locale: ILocale): string {\n return `${locale.display.resultPrefix}${min}:${max}`;\n}\n\nfunction formatPercentage(value: number, locale: ILocale, settings: FormattingSettings): string {\n // ValueType.Percentage stores a fraction (0.25 for 25%). See Value.ts's\n // documented contract and the sole producer, VM.ts's TO_PERCENTAGE opcode\n // (`right/left - 1`, e.g. 0.25 for \"800 to 1000\"). Multiply by 100 before\n // formatting; without this every percentage-change result displayed as\n // e.g. \"0.25%\" instead of \"25.00%\".\n const dp = settings.percentageResult.decimalPlaces;\n const formatted = (value * 100).toFixed(dp);\n return `= ${formatted}${locale.display.percentageSuffix}`;\n}\n\nfunction formatUnit(value: number, unit: string | undefined): string {\n return `= ${value} ${unit || \"\"}`.trim();\n}\n\n/**\n * Render an evaluated {@link Value} as a display string, dispatching on\n * `value.type` to the type-specific formatter (number, hex, datetime, unit\n * of measurement, matrix, range, percentage, ...).\n *\n * Most branches produce a `\"= \"`-prefixed result string (matching the\n * plugin's inline-result convention); `ValueType.Error` is the one\n * exception, it returns the human-readable error message directly\n * (stored in `value.unit`), not an `\"= \"`-prefixed string.\n *\n * @param value - The evaluated value to format.\n * @param settings - Locale/precision/separator options; defaults to\n * {@link DEFAULT_FORMATTING_SETTINGS} when omitted.\n * @example\n * ```typescript\n * const [value] = engine.evaluateExpression(\"10 USD to GBP\");\n * formatValue(value); // \"= £7.85\" (exact output depends on live exchange rates)\n * ```\n */\nexport function formatValue(value: Value, settings?: FormattingSettings): string {\n const us = settings || DEFAULT_FORMATTING_SETTINGS;\n const localeCode = us.numberResult.decimalSeparatorLocale || \"en\";\n const locale = getLocale(localeCode);\n\n switch (value.type) {\n case ValueType.Number:\n return formatNumber(value.value as number, locale, us);\n case ValueType.Hex:\n return formatHex(value.value as number, us, value.unit);\n case ValueType.BigInt:\n return formatBigInt(value.value as bigint);\n case ValueType.String:\n return formatString(value.value as string);\n case ValueType.Boolean:\n return formatBoolean(value.value as boolean);\n case ValueType.Datetime:\n return formatDatetime(value.value as number, locale);\n case ValueType.Uom:\n return formatUom(value.value as number, value.unit, locale, us);\n case ValueType.Matrix:\n return formatMatrix(value.value as MatrixData, locale, us);\n case ValueType.Range: {\n const r = value.value as RangeData;\n return formatRange(r.min, r.max, locale);\n }\n case ValueType.Symbolic:\n return formatSymbolic(value.value as SymbolicNode);\n case ValueType.Percentage:\n return formatPercentage(value.value as number, locale, us);\n case ValueType.Unit:\n return formatUnit(value.value as number, value.unit);\n case ValueType.Error:\n // errorValue(code, message) stores the human-readable message in\n // `.unit` (code goes in `.value`), falling through to the default\n // case here previously displayed the raw code (e.g.\n // \"CURRENCY_RATE_UNAVAILABLE\") instead of the actual message.\n return value.unit ?? String(value.value);\n default:\n return `= ${String(value.value)}`;\n }\n}\n"]}
package/dist/index.cjs CHANGED
@@ -1,35 +1,34 @@
1
1
  'use strict';
2
2
 
3
- var chunkJBSYC7BB_cjs = require('./chunk-JBSYC7BB.cjs');
4
- var chunkNMCRQP3Z_cjs = require('./chunk-NMCRQP3Z.cjs');
3
+ var chunkX2BWAZAU_cjs = require('./chunk-X2BWAZAU.cjs');
4
+ var chunkMTX2KVU7_cjs = require('./chunk-MTX2KVU7.cjs');
5
5
  require('./chunk-NGBBNL4K.cjs');
6
6
  require('./chunk-X6K3HUOY.cjs');
7
7
  require('./chunk-5X2PTP6F.cjs');
8
- require('./chunk-6NTVRDQV.cjs');
8
+ require('./chunk-M4Q47KHF.cjs');
9
9
  require('./chunk-47LRVGOT.cjs');
10
- require('./chunk-RZCWSXTA.cjs');
11
- require('./chunk-C4XZV6E7.cjs');
12
- require('./chunk-EBSPLUW4.cjs');
10
+ require('./chunk-43G3JT2E.cjs');
11
+ require('./chunk-NZFKROS7.cjs');
12
+ require('./chunk-AHCLWAM5.cjs');
13
13
  require('./chunk-N5YFR4UP.cjs');
14
- require('./chunk-WQTTOGXC.cjs');
14
+ require('./chunk-4AVD7NZW.cjs');
15
15
  require('./chunk-Z2XP767T.cjs');
16
- require('./chunk-J73SJHR3.cjs');
16
+ require('./chunk-2NPS5DQ3.cjs');
17
17
  var chunkFEAIOR7N_cjs = require('./chunk-FEAIOR7N.cjs');
18
- require('./chunk-XJCMXB2E.cjs');
18
+ require('./chunk-2MV4HBKC.cjs');
19
19
  require('./chunk-MZOVP3WI.cjs');
20
20
  require('./chunk-VEKIWQ3F.cjs');
21
- require('./chunk-OT6OJY7C.cjs');
22
- require('./chunk-HTXVVJRA.cjs');
21
+ require('./chunk-4CVLFLOB.cjs');
23
22
  var chunkOC6LTXYS_cjs = require('./chunk-OC6LTXYS.cjs');
24
- require('./chunk-QNJ4ACRT.cjs');
25
- var chunkLR7YASZF_cjs = require('./chunk-LR7YASZF.cjs');
26
- var chunkCLL7RUQV_cjs = require('./chunk-CLL7RUQV.cjs');
27
- require('./chunk-6BKTCEUP.cjs');
28
- require('./chunk-NMD5VRN4.cjs');
29
- require('./chunk-GW32KPCU.cjs');
30
- require('./chunk-3YNVWKR2.cjs');
31
- require('./chunk-EHAHVROS.cjs');
32
- require('./chunk-53B6KDDJ.cjs');
23
+ require('./chunk-NJTXJ5AG.cjs');
24
+ var chunkEAAHVJ4P_cjs = require('./chunk-EAAHVJ4P.cjs');
25
+ var chunkWWOLFXTX_cjs = require('./chunk-WWOLFXTX.cjs');
26
+ require('./chunk-TY3TLZAW.cjs');
27
+ require('./chunk-M4F66R4O.cjs');
28
+ require('./chunk-R24DI24X.cjs');
29
+ require('./chunk-OUHT5Z36.cjs');
30
+ require('./chunk-CLVQBF5C.cjs');
31
+ require('./chunk-MBNQVDVC.cjs');
33
32
  require('./chunk-M4N5GA5Z.cjs');
34
33
  require('./chunk-B7LXJ6LC.cjs');
35
34
 
@@ -39,18 +38,18 @@ var PackageRegistry = class {
39
38
  this.Value = chunkOC6LTXYS_cjs.Value;
40
39
  }
41
40
  registerPrefixParselet(tokenType, parselet) {
42
- chunkNMCRQP3Z_cjs.sharedParseletRegistry.registerPrefix(tokenType, parselet);
41
+ chunkMTX2KVU7_cjs.sharedParseletRegistry.registerPrefix(tokenType, parselet);
43
42
  }
44
43
  registerInfixParselet(tokenType, parselet) {
45
- chunkNMCRQP3Z_cjs.sharedParseletRegistry.registerInfix(tokenType, parselet);
44
+ chunkMTX2KVU7_cjs.sharedParseletRegistry.registerInfix(tokenType, parselet);
46
45
  }
47
46
  registerVariableSource(source) {
48
47
  chunkFEAIOR7N_cjs.sharedVariableResolver.registerSource(source);
49
48
  }
50
49
  registerPackage(pkg) {
51
- chunkJBSYC7BB_cjs.assertEngineVersionCompatible(pkg);
50
+ chunkX2BWAZAU_cjs.assertEngineVersionCompatible(pkg);
52
51
  if (pkg.lexerVocabulary) {
53
- chunkCLL7RUQV_cjs.sharedLexer.registerVocabulary(pkg.lexerVocabulary);
52
+ chunkWWOLFXTX_cjs.sharedLexer.registerVocabulary(pkg.lexerVocabulary);
54
53
  }
55
54
  if (pkg.prefixParselets) {
56
55
  for (const pp of pkg.prefixParselets) {
@@ -73,23 +72,23 @@ var packageRegistry = new PackageRegistry();
73
72
 
74
73
  Object.defineProperty(exports, "ExpressionEngine", {
75
74
  enumerable: true,
76
- get: function () { return chunkJBSYC7BB_cjs.ExpressionEngine; }
75
+ get: function () { return chunkX2BWAZAU_cjs.ExpressionEngine; }
77
76
  });
78
77
  Object.defineProperty(exports, "assertEngineVersionCompatible", {
79
78
  enumerable: true,
80
- get: function () { return chunkJBSYC7BB_cjs.assertEngineVersionCompatible; }
79
+ get: function () { return chunkX2BWAZAU_cjs.assertEngineVersionCompatible; }
81
80
  });
82
81
  Object.defineProperty(exports, "checkEngineVersionCompatibility", {
83
82
  enumerable: true,
84
- get: function () { return chunkJBSYC7BB_cjs.checkEngineVersionCompatibility; }
83
+ get: function () { return chunkX2BWAZAU_cjs.checkEngineVersionCompatibility; }
85
84
  });
86
85
  Object.defineProperty(exports, "checkPackageCompatibility", {
87
86
  enumerable: true,
88
- get: function () { return chunkJBSYC7BB_cjs.checkPackageCompatibility; }
87
+ get: function () { return chunkX2BWAZAU_cjs.checkPackageCompatibility; }
89
88
  });
90
89
  Object.defineProperty(exports, "ENGINE_VERSION", {
91
90
  enumerable: true,
92
- get: function () { return chunkLR7YASZF_cjs.ENGINE_VERSION; }
91
+ get: function () { return chunkEAAHVJ4P_cjs.ENGINE_VERSION; }
93
92
  });
94
93
  exports.PackageRegistry = PackageRegistry;
95
94
  exports.packageRegistry = packageRegistry;
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/api/PackageRegistry.ts"],"names":["Value","sharedParseletRegistry","sharedVariableResolver","assertEngineVersionCompatible","sharedLexer"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA+PO,IAAM,kBAAN,MAAkD;AAAA,EAAlD,WAAA,GAAA;AACL,IAAA,IAAA,CAAA,KAAA,GAAQA,uBAAA;AAAA,EAAA;AAAA,EAER,sBAAA,CAAuB,WAAmB,QAAA,EAAgC;AACxE,IAAAC,wCAAA,CAAuB,cAAA,CAAe,WAAW,QAAQ,CAAA;AAAA,EAC3D;AAAA,EAEA,qBAAA,CAAsB,WAAmB,QAAA,EAA+B;AACtE,IAAAA,wCAAA,CAAuB,aAAA,CAAc,WAAW,QAAQ,CAAA;AAAA,EAC1D;AAAA,EAEA,uBAAuB,MAAA,EAA+B;AACpD,IAAAC,wCAAA,CAAuB,eAAe,MAAM,CAAA;AAAA,EAC9C;AAAA,EAEA,gBAAgB,GAAA,EAA2B;AAMzC,IAAAC,+CAAA,CAA8B,GAAG,CAAA;AAEjC,IAAA,IAAI,IAAI,eAAA,EAAiB;AACvB,MAAAC,6BAAA,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.cjs","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"]}
1
+ {"version":3,"sources":["../src/api/PackageRegistry.ts"],"names":["Value","sharedParseletRegistry","sharedVariableResolver","assertEngineVersionCompatible","sharedLexer"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA+PO,IAAM,kBAAN,MAAkD;AAAA,EAAlD,WAAA,GAAA;AACL,IAAA,IAAA,CAAA,KAAA,GAAQA,uBAAA;AAAA,EAAA;AAAA,EAER,sBAAA,CAAuB,WAAmB,QAAA,EAAgC;AACxE,IAAAC,wCAAA,CAAuB,cAAA,CAAe,WAAW,QAAQ,CAAA;AAAA,EAC3D;AAAA,EAEA,qBAAA,CAAsB,WAAmB,QAAA,EAA+B;AACtE,IAAAA,wCAAA,CAAuB,aAAA,CAAc,WAAW,QAAQ,CAAA;AAAA,EAC1D;AAAA,EAEA,uBAAuB,MAAA,EAA+B;AACpD,IAAAC,wCAAA,CAAuB,eAAe,MAAM,CAAA;AAAA,EAC9C;AAAA,EAEA,gBAAgB,GAAA,EAA2B;AAMzC,IAAAC,+CAAA,CAA8B,GAAG,CAAA;AAEjC,IAAA,IAAI,IAAI,eAAA,EAAiB;AACvB,MAAAC,6BAAA,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.cjs","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"]}
package/dist/index.d.cts CHANGED
@@ -1,16 +1,16 @@
1
- import { I as IEnginePackage } from './PackageRegistry-D-Tv_7ca.cjs';
2
- export { E as EvalResults, a as ExpressionEngine, b as IPackageRegistry, L as LineEvaluation, P as PackageRegistry, p as packageRegistry } from './PackageRegistry-D-Tv_7ca.cjs';
1
+ import { I as IEnginePackage } from './PackageRegistry-pHtKythi.cjs';
2
+ export { E as EvalResults, a as ExpressionEngine, b as IPackageRegistry, L as LineEvaluation, P as PackageRegistry, p as packageRegistry } from './PackageRegistry-pHtKythi.cjs';
3
3
  export { ENGINE_VERSION } from './constants.cjs';
4
- import './Parselet-CK8cQu2a.cjs';
4
+ import './Parselet-Cu0bLSis.cjs';
5
5
  import './BytecodeBuilder-3egMyuDi.cjs';
6
- import './Token-0jpvUdPY.cjs';
6
+ import './Token-BzG5G4ja.cjs';
7
7
  import './pipeline-DPUQt7yL.cjs';
8
8
  import './Value-KtAQNHur.cjs';
9
9
  import './variables.cjs';
10
- import './Lexer-BJdhlnej.cjs';
10
+ import './Lexer-Cfm79Dak.cjs';
11
11
  import './resolvers.cjs';
12
12
  import '@tanstack/query-core';
13
- import './NormalizerRule-BrVoVjmP.cjs';
13
+ import './TokenNormalizer-DGVa24Q-.cjs';
14
14
  import './ScopeManager-CKfQtl7l.cjs';
15
15
  import './EngineError-DvPSnhZi.cjs';
16
16
  import './Configuration-fVpd-HC-.cjs';
package/dist/index.d.ts CHANGED
@@ -1,16 +1,16 @@
1
- import { I as IEnginePackage } from './PackageRegistry-DaH4aIKP.js';
2
- export { E as EvalResults, a as ExpressionEngine, b as IPackageRegistry, L as LineEvaluation, P as PackageRegistry, p as packageRegistry } from './PackageRegistry-DaH4aIKP.js';
1
+ import { I as IEnginePackage } from './PackageRegistry-CjDt-Jy_.js';
2
+ export { E as EvalResults, a as ExpressionEngine, b as IPackageRegistry, L as LineEvaluation, P as PackageRegistry, p as packageRegistry } from './PackageRegistry-CjDt-Jy_.js';
3
3
  export { ENGINE_VERSION } from './constants.js';
4
- import './Parselet-ConOIdRO.js';
4
+ import './Parselet-DEdF9I7n.js';
5
5
  import './BytecodeBuilder-3egMyuDi.js';
6
- import './Token-0jpvUdPY.js';
6
+ import './Token-BzG5G4ja.js';
7
7
  import './pipeline-CrsRPmsv.js';
8
8
  import './Value-KtAQNHur.js';
9
9
  import './variables.js';
10
- import './Lexer-D9l4Yrk2.js';
10
+ import './Lexer-W9MBOP0V.js';
11
11
  import './resolvers.js';
12
12
  import '@tanstack/query-core';
13
- import './NormalizerRule-CEjf1FyD.js';
13
+ import './TokenNormalizer-t_GotBxr.js';
14
14
  import './ScopeManager-B5k0nk05.js';
15
15
  import './EngineError-DvPSnhZi.js';
16
16
  import './Configuration-fVpd-HC-.js';
package/dist/index.js CHANGED
@@ -1,34 +1,33 @@
1
- import { assertEngineVersionCompatible } from './chunk-M5LX5AOO.js';
2
- export { ExpressionEngine, assertEngineVersionCompatible, checkEngineVersionCompatibility, checkPackageCompatibility } from './chunk-M5LX5AOO.js';
3
- import { sharedParseletRegistry } from './chunk-SDGRK7EP.js';
1
+ import { assertEngineVersionCompatible } from './chunk-DDQVZGLO.js';
2
+ export { ExpressionEngine, assertEngineVersionCompatible, checkEngineVersionCompatibility, checkPackageCompatibility } from './chunk-DDQVZGLO.js';
3
+ import { sharedParseletRegistry } from './chunk-MEOHSQEH.js';
4
4
  import './chunk-2RWAXT6O.js';
5
5
  import './chunk-3VGDNEE3.js';
6
6
  import './chunk-IF532O7C.js';
7
- import './chunk-QWY3VEZN.js';
7
+ import './chunk-3MNKQZ77.js';
8
8
  import './chunk-G535KJEG.js';
9
- import './chunk-3PPFLFH4.js';
10
- import './chunk-5YEMOYSE.js';
11
- import './chunk-64W6GLLZ.js';
9
+ import './chunk-FIFQPKBA.js';
10
+ import './chunk-6GCKCWLB.js';
11
+ import './chunk-OFXOTECC.js';
12
12
  import './chunk-PFSDGGYL.js';
13
- import './chunk-EPOXXJBK.js';
13
+ import './chunk-YU2CUNFO.js';
14
14
  import './chunk-NLNDMWZC.js';
15
- import './chunk-4QADQTWS.js';
15
+ import './chunk-KHJUSHFU.js';
16
16
  import { sharedVariableResolver } from './chunk-GCM5JHMX.js';
17
- import './chunk-DM3LMRBC.js';
17
+ import './chunk-UKPGSAZW.js';
18
18
  import './chunk-75JP4BZX.js';
19
19
  import './chunk-Z4X2RWOF.js';
20
- import './chunk-RFYD5TJE.js';
21
- import './chunk-34RRD7PC.js';
20
+ import './chunk-CYFK5SY2.js';
22
21
  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';
22
+ import './chunk-WC5FFSHB.js';
23
+ export { ENGINE_VERSION } from './chunk-OWRHDUJD.js';
24
+ import { sharedLexer } from './chunk-2CS6OMZK.js';
25
+ import './chunk-AJA6LUI7.js';
26
+ import './chunk-SFQWJMKT.js';
27
+ import './chunk-GPPLSM2Z.js';
28
+ import './chunk-XBTEO4OB.js';
29
+ import './chunk-M5E34VG5.js';
30
+ import './chunk-PCPX42KL.js';
32
31
  import './chunk-6RDSQURE.js';
33
32
  import './chunk-YLJ4XMA6.js';
34
33
 
package/dist/index.js.map CHANGED
@@ -1 +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"]}
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"]}
package/dist/language.cjs CHANGED
@@ -1,9 +1,9 @@
1
1
  'use strict';
2
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');
3
+ var chunkNJTXJ5AG_cjs = require('./chunk-NJTXJ5AG.cjs');
4
+ var chunkTY3TLZAW_cjs = require('./chunk-TY3TLZAW.cjs');
5
+ var chunkCLVQBF5C_cjs = require('./chunk-CLVQBF5C.cjs');
6
+ require('./chunk-MBNQVDVC.cjs');
7
7
  require('./chunk-B7LXJ6LC.cjs');
8
8
 
9
9
  // src/language/LanguageService.ts
@@ -58,6 +58,7 @@ var LanguageService = class {
58
58
  this.staticCompletionIndex = null;
59
59
  this.engine = engine ?? null;
60
60
  this.variableNameSource = options?.variableNameSource ?? (() => this.defaultVariableNames());
61
+ this.normalizeForHighlighting = options?.normalizeForHighlighting ?? false;
61
62
  }
62
63
  defaultVariableNames() {
63
64
  if (!this.engine) return [];
@@ -68,6 +69,67 @@ var LanguageService = class {
68
69
  }
69
70
  return names;
70
71
  }
72
+ /**
73
+ * Lex a line, normalize it, and place every resulting token back in the
74
+ * source text.
75
+ *
76
+ * ## Why placing them back is the hard part
77
+ *
78
+ * Normalization produces three kinds of token and only one of them can be
79
+ * highlighted the obvious way.
80
+ *
81
+ * A token the normalizer left alone still describes its own text, so its
82
+ * span is `offset` to `offset + value.length`, exactly as before.
83
+ *
84
+ * A FUSED token does not. `10 frames` becomes a FRAME_COUNT whose value is
85
+ * `10`, and a timecode becomes a token whose value is a comma-separated
86
+ * tuple appearing nowhere on the line. Those carry `sourceEnd`, set by
87
+ * `createFusedToken`, which is the only place that knows where the fusion
88
+ * ended.
89
+ *
90
+ * An INSERTED token has no text at all. Implicit multiplication puts a STAR
91
+ * at the following token's offset, so `5(3)` gains a `*` sitting exactly
92
+ * where the `(` is. Painting it would colour a character the reader never
93
+ * typed as an operator, and would overlap the token that really is there.
94
+ * These are dropped, detected by the one test that needs no cooperation
95
+ * from any rule: a token with no recorded fusion span must match the text
96
+ * at its own offset, and an inserted one does not.
97
+ *
98
+ * @param lineText - The raw line.
99
+ * @returns Spans in the same shape `Lexer.getHighlightTokens` returns.
100
+ */
101
+ classifyNormalized(lineText) {
102
+ const lexer = this.engine.getLexer();
103
+ const raw = lexer.getHighlightTokenObjects(lineText);
104
+ if (raw.length === 0) return [];
105
+ const source = lineText.startsWith("> ") && lexer.classifyLine(lineText).skip ? lineText.slice(2) : lineText;
106
+ let normalized;
107
+ try {
108
+ normalized = this.engine.getNormalizer().normalize(raw);
109
+ } catch {
110
+ normalized = raw;
111
+ }
112
+ const out = [];
113
+ for (const token of normalized) {
114
+ let end;
115
+ if (token.sourceEnd !== void 0) {
116
+ end = token.sourceEnd;
117
+ } else if (source.startsWith(token.value, token.offset)) {
118
+ end = token.offset + token.value.length;
119
+ } else {
120
+ continue;
121
+ }
122
+ out.push({
123
+ type: token.type,
124
+ value: token.value,
125
+ offset: token.offset,
126
+ col: token.col,
127
+ length: end - token.offset,
128
+ category: chunkTY3TLZAW_cjs.getTokenCategory(token.type)
129
+ });
130
+ }
131
+ return out;
132
+ }
71
133
  /**
72
134
  * Classify every recognized token on one line.
73
135
  *
@@ -88,7 +150,7 @@ var LanguageService = class {
88
150
  return [];
89
151
  }
90
152
  const lexer = this.engine.getLexer();
91
- const lexed = lexer.getHighlightTokens(lineText);
153
+ const lexed = this.normalizeForHighlighting ? this.classifyNormalized(lineText) : lexer.getHighlightTokens(lineText);
92
154
  if (lexed.length === 0) {
93
155
  this.putCache(lineNumber, lineText, []);
94
156
  return [];
@@ -166,12 +228,12 @@ var LanguageService = class {
166
228
  if (this.staticCompletionCandidates) return this.staticCompletionCandidates;
167
229
  const items = [];
168
230
  for (const [word, tokenType] of Object.entries(this.engine.getLexer().getKeywords())) {
169
- const category = chunk6BKTCEUP_cjs.getTokenCategory(tokenType);
231
+ const category = chunkTY3TLZAW_cjs.getTokenCategory(tokenType);
170
232
  if (!category) continue;
171
233
  items.push({ label: word, category });
172
234
  }
173
- for (const unit of chunkEHAHVROS_cjs.knownUnits) {
174
- items.push({ label: unit, category: "unit", detail: chunkQNJ4ACRT_cjs.getMeasure(unit) });
235
+ for (const unit of chunkCLVQBF5C_cjs.knownUnits) {
236
+ items.push({ label: unit, category: "unit", detail: chunkNJTXJ5AG_cjs.getMeasure(unit) });
175
237
  }
176
238
  items.push(...this.engine.getPackageCompletionItems());
177
239
  this.staticCompletionCandidates = items;
@@ -285,19 +347,19 @@ function completionItemToOption(item) {
285
347
 
286
348
  Object.defineProperty(exports, "UNCATEGORIZED_TOKEN_TYPES", {
287
349
  enumerable: true,
288
- get: function () { return chunk6BKTCEUP_cjs.UNCATEGORIZED_TOKEN_TYPES; }
350
+ get: function () { return chunkTY3TLZAW_cjs.UNCATEGORIZED_TOKEN_TYPES; }
289
351
  });
290
352
  Object.defineProperty(exports, "getTokenCategory", {
291
353
  enumerable: true,
292
- get: function () { return chunk6BKTCEUP_cjs.getTokenCategory; }
354
+ get: function () { return chunkTY3TLZAW_cjs.getTokenCategory; }
293
355
  });
294
356
  Object.defineProperty(exports, "registerTokenCategory", {
295
357
  enumerable: true,
296
- get: function () { return chunk6BKTCEUP_cjs.registerTokenCategory; }
358
+ get: function () { return chunkTY3TLZAW_cjs.registerTokenCategory; }
297
359
  });
298
360
  Object.defineProperty(exports, "unregisterTokenCategory", {
299
361
  enumerable: true,
300
- get: function () { return chunk6BKTCEUP_cjs.unregisterTokenCategory; }
362
+ get: function () { return chunkTY3TLZAW_cjs.unregisterTokenCategory; }
301
363
  });
302
364
  exports.DEFAULT_TOKEN_CLASS_PREFIX = DEFAULT_TOKEN_CLASS_PREFIX;
303
365
  exports.LanguageService = LanguageService;