solve-engine 1.0.1 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (237) hide show
  1. package/dist/{BytecodeBuilder-B0xskcv5.d.cts → BytecodeBuilder-Bp9xeTmX.d.cts} +4 -0
  2. package/dist/{BytecodeBuilder-B0xskcv5.d.ts → BytecodeBuilder-Bp9xeTmX.d.ts} +4 -0
  3. package/dist/{Configuration-B-G5gTRn.d.cts → Configuration-C9W8tJv_.d.cts} +53 -1
  4. package/dist/{Configuration-B-G5gTRn.d.ts → Configuration-C9W8tJv_.d.ts} +53 -1
  5. package/dist/{EngineError-LU7W7AgI.d.cts → EngineError-B61GS1jp.d.cts} +14 -0
  6. package/dist/{EngineError-LU7W7AgI.d.ts → EngineError-B61GS1jp.d.ts} +14 -0
  7. package/dist/FormattingSettings-CJHyxcYu.d.cts +27 -0
  8. package/dist/FormattingSettings-CJHyxcYu.d.ts +27 -0
  9. package/dist/{Lexer-D78E2yDW.d.cts → Lexer-BOs7euZe.d.cts} +58 -1
  10. package/dist/{Lexer-BN5mt30n.d.ts → Lexer-CSI_lwbW.d.ts} +58 -1
  11. package/dist/PackageCompatibility-B-7rK1TD.d.cts +76 -0
  12. package/dist/PackageCompatibility-Dh59eF-X.d.ts +76 -0
  13. package/dist/{PackageRegistry-B37mRvvz.d.cts → PackageRegistry-BHWJP83F.d.cts} +449 -11
  14. package/dist/{PackageRegistry-DCUEzbQt.d.ts → PackageRegistry-_8rDlvxI.d.ts} +449 -11
  15. package/dist/{Parselet-BaySkMV3.d.ts → Parselet-BBT8riYh.d.ts} +12 -3
  16. package/dist/{Parselet-DuI1Pjiq.d.cts → Parselet-BHcgK9S7.d.cts} +12 -3
  17. package/dist/{ScopeManager-B6GzdhVG.d.cts → ScopeManager-8vf02dwj.d.cts} +56 -5
  18. package/dist/{ScopeManager-udv4Twwq.d.ts → ScopeManager-CxA24W5n.d.ts} +56 -5
  19. package/dist/{Token-BzG5G4ja.d.cts → Token-B1hdkedD.d.cts} +9 -0
  20. package/dist/{Token-BzG5G4ja.d.ts → Token-B1hdkedD.d.ts} +9 -0
  21. package/dist/{TokenNormalizer-t_GotBxr.d.ts → TokenNormalizer-C6VzZgHa.d.ts} +1 -1
  22. package/dist/{TokenNormalizer-DGVa24Q-.d.cts → TokenNormalizer-DRc1Js1V.d.cts} +1 -1
  23. package/dist/{VMCheckpoints-DwLjivM7.d.cts → VMCheckpoints-ELYqITdF.d.cts} +3 -3
  24. package/dist/{VMCheckpoints-BiaIlOOY.d.ts → VMCheckpoints-MK--EBH2.d.ts} +3 -3
  25. package/dist/{Value-CXJqDH9J.d.cts → Value-BUi1RA3S.d.cts} +124 -5
  26. package/dist/{Value-CXJqDH9J.d.ts → Value-BUi1RA3S.d.ts} +124 -5
  27. package/dist/WorkerError-_-RkoQ5P.d.ts +75 -0
  28. package/dist/WorkerError-gzmaopj2.d.cts +75 -0
  29. package/dist/{chunk-GQCOSXMG.js → chunk-267JPOTF.js} +43 -5
  30. package/dist/chunk-267JPOTF.js.map +1 -0
  31. package/dist/{chunk-JMXUNXQS.cjs → chunk-2TZKENDH.cjs} +51 -8
  32. package/dist/chunk-2TZKENDH.cjs.map +1 -0
  33. package/dist/chunk-3OWCDIPN.js +215 -0
  34. package/dist/chunk-3OWCDIPN.js.map +1 -0
  35. package/dist/{chunk-VB37OC6I.js → chunk-4D6NIHE2.js} +40 -3
  36. package/dist/chunk-4D6NIHE2.js.map +1 -0
  37. package/dist/{chunk-TY3TLZAW.cjs → chunk-536WPM2V.cjs} +18 -2
  38. package/dist/chunk-536WPM2V.cjs.map +1 -0
  39. package/dist/chunk-5HRB36DK.js +405 -0
  40. package/dist/chunk-5HRB36DK.js.map +1 -0
  41. package/dist/chunk-5ON7PUAZ.js +12 -0
  42. package/dist/chunk-5ON7PUAZ.js.map +1 -0
  43. package/dist/{chunk-KV7UW6T6.js → chunk-7KYSUDQO.js} +12 -5
  44. package/dist/chunk-7KYSUDQO.js.map +1 -0
  45. package/dist/{chunk-6WFMPTGB.cjs → chunk-7ZX6B7SY.cjs} +774 -528
  46. package/dist/chunk-7ZX6B7SY.cjs.map +1 -0
  47. package/dist/chunk-ALYRJ72W.cjs +434 -0
  48. package/dist/chunk-ALYRJ72W.cjs.map +1 -0
  49. package/dist/{chunk-5KMIY374.cjs → chunk-B4HBEFTB.cjs} +17 -2
  50. package/dist/chunk-B4HBEFTB.cjs.map +1 -0
  51. package/dist/chunk-B7NLZMQ3.cjs +74 -0
  52. package/dist/chunk-B7NLZMQ3.cjs.map +1 -0
  53. package/dist/{chunk-UQ3UIZJC.js → chunk-BLI4NIQY.js} +6 -2
  54. package/dist/chunk-BLI4NIQY.js.map +1 -0
  55. package/dist/{chunk-BHTNFEGZ.cjs → chunk-C75AFGVD.cjs} +11 -11
  56. package/dist/{chunk-BHTNFEGZ.cjs.map → chunk-C75AFGVD.cjs.map} +1 -1
  57. package/dist/{chunk-G535KJEG.js → chunk-CCBZZQAE.js} +2 -2
  58. package/dist/{chunk-G535KJEG.js.map → chunk-CCBZZQAE.js.map} +1 -1
  59. package/dist/chunk-CKQMXMHR.cjs +219 -0
  60. package/dist/chunk-CKQMXMHR.cjs.map +1 -0
  61. package/dist/{chunk-PFUESQTW.cjs → chunk-EJ3ILXX6.cjs} +48 -2
  62. package/dist/chunk-EJ3ILXX6.cjs.map +1 -0
  63. package/dist/{chunk-526PMQOA.js → chunk-ENKKJYD3.js} +10 -4
  64. package/dist/chunk-ENKKJYD3.js.map +1 -0
  65. package/dist/{chunk-5X2PTP6F.cjs → chunk-ENRIK36Q.cjs} +2 -12
  66. package/dist/chunk-ENRIK36Q.cjs.map +1 -0
  67. package/dist/{chunk-3D7V24DG.js → chunk-ERCOHGXD.js} +17 -2
  68. package/dist/chunk-ERCOHGXD.js.map +1 -0
  69. package/dist/{chunk-IF532O7C.js → chunk-FD5ZZHEU.js} +3 -12
  70. package/dist/chunk-FD5ZZHEU.js.map +1 -0
  71. package/dist/{chunk-TBN7DEHO.js → chunk-G7Z4HJQA.js} +11 -5
  72. package/dist/chunk-G7Z4HJQA.js.map +1 -0
  73. package/dist/{chunk-QY25VWBF.js → chunk-GQMUHVE3.js} +142 -6
  74. package/dist/chunk-GQMUHVE3.js.map +1 -0
  75. package/dist/chunk-GUG7SNSV.js +1764 -0
  76. package/dist/chunk-GUG7SNSV.js.map +1 -0
  77. package/dist/{chunk-UM6BVY2S.cjs → chunk-HIQ5HSZL.cjs} +175 -43
  78. package/dist/chunk-HIQ5HSZL.cjs.map +1 -0
  79. package/dist/{chunk-HVQFNJKE.cjs → chunk-HVRVSI2Z.cjs} +104 -86
  80. package/dist/chunk-HVRVSI2Z.cjs.map +1 -0
  81. package/dist/{chunk-3LAEG75D.js → chunk-I4GAWIPW.js} +24 -6
  82. package/dist/chunk-I4GAWIPW.js.map +1 -0
  83. package/dist/{chunk-PA4VC73I.cjs → chunk-J45BCEZ4.cjs} +33 -27
  84. package/dist/chunk-J45BCEZ4.cjs.map +1 -0
  85. package/dist/{chunk-V5PYO44Y.js → chunk-JIPATHVY.js} +1853 -2638
  86. package/dist/chunk-JIPATHVY.js.map +1 -0
  87. package/dist/{chunk-YPPPYLSR.js → chunk-JJIXHXFQ.js} +5 -5
  88. package/dist/{chunk-YPPPYLSR.js.map → chunk-JJIXHXFQ.js.map} +1 -1
  89. package/dist/chunk-L2TE7PMO.cjs +14 -0
  90. package/dist/chunk-L2TE7PMO.cjs.map +1 -0
  91. package/dist/{chunk-B7TLZABL.cjs → chunk-MG6Q3DUO.cjs} +1962 -2746
  92. package/dist/chunk-MG6Q3DUO.cjs.map +1 -0
  93. package/dist/{chunk-R3PY4G7J.js → chunk-MVTOCRV2.js} +48 -3
  94. package/dist/chunk-MVTOCRV2.js.map +1 -0
  95. package/dist/{chunk-Y7FT4IQT.js → chunk-NKW7LKYU.js} +266 -28
  96. package/dist/chunk-NKW7LKYU.js.map +1 -0
  97. package/dist/{chunk-O3ANBHSA.js → chunk-NNQ2TYDF.js} +123 -5
  98. package/dist/chunk-NNQ2TYDF.js.map +1 -0
  99. package/dist/{chunk-5F4C26RU.js → chunk-OADDUPT3.js} +4 -4
  100. package/dist/{chunk-5F4C26RU.js.map → chunk-OADDUPT3.js.map} +1 -1
  101. package/dist/{chunk-3AFRJYP4.cjs → chunk-OTN6SZOD.cjs} +1868 -393
  102. package/dist/chunk-OTN6SZOD.cjs.map +1 -0
  103. package/dist/{chunk-5LI5EPGJ.cjs → chunk-QFTDTX6K.cjs} +40 -3
  104. package/dist/chunk-QFTDTX6K.cjs.map +1 -0
  105. package/dist/{chunk-HMOISHXR.js → chunk-RRLHC37V.js} +1530 -57
  106. package/dist/chunk-RRLHC37V.js.map +1 -0
  107. package/dist/chunk-SNHVVOJK.cjs +1768 -0
  108. package/dist/chunk-SNHVVOJK.cjs.map +1 -0
  109. package/dist/{chunk-HDP7VK3C.cjs → chunk-SRGQ72IR.cjs} +649 -349
  110. package/dist/chunk-SRGQ72IR.cjs.map +1 -0
  111. package/dist/{chunk-4B2CNWQU.cjs → chunk-SSV46KFA.cjs} +14 -7
  112. package/dist/chunk-SSV46KFA.cjs.map +1 -0
  113. package/dist/{chunk-AJA6LUI7.js → chunk-UO6BUV6K.js} +18 -2
  114. package/dist/chunk-UO6BUV6K.js.map +1 -0
  115. package/dist/chunk-WWQEY7BV.js +67 -0
  116. package/dist/chunk-WWQEY7BV.js.map +1 -0
  117. package/dist/{chunk-5WVP4YHP.js → chunk-WXEHD6TT.js} +333 -33
  118. package/dist/chunk-WXEHD6TT.js.map +1 -0
  119. package/dist/{chunk-47LRVGOT.cjs → chunk-XA4CKRML.cjs} +2 -2
  120. package/dist/{chunk-47LRVGOT.cjs.map → chunk-XA4CKRML.cjs.map} +1 -1
  121. package/dist/{chunk-6KFYJ6TD.cjs → chunk-XIYYHA65.cjs} +6 -2
  122. package/dist/chunk-XIYYHA65.cjs.map +1 -0
  123. package/dist/{chunk-KBSXGXPM.cjs → chunk-XJGJCF2R.cjs} +6 -6
  124. package/dist/{chunk-KBSXGXPM.cjs.map → chunk-XJGJCF2R.cjs.map} +1 -1
  125. package/dist/{chunk-YWQ6V4ZN.cjs → chunk-YTIYVHL7.cjs} +211 -75
  126. package/dist/chunk-YTIYVHL7.cjs.map +1 -0
  127. package/dist/{chunk-TMA4RCEN.js → chunk-Z7XGGLI2.js} +3 -3
  128. package/dist/{chunk-TMA4RCEN.js.map → chunk-Z7XGGLI2.js.map} +1 -1
  129. package/dist/{chunk-A2N2GFCG.cjs → chunk-ZVTWQLK4.cjs} +10 -10
  130. package/dist/{chunk-A2N2GFCG.cjs.map → chunk-ZVTWQLK4.cjs.map} +1 -1
  131. package/dist/{chunk-2XLQPQKI.cjs → chunk-ZXCESUJS.cjs} +15 -9
  132. package/dist/chunk-ZXCESUJS.cjs.map +1 -0
  133. package/dist/constants.cjs +5 -5
  134. package/dist/constants.d.cts +1 -1
  135. package/dist/constants.d.ts +1 -1
  136. package/dist/constants.js +2 -2
  137. package/dist/engine.cjs +52 -35
  138. package/dist/engine.d.cts +13 -13
  139. package/dist/engine.d.ts +13 -13
  140. package/dist/engine.js +26 -21
  141. package/dist/errors.cjs +44 -19
  142. package/dist/errors.d.cts +3 -2
  143. package/dist/errors.d.ts +3 -2
  144. package/dist/errors.js +2 -1
  145. package/dist/format.cjs +19 -169
  146. package/dist/format.cjs.map +1 -1
  147. package/dist/format.d.cts +18 -26
  148. package/dist/format.d.ts +18 -26
  149. package/dist/format.js +7 -171
  150. package/dist/format.js.map +1 -1
  151. package/dist/index.cjs +250 -31
  152. package/dist/index.cjs.map +1 -1
  153. package/dist/index.d.cts +110 -74
  154. package/dist/index.d.ts +110 -74
  155. package/dist/index.js +228 -23
  156. package/dist/index.js.map +1 -1
  157. package/dist/language.cjs +9 -9
  158. package/dist/language.d.cts +12 -12
  159. package/dist/language.d.ts +12 -12
  160. package/dist/language.js +3 -3
  161. package/dist/lexer.cjs +16 -16
  162. package/dist/lexer.d.cts +3 -3
  163. package/dist/lexer.d.ts +3 -3
  164. package/dist/lexer.js +5 -5
  165. package/dist/normalizer.cjs +10 -10
  166. package/dist/normalizer.d.cts +3 -3
  167. package/dist/normalizer.d.ts +3 -3
  168. package/dist/normalizer.js +4 -4
  169. package/dist/packages.cjs +45 -36
  170. package/dist/packages.d.cts +135 -31
  171. package/dist/packages.d.ts +135 -31
  172. package/dist/packages.js +14 -13
  173. package/dist/parser.cjs +16 -16
  174. package/dist/parser.d.cts +6 -5
  175. package/dist/parser.d.ts +6 -5
  176. package/dist/parser.js +6 -6
  177. package/dist/{pipeline-BEb3hujr.d.cts → pipeline-CtfJtPQc.d.cts} +3 -3
  178. package/dist/{pipeline-B6k5lCB7.d.ts → pipeline-DCd5M6Gk.d.ts} +3 -3
  179. package/dist/resolvers.d.cts +3 -3
  180. package/dist/resolvers.d.ts +3 -3
  181. package/dist/testing.cjs +478 -0
  182. package/dist/testing.cjs.map +1 -0
  183. package/dist/testing.d.cts +271 -0
  184. package/dist/testing.d.ts +271 -0
  185. package/dist/testing.js +470 -0
  186. package/dist/testing.js.map +1 -0
  187. package/dist/uom.cjs +16 -16
  188. package/dist/uom.d.cts +3 -3
  189. package/dist/uom.d.ts +3 -3
  190. package/dist/uom.js +6 -6
  191. package/dist/utilities.cjs +6 -5
  192. package/dist/utilities.js +2 -1
  193. package/dist/vm.cjs +34 -34
  194. package/dist/vm.d.cts +8 -8
  195. package/dist/vm.d.ts +8 -8
  196. package/dist/vm.js +9 -9
  197. package/dist/worker.cjs +493 -0
  198. package/dist/worker.cjs.map +1 -0
  199. package/dist/worker.d.cts +509 -0
  200. package/dist/worker.d.ts +509 -0
  201. package/dist/worker.js +484 -0
  202. package/dist/worker.js.map +1 -0
  203. package/package.json +21 -1
  204. package/dist/chunk-2XLQPQKI.cjs.map +0 -1
  205. package/dist/chunk-3AFRJYP4.cjs.map +0 -1
  206. package/dist/chunk-3D7V24DG.js.map +0 -1
  207. package/dist/chunk-3LAEG75D.js.map +0 -1
  208. package/dist/chunk-4B2CNWQU.cjs.map +0 -1
  209. package/dist/chunk-526PMQOA.js.map +0 -1
  210. package/dist/chunk-5KMIY374.cjs.map +0 -1
  211. package/dist/chunk-5LI5EPGJ.cjs.map +0 -1
  212. package/dist/chunk-5WVP4YHP.js.map +0 -1
  213. package/dist/chunk-5X2PTP6F.cjs.map +0 -1
  214. package/dist/chunk-6KFYJ6TD.cjs.map +0 -1
  215. package/dist/chunk-6WFMPTGB.cjs.map +0 -1
  216. package/dist/chunk-AJA6LUI7.js.map +0 -1
  217. package/dist/chunk-B7TLZABL.cjs.map +0 -1
  218. package/dist/chunk-GQCOSXMG.js.map +0 -1
  219. package/dist/chunk-HDP7VK3C.cjs.map +0 -1
  220. package/dist/chunk-HMOISHXR.js.map +0 -1
  221. package/dist/chunk-HVQFNJKE.cjs.map +0 -1
  222. package/dist/chunk-IF532O7C.js.map +0 -1
  223. package/dist/chunk-JMXUNXQS.cjs.map +0 -1
  224. package/dist/chunk-KV7UW6T6.js.map +0 -1
  225. package/dist/chunk-O3ANBHSA.js.map +0 -1
  226. package/dist/chunk-PA4VC73I.cjs.map +0 -1
  227. package/dist/chunk-PFUESQTW.cjs.map +0 -1
  228. package/dist/chunk-QY25VWBF.js.map +0 -1
  229. package/dist/chunk-R3PY4G7J.js.map +0 -1
  230. package/dist/chunk-TBN7DEHO.js.map +0 -1
  231. package/dist/chunk-TY3TLZAW.cjs.map +0 -1
  232. package/dist/chunk-UM6BVY2S.cjs.map +0 -1
  233. package/dist/chunk-UQ3UIZJC.js.map +0 -1
  234. package/dist/chunk-V5PYO44Y.js.map +0 -1
  235. package/dist/chunk-VB37OC6I.js.map +0 -1
  236. package/dist/chunk-Y7FT4IQT.js.map +0 -1
  237. package/dist/chunk-YWQ6V4ZN.cjs.map +0 -1
@@ -1,45 +1,49 @@
1
1
  'use strict';
2
2
 
3
- var chunkPA4VC73I_cjs = require('./chunk-PA4VC73I.cjs');
4
- var chunkBHTNFEGZ_cjs = require('./chunk-BHTNFEGZ.cjs');
3
+ var chunkJ45BCEZ4_cjs = require('./chunk-J45BCEZ4.cjs');
4
+ var chunkC75AFGVD_cjs = require('./chunk-C75AFGVD.cjs');
5
5
  var chunkADBA3XBE_cjs = require('./chunk-ADBA3XBE.cjs');
6
- var chunk4B2CNWQU_cjs = require('./chunk-4B2CNWQU.cjs');
6
+ var chunkSSV46KFA_cjs = require('./chunk-SSV46KFA.cjs');
7
7
  var chunkZ2XP767T_cjs = require('./chunk-Z2XP767T.cjs');
8
- var chunk6WFMPTGB_cjs = require('./chunk-6WFMPTGB.cjs');
8
+ var chunk7ZX6B7SY_cjs = require('./chunk-7ZX6B7SY.cjs');
9
+ var chunkZVTWQLK4_cjs = require('./chunk-ZVTWQLK4.cjs');
9
10
  var chunkVEKIWQ3F_cjs = require('./chunk-VEKIWQ3F.cjs');
10
- var chunkUM6BVY2S_cjs = require('./chunk-UM6BVY2S.cjs');
11
- var chunkJMXUNXQS_cjs = require('./chunk-JMXUNXQS.cjs');
12
- var chunkPFUESQTW_cjs = require('./chunk-PFUESQTW.cjs');
13
- var chunkYWQ6V4ZN_cjs = require('./chunk-YWQ6V4ZN.cjs');
14
- var chunk5LI5EPGJ_cjs = require('./chunk-5LI5EPGJ.cjs');
11
+ var chunkALYRJ72W_cjs = require('./chunk-ALYRJ72W.cjs');
12
+ var chunkHIQ5HSZL_cjs = require('./chunk-HIQ5HSZL.cjs');
13
+ var chunk2TZKENDH_cjs = require('./chunk-2TZKENDH.cjs');
14
+ var chunkEJ3ILXX6_cjs = require('./chunk-EJ3ILXX6.cjs');
15
+ var chunkYTIYVHL7_cjs = require('./chunk-YTIYVHL7.cjs');
16
+ var chunkQFTDTX6K_cjs = require('./chunk-QFTDTX6K.cjs');
15
17
  var chunkOUHT5Z36_cjs = require('./chunk-OUHT5Z36.cjs');
16
18
  var chunkAQNHHJMJ_cjs = require('./chunk-AQNHHJMJ.cjs');
17
19
  var chunkAMOJYLG2_cjs = require('./chunk-AMOJYLG2.cjs');
18
- var chunk5KMIY374_cjs = require('./chunk-5KMIY374.cjs');
20
+ var chunkB4HBEFTB_cjs = require('./chunk-B4HBEFTB.cjs');
19
21
 
20
22
  // src/packages/arithmetic/parselets/NumberParselet.ts
21
23
  var CHAINED_DOT_THOUSANDS_GROUPS = /^\d{1,3}(\.\d{3}){2,}$/;
24
+ var PLAIN_DECIMAL = /^\d*\.\d*$/;
22
25
  var NumberParselet = class {
23
26
  constructor() {
24
27
  this.category = "Arithmetic";
25
28
  }
26
29
  parse(parser, token, builder) {
27
30
  let v;
31
+ let decimalText = null;
28
32
  const raw = token.value;
29
33
  if (raw.startsWith("0x") || raw.startsWith("0X")) {
30
34
  v = parseInt(raw, 16);
31
35
  if (Number.isNaN(v)) {
32
- throw chunk5KMIY374_cjs.ErrorFactory.parsing("INVALID_NUMBER_LITERAL", `Invalid hex literal: "${raw}"`, { raw });
36
+ throw chunkB4HBEFTB_cjs.ErrorFactory.parsing("INVALID_NUMBER_LITERAL", `Invalid hex literal: "${raw}"`, { raw });
33
37
  }
34
38
  } else if (raw.startsWith("0b") || raw.startsWith("0B")) {
35
39
  v = parseInt(raw.slice(2), 2);
36
40
  if (Number.isNaN(v)) {
37
- throw chunk5KMIY374_cjs.ErrorFactory.parsing("INVALID_NUMBER_LITERAL", `Invalid binary literal: "${raw}"`, { raw });
41
+ throw chunkB4HBEFTB_cjs.ErrorFactory.parsing("INVALID_NUMBER_LITERAL", `Invalid binary literal: "${raw}"`, { raw });
38
42
  }
39
43
  } else if (raw.startsWith("0o") || raw.startsWith("0O")) {
40
44
  v = parseInt(raw.slice(2), 8);
41
45
  if (Number.isNaN(v)) {
42
- throw chunk5KMIY374_cjs.ErrorFactory.parsing("INVALID_NUMBER_LITERAL", `Invalid octal literal: "${raw}"`, { raw });
46
+ throw chunkB4HBEFTB_cjs.ErrorFactory.parsing("INVALID_NUMBER_LITERAL", `Invalid octal literal: "${raw}"`, { raw });
43
47
  }
44
48
  } else if (CHAINED_DOT_THOUSANDS_GROUPS.test(raw)) {
45
49
  v = parseFloat(raw.split(".").join(""));
@@ -55,6 +59,12 @@ var NumberParselet = class {
55
59
  normalized = normalized.replace(decimalSep, ".");
56
60
  }
57
61
  v = parseFloat(normalized);
62
+ if (PLAIN_DECIMAL.test(normalized)) decimalText = normalized;
63
+ }
64
+ if (decimalText !== null) {
65
+ builder.emitOpcode(16 /* PUSH_DECIMAL */);
66
+ builder.emitString(decimalText);
67
+ return;
58
68
  }
59
69
  builder.emitOpcode(10 /* PUSH_NUMBER */);
60
70
  builder.emitNumber(v);
@@ -141,7 +151,7 @@ var WORD_MAGNITUDE = {
141
151
  trillions: 12,
142
152
  tn: 12
143
153
  };
144
- var PLAIN_DECIMAL = /^\d+(\.\d+)?$/;
154
+ var PLAIN_DECIMAL2 = /^\d+(\.\d+)?$/;
145
155
  function scaleDecimalString(raw, magnitude) {
146
156
  const dotIdx = raw.indexOf(".");
147
157
  const intPart = dotIdx === -1 ? raw : raw.slice(0, dotIdx);
@@ -160,7 +170,7 @@ function largeNumberSuffixNormalizerRule(priority = 65) {
160
170
  match(tokens, pos) {
161
171
  const numberToken = tokens[pos];
162
172
  if (numberToken.type !== "NUMBER") return null;
163
- if (!PLAIN_DECIMAL.test(numberToken.text)) return null;
173
+ if (!PLAIN_DECIMAL2.test(numberToken.text)) return null;
164
174
  const suffixToken = tokens[pos + 1];
165
175
  if (!suffixToken) return null;
166
176
  if (suffixToken.type !== "IDENT" && suffixToken.type !== "UNIT") return null;
@@ -171,7 +181,7 @@ function largeNumberSuffixNormalizerRule(priority = 65) {
171
181
  const scaled = scaleDecimalString(numberToken.text, magnitude);
172
182
  return {
173
183
  consumed: 2,
174
- replacement: [chunkBHTNFEGZ_cjs.createFusedToken("NUMBER", scaled, tokens.slice(pos, pos + 2))],
184
+ replacement: [chunkC75AFGVD_cjs.createFusedToken("NUMBER", scaled, tokens.slice(pos, pos + 2))],
175
185
  ruleName: "arithmetic:large-number-suffix"
176
186
  };
177
187
  }
@@ -194,35 +204,35 @@ var ARITHMETIC_PACKAGE = {
194
204
  { tokenType: "BIT_NOT", parselet: new PrefixOpParselet(36 /* BIT_NOT */) }
195
205
  ],
196
206
  infixParselets: [
197
- { tokenType: "PLUS", parselet: new BinaryOpParselet(chunkPA4VC73I_cjs.BindingPower.Sum, 20 /* ADD */) },
207
+ { tokenType: "PLUS", parselet: new BinaryOpParselet(chunkJ45BCEZ4_cjs.BindingPower.Sum, 20 /* ADD */) },
198
208
  // The word "and", which adds exactly like "+" but at a looser binding
199
209
  // power so a phrase parselet can use it as a list separator. See
200
210
  // Token.ts's AND_CONJ comment for why it is not simply mapped to PLUS.
201
- { tokenType: "AND_CONJ", parselet: new BinaryOpParselet(chunkPA4VC73I_cjs.BindingPower.Conjunction, 20 /* ADD */) },
202
- { tokenType: "MINUS", parselet: new BinaryOpParselet(chunkPA4VC73I_cjs.BindingPower.Sum, 21 /* SUB */) },
203
- { tokenType: "STAR", parselet: new BinaryOpParselet(chunkPA4VC73I_cjs.BindingPower.Product, 22 /* MUL */) },
204
- { tokenType: "SLASH", parselet: new BinaryOpParselet(chunkPA4VC73I_cjs.BindingPower.Product, 23 /* DIV */) },
205
- { tokenType: "MOD", parselet: new BinaryOpParselet(chunkPA4VC73I_cjs.BindingPower.Product, 24 /* MOD */) },
211
+ { tokenType: "AND_CONJ", parselet: new BinaryOpParselet(chunkJ45BCEZ4_cjs.BindingPower.Conjunction, 20 /* ADD */) },
212
+ { tokenType: "MINUS", parselet: new BinaryOpParselet(chunkJ45BCEZ4_cjs.BindingPower.Sum, 21 /* SUB */) },
213
+ { tokenType: "STAR", parselet: new BinaryOpParselet(chunkJ45BCEZ4_cjs.BindingPower.Product, 22 /* MUL */) },
214
+ { tokenType: "SLASH", parselet: new BinaryOpParselet(chunkJ45BCEZ4_cjs.BindingPower.Product, 23 /* DIV */) },
215
+ { tokenType: "MOD", parselet: new BinaryOpParselet(chunkJ45BCEZ4_cjs.BindingPower.Product, 24 /* MOD */) },
206
216
  // `^` is right-associative, so "2 ^ 3 ^ 2" is 2^(3^2) = 512. The Tier 1
207
217
  // fast path in PrecedenceParser is what actually runs for `^`; this
208
218
  // registration has to agree with it or the two tiers describe different
209
219
  // languages.
210
- { tokenType: "CARET", parselet: new BinaryOpParselet(chunkPA4VC73I_cjs.BindingPower.Exponent, 25 /* EXP */, true) },
211
- { tokenType: "TIMES_BY", parselet: new BinaryOpParselet(chunkPA4VC73I_cjs.BindingPower.Product, 22 /* MUL */) },
212
- { tokenType: "MULTIPLY_BY", parselet: new BinaryOpParselet(chunkPA4VC73I_cjs.BindingPower.Product, 22 /* MUL */) },
213
- { tokenType: "DIVIDE_BY", parselet: new BinaryOpParselet(chunkPA4VC73I_cjs.BindingPower.Product, 23 /* DIV */) },
220
+ { tokenType: "CARET", parselet: new BinaryOpParselet(chunkJ45BCEZ4_cjs.BindingPower.Exponent, 25 /* EXP */, true) },
221
+ { tokenType: "TIMES_BY", parselet: new BinaryOpParselet(chunkJ45BCEZ4_cjs.BindingPower.Product, 22 /* MUL */) },
222
+ { tokenType: "MULTIPLY_BY", parselet: new BinaryOpParselet(chunkJ45BCEZ4_cjs.BindingPower.Product, 22 /* MUL */) },
223
+ { tokenType: "DIVIDE_BY", parselet: new BinaryOpParselet(chunkJ45BCEZ4_cjs.BindingPower.Product, 23 /* DIV */) },
214
224
  // The shifts and the bitwise trio, at the levels BindingPower gives them
215
225
  // and therefore at C and JavaScript precedence. These six are declared
216
226
  // twice, here and in BUILTIN_INFIX_BP, and only the Tier 1 numbers are
217
227
  // ever read, so the levels must be the SAME names in both places. When
218
228
  // they were not, the four that had a fast-path entry ran at one level
219
229
  // while `>>>`, which had none, ran at another.
220
- { tokenType: "LSHIFT", parselet: new BinaryOpParselet(chunkPA4VC73I_cjs.BindingPower.Shift, 30 /* LSHIFT */) },
221
- { tokenType: "RSHIFT", parselet: new BinaryOpParselet(chunkPA4VC73I_cjs.BindingPower.Shift, 31 /* RSHIFT */) },
222
- { tokenType: "URSHIFT", parselet: new BinaryOpParselet(chunkPA4VC73I_cjs.BindingPower.Shift, 32 /* URSHIFT */) },
223
- { tokenType: "BIT_AND", parselet: new BinaryOpParselet(chunkPA4VC73I_cjs.BindingPower.BitwiseAnd, 33 /* BIT_AND */) },
224
- { tokenType: "BIT_OR", parselet: new BinaryOpParselet(chunkPA4VC73I_cjs.BindingPower.BitwiseOr, 34 /* BIT_OR */) },
225
- { tokenType: "BIT_XOR", parselet: new BinaryOpParselet(chunkPA4VC73I_cjs.BindingPower.BitwiseXor, 35 /* BIT_XOR */) }
230
+ { tokenType: "LSHIFT", parselet: new BinaryOpParselet(chunkJ45BCEZ4_cjs.BindingPower.Shift, 30 /* LSHIFT */) },
231
+ { tokenType: "RSHIFT", parselet: new BinaryOpParselet(chunkJ45BCEZ4_cjs.BindingPower.Shift, 31 /* RSHIFT */) },
232
+ { tokenType: "URSHIFT", parselet: new BinaryOpParselet(chunkJ45BCEZ4_cjs.BindingPower.Shift, 32 /* URSHIFT */) },
233
+ { tokenType: "BIT_AND", parselet: new BinaryOpParselet(chunkJ45BCEZ4_cjs.BindingPower.BitwiseAnd, 33 /* BIT_AND */) },
234
+ { tokenType: "BIT_OR", parselet: new BinaryOpParselet(chunkJ45BCEZ4_cjs.BindingPower.BitwiseOr, 34 /* BIT_OR */) },
235
+ { tokenType: "BIT_XOR", parselet: new BinaryOpParselet(chunkJ45BCEZ4_cjs.BindingPower.BitwiseXor, 35 /* BIT_XOR */) }
226
236
  ],
227
237
  normalizerRules: [largeNumberSuffixNormalizerRule()]
228
238
  };
@@ -231,7 +241,7 @@ var ARITHMETIC_PACKAGE = {
231
241
  var PercentParselet = class {
232
242
  constructor() {
233
243
  this.category = "Percentage";
234
- this.bindingPower = chunkPA4VC73I_cjs.BindingPower.Prefix;
244
+ this.bindingPower = chunkJ45BCEZ4_cjs.BindingPower.Prefix;
235
245
  }
236
246
  parse(parser, left, token, builder) {
237
247
  builder.emitOpcode(10 /* PUSH_NUMBER */);
@@ -247,7 +257,7 @@ var IsWhatParselet = class {
247
257
  this.category = "Percentage";
248
258
  // Below arithmetic, so the whole expression to the left is the value being
249
259
  // asked about: `10 + 10 is what % of 200` asks about 20.
250
- this.bindingPower = chunkPA4VC73I_cjs.BindingPower.Conditional;
260
+ this.bindingPower = chunkJ45BCEZ4_cjs.BindingPower.Conditional;
251
261
  }
252
262
  parse(parser, _left, _token, builder) {
253
263
  if (nextWordIs(parser, "what")) {
@@ -262,7 +272,7 @@ var IsWhatParselet = class {
262
272
  */
263
273
  parseWhatRate(parser, builder) {
264
274
  if (!parser.match("PERCENT")) {
265
- throw chunk5KMIY374_cjs.ErrorFactory.parsing(
275
+ throw chunkB4HBEFTB_cjs.ErrorFactory.parsing(
266
276
  "IS_WHAT_EXPECTED_PERCENT",
267
277
  'Expected "%" after "is what", as in "20 is what % of 200"'
268
278
  );
@@ -272,7 +282,7 @@ var IsWhatParselet = class {
272
282
  builder.emitOpcode(74 /* TO_PERCENTAGE */);
273
283
  return;
274
284
  }
275
- parser.parseExpression(chunkPA4VC73I_cjs.BindingPower.Conditional, builder);
285
+ parser.parseExpression(chunkJ45BCEZ4_cjs.BindingPower.Conditional, builder);
276
286
  builder.emitOpcode(23 /* DIV */);
277
287
  if (preposition === "off") {
278
288
  builder.emitOpcode(10 /* PUSH_NUMBER */);
@@ -290,7 +300,7 @@ var IsWhatParselet = class {
290
300
  * `<part> is <rate> of|off|on what`, solving for the base.
291
301
  */
292
302
  parseWhatBase(parser, builder) {
293
- parser.parseExpression(chunkPA4VC73I_cjs.BindingPower.Conditional, builder);
303
+ parser.parseExpression(chunkJ45BCEZ4_cjs.BindingPower.Conditional, builder);
294
304
  if (parser.match("TO")) {
295
305
  expectWord(parser, "what", '"81 is 9 to what power"');
296
306
  expectWord(parser, "power", '"81 is 9 to what power"');
@@ -307,7 +317,7 @@ var IsWhatParselet = class {
307
317
  }
308
318
  const preposition = parser.match("OF_WHAT") ? "of" : parser.match("OFF_WHAT") ? "off" : parser.match("ON_WHAT") ? "on" : null;
309
319
  if (preposition === null) {
310
- throw chunk5KMIY374_cjs.ErrorFactory.parsing(
320
+ throw chunkB4HBEFTB_cjs.ErrorFactory.parsing(
311
321
  "IS_WHAT_EXPECTED_PREPOSITION",
312
322
  'Expected "of what", "off what" or "on what", as in "20 is 10% of what"'
313
323
  );
@@ -333,7 +343,7 @@ function nextWordIs(parser, word) {
333
343
  }
334
344
  function expectWord(parser, word, example) {
335
345
  if (!nextWordIs(parser, word)) {
336
- throw chunk5KMIY374_cjs.ErrorFactory.parsing(
346
+ throw chunkB4HBEFTB_cjs.ErrorFactory.parsing(
337
347
  "IS_WHAT_EXPECTED_WORD",
338
348
  `Expected "${word}" here, as in ${example}`
339
349
  );
@@ -356,10 +366,10 @@ var OnOffBaseParselet = class {
356
366
  constructor(sign) {
357
367
  this.sign = sign;
358
368
  this.category = "Percentage";
359
- this.bindingPower = chunkPA4VC73I_cjs.BindingPower.Conditional;
369
+ this.bindingPower = chunkJ45BCEZ4_cjs.BindingPower.Conditional;
360
370
  }
361
371
  parse(parser, _left, _token, builder) {
362
- parser.parseExpression(chunkPA4VC73I_cjs.BindingPower.Conditional, builder);
372
+ parser.parseExpression(chunkJ45BCEZ4_cjs.BindingPower.Conditional, builder);
363
373
  builder.emitOpcode(2 /* SWAP */);
364
374
  builder.emitOpcode(10 /* PUSH_NUMBER */);
365
375
  builder.emitNumber(1);
@@ -390,7 +400,7 @@ function percentOnOffNormalizerRule(priority = 68) {
390
400
  consumed: 2,
391
401
  replacement: [
392
402
  tokens[pos],
393
- chunkBHTNFEGZ_cjs.createFusedToken(text === "on" ? "PCT_ON" : "PCT_OFF", text, [word])
403
+ chunkC75AFGVD_cjs.createFusedToken(text === "on" ? "PCT_ON" : "PCT_OFF", text, [word])
394
404
  ],
395
405
  ruleName: "percentage:on-off"
396
406
  };
@@ -402,7 +412,7 @@ function percentOnOffNormalizerRule(priority = 68) {
402
412
  var OfParselet = class {
403
413
  constructor() {
404
414
  this.category = "Percentage";
405
- this.bindingPower = chunkPA4VC73I_cjs.BindingPower.Product;
415
+ this.bindingPower = chunkJ45BCEZ4_cjs.BindingPower.Product;
406
416
  }
407
417
  parse(parser, left, token, builder) {
408
418
  parser.parseExpression(this.bindingPower, builder);
@@ -414,7 +424,7 @@ var OfParselet = class {
414
424
  var OfWhatIsParselet = class {
415
425
  constructor() {
416
426
  this.category = "Percentage";
417
- this.bindingPower = chunkPA4VC73I_cjs.BindingPower.Product;
427
+ this.bindingPower = chunkJ45BCEZ4_cjs.BindingPower.Product;
418
428
  }
419
429
  parse(parser, left, token, builder) {
420
430
  parser.parseExpression(this.bindingPower, builder);
@@ -428,7 +438,7 @@ var OnOffWhatIsParselet = class {
428
438
  constructor(sign) {
429
439
  this.sign = sign;
430
440
  this.category = "Percentage";
431
- this.bindingPower = chunkPA4VC73I_cjs.BindingPower.Product;
441
+ this.bindingPower = chunkJ45BCEZ4_cjs.BindingPower.Product;
432
442
  }
433
443
  parse(parser, left, token, builder) {
434
444
  builder.emitOpcode(10 /* PUSH_NUMBER */);
@@ -471,7 +481,7 @@ var IncreaseByParselet = class {
471
481
  constructor(multiplier) {
472
482
  this.multiplier = multiplier;
473
483
  this.category = "Percentage";
474
- this.bindingPower = chunkPA4VC73I_cjs.BindingPower.Conditional;
484
+ this.bindingPower = chunkJ45BCEZ4_cjs.BindingPower.Conditional;
475
485
  }
476
486
  parse(parser, left, token, builder) {
477
487
  parser.parseExpression(this.bindingPower, builder);
@@ -487,7 +497,7 @@ var IncreaseByParselet = class {
487
497
  var PercentageChangeParselet = class {
488
498
  constructor() {
489
499
  this.category = "Percentage";
490
- this.bindingPower = chunkPA4VC73I_cjs.BindingPower.Conditional;
500
+ this.bindingPower = chunkJ45BCEZ4_cjs.BindingPower.Conditional;
491
501
  }
492
502
  parse(parser, left, token, builder) {
493
503
  if (left.type === "UNIT") {
@@ -511,6 +521,119 @@ var PercentageChangeParselet = class {
511
521
  }
512
522
  };
513
523
 
524
+ // src/packages/percentage/parselets/UpDownParselet.ts
525
+ var COUNT_WORDS = {
526
+ one: 1,
527
+ two: 2,
528
+ three: 3,
529
+ four: 4,
530
+ five: 5,
531
+ six: 6,
532
+ seven: 7,
533
+ eight: 8,
534
+ nine: 9,
535
+ ten: 10,
536
+ eleven: 11,
537
+ twelve: 12
538
+ };
539
+ function resolveCount(token) {
540
+ if (token.type === "NUMBER") {
541
+ const n = Number(token.value);
542
+ return Number.isInteger(n) && n >= 0 ? n : null;
543
+ }
544
+ if (token.type === "IDENT") {
545
+ return COUNT_WORDS[token.value.toLowerCase()] ?? null;
546
+ }
547
+ return null;
548
+ }
549
+ var UpDownParselet = class {
550
+ /** @param sign `1` for `up` (grow), `-1` for `down` (shrink). */
551
+ constructor(sign) {
552
+ this.sign = sign;
553
+ this.category = "Percentage";
554
+ this.bindingPower = chunkJ45BCEZ4_cjs.BindingPower.Conditional;
555
+ }
556
+ parse(parser, _left, _token, builder) {
557
+ parser.parseExpression(this.bindingPower, builder);
558
+ builder.emitOpcode(10 /* PUSH_NUMBER */);
559
+ builder.emitNumber(1);
560
+ builder.emitOpcode(2 /* SWAP */);
561
+ builder.emitOpcode(this.sign > 0 ? 20 /* ADD */ : 21 /* SUB */);
562
+ const repeat = this.tryConsumeRepeat(parser);
563
+ if (repeat !== null) {
564
+ builder.emitOpcode(10 /* PUSH_NUMBER */);
565
+ builder.emitNumber(repeat);
566
+ builder.emitOpcode(25 /* EXP */);
567
+ }
568
+ builder.emitOpcode(22 /* MUL */);
569
+ this.consumeChainThen(parser);
570
+ }
571
+ /**
572
+ * Consume a trailing `N times` (`3 times`, `three times`) if present,
573
+ * returning the count, or `null` when there is no repeat here. The word
574
+ * "times" lexes as the multiply token but keeps its source text, which is
575
+ * what tells it apart from a literal `*`.
576
+ */
577
+ tryConsumeRepeat(parser) {
578
+ const countTok = parser.peek();
579
+ const timesTok = parser.peekAt(1);
580
+ if (!countTok || !timesTok) return null;
581
+ if (timesTok.type !== "STAR" || timesTok.value.toLowerCase() !== "times") return null;
582
+ const count = resolveCount(countTok);
583
+ if (count === null) return null;
584
+ parser.consume();
585
+ parser.consume();
586
+ return count;
587
+ }
588
+ /** Drop a connective `then` that sits directly before the next step. */
589
+ consumeChainThen(parser) {
590
+ if (parser.peek()?.type !== "THEN") return;
591
+ const next = parser.peekAt(1)?.type;
592
+ if (next === "PCT_UP" || next === "PCT_DOWN") {
593
+ parser.consume();
594
+ }
595
+ }
596
+ };
597
+
598
+ // src/packages/percentage/normalizer/PercentUpDownNormalizerRule.ts
599
+ function stepTokenAt(tokens, i) {
600
+ const word = tokens[i];
601
+ if (word?.type !== "IDENT") return null;
602
+ const text = (word.text ?? word.value ?? "").toLowerCase();
603
+ if (text !== "up" && text !== "down") return null;
604
+ if (tokens[i + 1]?.type !== "NUMBER") return null;
605
+ if (tokens[i + 2]?.type !== "PERCENT") return null;
606
+ return chunkC75AFGVD_cjs.createFusedToken(text === "up" ? "PCT_UP" : "PCT_DOWN", text, [word]);
607
+ }
608
+ function percentUpDownNormalizerRule(priority = 68) {
609
+ return {
610
+ name: "percentage:up-down",
611
+ priority,
612
+ match(tokens, pos) {
613
+ const here = tokens[pos];
614
+ if (here && (here.type === "NUMBER" || here.type === "RPAREN")) {
615
+ const step2 = stepTokenAt(tokens, pos + 1);
616
+ if (step2) {
617
+ return {
618
+ consumed: 2,
619
+ replacement: [here, step2],
620
+ ruleName: "percentage:up-down"
621
+ };
622
+ }
623
+ }
624
+ const step = stepTokenAt(tokens, pos);
625
+ if (step) {
626
+ return {
627
+ consumed: 1,
628
+ replacement: [step],
629
+ ruleName: "percentage:up-down"
630
+ };
631
+ }
632
+ return null;
633
+ }
634
+ };
635
+ }
636
+
514
637
  // src/packages/percentage/PercentagePackage.ts
515
638
  var PERCENTAGE_PACKAGE = {
516
639
  name: "solve-percentage",
@@ -538,14 +661,18 @@ var PERCENTAGE_PACKAGE = {
538
661
  { tokenType: "OFF_WHAT_IS", parselet: new OnOffWhatIsParselet(-1) },
539
662
  { tokenType: "TO", parselet: new PercentageChangeParselet() },
540
663
  { tokenType: "INCREASE_BY", parselet: new IncreaseByParselet(1) },
541
- { tokenType: "DECREASE_BY", parselet: new IncreaseByParselet(-1) }
664
+ { tokenType: "DECREASE_BY", parselet: new IncreaseByParselet(-1) },
665
+ // Successive change: "120 up 10% then down 10%" is 118.80, not 120.
666
+ { tokenType: "PCT_UP", parselet: new UpDownParselet(1) },
667
+ { tokenType: "PCT_DOWN", parselet: new UpDownParselet(-1) }
542
668
  ],
543
669
  prefixParselets: [
544
670
  { tokenType: "INCREASE", parselet: new IncreaseDecreaseParselet(1) },
545
671
  { tokenType: "DECREASE", parselet: new IncreaseDecreaseParselet(-1) }
546
672
  ],
547
673
  normalizerRules: [
548
- percentOnOffNormalizerRule()
674
+ percentOnOffNormalizerRule(),
675
+ percentUpDownNormalizerRule()
549
676
  ]
550
677
  };
551
678
 
@@ -656,7 +783,7 @@ var FunctionCallParselet = class {
656
783
  const fnName = token.value.toLowerCase();
657
784
  const fnIdx = builtinNameToIndex[fnName];
658
785
  if (fnIdx === void 0) {
659
- throw chunk5KMIY374_cjs.ErrorFactory.execution(
786
+ throw chunkB4HBEFTB_cjs.ErrorFactory.execution(
660
787
  "UNKNOWN_FUNCTION",
661
788
  `Unknown function: ${fnName}`,
662
789
  { functionName: fnName }
@@ -665,10 +792,10 @@ var FunctionCallParselet = class {
665
792
  parser.consume("LPAREN");
666
793
  let argCount = 0;
667
794
  if (parser.peek()?.type !== "RPAREN") {
668
- parser.parseExpression(chunkPA4VC73I_cjs.BindingPower.Lowest, builder);
795
+ parser.parseExpression(chunkJ45BCEZ4_cjs.BindingPower.Lowest, builder);
669
796
  argCount++;
670
797
  while (parser.match("COMMA")) {
671
- parser.parseExpression(chunkPA4VC73I_cjs.BindingPower.Lowest, builder);
798
+ parser.parseExpression(chunkJ45BCEZ4_cjs.BindingPower.Lowest, builder);
672
799
  argCount++;
673
800
  }
674
801
  }
@@ -725,7 +852,7 @@ var NextLastParselet = class {
725
852
  const dayToken = parser.peek();
726
853
  const targetDay = dayToken ? WEEKDAY_TOKEN_INDEX[dayToken.type] : void 0;
727
854
  if (targetDay === void 0) {
728
- throw chunk5KMIY374_cjs.ErrorFactory.parsing(
855
+ throw chunkB4HBEFTB_cjs.ErrorFactory.parsing(
729
856
  "MISSING_WEEKDAY",
730
857
  `"${token.value}" must be followed by a day of the week (e.g. "${token.value} Saturday")`,
731
858
  { keyword: token.value, actualType: dayToken?.type, actualValue: dayToken?.value }
@@ -794,27 +921,27 @@ function unixTimestampToEpochMs(n) {
794
921
  }
795
922
 
796
923
  // src/packages/datetime/parselets/DatetimeTimestampPluginFunctions.ts
797
- var WORKDAYS_IN_FN_IDX = chunk6WFMPTGB_cjs.allocatePluginFunctionIndex();
798
- var WEEKDAY_ON_FN_IDX = chunk6WFMPTGB_cjs.allocatePluginFunctionIndex();
799
- var TO_DATE_FN_IDX = chunk6WFMPTGB_cjs.allocatePluginFunctionIndex();
800
- var TO_TIMESTAMP_FN_IDX = chunk6WFMPTGB_cjs.allocatePluginFunctionIndex();
801
- var MONTH_ON_FN_IDX = chunk6WFMPTGB_cjs.allocatePluginFunctionIndex();
802
- var WEEK_ON_FN_IDX = chunk6WFMPTGB_cjs.allocatePluginFunctionIndex();
803
- var IS_WEEKEND_FN_IDX = chunk6WFMPTGB_cjs.allocatePluginFunctionIndex();
804
- var IS_WORKDAY_FN_IDX = chunk6WFMPTGB_cjs.allocatePluginFunctionIndex();
805
- var SPAN_BETWEEN_FN_IDX = chunk6WFMPTGB_cjs.allocatePluginFunctionIndex();
924
+ var WORKDAYS_IN_FN_IDX = chunk7ZX6B7SY_cjs.allocatePluginFunctionIndex();
925
+ var WEEKDAY_ON_FN_IDX = chunk7ZX6B7SY_cjs.allocatePluginFunctionIndex();
926
+ var TO_DATE_FN_IDX = chunk7ZX6B7SY_cjs.allocatePluginFunctionIndex();
927
+ var TO_TIMESTAMP_FN_IDX = chunk7ZX6B7SY_cjs.allocatePluginFunctionIndex();
928
+ var MONTH_ON_FN_IDX = chunk7ZX6B7SY_cjs.allocatePluginFunctionIndex();
929
+ var WEEK_ON_FN_IDX = chunk7ZX6B7SY_cjs.allocatePluginFunctionIndex();
930
+ var IS_WEEKEND_FN_IDX = chunk7ZX6B7SY_cjs.allocatePluginFunctionIndex();
931
+ var IS_WORKDAY_FN_IDX = chunk7ZX6B7SY_cjs.allocatePluginFunctionIndex();
932
+ var SPAN_BETWEEN_FN_IDX = chunk7ZX6B7SY_cjs.allocatePluginFunctionIndex();
806
933
  function workdaysInDurationHandler(args) {
807
934
  const v = args[0];
808
935
  let totalDays;
809
936
  if (v.type === 6 /* Uom */ && v.unit) {
810
- const measure = chunkPFUESQTW_cjs.getMeasure(v.unit);
937
+ const measure = chunkEJ3ILXX6_cjs.getMeasure(v.unit);
811
938
  if (measure !== "time") {
812
- return chunkJMXUNXQS_cjs.errorValue(
939
+ return chunk2TZKENDH_cjs.errorValue(
813
940
  "WORKDAYS_IN_EXPECTED_DURATION",
814
941
  `"workdays in" expects a time duration, got unit "${v.unit}"`
815
942
  );
816
943
  }
817
- totalDays = chunkPFUESQTW_cjs.convertUnit(v.toNumber(), v.unit, "day");
944
+ totalDays = chunkEJ3ILXX6_cjs.convertUnit(v.toNumber(), v.unit, "day");
818
945
  } else {
819
946
  totalDays = v.toNumber();
820
947
  }
@@ -823,7 +950,7 @@ function workdaysInDurationHandler(args) {
823
950
  const fullWeeks = Math.floor(magnitudeDays / 7);
824
951
  const remainderDays = magnitudeDays - fullWeeks * 7;
825
952
  const workdays = sign * (fullWeeks * 5 + Math.min(remainderDays, 5));
826
- return chunkJMXUNXQS_cjs.numberValue(workdays);
953
+ return chunk2TZKENDH_cjs.numberValue(workdays);
827
954
  }
828
955
  var WEEKDAY_NAMES = [
829
956
  "Sunday",
@@ -836,15 +963,15 @@ var WEEKDAY_NAMES = [
836
963
  ];
837
964
  function asEpochMs(value, fieldName) {
838
965
  if (value.type === 4 /* Datetime */) return value.toNumber();
839
- return chunkJMXUNXQS_cjs.errorValue(
966
+ return chunk2TZKENDH_cjs.errorValue(
840
967
  "DATE_FIELD_EXPECTED_DATE",
841
- `"${fieldName}" expects a date, got ${chunkJMXUNXQS_cjs.ValueType[value.type] ?? "an unsupported value"}`
968
+ `"${fieldName}" expects a date, got ${chunk2TZKENDH_cjs.ValueType[value.type] ?? "an unsupported value"}`
842
969
  );
843
970
  }
844
971
  function weekdayOnDateHandler(args) {
845
972
  const epochMs = asEpochMs(args[0], "weekday");
846
973
  if (typeof epochMs !== "number") return epochMs;
847
- return chunkJMXUNXQS_cjs.stringValue(WEEKDAY_NAMES[new Date(epochMs).getDay()]);
974
+ return chunk2TZKENDH_cjs.stringValue(WEEKDAY_NAMES[new Date(epochMs).getDay()]);
848
975
  }
849
976
  var MONTH_NAMES = [
850
977
  "January",
@@ -863,7 +990,7 @@ var MONTH_NAMES = [
863
990
  function monthOnDateHandler(args) {
864
991
  const epochMs = asEpochMs(args[0], "month");
865
992
  if (typeof epochMs !== "number") return epochMs;
866
- return chunkJMXUNXQS_cjs.stringValue(MONTH_NAMES[new Date(epochMs).getMonth()]);
993
+ return chunk2TZKENDH_cjs.stringValue(MONTH_NAMES[new Date(epochMs).getMonth()]);
867
994
  }
868
995
  function weekOnDateHandler(args) {
869
996
  const epochMs = asEpochMs(args[0], "week");
@@ -874,7 +1001,7 @@ function weekOnDateHandler(args) {
874
1001
  target.setUTCDate(target.getUTCDate() + 4 - isoDay);
875
1002
  const yearStart = Date.UTC(target.getUTCFullYear(), 0, 1);
876
1003
  const days = Math.floor((target.getTime() - yearStart) / 864e5);
877
- return chunkJMXUNXQS_cjs.numberValue(Math.floor(days / 7) + 1);
1004
+ return chunk2TZKENDH_cjs.numberValue(Math.floor(days / 7) + 1);
878
1005
  }
879
1006
  function isWeekendDate(epochMs) {
880
1007
  const day = new Date(epochMs).getDay();
@@ -883,15 +1010,15 @@ function isWeekendDate(epochMs) {
883
1010
  function isWeekendOnDateHandler(args) {
884
1011
  const epochMs = asEpochMs(args[0], "is a weekend");
885
1012
  if (typeof epochMs !== "number") return epochMs;
886
- return chunkJMXUNXQS_cjs.boolValue(isWeekendDate(epochMs));
1013
+ return chunk2TZKENDH_cjs.boolValue(isWeekendDate(epochMs));
887
1014
  }
888
1015
  function isWorkdayOnDateHandler(args) {
889
1016
  const epochMs = asEpochMs(args[0], "is a workday");
890
1017
  if (typeof epochMs !== "number") return epochMs;
891
- return chunkJMXUNXQS_cjs.boolValue(!isWeekendDate(epochMs));
1018
+ return chunk2TZKENDH_cjs.boolValue(!isWeekendDate(epochMs));
892
1019
  }
893
1020
  function spanBetweenDatesHandler(args) {
894
- return chunkJMXUNXQS_cjs.uomValue(Math.abs(args[0].toNumber() - args[1].toNumber()), "ms");
1021
+ return chunk2TZKENDH_cjs.uomValue(Math.abs(args[0].toNumber() - args[1].toNumber()), "ms");
895
1022
  }
896
1023
  function toDateFromAnyHandler(args) {
897
1024
  const v = args[0];
@@ -901,28 +1028,28 @@ function toDateFromAnyHandler(args) {
901
1028
  if (v.type === 3 /* String */) {
902
1029
  const ms = parseIso8601(v.value);
903
1030
  if (ms === null) {
904
- return chunkJMXUNXQS_cjs.errorValue(
1031
+ return chunk2TZKENDH_cjs.errorValue(
905
1032
  "INVALID_ISO8601_STRING",
906
1033
  `"${v.value}" is not a recognizable ISO8601 date/time string`
907
1034
  );
908
1035
  }
909
- return chunkJMXUNXQS_cjs.datetimeValue(ms);
1036
+ return chunk2TZKENDH_cjs.datetimeValue(ms);
910
1037
  }
911
- return chunkJMXUNXQS_cjs.datetimeValue(unixTimestampToEpochMs(v.toNumber()));
1038
+ return chunk2TZKENDH_cjs.datetimeValue(unixTimestampToEpochMs(v.toNumber()));
912
1039
  }
913
1040
  function toTimestampFromAnyHandler(args) {
914
1041
  const v = args[0];
915
1042
  if (v.type === 3 /* String */) {
916
1043
  const ms = parseIso8601(v.value);
917
1044
  if (ms === null) {
918
- return chunkJMXUNXQS_cjs.errorValue(
1045
+ return chunk2TZKENDH_cjs.errorValue(
919
1046
  "INVALID_ISO8601_STRING",
920
1047
  `"${v.value}" is not a recognizable ISO8601 date/time string`
921
1048
  );
922
1049
  }
923
- return chunkJMXUNXQS_cjs.numberValue(Math.floor(ms / 1e3));
1050
+ return chunk2TZKENDH_cjs.numberValue(Math.floor(ms / 1e3));
924
1051
  }
925
- return chunkJMXUNXQS_cjs.numberValue(Math.floor(v.toNumber() / 1e3));
1052
+ return chunk2TZKENDH_cjs.numberValue(Math.floor(v.toNumber() / 1e3));
926
1053
  }
927
1054
  var workdaysInDuration = workdaysInDurationHandler;
928
1055
  var weekdayOnDate = weekdayOnDateHandler;
@@ -947,6 +1074,34 @@ var WorkdaysInParselet = class {
947
1074
  }
948
1075
  };
949
1076
 
1077
+ // src/packages/datetime/parselets/WorkdayOffsetParselet.ts
1078
+ var WorkdayOffsetParselet = class {
1079
+ /** @param direction "forward" for after/from, "backward" for before. */
1080
+ constructor(direction) {
1081
+ this.direction = direction;
1082
+ this.category = "Date/Time";
1083
+ this.bindingPower = chunkJ45BCEZ4_cjs.BindingPower.Conditional;
1084
+ }
1085
+ parse(parser, _left, _token, builder) {
1086
+ parser.parseExpression(chunkJ45BCEZ4_cjs.BindingPower.Sum, builder);
1087
+ builder.emitOpcode(96 /* DATE_WORKDAY_OFFSET */);
1088
+ builder.emitByte(this.direction === "backward" ? 1 : 0);
1089
+ }
1090
+ };
1091
+
1092
+ // src/packages/datetime/parselets/WorkdaysBetweenParselet.ts
1093
+ var WorkdaysBetweenParselet = class {
1094
+ constructor() {
1095
+ this.category = "Date/Time";
1096
+ }
1097
+ parse(parser, _token, builder) {
1098
+ parser.parseExpression(chunkJ45BCEZ4_cjs.BindingPower.Product, builder);
1099
+ parser.consume("AND_CONJ");
1100
+ parser.parseExpression(chunkJ45BCEZ4_cjs.BindingPower.Lowest, builder);
1101
+ builder.emitOpcode(97 /* DATE_WORKDAYS_BETWEEN */);
1102
+ }
1103
+ };
1104
+
950
1105
  // src/packages/datetime/parselets/DateFieldQueryParselet.ts
951
1106
  var DateFieldQueryParselet = class {
952
1107
  constructor(functionIndex, mode) {
@@ -977,9 +1132,9 @@ var DurationBetweenParselet = class {
977
1132
  }
978
1133
  parse(parser, token, builder) {
979
1134
  const unit = token.value;
980
- parser.parseExpression(chunkPA4VC73I_cjs.BindingPower.Product, builder);
1135
+ parser.parseExpression(chunkJ45BCEZ4_cjs.BindingPower.Product, builder);
981
1136
  parser.consume("AND_CONJ");
982
- parser.parseExpression(chunkPA4VC73I_cjs.BindingPower.Lowest, builder);
1137
+ parser.parseExpression(chunkJ45BCEZ4_cjs.BindingPower.Lowest, builder);
983
1138
  builder.emitOpcode(50 /* CALL_PLUGIN */);
984
1139
  builder.emitIndex(SPAN_BETWEEN_FN_IDX);
985
1140
  builder.emitIndex(2);
@@ -994,7 +1149,7 @@ var DayTypePredicateParselet = class {
994
1149
  constructor(functionIndex) {
995
1150
  this.functionIndex = functionIndex;
996
1151
  this.category = "Date/Time";
997
- this.bindingPower = chunkPA4VC73I_cjs.BindingPower.Conditional;
1152
+ this.bindingPower = chunkJ45BCEZ4_cjs.BindingPower.Conditional;
998
1153
  }
999
1154
  parse(parser, left, token, builder) {
1000
1155
  builder.emitOpcode(50 /* CALL_PLUGIN */);
@@ -1020,7 +1175,7 @@ var CurrentTimestampParselet = class {
1020
1175
  var ToDateParselet = class {
1021
1176
  constructor() {
1022
1177
  this.category = "Date/Time";
1023
- this.bindingPower = chunkPA4VC73I_cjs.BindingPower.Conditional;
1178
+ this.bindingPower = chunkJ45BCEZ4_cjs.BindingPower.Conditional;
1024
1179
  }
1025
1180
  parse(parser, left, token, builder) {
1026
1181
  builder.emitOpcode(50 /* CALL_PLUGIN */);
@@ -1033,7 +1188,7 @@ var ToDateParselet = class {
1033
1188
  var ToTimestampParselet = class {
1034
1189
  constructor() {
1035
1190
  this.category = "Date/Time";
1036
- this.bindingPower = chunkPA4VC73I_cjs.BindingPower.Conditional;
1191
+ this.bindingPower = chunkJ45BCEZ4_cjs.BindingPower.Conditional;
1037
1192
  }
1038
1193
  parse(parser, left, token, builder) {
1039
1194
  builder.emitOpcode(50 /* CALL_PLUGIN */);
@@ -1067,7 +1222,7 @@ function untilSinceNormalizerRule(priority = 60) {
1067
1222
  const fusedType = keywordToken.type === "UNTIL" ? "UNTIL_UNIT" : "SINCE_UNIT";
1068
1223
  return {
1069
1224
  consumed: 2,
1070
- replacement: [chunkBHTNFEGZ_cjs.createFusedToken(fusedType, unitToken.value, [unitToken, keywordToken])],
1225
+ replacement: [chunkC75AFGVD_cjs.createFusedToken(fusedType, unitToken.value, [unitToken, keywordToken])],
1071
1226
  ruleName: `datetime:${keywordToken.type.toLowerCase()}`
1072
1227
  };
1073
1228
  }
@@ -1104,7 +1259,7 @@ function betweenUnitNormalizerRule(priority = 60) {
1104
1259
  }
1105
1260
  return {
1106
1261
  consumed: howManyLength + 2,
1107
- replacement: [chunkBHTNFEGZ_cjs.createFusedToken("BETWEEN_UNIT", unitToken.value, sources)],
1262
+ replacement: [chunkC75AFGVD_cjs.createFusedToken("BETWEEN_UNIT", unitToken.value, sources)],
1108
1263
  ruleName: "datetime:between"
1109
1264
  };
1110
1265
  }
@@ -1125,7 +1280,7 @@ function workdayRateDenominatorNormalizerRule(priority = 70) {
1125
1280
  const syntheticOne = {
1126
1281
  ...unit,
1127
1282
  type: "NUMBER",
1128
- typeId: chunk5LI5EPGJ_cjs.tokenTypeId("NUMBER"),
1283
+ typeId: chunkQFTDTX6K_cjs.tokenTypeId("NUMBER"),
1129
1284
  value: "1",
1130
1285
  text: "1"
1131
1286
  };
@@ -1140,7 +1295,7 @@ function workdayRateDenominatorNormalizerRule(priority = 70) {
1140
1295
 
1141
1296
  // src/packages/datetime/normalizer/DateLiteralNormalizerRule.ts
1142
1297
  var DATETIME_LITERAL_TYPE = "DATETIME_LITERAL";
1143
- var DATETIME_LITERAL_TYPE_ID = chunk5LI5EPGJ_cjs.tokenTypeId(DATETIME_LITERAL_TYPE);
1298
+ var DATETIME_LITERAL_TYPE_ID = chunkQFTDTX6K_cjs.tokenTypeId(DATETIME_LITERAL_TYPE);
1144
1299
  var PLAIN_INTEGER = /^\d+$/;
1145
1300
  var DOT_DAY_MONTH = /^\d{1,2}\.\d{1,2}$/;
1146
1301
  var DOT_LEADING_YEAR = /^\.(\d{2}|\d{4})$/;
@@ -1169,7 +1324,7 @@ function buildDateToken(day, month, year, sourceTokens, ruleName) {
1169
1324
  }
1170
1325
  const first = sourceTokens[0];
1171
1326
  const text = sourceTokens.map((t) => t.value).join("");
1172
- const fusedToken = new chunkYWQ6V4ZN_cjs.LexerToken(
1327
+ const fusedToken = new chunkYTIYVHL7_cjs.LexerToken(
1173
1328
  DATETIME_LITERAL_TYPE,
1174
1329
  DATETIME_LITERAL_TYPE_ID,
1175
1330
  String(date.getTime()),
@@ -1210,7 +1365,7 @@ function fuseIsoTimestamp(tokens, pos, ruleName) {
1210
1365
  if (covered !== matched[0].length) return null;
1211
1366
  const epochMs = parseIso8601(matched[0]);
1212
1367
  if (epochMs === null) return null;
1213
- const fusedToken = new chunkYWQ6V4ZN_cjs.LexerToken(
1368
+ const fusedToken = new chunkYTIYVHL7_cjs.LexerToken(
1214
1369
  DATETIME_LITERAL_TYPE,
1215
1370
  DATETIME_LITERAL_TYPE_ID,
1216
1371
  String(epochMs),
@@ -1403,7 +1558,7 @@ var DaysInPeriodParselet = class {
1403
1558
  }
1404
1559
  const year = this.readOptionalYear(parser);
1405
1560
  if (year !== null) return daysInYear(year);
1406
- throw chunk5KMIY374_cjs.ErrorFactory.parsing(
1561
+ throw chunkB4HBEFTB_cjs.ErrorFactory.parsing(
1407
1562
  "DAYS_IN_EXPECTED_PERIOD",
1408
1563
  'Expected a month, quarter or year, as in "days in February 2020" or "days in Q3"'
1409
1564
  );
@@ -1469,7 +1624,7 @@ function daysInPeriodNormalizerRule(priority = 60) {
1469
1624
  if (!startsPeriod(tokens[pos + 2])) return null;
1470
1625
  return {
1471
1626
  consumed: 2,
1472
- replacement: [chunkBHTNFEGZ_cjs.createFusedToken("DAYS_IN_PERIOD", "days in", [days, tokens[pos + 1]])],
1627
+ replacement: [chunkC75AFGVD_cjs.createFusedToken("DAYS_IN_PERIOD", "days in", [days, tokens[pos + 1]])],
1473
1628
  ruleName: "datetime:days-in-period"
1474
1629
  };
1475
1630
  }
@@ -1501,6 +1656,26 @@ var DATETIME_PACKAGE = {
1501
1656
  name: "solve-datetime",
1502
1657
  phrases: {
1503
1658
  "workdays in": "WORKDAYS_IN",
1659
+ // Business-day arithmetic in words. "after"/"from" count forward, "before"
1660
+ // back; the count is the left operand, so these fuse as INFIX tokens (see
1661
+ // WorkdayOffsetParselet). Both "working" and "business" days, singular and
1662
+ // plural, so "1 working day after" and "5 business days from" both read.
1663
+ "working days after": "WORKDAYS_AFTER",
1664
+ "business days after": "WORKDAYS_AFTER",
1665
+ "working day after": "WORKDAYS_AFTER",
1666
+ "business day after": "WORKDAYS_AFTER",
1667
+ "working days from": "WORKDAYS_AFTER",
1668
+ "business days from": "WORKDAYS_AFTER",
1669
+ "working day from": "WORKDAYS_AFTER",
1670
+ "business day from": "WORKDAYS_AFTER",
1671
+ "working days before": "WORKDAYS_BEFORE",
1672
+ "business days before": "WORKDAYS_BEFORE",
1673
+ "working day before": "WORKDAYS_BEFORE",
1674
+ "business day before": "WORKDAYS_BEFORE",
1675
+ // The count form (prefix). Fusing the whole "working days between" keeps
1676
+ // the bare `days between` rule from claiming the "days" first.
1677
+ "working days between": "WORKDAYS_BETWEEN",
1678
+ "business days between": "WORKDAYS_BETWEEN",
1504
1679
  "day of the week on": "WEEKDAY_ON",
1505
1680
  "weekday on": "WEEKDAY_ON",
1506
1681
  "current timestamp": "CURRENT_TIMESTAMP",
@@ -1548,6 +1723,7 @@ var DATETIME_PACKAGE = {
1548
1723
  { tokenType: "UNTIL_UNIT", parselet: new UntilSinceParselet("until") },
1549
1724
  { tokenType: "SINCE_UNIT", parselet: new UntilSinceParselet("since") },
1550
1725
  { tokenType: "WORKDAYS_IN", parselet: new WorkdaysInParselet() },
1726
+ { tokenType: "WORKDAYS_BETWEEN", parselet: new WorkdaysBetweenParselet() },
1551
1727
  { tokenType: "WEEKDAY_ON", parselet: new DateFieldQueryParselet(WEEKDAY_ON_FN_IDX, "on") },
1552
1728
  { tokenType: "WEEKDAY_IN", parselet: new DateFieldQueryParselet(WEEKDAY_ON_FN_IDX, "in") },
1553
1729
  { tokenType: "MONTH_ON", parselet: new DateFieldQueryParselet(MONTH_ON_FN_IDX, "on") },
@@ -1559,6 +1735,8 @@ var DATETIME_PACKAGE = {
1559
1735
  { tokenType: "DATETIME_LITERAL", parselet: new DateLiteralParselet() }
1560
1736
  ],
1561
1737
  infixParselets: [
1738
+ { tokenType: "WORKDAYS_AFTER", parselet: new WorkdayOffsetParselet("forward") },
1739
+ { tokenType: "WORKDAYS_BEFORE", parselet: new WorkdayOffsetParselet("backward") },
1562
1740
  { tokenType: "TO_DATE", parselet: new ToDateParselet() },
1563
1741
  { tokenType: "TO_TIMESTAMP", parselet: new ToTimestampParselet() },
1564
1742
  { tokenType: "IS_WEEKEND", parselet: new DayTypePredicateParselet(IS_WEEKEND_FN_IDX) },
@@ -1588,7 +1766,7 @@ var DATETIME_PACKAGE = {
1588
1766
  { index: SPAN_BETWEEN_FN_IDX, handler: spanBetweenDates }
1589
1767
  ],
1590
1768
  asConverters: {
1591
- iso8601: (value) => chunkJMXUNXQS_cjs.stringValue(formatIso8601Local(value.toNumber())),
1769
+ iso8601: (value) => chunk2TZKENDH_cjs.stringValue(formatIso8601Local(value.toNumber())),
1592
1770
  // The same three fields the "what X is it" questions answer, in the
1593
1771
  // composable form: `next friday + 2 weeks as weekday`. Question phrases
1594
1772
  // only ever take a bare date expression, whereas `as` binds after a
@@ -1912,10 +2090,10 @@ function tryConsumeZoneReference(parser) {
1912
2090
  }
1913
2091
 
1914
2092
  // src/packages/time/parselets/TimezonePluginFunctions.ts
1915
- var ZONE_CONVERT_FN_IDX = chunk6WFMPTGB_cjs.allocatePluginFunctionIndex();
1916
- var TIME_IN_ZONE_FN_IDX = chunk6WFMPTGB_cjs.allocatePluginFunctionIndex();
1917
- var DATE_IN_ZONE_FN_IDX = chunk6WFMPTGB_cjs.allocatePluginFunctionIndex();
1918
- var TIME_DIFFERENCE_FN_IDX = chunk6WFMPTGB_cjs.allocatePluginFunctionIndex();
2093
+ var ZONE_CONVERT_FN_IDX = chunk7ZX6B7SY_cjs.allocatePluginFunctionIndex();
2094
+ var TIME_IN_ZONE_FN_IDX = chunk7ZX6B7SY_cjs.allocatePluginFunctionIndex();
2095
+ var DATE_IN_ZONE_FN_IDX = chunk7ZX6B7SY_cjs.allocatePluginFunctionIndex();
2096
+ var TIME_DIFFERENCE_FN_IDX = chunk7ZX6B7SY_cjs.allocatePluginFunctionIndex();
1919
2097
  function zoneConvertHandler(args) {
1920
2098
  const totalMinutes = args[0].toNumber();
1921
2099
  const sourceZoneRef = args[1].value;
@@ -1931,17 +2109,17 @@ function zoneConvertHandler(args) {
1931
2109
  );
1932
2110
  const time = formatTimeInZone(utcMs, targetZoneRef);
1933
2111
  const dayShift = dayShiftBetweenZones(utcMs, targetZoneRef, sourceZoneRef);
1934
- if (dayShift === 0) return chunkJMXUNXQS_cjs.stringValue(time);
2112
+ if (dayShift === 0) return chunk2TZKENDH_cjs.stringValue(time);
1935
2113
  const sign = dayShift > 0 ? "+" : "";
1936
- return chunkJMXUNXQS_cjs.stringValue(`${time} (${sign}${dayShift} day${Math.abs(dayShift) === 1 ? "" : "s"})`);
2114
+ return chunk2TZKENDH_cjs.stringValue(`${time} (${sign}${dayShift} day${Math.abs(dayShift) === 1 ? "" : "s"})`);
1937
2115
  }
1938
2116
  function timeInZoneHandler(args) {
1939
2117
  const zoneRef = args[0].value;
1940
- return chunkJMXUNXQS_cjs.stringValue(formatTimeInZone(Date.now(), zoneRef));
2118
+ return chunk2TZKENDH_cjs.stringValue(formatTimeInZone(Date.now(), zoneRef));
1941
2119
  }
1942
2120
  function dateInZoneHandler(args) {
1943
2121
  const zoneRef = args[0].value;
1944
- return chunkJMXUNXQS_cjs.stringValue(formatDateInZone(Date.now(), zoneRef));
2122
+ return chunk2TZKENDH_cjs.stringValue(formatDateInZone(Date.now(), zoneRef));
1945
2123
  }
1946
2124
  function timeDifferenceHandler(args) {
1947
2125
  const zoneRef1 = args[0].value;
@@ -1953,7 +2131,7 @@ function timeDifferenceHandler(args) {
1953
2131
  const offset2 = resolveOffsetMinutes(zoneRef2, now);
1954
2132
  const diff = offset2 - offset1;
1955
2133
  if (diff === 0) {
1956
- return chunkJMXUNXQS_cjs.stringValue(`${label2} and ${label1} currently share the same UTC offset`);
2134
+ return chunk2TZKENDH_cjs.stringValue(`${label2} and ${label1} currently share the same UTC offset`);
1957
2135
  }
1958
2136
  const ahead = diff > 0 ? label2 : label1;
1959
2137
  const behind = diff > 0 ? label1 : label2;
@@ -1963,7 +2141,7 @@ function timeDifferenceHandler(args) {
1963
2141
  const parts = [];
1964
2142
  if (hours > 0) parts.push(`${hours} hour${hours === 1 ? "" : "s"}`);
1965
2143
  if (minutes > 0) parts.push(`${minutes} minute${minutes === 1 ? "" : "s"}`);
1966
- return chunkJMXUNXQS_cjs.stringValue(`${ahead} is ${parts.join(" ")} ahead of ${behind}`);
2144
+ return chunk2TZKENDH_cjs.stringValue(`${ahead} is ${parts.join(" ")} ahead of ${behind}`);
1967
2145
  }
1968
2146
 
1969
2147
  // src/packages/time/parselets/ClockTimeParselet.ts
@@ -1976,7 +2154,7 @@ var ClockTimeParselet = class {
1976
2154
  const sourceZoneRef = tryConsumeZoneReference(parser);
1977
2155
  if (sourceZoneRef !== null) {
1978
2156
  if (parser.peek()?.type !== "IN") {
1979
- throw chunk5KMIY374_cjs.ErrorFactory.parsing(
2157
+ throw chunkB4HBEFTB_cjs.ErrorFactory.parsing(
1980
2158
  "TIME_ZONE_EXPECTED_IN",
1981
2159
  `Expected "in <city>" after the zone name (e.g. "6pm Sydney in Chicago") but got ${parser.peek() ? `"${parser.peek().value}"` : "end of input"}`
1982
2160
  );
@@ -1984,7 +2162,7 @@ var ClockTimeParselet = class {
1984
2162
  parser.consume();
1985
2163
  const target = tryConsumeZoneReference(parser);
1986
2164
  if (target === null) {
1987
- throw chunk5KMIY374_cjs.ErrorFactory.parsing(
2165
+ throw chunkB4HBEFTB_cjs.ErrorFactory.parsing(
1988
2166
  "TIME_ZONE_EXPECTED_TARGET",
1989
2167
  `Expected a city or zone name after "in" (e.g. "6pm Sydney in Chicago")`
1990
2168
  );
@@ -2059,7 +2237,7 @@ function timeOrDateInZoneParselet(pluginFnIdx) {
2059
2237
  parse(parser, token, builder) {
2060
2238
  const zone = tryConsumeZoneReference(parser);
2061
2239
  if (zone === null) {
2062
- throw chunk5KMIY374_cjs.ErrorFactory.parsing(
2240
+ throw chunkB4HBEFTB_cjs.ErrorFactory.parsing(
2063
2241
  "TIME_ZONE_EXPECTED_CITY",
2064
2242
  `Expected a city or zone name after "${token.value}" (e.g. "time in Paris")`
2065
2243
  );
@@ -2081,7 +2259,7 @@ var TimeDifferenceParselet = class {
2081
2259
  parse(parser, token, builder) {
2082
2260
  const zone1 = tryConsumeZoneReference(parser);
2083
2261
  if (zone1 === null) {
2084
- throw chunk5KMIY374_cjs.ErrorFactory.parsing(
2262
+ throw chunkB4HBEFTB_cjs.ErrorFactory.parsing(
2085
2263
  "TIME_DIFFERENCE_EXPECTED_CITY",
2086
2264
  `Expected a city or zone name after "time difference between" (e.g. "time difference between Seattle and Moscow")`
2087
2265
  );
@@ -2089,7 +2267,7 @@ var TimeDifferenceParselet = class {
2089
2267
  parser.consume("AND_CONJ");
2090
2268
  const zone2 = tryConsumeZoneReference(parser);
2091
2269
  if (zone2 === null) {
2092
- throw chunk5KMIY374_cjs.ErrorFactory.parsing(
2270
+ throw chunkB4HBEFTB_cjs.ErrorFactory.parsing(
2093
2271
  "TIME_DIFFERENCE_EXPECTED_SECOND_CITY",
2094
2272
  `Expected a second city or zone name after "and" (e.g. "time difference between Seattle and Moscow")`
2095
2273
  );
@@ -2117,7 +2295,7 @@ var VideoTimecodeParselet = class {
2117
2295
  const [hours, minutes, seconds, frames] = token.value.split(",").map(Number);
2118
2296
  const separator = parser.peek();
2119
2297
  if (!separator || separator.type !== "RATE_AT" && separator.type !== "AT") {
2120
- throw chunk5KMIY374_cjs.ErrorFactory.parsing(
2298
+ throw chunkB4HBEFTB_cjs.ErrorFactory.parsing(
2121
2299
  "TIMECODE_EXPECTED_FPS",
2122
2300
  `A video timecode must specify a frame rate, e.g. "${token.value.split(",").join(":")} at 30 fps" or "... @ 30 fps"`
2123
2301
  );
@@ -2125,7 +2303,7 @@ var VideoTimecodeParselet = class {
2125
2303
  parser.consume();
2126
2304
  const fpsToken = parser.peek();
2127
2305
  if (!fpsToken || fpsToken.type !== "FPS_RATE") {
2128
- throw chunk5KMIY374_cjs.ErrorFactory.parsing(
2306
+ throw chunkB4HBEFTB_cjs.ErrorFactory.parsing(
2129
2307
  "TIMECODE_EXPECTED_FPS",
2130
2308
  `Expected "<number> fps" after "${separator.value}" (e.g. "at 30 fps")`
2131
2309
  );
@@ -2134,18 +2312,18 @@ var VideoTimecodeParselet = class {
2134
2312
  const fps = parseFloat(fpsToken.value);
2135
2313
  const fpsWhole = Math.round(fps);
2136
2314
  if (frames >= fpsWhole) {
2137
- throw chunk5KMIY374_cjs.ErrorFactory.parsing(
2315
+ throw chunkB4HBEFTB_cjs.ErrorFactory.parsing(
2138
2316
  "TIMECODE_FRAME_OUT_OF_RANGE",
2139
2317
  `Frame number ${frames} is out of range for ${fps} fps (must be 0-${fpsWhole - 1})`
2140
2318
  );
2141
2319
  }
2142
2320
  const totalFrames = (hours * 3600 + minutes * 60 + seconds) * fpsWhole + frames;
2143
- let unit = chunkJMXUNXQS_cjs.timecodeUnit(fps);
2321
+ let unit = chunk2TZKENDH_cjs.timecodeUnit(fps);
2144
2322
  if (parser.peek()?.type === "IN") {
2145
2323
  parser.consume();
2146
2324
  const framesWord = parser.peek();
2147
2325
  if (!framesWord || framesWord.type !== "IDENT" || framesWord.value.toLowerCase() !== "frames") {
2148
- throw chunk5KMIY374_cjs.ErrorFactory.parsing(
2326
+ throw chunkB4HBEFTB_cjs.ErrorFactory.parsing(
2149
2327
  "TIMECODE_EXPECTED_FRAMES",
2150
2328
  `Expected "frames" after "in" (e.g. "... in frames") but got ${framesWord ? `"${framesWord.value}"` : "end of input"}`
2151
2329
  );
@@ -2186,7 +2364,7 @@ var FrameCountParselet = class {
2186
2364
  parser.consume();
2187
2365
  const fpsToken = parser.peek();
2188
2366
  if (!fpsToken || fpsToken.type !== "FPS_RATE") {
2189
- throw chunk5KMIY374_cjs.ErrorFactory.parsing(
2367
+ throw chunkB4HBEFTB_cjs.ErrorFactory.parsing(
2190
2368
  "TIMECODE_EXPECTED_FPS",
2191
2369
  `Expected "<number> fps" after "${separator.value}" (e.g. "@ 30 fps")`
2192
2370
  );
@@ -2225,7 +2403,7 @@ function clockTimeNormalizerRule(priority = 65) {
2225
2403
  name: "time:clock-time",
2226
2404
  priority,
2227
2405
  match(tokens, pos) {
2228
- if (chunkBHTNFEGZ_cjs.isInsideRangeContext(tokens, pos)) return null;
2406
+ if (chunkC75AFGVD_cjs.isInsideRangeContext(tokens, pos)) return null;
2229
2407
  const hourToken = tokens[pos];
2230
2408
  if (hourToken.type !== "NUMBER") return null;
2231
2409
  const hour = parseInt(hourToken.value, 10);
@@ -2241,7 +2419,7 @@ function clockTimeNormalizerRule(priority = 65) {
2241
2419
  const consumed = hasAmPm ? 4 : 3;
2242
2420
  return {
2243
2421
  consumed,
2244
- replacement: [chunkBHTNFEGZ_cjs.createFusedToken("CLOCK_TIME", String(totalMinutes), tokens.slice(pos, pos + consumed))],
2422
+ replacement: [chunkC75AFGVD_cjs.createFusedToken("CLOCK_TIME", String(totalMinutes), tokens.slice(pos, pos + consumed))],
2245
2423
  ruleName: "time:clock-time"
2246
2424
  };
2247
2425
  }
@@ -2251,7 +2429,7 @@ function clockTimeNormalizerRule(priority = 65) {
2251
2429
  if (totalMinutes === null) return null;
2252
2430
  return {
2253
2431
  consumed: 2,
2254
- replacement: [chunkBHTNFEGZ_cjs.createFusedToken("CLOCK_TIME", String(totalMinutes), tokens.slice(pos, pos + 2))],
2432
+ replacement: [chunkC75AFGVD_cjs.createFusedToken("CLOCK_TIME", String(totalMinutes), tokens.slice(pos, pos + 2))],
2255
2433
  ruleName: "time:clock-time"
2256
2434
  };
2257
2435
  }
@@ -2274,7 +2452,7 @@ function clockTimeIntervalNormalizerRule(priority = 66) {
2274
2452
  if (end?.type !== "CLOCK_TIME") return null;
2275
2453
  return {
2276
2454
  consumed: 3,
2277
- replacement: [chunkBHTNFEGZ_cjs.createFusedToken("CLOCK_TIME_INTERVAL", `${start.value}:${end.value}`, tokens.slice(pos, pos + 3))],
2455
+ replacement: [chunkC75AFGVD_cjs.createFusedToken("CLOCK_TIME_INTERVAL", `${start.value}:${end.value}`, tokens.slice(pos, pos + 3))],
2278
2456
  ruleName: "time:clock-time-interval"
2279
2457
  };
2280
2458
  }
@@ -2293,7 +2471,7 @@ function fpsRateNormalizerRule(priority = 65) {
2293
2471
  if (fpsToken?.type !== "IDENT" || fpsToken.value.toLowerCase() !== "fps") return null;
2294
2472
  return {
2295
2473
  consumed: 2,
2296
- replacement: [chunkBHTNFEGZ_cjs.createFusedToken("FPS_RATE", numberToken.value, tokens.slice(pos, pos + 2))],
2474
+ replacement: [chunkC75AFGVD_cjs.createFusedToken("FPS_RATE", numberToken.value, tokens.slice(pos, pos + 2))],
2297
2475
  ruleName: "time:fps-rate"
2298
2476
  };
2299
2477
  }
@@ -2306,7 +2484,7 @@ function laptimeNormalizerRule(priority = 70) {
2306
2484
  name: "time:laptime",
2307
2485
  priority,
2308
2486
  match(tokens, pos) {
2309
- if (chunkBHTNFEGZ_cjs.isInsideRangeContext(tokens, pos)) return null;
2487
+ if (chunkC75AFGVD_cjs.isInsideRangeContext(tokens, pos)) return null;
2310
2488
  const h = tokens[pos];
2311
2489
  const c1 = tokens[pos + 1];
2312
2490
  const m = tokens[pos + 2];
@@ -2325,7 +2503,7 @@ function laptimeNormalizerRule(priority = 70) {
2325
2503
  const totalSeconds = hours * 3600 + minutes * 60 + seconds;
2326
2504
  return {
2327
2505
  consumed: 5,
2328
- replacement: [chunkBHTNFEGZ_cjs.createFusedToken("LAPTIME", String(totalSeconds), tokens.slice(pos, pos + 5))],
2506
+ replacement: [chunkC75AFGVD_cjs.createFusedToken("LAPTIME", String(totalSeconds), tokens.slice(pos, pos + 5))],
2329
2507
  ruleName: "time:laptime"
2330
2508
  };
2331
2509
  }
@@ -2338,7 +2516,7 @@ function videoTimecodeNormalizerRule(priority = 75) {
2338
2516
  name: "time:video-timecode",
2339
2517
  priority,
2340
2518
  match(tokens, pos) {
2341
- if (chunkBHTNFEGZ_cjs.isInsideRangeContext(tokens, pos)) return null;
2519
+ if (chunkC75AFGVD_cjs.isInsideRangeContext(tokens, pos)) return null;
2342
2520
  const h = tokens[pos];
2343
2521
  const c1 = tokens[pos + 1];
2344
2522
  const m = tokens[pos + 2];
@@ -2364,7 +2542,7 @@ function videoTimecodeNormalizerRule(priority = 75) {
2364
2542
  return {
2365
2543
  consumed: 7,
2366
2544
  replacement: [
2367
- chunkBHTNFEGZ_cjs.createFusedToken("VIDEO_TIMECODE", `${hours},${minutes},${seconds},${frames}`, tokens.slice(pos, pos + 7))
2545
+ chunkC75AFGVD_cjs.createFusedToken("VIDEO_TIMECODE", `${hours},${minutes},${seconds},${frames}`, tokens.slice(pos, pos + 7))
2368
2546
  ],
2369
2547
  ruleName: "time:video-timecode"
2370
2548
  };
@@ -2384,7 +2562,7 @@ function frameCountNormalizerRule(priority = 65) {
2384
2562
  if (framesToken?.type !== "IDENT" || framesToken.value.toLowerCase() !== "frames") return null;
2385
2563
  return {
2386
2564
  consumed: 2,
2387
- replacement: [chunkBHTNFEGZ_cjs.createFusedToken("FRAME_COUNT", numberToken.value, tokens.slice(pos, pos + 2))],
2565
+ replacement: [chunkC75AFGVD_cjs.createFusedToken("FRAME_COUNT", numberToken.value, tokens.slice(pos, pos + 2))],
2388
2566
  ruleName: "time:frame-count"
2389
2567
  };
2390
2568
  }
@@ -2410,7 +2588,7 @@ function durationSeconds(value) {
2410
2588
  function toTimespanString(value) {
2411
2589
  const total = durationSeconds(value);
2412
2590
  if (total === null) {
2413
- return chunkJMXUNXQS_cjs.stringValue(`"as timespan" needs a duration, got ${value.unit ?? "a non-duration"}`);
2591
+ return chunk2TZKENDH_cjs.stringValue(`"as timespan" needs a duration, got ${value.unit ?? "a non-duration"}`);
2414
2592
  }
2415
2593
  const negative = total < 0;
2416
2594
  let remaining = Math.abs(total);
@@ -2427,12 +2605,12 @@ function toTimespanString(value) {
2427
2605
  if (secondsLeft > 0 || parts.length === 0) {
2428
2606
  parts.push(`${secondsLeft} ${secondsLeft === 1 ? "second" : "seconds"}`);
2429
2607
  }
2430
- return chunkJMXUNXQS_cjs.stringValue((negative ? "-" : "") + parts.join(" "));
2608
+ return chunk2TZKENDH_cjs.stringValue((negative ? "-" : "") + parts.join(" "));
2431
2609
  }
2432
2610
  function toLaptimeString(value) {
2433
2611
  const total = durationSeconds(value);
2434
2612
  if (total === null) {
2435
- return chunkJMXUNXQS_cjs.stringValue(`"as laptime" needs a duration, got ${value.unit ?? "a non-duration"}`);
2613
+ return chunk2TZKENDH_cjs.stringValue(`"as laptime" needs a duration, got ${value.unit ?? "a non-duration"}`);
2436
2614
  }
2437
2615
  const negative = total < 0;
2438
2616
  const rounded = Math.round(Math.abs(total) * 1e3) / 1e3;
@@ -2442,7 +2620,7 @@ function toLaptimeString(value) {
2442
2620
  const whole = Math.floor(seconds);
2443
2621
  const fraction = Math.round((seconds - whole) * 1e3);
2444
2622
  const base = `${pad(hours)}:${pad(minutes)}:${pad(whole)}`;
2445
- return chunkJMXUNXQS_cjs.stringValue((negative ? "-" : "") + (fraction > 0 ? `${base}.${String(fraction).padStart(3, "0")}` : base));
2623
+ return chunk2TZKENDH_cjs.stringValue((negative ? "-" : "") + (fraction > 0 ? `${base}.${String(fraction).padStart(3, "0")}` : base));
2446
2624
  }
2447
2625
  function pad(n) {
2448
2626
  return n < 10 ? `0${n}` : String(n);
@@ -2495,7 +2673,7 @@ function definePhrasePattern(opts) {
2495
2673
  for (const alt of opts.alternatives) {
2496
2674
  const first = alt.slots[0];
2497
2675
  if (!first || first.kind !== "keyword") {
2498
- throw chunk5KMIY374_cjs.ErrorFactory.config(
2676
+ throw chunkB4HBEFTB_cjs.ErrorFactory.config(
2499
2677
  "INVALID_PHRASE_PATTERN",
2500
2678
  "Every PhraseAlternative's first slot must be a `keyword` slot \u2014 alternatives are chosen by peeking the next token, which only works if the choice is decidable from one leading keyword.",
2501
2679
  { category: opts.category }
@@ -2512,7 +2690,7 @@ function definePhrasePattern(opts) {
2512
2690
  });
2513
2691
  if (!alternative) {
2514
2692
  const expected = opts.alternatives.map((alt) => alt.slots[0].tokenTypes.join("|")).join(", ");
2515
- throw chunk5KMIY374_cjs.ErrorFactory.parsing(
2693
+ throw chunkB4HBEFTB_cjs.ErrorFactory.parsing(
2516
2694
  "NO_MATCHING_PHRASE_ALTERNATIVE",
2517
2695
  `Expected one of [${expected}] but got "${next?.type ?? "end of input"}" ("${next?.value ?? ""}")`,
2518
2696
  { category: opts.category, expected, actualType: next?.type, actualValue: next?.value }
@@ -2523,7 +2701,7 @@ function definePhrasePattern(opts) {
2523
2701
  if (slot.kind === "keyword") {
2524
2702
  const token = parser.peek();
2525
2703
  if (!token || !slot.tokenTypes.includes(token.type)) {
2526
- throw chunk5KMIY374_cjs.ErrorFactory.parsing(
2704
+ throw chunkB4HBEFTB_cjs.ErrorFactory.parsing(
2527
2705
  "PHRASE_KEYWORD_MISMATCH",
2528
2706
  `Expected one of [${slot.tokenTypes.join("|")}] but got "${token?.type ?? "end of input"}" ("${token?.value ?? ""}")`,
2529
2707
  { category: opts.category, expected: slot.tokenTypes, actualType: token?.type, actualValue: token?.value }
@@ -2553,7 +2731,7 @@ var keywordLedRollPattern = definePhrasePattern({
2553
2731
  {
2554
2732
  slots: [
2555
2733
  { kind: "keyword", tokenTypes: ["BETWEEN"] },
2556
- { kind: "expr", bindingPower: chunkPA4VC73I_cjs.BindingPower.Product },
2734
+ { kind: "expr", bindingPower: chunkJ45BCEZ4_cjs.BindingPower.Product },
2557
2735
  // The word "and" lexes as PLUS, not a dedicated AND token, en.ts's
2558
2736
  // keywordMap maps `and: "PLUS"` (a synonym for arithmetic "+").
2559
2737
  // The original hand-written parselet never actually checked the
@@ -2568,7 +2746,7 @@ var keywordLedRollPattern = definePhrasePattern({
2568
2746
  {
2569
2747
  slots: [
2570
2748
  { kind: "keyword", tokenTypes: ["FROM"] },
2571
- { kind: "expr", bindingPower: chunkPA4VC73I_cjs.BindingPower.Product },
2749
+ { kind: "expr", bindingPower: chunkJ45BCEZ4_cjs.BindingPower.Product },
2572
2750
  { kind: "keyword", tokenTypes: ["TO"] },
2573
2751
  { kind: "expr" }
2574
2752
  ],
@@ -2623,7 +2801,7 @@ var VariableParselet = class {
2623
2801
  parse(parser, _token, builder) {
2624
2802
  const nameToken = parser.consume();
2625
2803
  if (nameToken.type !== "IDENT" && nameToken.type !== "UNIT") {
2626
- throw chunk5KMIY374_cjs.ErrorFactory.parsing(
2804
+ throw chunkB4HBEFTB_cjs.ErrorFactory.parsing(
2627
2805
  "EXPECTED_IDENTIFIER",
2628
2806
  `Expected identifier or unit after colon, got ${nameToken.type}`,
2629
2807
  { tokenType: nameToken.type }
@@ -2662,7 +2840,7 @@ var GlobalVariableParselet = class {
2662
2840
  parser.consume("COLON");
2663
2841
  const nameToken = parser.consume();
2664
2842
  if (nameToken.type !== "IDENT" && nameToken.type !== "UNIT") {
2665
- throw chunk5KMIY374_cjs.ErrorFactory.parsing(
2843
+ throw chunkB4HBEFTB_cjs.ErrorFactory.parsing(
2666
2844
  "EXPECTED_IDENTIFIER",
2667
2845
  `Expected identifier after 'global :', got ${nameToken.type}`,
2668
2846
  { tokenType: nameToken.type }
@@ -2712,11 +2890,11 @@ var GlobalVariableAsyncResolver = class {
2712
2890
  const op = opcodes[i];
2713
2891
  if (op === 62 /* LOAD_GLOBAL_VAR */) {
2714
2892
  const varName = strings[opcodes[i + 1]];
2715
- if (!chunk6WFMPTGB_cjs.sharedGlobalVariableStore.has(varName)) {
2893
+ if (!chunk7ZX6B7SY_cjs.sharedGlobalVariableStore.has(varName)) {
2716
2894
  return this.pendingResultFor(varName, packageId, signal);
2717
2895
  }
2718
2896
  }
2719
- i = chunk4B2CNWQU_cjs.nextInstruction(opcodes, i);
2897
+ i = chunkSSV46KFA_cjs.nextInstruction(opcodes, i);
2720
2898
  }
2721
2899
  return null;
2722
2900
  }
@@ -2724,7 +2902,7 @@ var GlobalVariableAsyncResolver = class {
2724
2902
  let resolver = this.pending.get(varName);
2725
2903
  if (!resolver) {
2726
2904
  resolver = new Promise((resolve) => {
2727
- const unsubscribe = chunk6WFMPTGB_cjs.sharedGlobalVariableStore.subscribe((name, value) => {
2905
+ const unsubscribe = chunk7ZX6B7SY_cjs.sharedGlobalVariableStore.subscribe((name, value) => {
2728
2906
  if (name !== varName) return;
2729
2907
  unsubscribe();
2730
2908
  this.pending.delete(varName);
@@ -2769,26 +2947,193 @@ var VARIABLES_PACKAGE = {
2769
2947
  ]
2770
2948
  };
2771
2949
 
2950
+ // src/uom/HistoricalCurrency.ts
2951
+ var HISTORICAL_CURRENCY_NS = "currency-historical";
2952
+ var HISTORICAL_CURRENCY_FN_IDX = chunk7ZX6B7SY_cjs.allocatePluginFunctionIndex();
2953
+ var HistoricalCurrencyErrorCodes = {
2954
+ /** No {@link HistoricalRateProvider} was supplied, so `on <date>` conversions cannot be answered. NOT a fall back to today's rate. */
2955
+ NOT_CONFIGURED: "HISTORICAL_RATES_NOT_CONFIGURED",
2956
+ /** The host provider threw or timed out for one pair/date. Transient, evicted after a cooldown so a retry can happen. */
2957
+ QUERY_FAILED: "HISTORICAL_RATE_QUERY_FAILED",
2958
+ /** The VM reached the conversion before preflight cached its rate, a "shouldn't happen" invariant break, not a user error. */
2959
+ NOT_PREFLIGHTED: "HISTORICAL_RATE_NOT_PREFLIGHTED",
2960
+ /** The amount being converted was not a currency Value (bad bytecode, or a non-currency left operand). */
2961
+ INVALID_OPERAND: "HISTORICAL_CURRENCY_INVALID_OPERAND"
2962
+ };
2963
+ var FAILURE_COOLDOWN_MS = 3e4;
2964
+ var HISTORICAL_RATE_STALE_TIME_MS = Infinity;
2965
+ var FETCH_TIMEOUT_MS = 1e4;
2966
+ function historicalRateQueryKey(from, to, isoDate) {
2967
+ return [HISTORICAL_CURRENCY_NS, `${from.toUpperCase()}:${to.toUpperCase()}:${isoDate}`];
2968
+ }
2969
+ function pad2(n) {
2970
+ return n < 10 ? `0${n}` : String(n);
2971
+ }
2972
+ function tryConsumeCurrencyOnDate(parser, to, from) {
2973
+ if (!chunkZVTWQLK4_cjs.sharedCurrencyExchange.isCurrency(to)) return null;
2974
+ if (from !== void 0 && !chunkZVTWQLK4_cjs.sharedCurrencyExchange.isCurrency(from)) return null;
2975
+ const onToken = parser.peek();
2976
+ if (!onToken || onToken.type !== "IDENT" || onToken.value.toLowerCase() !== "on") return null;
2977
+ const dateToken = parser.peekAt(1);
2978
+ if (!dateToken || dateToken.type !== "DATETIME_LITERAL") return null;
2979
+ parser.consume();
2980
+ parser.consume();
2981
+ const date = new Date(Number(dateToken.value));
2982
+ return `${date.getFullYear()}-${pad2(date.getMonth() + 1)}-${pad2(date.getDate())}`;
2983
+ }
2984
+ async function fetchHistoricalRate(provider, from, to, isoDate, signal, queryClient) {
2985
+ const fromUpper = from.toUpperCase();
2986
+ const toUpper = to.toUpperCase();
2987
+ if (!provider) {
2988
+ return chunk2TZKENDH_cjs.errorValue(
2989
+ HistoricalCurrencyErrorCodes.NOT_CONFIGURED,
2990
+ `Historical exchange rates are not configured. Supply historicalRateProvider via createCurrencyPackage({ ... }) to convert ${fromUpper} to ${toUpper} on ${isoDate}.`
2991
+ );
2992
+ }
2993
+ const { signal: fetchSignal, cleanup } = chunkVEKIWQ3F_cjs.createTimeoutSignal(signal, FETCH_TIMEOUT_MS, "Historical currency rate fetch");
2994
+ try {
2995
+ const rate = await provider(fromUpper, toUpper, isoDate, fetchSignal);
2996
+ return chunk2TZKENDH_cjs.numberValue(rate);
2997
+ } catch (error) {
2998
+ const failedValue = chunk2TZKENDH_cjs.errorValue(
2999
+ HistoricalCurrencyErrorCodes.QUERY_FAILED,
3000
+ `Failed to resolve the historical rate for ${fromUpper} to ${toUpper} on ${isoDate}: ${error instanceof Error ? error.message : String(error)}`
3001
+ );
3002
+ const key = historicalRateQueryKey(from, to, isoDate);
3003
+ const cooldownTimer = setTimeout(() => {
3004
+ if (queryClient.getQueryData(key) === failedValue) {
3005
+ queryClient.removeQueries({ queryKey: key, exact: true });
3006
+ }
3007
+ }, FAILURE_COOLDOWN_MS);
3008
+ cooldownTimer.unref?.();
3009
+ return failedValue;
3010
+ } finally {
3011
+ cleanup();
3012
+ }
3013
+ }
3014
+ function createHistoricalCurrencyPluginFunction(provider) {
3015
+ return (args) => {
3016
+ const amount = args[0];
3017
+ const toArg = args[1];
3018
+ const dateArg = args[2];
3019
+ const fault = chunk2TZKENDH_cjs.faultedOperand(amount);
3020
+ if (fault) return fault;
3021
+ if (amount.type !== 6 /* Uom */ || amount.unit === void 0 || !chunkZVTWQLK4_cjs.sharedCurrencyExchange.isCurrency(amount.unit)) {
3022
+ return chunk2TZKENDH_cjs.errorValue(
3023
+ HistoricalCurrencyErrorCodes.INVALID_OPERAND,
3024
+ `Historical conversion needs an amount in a currency, for example "100 USD in GBP on 2024-01-15".`
3025
+ );
3026
+ }
3027
+ const from = amount.unit;
3028
+ const to = String(toArg.value);
3029
+ const isoDate = String(dateArg.value);
3030
+ if (from.toUpperCase() === to.toUpperCase()) {
3031
+ return chunk2TZKENDH_cjs.uomValue(amount.toNumber(), to);
3032
+ }
3033
+ const queryClient = chunkZ2XP767T_cjs.getActiveQueryClient();
3034
+ const key = historicalRateQueryKey(from, to, isoDate);
3035
+ const cached = queryClient?.getQueryData(key);
3036
+ if (cached === void 0) {
3037
+ if (!queryClient) {
3038
+ return chunk2TZKENDH_cjs.errorValue(
3039
+ HistoricalCurrencyErrorCodes.NOT_PREFLIGHTED,
3040
+ `No historical rate resolved for ${from.toUpperCase()} to ${to.toUpperCase()} on ${isoDate}.`
3041
+ );
3042
+ }
3043
+ return queryClient.fetchQuery({
3044
+ queryKey: key,
3045
+ queryFn: ({ signal }) => fetchHistoricalRate(provider, from, to, isoDate, signal, queryClient),
3046
+ staleTime: HISTORICAL_RATE_STALE_TIME_MS
3047
+ });
3048
+ }
3049
+ if (cached.type === 13 /* Error */) return cached;
3050
+ const rate = cached.value;
3051
+ return chunk2TZKENDH_cjs.uomValue(amount.toNumber() * rate, to);
3052
+ };
3053
+ }
3054
+ function createHistoricalCurrencyResolver(provider) {
3055
+ return {
3056
+ namespace: HISTORICAL_CURRENCY_NS,
3057
+ preflight(_tokens, bytecode, packageId, signal, queryClient) {
3058
+ const { opcodes, strings } = bytecode;
3059
+ const len = opcodes.length;
3060
+ let i = 0;
3061
+ let stringIdxs = [];
3062
+ while (i < len) {
3063
+ const op = opcodes[i];
3064
+ if (op === 13 /* PUSH_STRING */) {
3065
+ stringIdxs.push(opcodes[i + 1]);
3066
+ } else if (op === 50 /* CALL_PLUGIN */) {
3067
+ const fnIdx = opcodes[i + 1];
3068
+ const argCount = opcodes[i + 2];
3069
+ if (fnIdx === HISTORICAL_CURRENCY_FN_IDX && argCount === 3 && stringIdxs.length >= 3) {
3070
+ const isoDate = strings[stringIdxs[stringIdxs.length - 1]];
3071
+ const to = strings[stringIdxs[stringIdxs.length - 2]];
3072
+ const sources = /* @__PURE__ */ new Set();
3073
+ for (let k = 0; k < stringIdxs.length - 2; k++) {
3074
+ const s = strings[stringIdxs[k]];
3075
+ if (chunkZVTWQLK4_cjs.sharedCurrencyExchange.isCurrency(s)) sources.add(s.toUpperCase());
3076
+ }
3077
+ if (sources.size === 1) {
3078
+ const from = sources.values().next().value;
3079
+ if (from !== to.toUpperCase()) {
3080
+ const key = historicalRateQueryKey(from, to, isoDate);
3081
+ if (queryClient.getQueryData(key) === void 0) {
3082
+ const resolver = queryClient.fetchQuery({
3083
+ queryKey: key,
3084
+ queryFn: ({ signal: qSignal }) => fetchHistoricalRate(provider, from, to, isoDate, qSignal, queryClient),
3085
+ staleTime: HISTORICAL_RATE_STALE_TIME_MS
3086
+ });
3087
+ return { queryKey: key.join(":"), resolver, packageId, signal, metadata: { from, to, isoDate } };
3088
+ }
3089
+ }
3090
+ }
3091
+ }
3092
+ stringIdxs = [];
3093
+ }
3094
+ i = chunkSSV46KFA_cjs.nextInstruction(opcodes, i);
3095
+ }
3096
+ return null;
3097
+ },
3098
+ destroy() {
3099
+ }
3100
+ };
3101
+ }
3102
+
2772
3103
  // src/packages/uom/parselets/UomLiteralParselet.ts
2773
3104
  function resolveUnitAlias(rawUnit) {
2774
- return chunkUM6BVY2S_cjs.resolveCurrencyAlias(rawUnit) ?? rawUnit;
3105
+ return chunkHIQ5HSZL_cjs.resolveCurrencyAlias(rawUnit) ?? rawUnit;
2775
3106
  }
2776
3107
  var UomLiteralParselet = class {
2777
3108
  constructor() {
2778
3109
  this.category = "UoM";
2779
- this.bindingPower = chunkPA4VC73I_cjs.BindingPower.Postfix;
3110
+ this.bindingPower = chunkJ45BCEZ4_cjs.BindingPower.Postfix;
2780
3111
  }
2781
3112
  parse(parser, left, token, builder) {
2782
3113
  const unit = resolveUnitAlias(token.value);
2783
3114
  builder.emitOpcode(13 /* PUSH_STRING */);
2784
3115
  builder.emitString(unit);
2785
- if (parser.peek()?.type === "TO" || parser.peek()?.type === "IN") {
3116
+ const boundInsideProduct = parser.infixMinBindingPower >= chunkJ45BCEZ4_cjs.BindingPower.Product;
3117
+ if (!boundInsideProduct && (parser.peek()?.type === "TO" || parser.peek()?.type === "IN")) {
2786
3118
  parser.consume();
2787
3119
  const targetToken = parser.peek();
2788
3120
  if (targetToken?.type === "UNIT" || targetToken?.type === "IN") {
2789
3121
  parser.consume();
3122
+ const targetUnit = resolveUnitAlias(targetToken.value);
3123
+ const isoDate = tryConsumeCurrencyOnDate(parser, targetUnit, unit);
3124
+ if (isoDate !== null) {
3125
+ builder.emitOpcode(80 /* UOM_CONVERT */);
3126
+ builder.emitOpcode(13 /* PUSH_STRING */);
3127
+ builder.emitString(targetUnit);
3128
+ builder.emitOpcode(13 /* PUSH_STRING */);
3129
+ builder.emitString(isoDate);
3130
+ builder.emitOpcode(50 /* CALL_PLUGIN */);
3131
+ builder.emitIndex(HISTORICAL_CURRENCY_FN_IDX);
3132
+ builder.emitIndex(3);
3133
+ return;
3134
+ }
2790
3135
  builder.emitOpcode(13 /* PUSH_STRING */);
2791
- builder.emitString(resolveUnitAlias(targetToken.value));
3136
+ builder.emitString(targetUnit);
2792
3137
  builder.emitOpcode(81 /* UOM_CONVERT_TO */);
2793
3138
  return;
2794
3139
  }
@@ -2809,14 +3154,14 @@ var UomLiteralParselet = class {
2809
3154
 
2810
3155
  // src/packages/uom/parselets/ConvertParselet.ts
2811
3156
  function resolveUnitAlias2(rawUnit) {
2812
- return chunkUM6BVY2S_cjs.resolveCurrencyAlias(rawUnit) ?? rawUnit;
3157
+ return chunkHIQ5HSZL_cjs.resolveCurrencyAlias(rawUnit) ?? rawUnit;
2813
3158
  }
2814
3159
  var ConvertParselet = class {
2815
3160
  constructor() {
2816
3161
  this.category = "UoM";
2817
3162
  }
2818
3163
  parse(parser, token, builder) {
2819
- parser.parseExpression(chunkPA4VC73I_cjs.BindingPower.Postfix, builder);
3164
+ parser.parseExpression(chunkJ45BCEZ4_cjs.BindingPower.Postfix, builder);
2820
3165
  const nextToken = parser.peek();
2821
3166
  if (nextToken && nextToken.type === "UNIT") {
2822
3167
  parser.consume();
@@ -2881,7 +3226,7 @@ function uomPossibilitiesNormalizerRule(priority = 60) {
2881
3226
  if (questionToken.type !== "QUESTION") return null;
2882
3227
  return {
2883
3228
  consumed: 3,
2884
- replacement: [chunkBHTNFEGZ_cjs.createFusedToken("UOM_POSSIBILITIES_QUERY", unitToken.value, [unitToken, toToken, questionToken])],
3229
+ replacement: [chunkC75AFGVD_cjs.createFusedToken("UOM_POSSIBILITIES_QUERY", unitToken.value, [unitToken, toToken, questionToken])],
2885
3230
  ruleName: "uom:possibilities"
2886
3231
  };
2887
3232
  }
@@ -2933,8 +3278,8 @@ function compoundQuantityNormalizerRule(priority = 63) {
2933
3278
  return {
2934
3279
  consumed,
2935
3280
  replacement: [
2936
- chunkBHTNFEGZ_cjs.createFusedToken("NUMBER", String(scaled), source),
2937
- chunkBHTNFEGZ_cjs.createFusedToken("UNIT", smallestUnit, [smallestUnitToken])
3281
+ chunkC75AFGVD_cjs.createFusedToken("NUMBER", String(scaled), source),
3282
+ chunkC75AFGVD_cjs.createFusedToken("UNIT", smallestUnit, [smallestUnitToken])
2938
3283
  ],
2939
3284
  ruleName: "uom:compound-quantity"
2940
3285
  };
@@ -2962,7 +3307,7 @@ function twoUnitConversionNormalizerRule(priority = 59) {
2962
3307
  return {
2963
3308
  consumed: 4,
2964
3309
  replacement: [
2965
- chunkBHTNFEGZ_cjs.createFusedToken("IN_TWO_UNITS", `${major} ${minor}`, tokens.slice(pos, pos + 4))
3310
+ chunkC75AFGVD_cjs.createFusedToken("IN_TWO_UNITS", `${major} ${minor}`, tokens.slice(pos, pos + 4))
2966
3311
  ],
2967
3312
  ruleName: "uom:two-unit-conversion"
2968
3313
  };
@@ -2983,7 +3328,7 @@ function degreeSymbolNormalizerRule(priority = 74) {
2983
3328
  if ((symbol.text ?? symbol.value ?? "") !== "\xB0") return null;
2984
3329
  return {
2985
3330
  consumed: 2,
2986
- replacement: [number, chunkBHTNFEGZ_cjs.createFusedToken("UNIT", "degrees", [symbol])],
3331
+ replacement: [number, chunkC75AFGVD_cjs.createFusedToken("UNIT", "degrees", [symbol])],
2987
3332
  ruleName: "uom:degree-symbol"
2988
3333
  };
2989
3334
  }
@@ -2995,7 +3340,7 @@ var TwoUnitConversionParselet = class {
2995
3340
  constructor(builtinIndex) {
2996
3341
  this.builtinIndex = builtinIndex;
2997
3342
  this.category = "Uom";
2998
- this.bindingPower = chunkPA4VC73I_cjs.BindingPower.Conditional;
3343
+ this.bindingPower = chunkJ45BCEZ4_cjs.BindingPower.Conditional;
2999
3344
  }
3000
3345
  parse(_parser, _left, token, builder) {
3001
3346
  const [major, minor] = String(token.value).split(" ");
@@ -3035,7 +3380,7 @@ function bareRateDenominatorNormalizerRule(priority = 75) {
3035
3380
  if (introduces && isUnit2(tokens[pos + 3])) {
3036
3381
  return {
3037
3382
  consumed: 2,
3038
- replacement: [head, chunkBHTNFEGZ_cjs.createFusedToken("UNIT", label.value, [label])],
3383
+ replacement: [head, chunkC75AFGVD_cjs.createFusedToken("UNIT", label.value, [label])],
3039
3384
  ruleName: "uom:bare-rate-denominator"
3040
3385
  };
3041
3386
  }
@@ -3049,7 +3394,7 @@ function bareRateDenominatorNormalizerRule(priority = 75) {
3049
3394
  return {
3050
3395
  consumed: 2,
3051
3396
  replacement: [
3052
- chunkBHTNFEGZ_cjs.createFusedToken("PER_UNIT", unitToken.value, [head, unitToken])
3397
+ chunkC75AFGVD_cjs.createFusedToken("PER_UNIT", unitToken.value, [head, unitToken])
3053
3398
  ],
3054
3399
  ruleName: "uom:bare-rate-denominator"
3055
3400
  };
@@ -3062,7 +3407,7 @@ var PerUnitParselet = class {
3062
3407
  constructor(builtinIndex) {
3063
3408
  this.builtinIndex = builtinIndex;
3064
3409
  this.category = "Uom";
3065
- this.bindingPower = chunkPA4VC73I_cjs.BindingPower.Product;
3410
+ this.bindingPower = chunkJ45BCEZ4_cjs.BindingPower.Product;
3066
3411
  }
3067
3412
  parse(_parser, _left, token, builder) {
3068
3413
  builder.emitOpcode(13 /* PUSH_STRING */);
@@ -3078,10 +3423,10 @@ var AtRateParselet = class {
3078
3423
  constructor(builtinIndex) {
3079
3424
  this.builtinIndex = builtinIndex;
3080
3425
  this.category = "Uom";
3081
- this.bindingPower = chunkPA4VC73I_cjs.BindingPower.Product;
3426
+ this.bindingPower = chunkJ45BCEZ4_cjs.BindingPower.Product;
3082
3427
  }
3083
3428
  parse(parser, _left, _token, builder) {
3084
- parser.parseExpression(chunkPA4VC73I_cjs.BindingPower.Conditional, builder);
3429
+ parser.parseExpression(chunkJ45BCEZ4_cjs.BindingPower.Conditional, builder);
3085
3430
  builder.emitOpcode(51 /* CALL_BUILTIN */);
3086
3431
  builder.emitIndex(this.builtinIndex);
3087
3432
  builder.emitIndex(2);
@@ -3114,7 +3459,7 @@ function atRateNormalizerRule(priority = 73) {
3114
3459
  if (!hasRateAhead(tokens, pos + 1)) return null;
3115
3460
  return {
3116
3461
  consumed: 1,
3117
- replacement: [chunkBHTNFEGZ_cjs.createFusedToken("AT_RATE", "at", [tokens[pos]])],
3462
+ replacement: [chunkC75AFGVD_cjs.createFusedToken("AT_RATE", "at", [tokens[pos]])],
3118
3463
  ruleName: "uom:at-rate"
3119
3464
  };
3120
3465
  }
@@ -3141,8 +3486,8 @@ function forDurationNormalizerRule(priority = 76) {
3141
3486
  return {
3142
3487
  consumed: 3,
3143
3488
  replacement: [
3144
- chunkBHTNFEGZ_cjs.createFusedToken("STAR", "*", [head]),
3145
- chunkBHTNFEGZ_cjs.createFusedToken("NUMBER", "1", [article]),
3489
+ chunkC75AFGVD_cjs.createFusedToken("STAR", "*", [head]),
3490
+ chunkC75AFGVD_cjs.createFusedToken("NUMBER", "1", [article]),
3146
3491
  tokens[pos + 2]
3147
3492
  ],
3148
3493
  ruleName: "uom:for-duration"
@@ -3179,7 +3524,7 @@ function reversedConversionNormalizerRule(priority = 61) {
3179
3524
  return {
3180
3525
  consumed: 4,
3181
3526
  replacement: [
3182
- chunkBHTNFEGZ_cjs.createFusedToken("NUMBER", "1", [third]),
3527
+ chunkC75AFGVD_cjs.createFusedToken("NUMBER", "1", [third]),
3183
3528
  tokens[pos + 3],
3184
3529
  tokens[pos + 1],
3185
3530
  target
@@ -3288,54 +3633,54 @@ function getIngredientDensity(name) {
3288
3633
  }
3289
3634
 
3290
3635
  // src/packages/uom/parselets/CookingPluginFunctions.ts
3291
- var COOKING_CONVERT_IDX = chunk6WFMPTGB_cjs.allocatePluginFunctionIndex();
3636
+ var COOKING_CONVERT_IDX = chunk7ZX6B7SY_cjs.allocatePluginFunctionIndex();
3292
3637
  function cookingConvertHandler(args) {
3293
3638
  const amountValue = args[0];
3294
3639
  const ingredientName = args[1].value;
3295
3640
  const targetUnitText = args[2].value;
3296
3641
  if (amountValue.type !== 6 /* Uom */ || !amountValue.unit) {
3297
- return chunkJMXUNXQS_cjs.errorValue(
3642
+ return chunk2TZKENDH_cjs.errorValue(
3298
3643
  "COOKING_CONVERSION_REQUIRES_UNIT",
3299
3644
  `Expected a value with a mass or volume unit (e.g. "300g", "10 cups"), got a plain number`
3300
3645
  );
3301
3646
  }
3302
3647
  const sourceUnit = amountValue.unit;
3303
3648
  const amount = amountValue.toNumber();
3304
- const sourceMeasure = chunkPFUESQTW_cjs.getMeasure(sourceUnit);
3305
- const targetMeasure = chunkPFUESQTW_cjs.getMeasure(targetUnitText);
3649
+ const sourceMeasure = chunkEJ3ILXX6_cjs.getMeasure(sourceUnit);
3650
+ const targetMeasure = chunkEJ3ILXX6_cjs.getMeasure(targetUnitText);
3306
3651
  const isMassOrVolume = (m) => m === "mass" || m === "volume";
3307
3652
  if (!isMassOrVolume(sourceMeasure)) {
3308
- return chunkJMXUNXQS_cjs.errorValue(
3653
+ return chunk2TZKENDH_cjs.errorValue(
3309
3654
  "COOKING_CONVERSION_UNSUPPORTED_UNIT",
3310
3655
  `"${sourceUnit}" is not a recognized mass or volume unit`
3311
3656
  );
3312
3657
  }
3313
3658
  if (!isMassOrVolume(targetMeasure)) {
3314
- return chunkJMXUNXQS_cjs.errorValue(
3659
+ return chunk2TZKENDH_cjs.errorValue(
3315
3660
  "COOKING_CONVERSION_UNSUPPORTED_UNIT",
3316
3661
  `"${targetUnitText}" is not a recognized mass or volume unit`
3317
3662
  );
3318
3663
  }
3319
3664
  if (sourceMeasure === targetMeasure) {
3320
- return chunkJMXUNXQS_cjs.uomValue(chunkPFUESQTW_cjs.convertUnit(amount, sourceUnit, targetUnitText), targetUnitText);
3665
+ return chunk2TZKENDH_cjs.uomValue(chunkEJ3ILXX6_cjs.convertUnit(amount, sourceUnit, targetUnitText), targetUnitText);
3321
3666
  }
3322
3667
  const density = getIngredientDensity(ingredientName);
3323
3668
  if (density === void 0) {
3324
- return chunkJMXUNXQS_cjs.errorValue(
3669
+ return chunk2TZKENDH_cjs.errorValue(
3325
3670
  "COOKING_UNKNOWN_INGREDIENT",
3326
3671
  `No density data for "${ingredientName}" \u2014 cannot convert between mass and volume for this ingredient`
3327
3672
  );
3328
3673
  }
3329
3674
  if (sourceMeasure === "mass") {
3330
- const grams2 = chunkPFUESQTW_cjs.convertUnit(amount, sourceUnit, "g");
3675
+ const grams2 = chunkEJ3ILXX6_cjs.convertUnit(amount, sourceUnit, "g");
3331
3676
  const mL2 = grams2 / density;
3332
- const result2 = chunkPFUESQTW_cjs.convertUnit(mL2, "ml", targetUnitText);
3333
- return chunkJMXUNXQS_cjs.uomValue(result2, targetUnitText);
3677
+ const result2 = chunkEJ3ILXX6_cjs.convertUnit(mL2, "ml", targetUnitText);
3678
+ return chunk2TZKENDH_cjs.uomValue(result2, targetUnitText);
3334
3679
  }
3335
- const mL = chunkPFUESQTW_cjs.convertUnit(amount, sourceUnit, "ml");
3680
+ const mL = chunkEJ3ILXX6_cjs.convertUnit(amount, sourceUnit, "ml");
3336
3681
  const grams = mL * density;
3337
- const result = chunkPFUESQTW_cjs.convertUnit(grams, "g", targetUnitText);
3338
- return chunkJMXUNXQS_cjs.uomValue(result, targetUnitText);
3682
+ const result = chunkEJ3ILXX6_cjs.convertUnit(grams, "g", targetUnitText);
3683
+ return chunk2TZKENDH_cjs.uomValue(result, targetUnitText);
3339
3684
  }
3340
3685
 
3341
3686
  // src/packages/uom/parselets/CookingConversionParselet.ts
@@ -3378,7 +3723,7 @@ function ingredientNameNormalizerRule(priority = 68) {
3378
3723
  const candidate = candidateTokens.map((t) => t.value.toLowerCase()).join(" ");
3379
3724
  return {
3380
3725
  consumed: wordCount,
3381
- replacement: [chunkBHTNFEGZ_cjs.createFusedToken("INGREDIENT_NAME", candidate, candidateTokens)],
3726
+ replacement: [chunkC75AFGVD_cjs.createFusedToken("INGREDIENT_NAME", candidate, candidateTokens)],
3382
3727
  ruleName: "uom-cooking:ingredient-name"
3383
3728
  };
3384
3729
  }
@@ -3401,13 +3746,35 @@ function multiWordUnitNormalizerRule(priority = 78) {
3401
3746
  if (chunkAMOJYLG2_cjs.UNIT_TABLE[spelling] === void 0) return null;
3402
3747
  return {
3403
3748
  consumed: 2,
3404
- replacement: [chunkBHTNFEGZ_cjs.createFusedToken("UNIT", spelling, tokens.slice(pos, pos + 2))],
3749
+ replacement: [chunkC75AFGVD_cjs.createFusedToken("UNIT", spelling, tokens.slice(pos, pos + 2))],
3405
3750
  ruleName: "uom:multi-word-unit"
3406
3751
  };
3407
3752
  }
3408
3753
  };
3409
3754
  }
3410
3755
 
3756
+ // src/packages/uom/normalizer/CompoundUnitNormalizerRule.ts
3757
+ function compoundUnitNormalizerRule(priority = 77) {
3758
+ return {
3759
+ name: "uom:compound-unit",
3760
+ priority,
3761
+ match(tokens, pos) {
3762
+ const numerator = tokens[pos];
3763
+ const slash = tokens[pos + 1];
3764
+ const denominator = tokens[pos + 2];
3765
+ if (numerator?.type !== "UNIT") return null;
3766
+ if (slash?.type !== "SLASH") return null;
3767
+ if (denominator?.type !== "UNIT") return null;
3768
+ const spelling = `${numerator.value}/${denominator.value}`;
3769
+ return {
3770
+ consumed: 3,
3771
+ replacement: [chunkC75AFGVD_cjs.createFusedToken("UNIT", spelling, [numerator, slash, denominator])],
3772
+ ruleName: "uom:compound-unit"
3773
+ };
3774
+ }
3775
+ };
3776
+ }
3777
+
3411
3778
  // src/packages/uom/UomPackage.ts
3412
3779
  var UOM_PACKAGE = {
3413
3780
  name: "solve-uom",
@@ -3426,6 +3793,9 @@ var UOM_PACKAGE = {
3426
3793
  // Highest priority of the unit rules: a two-word spelling has to become one
3427
3794
  // token before anything else reads the first word as a unit on its own.
3428
3795
  multiWordUnitNormalizerRule(),
3796
+ // Above the bare-denominator rule (75): a slash-notation compound unit like
3797
+ // `km/h` is claimed whole before the slash is read as a bare denominator.
3798
+ compoundUnitNormalizerRule(),
3429
3799
  uomPossibilitiesNormalizerRule(),
3430
3800
  compoundQuantityNormalizerRule(),
3431
3801
  twoUnitConversionNormalizerRule(),
@@ -3447,8 +3817,8 @@ var CurrencySymbolParselet = class {
3447
3817
  this.category = "UoM";
3448
3818
  }
3449
3819
  parse(parser, token, builder) {
3450
- const currency = chunkUM6BVY2S_cjs.resolveCurrencyAlias(token.value) ?? token.value.toUpperCase();
3451
- parser.parseExpression(chunkPA4VC73I_cjs.BindingPower.Prefix, builder);
3820
+ const currency = chunkHIQ5HSZL_cjs.resolveCurrencyAlias(token.value) ?? token.value.toUpperCase();
3821
+ parser.parseExpression(chunkJ45BCEZ4_cjs.BindingPower.Prefix, builder);
3452
3822
  if (parser.peek()?.type === "UNIT") {
3453
3823
  parser.consume();
3454
3824
  }
@@ -3468,32 +3838,59 @@ var InParselet = class {
3468
3838
  const targetToken = parser.peek();
3469
3839
  if (targetToken && (targetToken.type === "UNIT" || targetToken.type === "DOLLAR" || targetToken.type === "POUND" || targetToken.type === "EURO" || targetToken.type === "YEN" || targetToken.type === "RUBLE" || targetToken.type === "WON" || targetToken.type === "CURRENCY_SYMBOL" || targetToken.type === "IDENT" || targetToken.type === "IN")) {
3470
3840
  parser.consume();
3841
+ const targetUnit = chunkHIQ5HSZL_cjs.resolveCurrencyAlias(targetToken.value) ?? targetToken.value;
3842
+ const isoDate = tryConsumeCurrencyOnDate(parser, targetUnit);
3843
+ if (isoDate !== null) {
3844
+ builder.emitOpcode(13 /* PUSH_STRING */);
3845
+ builder.emitString(targetUnit);
3846
+ builder.emitOpcode(13 /* PUSH_STRING */);
3847
+ builder.emitString(isoDate);
3848
+ builder.emitOpcode(50 /* CALL_PLUGIN */);
3849
+ builder.emitIndex(HISTORICAL_CURRENCY_FN_IDX);
3850
+ builder.emitIndex(3);
3851
+ return;
3852
+ }
3471
3853
  builder.emitOpcode(13 /* PUSH_STRING */);
3472
- builder.emitString(chunkUM6BVY2S_cjs.resolveCurrencyAlias(targetToken.value) ?? targetToken.value);
3854
+ builder.emitString(targetUnit);
3473
3855
  builder.emitOpcode(84 /* UOM_CONVERT_IN */);
3474
3856
  }
3475
3857
  }
3476
3858
  };
3477
3859
 
3478
3860
  // src/packages/currency/CurrencyPackage.ts
3479
- var CURRENCY_PACKAGE = {
3480
- name: "solve-currency",
3481
- asyncResolvers: [new chunk4B2CNWQU_cjs.CurrencyAsyncResolver()],
3482
- prefixParselets: [
3483
- { tokenType: "DOLLAR", parselet: new CurrencySymbolParselet() },
3484
- { tokenType: "POUND", parselet: new CurrencySymbolParselet() },
3485
- { tokenType: "EURO", parselet: new CurrencySymbolParselet() },
3486
- { tokenType: "YEN", parselet: new CurrencySymbolParselet() },
3487
- { tokenType: "RUBLE", parselet: new CurrencySymbolParselet() },
3488
- { tokenType: "WON", parselet: new CurrencySymbolParselet() },
3489
- // Every currency symbol added after the original six above shares this
3490
- // one generic token type. See Token.ts's CURRENCY_SYMBOL doc comment.
3491
- { tokenType: "CURRENCY_SYMBOL", parselet: new CurrencySymbolParselet() }
3492
- ],
3493
- infixParselets: [
3494
- { tokenType: "IN", parselet: new InParselet() }
3495
- ]
3496
- };
3861
+ function createCurrencyPackage(config = {}) {
3862
+ return {
3863
+ name: "solve-currency",
3864
+ asyncResolvers: [
3865
+ new chunkSSV46KFA_cjs.CurrencyAsyncResolver(),
3866
+ createHistoricalCurrencyResolver(config.historicalRateProvider)
3867
+ ],
3868
+ prefixParselets: [
3869
+ { tokenType: "DOLLAR", parselet: new CurrencySymbolParselet() },
3870
+ { tokenType: "POUND", parselet: new CurrencySymbolParselet() },
3871
+ { tokenType: "EURO", parselet: new CurrencySymbolParselet() },
3872
+ { tokenType: "YEN", parselet: new CurrencySymbolParselet() },
3873
+ { tokenType: "RUBLE", parselet: new CurrencySymbolParselet() },
3874
+ { tokenType: "WON", parselet: new CurrencySymbolParselet() },
3875
+ // Every currency symbol added after the original six above shares this
3876
+ // one generic token type. See Token.ts's CURRENCY_SYMBOL doc comment.
3877
+ { tokenType: "CURRENCY_SYMBOL", parselet: new CurrencySymbolParselet() }
3878
+ ],
3879
+ infixParselets: [
3880
+ { tokenType: "IN", parselet: new InParselet() }
3881
+ ],
3882
+ pluginFunctions: [
3883
+ // Historical conversions (`<money> in <currency> on <date>`) compile to a
3884
+ // CALL_PLUGIN at this shared index (see uom/HistoricalCurrency.ts). One
3885
+ // index serves every pair and date, the query is the amount plus the
3886
+ // target and date strings. The handler carries the same provider as the
3887
+ // resolver, so a source currency known only at runtime (`x in GBP on
3888
+ // <date>`) can fetch the rate the bytecode scan could not preflight.
3889
+ { index: HISTORICAL_CURRENCY_FN_IDX, handler: createHistoricalCurrencyPluginFunction(config.historicalRateProvider) }
3890
+ ]
3891
+ };
3892
+ }
3893
+ var CURRENCY_PACKAGE = createCurrencyPackage();
3497
3894
 
3498
3895
  // src/packages/vector/parselets/VectorParselet.ts
3499
3896
  var VectorParselet = class {
@@ -3554,7 +3951,7 @@ var MatrixLiteralParselet = class {
3554
3951
  let rows = 0;
3555
3952
  let cols = -1;
3556
3953
  if (parser.peek()?.type === "RBRACKET") {
3557
- throw chunk5KMIY374_cjs.ErrorFactory.parsing(
3954
+ throw chunkB4HBEFTB_cjs.ErrorFactory.parsing(
3558
3955
  "EMPTY_MATRIX_LITERAL",
3559
3956
  "A matrix literal cannot be empty \u2014 `[]` has no valid shape.",
3560
3957
  {}
@@ -3571,7 +3968,7 @@ var MatrixLiteralParselet = class {
3571
3968
  if (cols === -1) {
3572
3969
  cols = rowCols;
3573
3970
  } else if (rowCols !== cols) {
3574
- throw chunk5KMIY374_cjs.ErrorFactory.parsing(
3971
+ throw chunkB4HBEFTB_cjs.ErrorFactory.parsing(
3575
3972
  "RAGGED_MATRIX_LITERAL",
3576
3973
  `Matrix literal rows must all have the same number of columns \u2014 row ${rows + 1} has ${rowCols}, but a previous row has ${cols}.`,
3577
3974
  { expectedCols: cols, actualCols: rowCols, row: rows + 1 }
@@ -3591,7 +3988,7 @@ var MatrixLiteralParselet = class {
3591
3988
  var MatrixIndexParselet = class {
3592
3989
  constructor() {
3593
3990
  this.category = "Matrix";
3594
- this.bindingPower = chunkPA4VC73I_cjs.BindingPower.Postfix;
3991
+ this.bindingPower = chunkJ45BCEZ4_cjs.BindingPower.Postfix;
3595
3992
  }
3596
3993
  parse(parser, _left, _token, builder) {
3597
3994
  const isSlice = this.hasTopLevelColon(parser);
@@ -3611,7 +4008,7 @@ var MatrixIndexParselet = class {
3611
4008
  return;
3612
4009
  }
3613
4010
  if (argCount !== 2) {
3614
- throw chunk5KMIY374_cjs.ErrorFactory.parsing(
4011
+ throw chunkB4HBEFTB_cjs.ErrorFactory.parsing(
3615
4012
  "INVALID_MATRIX_SLICE_ARITY",
3616
4013
  `Range-based matrix slicing needs exactly 2 arguments ("a[rowRange, colRange]"), got ${argCount}.`,
3617
4014
  { argCount }
@@ -3694,12 +4091,12 @@ function parseTransform(parser, builder) {
3694
4091
  }
3695
4092
  return { kind: 2, name };
3696
4093
  }
3697
- const transformBuilder = new chunkPA4VC73I_cjs.BytecodeBuilder();
3698
- parser.parseExpression(chunkPA4VC73I_cjs.BindingPower.Lowest, transformBuilder);
4094
+ const transformBuilder = new chunkJ45BCEZ4_cjs.BytecodeBuilder();
4095
+ parser.parseExpression(chunkJ45BCEZ4_cjs.BindingPower.Lowest, transformBuilder);
3699
4096
  parser.setBuilder(builder);
3700
4097
  const program = transformBuilder.build();
3701
4098
  if (program.hasAsync) {
3702
- throw chunk5KMIY374_cjs.ErrorFactory.parsing(
4099
+ throw chunkB4HBEFTB_cjs.ErrorFactory.parsing(
3703
4100
  "MAP_REDUCE_TRANSFORM_MUST_BE_SYNCHRONOUS",
3704
4101
  `map/reduce transform expressions must be synchronous (no weather/stocks/currency calls).`
3705
4102
  );
@@ -3717,7 +4114,7 @@ function consumeCollectionName(parser) {
3717
4114
  parser.consume();
3718
4115
  return token.value;
3719
4116
  }
3720
- throw chunk5KMIY374_cjs.ErrorFactory.parsing(
4117
+ throw chunkB4HBEFTB_cjs.ErrorFactory.parsing(
3721
4118
  "MAP_REDUCE_EXPECTED_COLLECTION_NAME",
3722
4119
  `Expected a collection name (e.g. "x" in "x=[1,2,3]") but got ${token ? `"${token.value}"` : "end of input"}.`
3723
4120
  );
@@ -3781,7 +4178,7 @@ var ReduceParselet = class {
3781
4178
  parseCollectionExpr(parser, builder);
3782
4179
  let hasInitial = 0;
3783
4180
  if (parser.match("COMMA")) {
3784
- parser.parseExpression(chunkPA4VC73I_cjs.BindingPower.Lowest, builder);
4181
+ parser.parseExpression(chunkJ45BCEZ4_cjs.BindingPower.Lowest, builder);
3785
4182
  hasInitial = 1;
3786
4183
  }
3787
4184
  parser.consume("RPAREN");
@@ -3796,15 +4193,15 @@ var SumParselet = class {
3796
4193
  }
3797
4194
  parse(parser, _token, builder) {
3798
4195
  parser.consume("LPAREN");
3799
- const bodyBuilder = new chunkPA4VC73I_cjs.BytecodeBuilder();
4196
+ const bodyBuilder = new chunkJ45BCEZ4_cjs.BytecodeBuilder();
3800
4197
  bodyBuilder.emitOpcode(60 /* LOAD_VAR */);
3801
4198
  bodyBuilder.emitString("acc");
3802
- parser.parseExpression(chunkPA4VC73I_cjs.BindingPower.Lowest, bodyBuilder);
4199
+ parser.parseExpression(chunkJ45BCEZ4_cjs.BindingPower.Lowest, bodyBuilder);
3803
4200
  parser.setBuilder(builder);
3804
4201
  bodyBuilder.emitOpcode(20 /* ADD */);
3805
4202
  const bodyProgram = bodyBuilder.build();
3806
4203
  if (bodyProgram.hasAsync) {
3807
- throw chunk5KMIY374_cjs.ErrorFactory.parsing(
4204
+ throw chunkB4HBEFTB_cjs.ErrorFactory.parsing(
3808
4205
  "MAP_REDUCE_TRANSFORM_MUST_BE_SYNCHRONOUS",
3809
4206
  `sum's element expression must be synchronous (no weather/stocks/currency calls).`
3810
4207
  );
@@ -3825,15 +4222,15 @@ var ProdParselet = class {
3825
4222
  }
3826
4223
  parse(parser, _token, builder) {
3827
4224
  parser.consume("LPAREN");
3828
- const bodyBuilder = new chunkPA4VC73I_cjs.BytecodeBuilder();
4225
+ const bodyBuilder = new chunkJ45BCEZ4_cjs.BytecodeBuilder();
3829
4226
  bodyBuilder.emitOpcode(60 /* LOAD_VAR */);
3830
4227
  bodyBuilder.emitString("acc");
3831
- parser.parseExpression(chunkPA4VC73I_cjs.BindingPower.Lowest, bodyBuilder);
4228
+ parser.parseExpression(chunkJ45BCEZ4_cjs.BindingPower.Lowest, bodyBuilder);
3832
4229
  parser.setBuilder(builder);
3833
4230
  bodyBuilder.emitOpcode(22 /* MUL */);
3834
4231
  const bodyProgram = bodyBuilder.build();
3835
4232
  if (bodyProgram.hasAsync) {
3836
- throw chunk5KMIY374_cjs.ErrorFactory.parsing(
4233
+ throw chunkB4HBEFTB_cjs.ErrorFactory.parsing(
3837
4234
  "MAP_REDUCE_TRANSFORM_MUST_BE_SYNCHRONOUS",
3838
4235
  `prod's element expression must be synchronous (no weather/stocks/currency calls).`
3839
4236
  );
@@ -3878,7 +4275,7 @@ function mapReduceCallNormalizerRule(priority = 80) {
3878
4275
  return {
3879
4276
  consumed: 1,
3880
4277
  replacement: [
3881
- new chunkYWQ6V4ZN_cjs.LexerToken(tokenType, chunk5LI5EPGJ_cjs.tokenTypeId(tokenType), token.value, token.value, token.offset, 0, token.line, token.col)
4278
+ new chunkYTIYVHL7_cjs.LexerToken(tokenType, chunkQFTDTX6K_cjs.tokenTypeId(tokenType), token.value, token.value, token.offset, 0, token.line, token.col)
3882
4279
  ],
3883
4280
  ruleName: "mapreduce:call"
3884
4281
  };
@@ -3920,7 +4317,7 @@ var ExpandParselet = class {
3920
4317
  }
3921
4318
  parse(parser, _token, builder) {
3922
4319
  parser.consume("LPAREN");
3923
- parser.parseExpression(chunkPA4VC73I_cjs.BindingPower.Lowest, builder);
4320
+ parser.parseExpression(chunkJ45BCEZ4_cjs.BindingPower.Lowest, builder);
3924
4321
  parser.consume("RPAREN");
3925
4322
  builder.emitOpcode(51 /* CALL_BUILTIN */);
3926
4323
  builder.emitIndex(SYMBOLIC_BUILTIN_EXPAND);
@@ -3935,7 +4332,7 @@ var FactorParselet = class {
3935
4332
  }
3936
4333
  parse(parser, _token, builder) {
3937
4334
  parser.consume("LPAREN");
3938
- parser.parseExpression(chunkPA4VC73I_cjs.BindingPower.Lowest, builder);
4335
+ parser.parseExpression(chunkJ45BCEZ4_cjs.BindingPower.Lowest, builder);
3939
4336
  parser.consume("RPAREN");
3940
4337
  builder.emitOpcode(51 /* CALL_BUILTIN */);
3941
4338
  builder.emitIndex(SYMBOLIC_BUILTIN_FACTOR);
@@ -3947,7 +4344,7 @@ var FactorParselet = class {
3947
4344
  function readVariableName(parser, verb) {
3948
4345
  const token = parser.peek();
3949
4346
  if (!token || token.type !== "IDENT" && token.type !== "UNIT") {
3950
- throw chunk5KMIY374_cjs.ErrorFactory.parsing(
4347
+ throw chunkB4HBEFTB_cjs.ErrorFactory.parsing(
3951
4348
  "SYMBOLIC_REQUIRES_VARIABLE_NAME",
3952
4349
  `${verb} needs the name of an unknown here, as in ${verb}(x^2, x).`,
3953
4350
  { found: token?.type ?? "end of input" }
@@ -3961,12 +4358,12 @@ function emitVariableName(builder, name) {
3961
4358
  builder.emitString(name);
3962
4359
  }
3963
4360
  function parseBoundExpression(parser, builder, verb) {
3964
- const inner = new chunkPA4VC73I_cjs.BytecodeBuilder();
3965
- parser.parseExpression(chunkPA4VC73I_cjs.BindingPower.Lowest, inner);
4361
+ const inner = new chunkJ45BCEZ4_cjs.BytecodeBuilder();
4362
+ parser.parseExpression(chunkJ45BCEZ4_cjs.BindingPower.Lowest, inner);
3966
4363
  parser.setBuilder(builder);
3967
4364
  const program = inner.build();
3968
4365
  if (program.hasAsync) {
3969
- throw chunk5KMIY374_cjs.ErrorFactory.parsing(
4366
+ throw chunkB4HBEFTB_cjs.ErrorFactory.parsing(
3970
4367
  "SYMBOLIC_ARGUMENT_MUST_BE_SYNCHRONOUS",
3971
4368
  `${verb}'s expression must be synchronous (no weather/stocks/currency calls).`
3972
4369
  );
@@ -3991,7 +4388,7 @@ var SolveParselet = class {
3991
4388
  parser.consume("COMMA");
3992
4389
  const variable = parser.peek();
3993
4390
  if (!variable || variable.type !== "IDENT" && variable.type !== "UNIT") {
3994
- throw chunk5KMIY374_cjs.ErrorFactory.parsing(
4391
+ throw chunkB4HBEFTB_cjs.ErrorFactory.parsing(
3995
4392
  "SOLVE_REQUIRES_VARIABLE_NAME",
3996
4393
  `solve's second argument must be the name of the unknown to solve for, as in solve(x^2-4=0, x).`,
3997
4394
  { found: variable?.type ?? "end of input" }
@@ -4026,7 +4423,7 @@ var DerParselet = class {
4026
4423
  emitBoundExpression(builder, expression, variable);
4027
4424
  emitVariableName(builder, variable);
4028
4425
  if (parser.match("COMMA")) {
4029
- parser.parseExpression(chunkPA4VC73I_cjs.BindingPower.Lowest, builder);
4426
+ parser.parseExpression(chunkJ45BCEZ4_cjs.BindingPower.Lowest, builder);
4030
4427
  } else {
4031
4428
  builder.emitOpcode(10 /* PUSH_NUMBER */);
4032
4429
  builder.emitNumber(1);
@@ -4070,9 +4467,9 @@ var TaylorParselet = class {
4070
4467
  emitBoundExpression(builder, expression, variable);
4071
4468
  emitVariableName(builder, variable);
4072
4469
  parser.consume("EQUALS");
4073
- parser.parseExpression(chunkPA4VC73I_cjs.BindingPower.Lowest, builder);
4470
+ parser.parseExpression(chunkJ45BCEZ4_cjs.BindingPower.Lowest, builder);
4074
4471
  parser.consume("COMMA");
4075
- parser.parseExpression(chunkPA4VC73I_cjs.BindingPower.Lowest, builder);
4472
+ parser.parseExpression(chunkJ45BCEZ4_cjs.BindingPower.Lowest, builder);
4076
4473
  parser.consume("RPAREN");
4077
4474
  builder.emitOpcode(51 /* CALL_BUILTIN */);
4078
4475
  builder.emitIndex(SYMBOLIC_BUILTIN_TAYLOR);
@@ -4089,7 +4486,7 @@ var JacobianParselet = class {
4089
4486
  parser.consume("LPAREN");
4090
4487
  let count = 0;
4091
4488
  do {
4092
- parser.parseExpression(chunkPA4VC73I_cjs.BindingPower.Lowest, builder);
4489
+ parser.parseExpression(chunkJ45BCEZ4_cjs.BindingPower.Lowest, builder);
4093
4490
  count++;
4094
4491
  } while (parser.match("COMMA"));
4095
4492
  parser.consume("RPAREN");
@@ -4120,7 +4517,7 @@ var ConjParselet = class {
4120
4517
  }
4121
4518
  parse(parser, _token, builder) {
4122
4519
  parser.consume("LPAREN");
4123
- parser.parseExpression(chunkPA4VC73I_cjs.BindingPower.Lowest, builder);
4520
+ parser.parseExpression(chunkJ45BCEZ4_cjs.BindingPower.Lowest, builder);
4124
4521
  parser.consume("RPAREN");
4125
4522
  builder.emitOpcode(51 /* CALL_BUILTIN */);
4126
4523
  builder.emitIndex(SYMBOLIC_BUILTIN_CONJ);
@@ -4135,7 +4532,7 @@ var ReParselet = class {
4135
4532
  }
4136
4533
  parse(parser, _token, builder) {
4137
4534
  parser.consume("LPAREN");
4138
- parser.parseExpression(chunkPA4VC73I_cjs.BindingPower.Lowest, builder);
4535
+ parser.parseExpression(chunkJ45BCEZ4_cjs.BindingPower.Lowest, builder);
4139
4536
  parser.consume("RPAREN");
4140
4537
  builder.emitOpcode(51 /* CALL_BUILTIN */);
4141
4538
  builder.emitIndex(SYMBOLIC_BUILTIN_RE);
@@ -4150,7 +4547,7 @@ var ImParselet = class {
4150
4547
  }
4151
4548
  parse(parser, _token, builder) {
4152
4549
  parser.consume("LPAREN");
4153
- parser.parseExpression(chunkPA4VC73I_cjs.BindingPower.Lowest, builder);
4550
+ parser.parseExpression(chunkJ45BCEZ4_cjs.BindingPower.Lowest, builder);
4154
4551
  parser.consume("RPAREN");
4155
4552
  builder.emitOpcode(51 /* CALL_BUILTIN */);
4156
4553
  builder.emitIndex(SYMBOLIC_BUILTIN_IM);
@@ -4165,7 +4562,7 @@ var CancelParselet = class {
4165
4562
  }
4166
4563
  parse(parser, _token, builder) {
4167
4564
  parser.consume("LPAREN");
4168
- parser.parseExpression(chunkPA4VC73I_cjs.BindingPower.Lowest, builder);
4565
+ parser.parseExpression(chunkJ45BCEZ4_cjs.BindingPower.Lowest, builder);
4169
4566
  parser.consume("RPAREN");
4170
4567
  builder.emitOpcode(51 /* CALL_BUILTIN */);
4171
4568
  builder.emitIndex(SYMBOLIC_BUILTIN_CANCEL);
@@ -4180,7 +4577,7 @@ var ApartParselet = class {
4180
4577
  }
4181
4578
  parse(parser, _token, builder) {
4182
4579
  parser.consume("LPAREN");
4183
- parser.parseExpression(chunkPA4VC73I_cjs.BindingPower.Lowest, builder);
4580
+ parser.parseExpression(chunkJ45BCEZ4_cjs.BindingPower.Lowest, builder);
4184
4581
  parser.consume("RPAREN");
4185
4582
  builder.emitOpcode(51 /* CALL_BUILTIN */);
4186
4583
  builder.emitIndex(SYMBOLIC_BUILTIN_APART);
@@ -4203,9 +4600,9 @@ function imaginaryLiteralNormalizerRule(priority = 75) {
4203
4600
  return {
4204
4601
  consumed: 2,
4205
4602
  replacement: [
4206
- new chunkYWQ6V4ZN_cjs.LexerToken(
4603
+ new chunkYTIYVHL7_cjs.LexerToken(
4207
4604
  "IMAGINARY",
4208
- chunk5LI5EPGJ_cjs.tokenTypeId("IMAGINARY"),
4605
+ chunkQFTDTX6K_cjs.tokenTypeId("IMAGINARY"),
4209
4606
  number.value,
4210
4607
  number.value,
4211
4608
  number.offset,
@@ -4249,7 +4646,7 @@ function symbolicCallNormalizerRule(priority = 80) {
4249
4646
  return {
4250
4647
  consumed: 1,
4251
4648
  replacement: [
4252
- new chunkYWQ6V4ZN_cjs.LexerToken(tokenType, chunk5LI5EPGJ_cjs.tokenTypeId(tokenType), token.value, token.value, token.offset, 0, token.line, token.col)
4649
+ new chunkYTIYVHL7_cjs.LexerToken(tokenType, chunkQFTDTX6K_cjs.tokenTypeId(tokenType), token.value, token.value, token.offset, 0, token.line, token.col)
4253
4650
  ],
4254
4651
  ruleName: "symbolic:call"
4255
4652
  };
@@ -4402,7 +4799,7 @@ var BigIntNumberParselet = class {
4402
4799
  }
4403
4800
  parse(parser, token, builder) {
4404
4801
  builder.emitOpcode(11 /* PUSH_BIGINT */);
4405
- builder.emitString(chunkPA4VC73I_cjs.bigIntLiteralDigits(token.value, parser.getLocaleCode()));
4802
+ builder.emitString(chunkJ45BCEZ4_cjs.bigIntLiteralDigits(token.value, parser.getLocaleCode()));
4406
4803
  }
4407
4804
  };
4408
4805
 
@@ -4419,7 +4816,7 @@ var ComparisonParselet = class {
4419
4816
  constructor(opcode) {
4420
4817
  this.opcode = opcode;
4421
4818
  this.category = "Conditionals";
4422
- this.bindingPower = chunkPA4VC73I_cjs.BindingPower.Conditional;
4819
+ this.bindingPower = chunkJ45BCEZ4_cjs.BindingPower.Conditional;
4423
4820
  }
4424
4821
  parse(parser, left, token, builder) {
4425
4822
  parser.parseExpression(this.bindingPower, builder);
@@ -4482,9 +4879,9 @@ var CONDITIONALS_PACKAGE = {
4482
4879
  { tokenType: "GT", parselet: new ComparisonParselet(44 /* GT */) },
4483
4880
  { tokenType: "LTE", parselet: new ComparisonParselet(43 /* LTE */) },
4484
4881
  { tokenType: "GTE", parselet: new ComparisonParselet(45 /* GTE */) },
4485
- { tokenType: "OR", parselet: new LogicalParselet(131 /* LOGICAL_OR */, chunkPA4VC73I_cjs.BindingPower.LogicalOr) },
4486
- { tokenType: "LOGICAL_AND", parselet: new LogicalParselet(130 /* LOGICAL_AND */, chunkPA4VC73I_cjs.BindingPower.LogicalAnd) },
4487
- { tokenType: "LOGICAL_OR", parselet: new LogicalParselet(131 /* LOGICAL_OR */, chunkPA4VC73I_cjs.BindingPower.LogicalOr) }
4882
+ { tokenType: "OR", parselet: new LogicalParselet(131 /* LOGICAL_OR */, chunkJ45BCEZ4_cjs.BindingPower.LogicalOr) },
4883
+ { tokenType: "LOGICAL_AND", parselet: new LogicalParselet(130 /* LOGICAL_AND */, chunkJ45BCEZ4_cjs.BindingPower.LogicalAnd) },
4884
+ { tokenType: "LOGICAL_OR", parselet: new LogicalParselet(131 /* LOGICAL_OR */, chunkJ45BCEZ4_cjs.BindingPower.LogicalOr) }
4488
4885
  ]
4489
4886
  };
4490
4887
 
@@ -4516,7 +4913,7 @@ var BASE_CONVERTERS = {
4516
4913
  var AsConverterParselet = class {
4517
4914
  constructor() {
4518
4915
  this.category = "Converters";
4519
- this.bindingPower = chunkPA4VC73I_cjs.BindingPower.Conditional;
4916
+ this.bindingPower = chunkJ45BCEZ4_cjs.BindingPower.Conditional;
4520
4917
  }
4521
4918
  parse(parser, left, token, builder) {
4522
4919
  const nextToken = parser.peek();
@@ -4526,7 +4923,7 @@ var AsConverterParselet = class {
4526
4923
  return;
4527
4924
  }
4528
4925
  if (!nextToken || nextToken.type !== "CONVERTER_NAME" && nextToken.type !== "IDENT" && nextToken.type !== "FUNC" && nextToken.type !== "UNIT") {
4529
- throw chunk5KMIY374_cjs.ErrorFactory.parsing(
4926
+ throw chunkB4HBEFTB_cjs.ErrorFactory.parsing(
4530
4927
  "AS_CONVERTER_EXPECTED_NAME",
4531
4928
  `Expected a converter name after "as" (e.g. "as hex", "as decimal") but got ${nextToken ? `"${nextToken.value}"` : "end of input"}`
4532
4929
  );
@@ -4538,7 +4935,7 @@ var AsConverterParselet = class {
4538
4935
  const radix = radixToken?.type === "NUMBER" ? Number(radixToken.value) : NaN;
4539
4936
  const opcode = BASE_CONVERTERS[radix];
4540
4937
  if (opcode === void 0) {
4541
- throw chunk5KMIY374_cjs.ErrorFactory.parsing(
4938
+ throw chunkB4HBEFTB_cjs.ErrorFactory.parsing(
4542
4939
  "AS_CONVERTER_UNSUPPORTED_BASE",
4543
4940
  `"as base ${radixToken?.value ?? "?"}": only bases ${Object.keys(BASE_CONVERTERS).join(", ")} can be written out`
4544
4941
  );
@@ -4550,7 +4947,7 @@ var AsConverterParselet = class {
4550
4947
  const builtinOp = BUILTIN_CONVERTERS[name];
4551
4948
  if (builtinOp !== void 0) {
4552
4949
  if (builtinOp === 141 /* TO_MULTIPLIER */ && parser.match("OF")) {
4553
- parser.parseExpression(chunkPA4VC73I_cjs.BindingPower.Conditional, builder);
4950
+ parser.parseExpression(chunkJ45BCEZ4_cjs.BindingPower.Conditional, builder);
4554
4951
  builder.emitOpcode(23 /* DIV */);
4555
4952
  }
4556
4953
  builder.emitOpcode(builtinOp);
@@ -4605,7 +5002,7 @@ function readDirection(parser) {
4605
5002
  var RoundedParselet = class {
4606
5003
  constructor() {
4607
5004
  this.category = "Converters";
4608
- this.bindingPower = chunkPA4VC73I_cjs.BindingPower.Conditional;
5005
+ this.bindingPower = chunkJ45BCEZ4_cjs.BindingPower.Conditional;
4609
5006
  }
4610
5007
  parse(parser, _left, _token, builder) {
4611
5008
  const direction = readDirection(parser);
@@ -4619,7 +5016,7 @@ var RoundedParselet = class {
4619
5016
  var ToNearestParselet = class {
4620
5017
  constructor() {
4621
5018
  this.category = "Converters";
4622
- this.bindingPower = chunkPA4VC73I_cjs.BindingPower.Conditional;
5019
+ this.bindingPower = chunkJ45BCEZ4_cjs.BindingPower.Conditional;
4623
5020
  }
4624
5021
  parse(parser, _left, _token, builder) {
4625
5022
  emitRoundToIncrement(builder, readIncrement(parser), "nearest");
@@ -4638,7 +5035,7 @@ function readIncrement(parser) {
4638
5035
  const value = Number(next.value);
4639
5036
  if (Number.isFinite(value) && value > 0) return value;
4640
5037
  }
4641
- throw chunk5KMIY374_cjs.ErrorFactory.parsing(
5038
+ throw chunkB4HBEFTB_cjs.ErrorFactory.parsing(
4642
5039
  "INVALID_ROUNDING_INCREMENT",
4643
5040
  `"to nearest ${next?.value ?? "?"}": expected a positive number or one of ${Object.keys(INCREMENT_WORDS).join(", ")}`
4644
5041
  );
@@ -4646,12 +5043,12 @@ function readIncrement(parser) {
4646
5043
  var DecimalPlacesParselet = class {
4647
5044
  constructor() {
4648
5045
  this.category = "Converters";
4649
- this.bindingPower = chunkPA4VC73I_cjs.BindingPower.Conditional;
5046
+ this.bindingPower = chunkJ45BCEZ4_cjs.BindingPower.Conditional;
4650
5047
  }
4651
5048
  parse(_parser, _left, token, builder) {
4652
5049
  const places = Number(token.value);
4653
5050
  if (!Number.isFinite(places) || places < 0 || places > 100) {
4654
- throw chunk5KMIY374_cjs.ErrorFactory.parsing(
5051
+ throw chunkB4HBEFTB_cjs.ErrorFactory.parsing(
4655
5052
  "INVALID_DECIMAL_PLACES",
4656
5053
  `"to ${token.value} dp": expected a place count between 0 and 100`
4657
5054
  );
@@ -4690,7 +5087,7 @@ function decimalPlacesNormalizerRule(priority = 66) {
4690
5087
  return {
4691
5088
  consumed,
4692
5089
  replacement: [
4693
- chunkBHTNFEGZ_cjs.createFusedToken("DECIMAL_PLACES", count.value, tokens.slice(pos, pos + consumed))
5090
+ chunkC75AFGVD_cjs.createFusedToken("DECIMAL_PLACES", count.value, tokens.slice(pos, pos + consumed))
4694
5091
  ],
4695
5092
  ruleName: "converters:decimal-places"
4696
5093
  };
@@ -4712,7 +5109,7 @@ function converterPrepositionNormalizerRule(priority = 67) {
4712
5109
  if (!isConverterTarget) return null;
4713
5110
  return {
4714
5111
  consumed: 1,
4715
- replacement: [chunkBHTNFEGZ_cjs.createFusedToken("AS", "as", [preposition])],
5112
+ replacement: [chunkC75AFGVD_cjs.createFusedToken("AS", "as", [preposition])],
4716
5113
  ruleName: "converters:preposition"
4717
5114
  };
4718
5115
  }
@@ -4749,10 +5146,10 @@ var VariadicAggregateParselet = class {
4749
5146
  }
4750
5147
  parse(parser, token, builder) {
4751
5148
  let argCount = 0;
4752
- parser.parseExpression(chunkPA4VC73I_cjs.BindingPower.Conjunction, builder);
5149
+ parser.parseExpression(chunkJ45BCEZ4_cjs.BindingPower.Conjunction, builder);
4753
5150
  argCount++;
4754
5151
  while (parser.match("COMMA") || parser.match("AND_CONJ")) {
4755
- parser.parseExpression(chunkPA4VC73I_cjs.BindingPower.Conjunction, builder);
5152
+ parser.parseExpression(chunkJ45BCEZ4_cjs.BindingPower.Conjunction, builder);
4756
5153
  argCount++;
4757
5154
  }
4758
5155
  builder.emitOpcode(51 /* CALL_BUILTIN */);
@@ -4766,7 +5163,7 @@ function unaryPhraseParselet(builtinIndex) {
4766
5163
  return {
4767
5164
  category: "MathPhrases",
4768
5165
  parse(parser, _token, builder) {
4769
- parser.parseExpression(chunkPA4VC73I_cjs.BindingPower.Lowest, builder);
5166
+ parser.parseExpression(chunkJ45BCEZ4_cjs.BindingPower.Lowest, builder);
4770
5167
  builder.emitOpcode(51 /* CALL_BUILTIN */);
4771
5168
  builder.emitIndex(builtinIndex);
4772
5169
  builder.emitIndex(1);
@@ -4778,28 +5175,28 @@ function unaryPhraseParselet(builtinIndex) {
4778
5175
  var remainderOfParselet = {
4779
5176
  category: "MathPhrases",
4780
5177
  parse(parser, _token, builder) {
4781
- parser.parseExpression(chunkPA4VC73I_cjs.BindingPower.Product, builder);
5178
+ parser.parseExpression(chunkJ45BCEZ4_cjs.BindingPower.Product, builder);
4782
5179
  if (!parser.match("DIVIDE_BY") && !parser.match("SLASH")) {
4783
- throw chunk5KMIY374_cjs.ErrorFactory.parsing(
5180
+ throw chunkB4HBEFTB_cjs.ErrorFactory.parsing(
4784
5181
  "REMAINDER_EXPECTED_DIVIDED_BY",
4785
5182
  'Expected "divided by" or "/", as in "remainder of 21 divided by 5"'
4786
5183
  );
4787
5184
  }
4788
- parser.parseExpression(chunkPA4VC73I_cjs.BindingPower.Product, builder);
5185
+ parser.parseExpression(chunkJ45BCEZ4_cjs.BindingPower.Product, builder);
4789
5186
  builder.emitOpcode(24 /* MOD */);
4790
5187
  }
4791
5188
  };
4792
5189
  var nthRootParselet = {
4793
5190
  category: "MathPhrases",
4794
5191
  parse(parser, _token, builder) {
4795
- parser.parseExpression(chunkPA4VC73I_cjs.BindingPower.Product, builder);
5192
+ parser.parseExpression(chunkJ45BCEZ4_cjs.BindingPower.Product, builder);
4796
5193
  if (!parser.match("OF")) {
4797
- throw chunk5KMIY374_cjs.ErrorFactory.parsing(
5194
+ throw chunkB4HBEFTB_cjs.ErrorFactory.parsing(
4798
5195
  "ROOT_EXPECTED_OF",
4799
5196
  'Expected "of", as in "root 5 of 100"'
4800
5197
  );
4801
5198
  }
4802
- parser.parseExpression(chunkPA4VC73I_cjs.BindingPower.Conditional, builder);
5199
+ parser.parseExpression(chunkJ45BCEZ4_cjs.BindingPower.Conditional, builder);
4803
5200
  builder.emitOpcode(2 /* SWAP */);
4804
5201
  builder.emitOpcode(10 /* PUSH_NUMBER */);
4805
5202
  builder.emitNumber(1);
@@ -4811,15 +5208,15 @@ var nthRootParselet = {
4811
5208
  var logBaseParselet = {
4812
5209
  category: "MathPhrases",
4813
5210
  parse(parser, _token, builder) {
4814
- parser.parseExpression(chunkPA4VC73I_cjs.BindingPower.Conditional, builder);
5211
+ parser.parseExpression(chunkJ45BCEZ4_cjs.BindingPower.Conditional, builder);
4815
5212
  emitNaturalLog(builder);
4816
5213
  if (!parser.match("LOG_BASE")) {
4817
- throw chunk5KMIY374_cjs.ErrorFactory.parsing(
5214
+ throw chunkB4HBEFTB_cjs.ErrorFactory.parsing(
4818
5215
  "LOG_EXPECTED_BASE",
4819
5216
  'Expected "base", as in "log 20 base 4"'
4820
5217
  );
4821
5218
  }
4822
- parser.parseExpression(chunkPA4VC73I_cjs.BindingPower.Conditional, builder);
5219
+ parser.parseExpression(chunkJ45BCEZ4_cjs.BindingPower.Conditional, builder);
4823
5220
  emitNaturalLog(builder);
4824
5221
  builder.emitOpcode(23 /* DIV */);
4825
5222
  }
@@ -4845,15 +5242,15 @@ function functionPhraseNormalizerRule(priority = 69) {
4845
5242
  if (word === "root") {
4846
5243
  return {
4847
5244
  consumed: 1,
4848
- replacement: [chunkBHTNFEGZ_cjs.createFusedToken("NTH_ROOT", "root", [head])],
5245
+ replacement: [chunkC75AFGVD_cjs.createFusedToken("NTH_ROOT", "root", [head])],
4849
5246
  ruleName: "mathphrases:function-phrase"
4850
5247
  };
4851
5248
  }
4852
5249
  const baseAt = findBase(tokens, pos + 1);
4853
5250
  if (baseAt === -1) return null;
4854
5251
  const replacement = tokens.slice(pos, baseAt + 1).map((token, index) => {
4855
- if (index === 0) return chunkBHTNFEGZ_cjs.createFusedToken("LOG_PHRASE", "log", [token]);
4856
- if (index === baseAt - pos) return chunkBHTNFEGZ_cjs.createFusedToken("LOG_BASE", "base", [token]);
5252
+ if (index === 0) return chunkC75AFGVD_cjs.createFusedToken("LOG_PHRASE", "log", [token]);
5253
+ if (index === baseAt - pos) return chunkC75AFGVD_cjs.createFusedToken("LOG_BASE", "base", [token]);
4857
5254
  return token;
4858
5255
  });
4859
5256
  return {
@@ -4878,9 +5275,9 @@ function largerSmallerParselet(builtinIndex) {
4878
5275
  return {
4879
5276
  category: "MathPhrases",
4880
5277
  parse(parser, token, builder) {
4881
- parser.parseExpression(chunkPA4VC73I_cjs.BindingPower.Conjunction, builder);
5278
+ parser.parseExpression(chunkJ45BCEZ4_cjs.BindingPower.Conjunction, builder);
4882
5279
  parser.consume("AND_CONJ");
4883
- parser.parseExpression(chunkPA4VC73I_cjs.BindingPower.Lowest, builder);
5280
+ parser.parseExpression(chunkJ45BCEZ4_cjs.BindingPower.Lowest, builder);
4884
5281
  builder.emitOpcode(51 /* CALL_BUILTIN */);
4885
5282
  builder.emitIndex(builtinIndex);
4886
5283
  builder.emitIndex(2);
@@ -4892,7 +5289,7 @@ function largerSmallerParselet(builtinIndex) {
4892
5289
  var halfParselet = {
4893
5290
  category: "MathPhrases",
4894
5291
  parse(parser, token, builder) {
4895
- parser.parseExpression(chunkPA4VC73I_cjs.BindingPower.Lowest, builder);
5292
+ parser.parseExpression(chunkJ45BCEZ4_cjs.BindingPower.Lowest, builder);
4896
5293
  builder.emitOpcode(10 /* PUSH_NUMBER */);
4897
5294
  builder.emitNumber(2);
4898
5295
  builder.emitOpcode(23 /* DIV */);
@@ -4903,9 +5300,9 @@ var halfParselet = {
4903
5300
  var midpointParselet = {
4904
5301
  category: "MathPhrases",
4905
5302
  parse(parser, token, builder) {
4906
- parser.parseExpression(chunkPA4VC73I_cjs.BindingPower.Conjunction, builder);
5303
+ parser.parseExpression(chunkJ45BCEZ4_cjs.BindingPower.Conjunction, builder);
4907
5304
  parser.consume("AND_CONJ");
4908
- parser.parseExpression(chunkPA4VC73I_cjs.BindingPower.Lowest, builder);
5305
+ parser.parseExpression(chunkJ45BCEZ4_cjs.BindingPower.Lowest, builder);
4909
5306
  builder.emitOpcode(20 /* ADD */);
4910
5307
  builder.emitOpcode(10 /* PUSH_NUMBER */);
4911
5308
  builder.emitNumber(2);
@@ -4920,7 +5317,7 @@ var randomNumberParselet = definePhrasePattern({
4920
5317
  {
4921
5318
  slots: [
4922
5319
  { kind: "keyword", tokenTypes: ["BETWEEN"] },
4923
- { kind: "expr", bindingPower: chunkPA4VC73I_cjs.BindingPower.Product },
5320
+ { kind: "expr", bindingPower: chunkJ45BCEZ4_cjs.BindingPower.Product },
4924
5321
  { kind: "keyword", tokenTypes: ["AND_CONJ"] },
4925
5322
  { kind: "expr" }
4926
5323
  ],
@@ -4940,20 +5337,20 @@ var ClampParselet = class {
4940
5337
  this.category = "MathPhrases";
4941
5338
  }
4942
5339
  parse(parser, token, builder) {
4943
- parser.parseExpression(chunkPA4VC73I_cjs.BindingPower.Lowest, builder);
5340
+ parser.parseExpression(chunkJ45BCEZ4_cjs.BindingPower.Lowest, builder);
4944
5341
  const mid = parser.peek();
4945
5342
  if (mid?.type === "BETWEEN") {
4946
5343
  parser.consume();
4947
- parser.parseExpression(chunkPA4VC73I_cjs.BindingPower.Product, builder);
5344
+ parser.parseExpression(chunkJ45BCEZ4_cjs.BindingPower.Product, builder);
4948
5345
  parser.consume("AND_CONJ");
4949
- parser.parseExpression(chunkPA4VC73I_cjs.BindingPower.Lowest, builder);
5346
+ parser.parseExpression(chunkJ45BCEZ4_cjs.BindingPower.Lowest, builder);
4950
5347
  } else if (mid?.type === "FROM") {
4951
5348
  parser.consume();
4952
- parser.parseExpression(chunkPA4VC73I_cjs.BindingPower.Lowest, builder);
5349
+ parser.parseExpression(chunkJ45BCEZ4_cjs.BindingPower.Lowest, builder);
4953
5350
  parser.consume("TO");
4954
- parser.parseExpression(chunkPA4VC73I_cjs.BindingPower.Lowest, builder);
5351
+ parser.parseExpression(chunkJ45BCEZ4_cjs.BindingPower.Lowest, builder);
4955
5352
  } else {
4956
- throw chunk5KMIY374_cjs.ErrorFactory.parsing(
5353
+ throw chunkB4HBEFTB_cjs.ErrorFactory.parsing(
4957
5354
  "CLAMP_EXPECTED_BETWEEN_OR_FROM",
4958
5355
  `Expected "between" or "from" after "clamp <value>" but got ${mid ? `"${mid.value}"` : "end of input"}`
4959
5356
  );
@@ -4969,7 +5366,7 @@ var PROPORTION_BUILTIN_INDEX = 46;
4969
5366
  var ProportionParselet = class {
4970
5367
  constructor() {
4971
5368
  this.category = "MathPhrases";
4972
- this.bindingPower = chunkPA4VC73I_cjs.BindingPower.Conditional;
5369
+ this.bindingPower = chunkJ45BCEZ4_cjs.BindingPower.Conditional;
4973
5370
  }
4974
5371
  parse(parser, left, token, builder) {
4975
5372
  parser.parseExpression(this.bindingPower, builder);
@@ -4978,7 +5375,7 @@ var ProportionParselet = class {
4978
5375
  parser.consume("IS_TO");
4979
5376
  const whatToken = parser.consume();
4980
5377
  if (!whatToken || whatToken.value.toLowerCase() !== "what") {
4981
- throw chunk5KMIY374_cjs.ErrorFactory.parsing(
5378
+ throw chunkB4HBEFTB_cjs.ErrorFactory.parsing(
4982
5379
  "PROPORTION_EXPECTED_WHAT",
4983
5380
  `Expected "what" but got ${whatToken ? `"${whatToken.value}"` : "end of input"}`
4984
5381
  );
@@ -5070,7 +5467,7 @@ function readCompoundingInterval(parser) {
5070
5467
  const name = (token?.text ?? token?.value ?? "").toLowerCase();
5071
5468
  const periods = PERIODS_PER_YEAR[name];
5072
5469
  if (periods === void 0) {
5073
- throw chunk5KMIY374_cjs.ErrorFactory.parsing(
5470
+ throw chunkB4HBEFTB_cjs.ErrorFactory.parsing(
5074
5471
  "UNKNOWN_COMPOUNDING_INTERVAL",
5075
5472
  `compounding ${name || "?"}: expected one of ${Object.keys(PERIODS_PER_YEAR).join(", ")}`
5076
5473
  );
@@ -5088,12 +5485,12 @@ var InvestmentGrowthParselet = class {
5088
5485
  this.annualIndex = annualIndex;
5089
5486
  this.intervalIndex = intervalIndex;
5090
5487
  this.category = "Finance";
5091
- this.bindingPower = chunkPA4VC73I_cjs.BindingPower.Conditional;
5488
+ this.bindingPower = chunkJ45BCEZ4_cjs.BindingPower.Conditional;
5092
5489
  }
5093
5490
  parse(parser, _left, _token, builder) {
5094
- parser.parseExpression(chunkPA4VC73I_cjs.BindingPower.Conditional, builder);
5491
+ parser.parseExpression(chunkJ45BCEZ4_cjs.BindingPower.Conditional, builder);
5095
5492
  if (!parser.match("RATE_AT")) parser.consume("AT");
5096
- parser.parseExpression(chunkPA4VC73I_cjs.BindingPower.Conditional, builder);
5493
+ parser.parseExpression(chunkJ45BCEZ4_cjs.BindingPower.Conditional, builder);
5097
5494
  const periods = readCompoundingInterval(parser);
5098
5495
  builder.emitOpcode(2 /* SWAP */);
5099
5496
  if (periods === 1) {
@@ -5115,13 +5512,13 @@ var PresentValueParselet = class {
5115
5512
  this.category = "Finance";
5116
5513
  }
5117
5514
  parse(parser, _token, builder) {
5118
- parser.parseExpression(chunkPA4VC73I_cjs.BindingPower.Conditional, builder);
5515
+ parser.parseExpression(chunkJ45BCEZ4_cjs.BindingPower.Conditional, builder);
5119
5516
  if (!parser.match("AFTER") && !parser.match("OVER") && !parser.match("IN")) {
5120
5517
  parser.consume("AFTER");
5121
5518
  }
5122
- parser.parseExpression(chunkPA4VC73I_cjs.BindingPower.Conditional, builder);
5519
+ parser.parseExpression(chunkJ45BCEZ4_cjs.BindingPower.Conditional, builder);
5123
5520
  if (!parser.match("RATE_AT")) parser.consume("AT");
5124
- parser.parseExpression(chunkPA4VC73I_cjs.BindingPower.Conditional, builder);
5521
+ parser.parseExpression(chunkJ45BCEZ4_cjs.BindingPower.Conditional, builder);
5125
5522
  builder.emitOpcode(2 /* SWAP */);
5126
5523
  builder.emitOpcode(51 /* CALL_BUILTIN */);
5127
5524
  builder.emitIndex(this.builtinIndex);
@@ -5132,10 +5529,10 @@ var ReturnOnInvestmentParselet = class {
5132
5529
  constructor(builtinIndex) {
5133
5530
  this.builtinIndex = builtinIndex;
5134
5531
  this.category = "Finance";
5135
- this.bindingPower = chunkPA4VC73I_cjs.BindingPower.Conditional;
5532
+ this.bindingPower = chunkJ45BCEZ4_cjs.BindingPower.Conditional;
5136
5533
  }
5137
5534
  parse(parser, _left, _token, builder) {
5138
- parser.parseExpression(chunkPA4VC73I_cjs.BindingPower.Conditional, builder);
5535
+ parser.parseExpression(chunkJ45BCEZ4_cjs.BindingPower.Conditional, builder);
5139
5536
  parser.consume("RETURNED");
5140
5537
  builder.emitOpcode(51 /* CALL_BUILTIN */);
5141
5538
  builder.emitIndex(this.builtinIndex);
@@ -5148,20 +5545,48 @@ var AnnualReturnParselet = class {
5148
5545
  this.category = "Finance";
5149
5546
  }
5150
5547
  parse(parser, _token, builder) {
5151
- parser.parseExpression(chunkPA4VC73I_cjs.BindingPower.Conditional, builder);
5548
+ parser.parseExpression(chunkJ45BCEZ4_cjs.BindingPower.Conditional, builder);
5152
5549
  parser.consume("INVESTED");
5153
- parser.parseExpression(chunkPA4VC73I_cjs.BindingPower.Conditional, builder);
5550
+ parser.parseExpression(chunkJ45BCEZ4_cjs.BindingPower.Conditional, builder);
5154
5551
  parser.consume("RETURNED");
5155
5552
  if (!parser.match("AFTER") && !parser.match("OVER") && !parser.match("IN")) {
5156
5553
  parser.consume("AFTER");
5157
5554
  }
5158
- parser.parseExpression(chunkPA4VC73I_cjs.BindingPower.Conditional, builder);
5555
+ parser.parseExpression(chunkJ45BCEZ4_cjs.BindingPower.Conditional, builder);
5159
5556
  builder.emitOpcode(51 /* CALL_BUILTIN */);
5160
5557
  builder.emitIndex(this.builtinIndex);
5161
5558
  builder.emitIndex(3);
5162
5559
  }
5163
5560
  };
5164
5561
 
5562
+ // src/packages/finance/parselets/TermRateClauses.ts
5563
+ function parseTermAndRate(parser, builder, termWords) {
5564
+ if (matchTerm(parser, termWords)) {
5565
+ parser.parseExpression(chunkJ45BCEZ4_cjs.BindingPower.Lowest, builder);
5566
+ consumeRate(parser);
5567
+ parser.parseExpression(chunkJ45BCEZ4_cjs.BindingPower.Lowest, builder);
5568
+ return { swap: true };
5569
+ }
5570
+ consumeRate(parser);
5571
+ parser.parseExpression(chunkJ45BCEZ4_cjs.BindingPower.Lowest, builder);
5572
+ consumeTerm(parser, termWords);
5573
+ parser.parseExpression(chunkJ45BCEZ4_cjs.BindingPower.Lowest, builder);
5574
+ return { swap: false };
5575
+ }
5576
+ function matchTerm(parser, termWords) {
5577
+ for (const word of termWords) {
5578
+ if (parser.match(word)) return true;
5579
+ }
5580
+ return false;
5581
+ }
5582
+ function consumeTerm(parser, termWords) {
5583
+ if (matchTerm(parser, termWords)) return;
5584
+ parser.consume(termWords[0]);
5585
+ }
5586
+ function consumeRate(parser) {
5587
+ if (!parser.match("RATE_AT")) parser.consume("AT");
5588
+ }
5589
+
5165
5590
  // src/packages/finance/parselets/CompoundInterestParselet.ts
5166
5591
  var CompoundInterestParselet = class {
5167
5592
  /**
@@ -5175,15 +5600,10 @@ var CompoundInterestParselet = class {
5175
5600
  this.category = "Finance";
5176
5601
  }
5177
5602
  parse(parser, _token, builder) {
5178
- parser.parseExpression(chunkPA4VC73I_cjs.BindingPower.Conditional, builder);
5179
- if (!parser.match("AFTER") && !parser.match("FOR_DURATION")) {
5180
- parser.consume("OVER");
5181
- }
5182
- parser.parseExpression(chunkPA4VC73I_cjs.BindingPower.Lowest, builder);
5183
- if (!parser.match("RATE_AT")) parser.consume("AT");
5184
- parser.parseExpression(chunkPA4VC73I_cjs.BindingPower.Lowest, builder);
5603
+ parser.parseExpression(chunkJ45BCEZ4_cjs.BindingPower.Conditional, builder);
5604
+ const { swap } = parseTermAndRate(parser, builder, ["AFTER", "FOR_DURATION", "OVER"]);
5185
5605
  const periods = readCompoundingInterval(parser);
5186
- builder.emitOpcode(2 /* SWAP */);
5606
+ if (swap) builder.emitOpcode(2 /* SWAP */);
5187
5607
  if (periods === 1) {
5188
5608
  builder.emitOpcode(51 /* CALL_BUILTIN */);
5189
5609
  builder.emitIndex(this.builtinIndex);
@@ -5206,12 +5626,9 @@ var LoanRepaymentParselet = class {
5206
5626
  this.category = "Finance";
5207
5627
  }
5208
5628
  parse(parser, _token, builder) {
5209
- parser.parseExpression(chunkPA4VC73I_cjs.BindingPower.Lowest, builder);
5210
- parser.consume("OVER");
5211
- parser.parseExpression(chunkPA4VC73I_cjs.BindingPower.Lowest, builder);
5212
- if (!parser.match("RATE_AT")) parser.consume("AT");
5213
- parser.parseExpression(chunkPA4VC73I_cjs.BindingPower.Lowest, builder);
5214
- builder.emitOpcode(2 /* SWAP */);
5629
+ parser.parseExpression(chunkJ45BCEZ4_cjs.BindingPower.Lowest, builder);
5630
+ const { swap } = parseTermAndRate(parser, builder, ["OVER"]);
5631
+ if (swap) builder.emitOpcode(2 /* SWAP */);
5215
5632
  builder.emitOpcode(10 /* PUSH_NUMBER */);
5216
5633
  builder.emitNumber(this.periodsPerYear);
5217
5634
  builder.emitOpcode(51 /* CALL_BUILTIN */);
@@ -5227,9 +5644,9 @@ var SalesTaxParselet = class {
5227
5644
  this.category = "Finance";
5228
5645
  }
5229
5646
  parse(parser, _token, builder) {
5230
- parser.parseExpression(chunkPA4VC73I_cjs.BindingPower.Lowest, builder);
5647
+ parser.parseExpression(chunkJ45BCEZ4_cjs.BindingPower.Lowest, builder);
5231
5648
  parser.consume("RATE_AT");
5232
- parser.parseExpression(chunkPA4VC73I_cjs.BindingPower.Lowest, builder);
5649
+ parser.parseExpression(chunkJ45BCEZ4_cjs.BindingPower.Lowest, builder);
5233
5650
  builder.emitOpcode(51 /* CALL_BUILTIN */);
5234
5651
  builder.emitIndex(this.builtinIndex);
5235
5652
  builder.emitIndex(2);
@@ -5237,30 +5654,30 @@ var SalesTaxParselet = class {
5237
5654
  };
5238
5655
 
5239
5656
  // src/packages/finance/parselets/InflationPluginFunctions.ts
5240
- var INFLATION_FROM_YEAR_TO_PRESENT_IDX = chunk6WFMPTGB_cjs.allocatePluginFunctionIndex();
5241
- var INFLATION_TO_YEAR_FROM_PRESENT_IDX = chunk6WFMPTGB_cjs.allocatePluginFunctionIndex();
5242
- var INFLATION_FUTURE_VALUE_IDX = chunk6WFMPTGB_cjs.allocatePluginFunctionIndex();
5657
+ var INFLATION_FROM_YEAR_TO_PRESENT_IDX = chunk7ZX6B7SY_cjs.allocatePluginFunctionIndex();
5658
+ var INFLATION_TO_YEAR_FROM_PRESENT_IDX = chunk7ZX6B7SY_cjs.allocatePluginFunctionIndex();
5659
+ var INFLATION_FUTURE_VALUE_IDX = chunk7ZX6B7SY_cjs.allocatePluginFunctionIndex();
5243
5660
  function yearRangeError(code, badYear) {
5244
- return chunkJMXUNXQS_cjs.errorValue(
5661
+ return chunk2TZKENDH_cjs.errorValue(
5245
5662
  code,
5246
- `Year ${badYear} is outside the bundled CPI table's range (${chunk6WFMPTGB_cjs.CPI_MIN_YEAR}-${chunk6WFMPTGB_cjs.CPI_MAX_YEAR})`
5663
+ `Year ${badYear} is outside the bundled CPI table's range (${chunk7ZX6B7SY_cjs.CPI_MIN_YEAR}-${chunk7ZX6B7SY_cjs.CPI_MAX_YEAR})`
5247
5664
  );
5248
5665
  }
5249
5666
  function inflationFromYearToPresentHandler(args) {
5250
5667
  const amountValue = args[0];
5251
5668
  const fromYear = args[1].toNumber();
5252
5669
  const toYear = (/* @__PURE__ */ new Date()).getFullYear();
5253
- const result = chunk6WFMPTGB_cjs.adjustForInflation(amountValue.toNumber(), fromYear, toYear);
5670
+ const result = chunk7ZX6B7SY_cjs.adjustForInflation(amountValue.toNumber(), fromYear, toYear);
5254
5671
  if (result === void 0) return yearRangeError("INFLATION_YEAR_OUT_OF_RANGE", fromYear);
5255
- return amountValue.type === 6 /* Uom */ ? chunkJMXUNXQS_cjs.uomValue(result, amountValue.unit) : chunkJMXUNXQS_cjs.numberValue(result);
5672
+ return amountValue.type === 6 /* Uom */ ? chunk2TZKENDH_cjs.uomValue(result, amountValue.unit) : chunk2TZKENDH_cjs.numberValue(result);
5256
5673
  }
5257
5674
  function inflationToYearFromPresentHandler(args) {
5258
5675
  const amountValue = args[0];
5259
5676
  const fromYear = (/* @__PURE__ */ new Date()).getFullYear();
5260
5677
  const toYear = args[1].toNumber();
5261
- const result = chunk6WFMPTGB_cjs.adjustForInflation(amountValue.toNumber(), fromYear, toYear);
5678
+ const result = chunk7ZX6B7SY_cjs.adjustForInflation(amountValue.toNumber(), fromYear, toYear);
5262
5679
  if (result === void 0) return yearRangeError("INFLATION_YEAR_OUT_OF_RANGE", toYear);
5263
- return amountValue.type === 6 /* Uom */ ? chunkJMXUNXQS_cjs.uomValue(result, amountValue.unit) : chunkJMXUNXQS_cjs.numberValue(result);
5680
+ return amountValue.type === 6 /* Uom */ ? chunk2TZKENDH_cjs.uomValue(result, amountValue.unit) : chunk2TZKENDH_cjs.numberValue(result);
5264
5681
  }
5265
5682
  function inflationFutureValueHandler(args) {
5266
5683
  const amountValue = args[0];
@@ -5268,11 +5685,11 @@ function inflationFutureValueHandler(args) {
5268
5685
  const rate = args[2].toNumber();
5269
5686
  const years = futureYear - (/* @__PURE__ */ new Date()).getFullYear();
5270
5687
  if (1 + rate <= 0) {
5271
- return chunkJMXUNXQS_cjs.errorValue("INVALID_RATE", `inflationFutureValue: rate ${rate} makes (1 + rate) non-positive`);
5688
+ return chunk2TZKENDH_cjs.errorValue("INVALID_RATE", `inflationFutureValue: rate ${rate} makes (1 + rate) non-positive`);
5272
5689
  }
5273
5690
  const amount = amountValue.toNumber();
5274
5691
  const worth = amount / Math.pow(1 + rate, years);
5275
- return amountValue.type === 6 /* Uom */ ? chunkJMXUNXQS_cjs.uomValue(worth, amountValue.unit) : chunkJMXUNXQS_cjs.numberValue(worth);
5692
+ return amountValue.type === 6 /* Uom */ ? chunk2TZKENDH_cjs.uomValue(worth, amountValue.unit) : chunk2TZKENDH_cjs.numberValue(worth);
5276
5693
  }
5277
5694
 
5278
5695
  // src/packages/finance/parselets/InflationQueryParselet.ts
@@ -5291,13 +5708,13 @@ var InflationQueryParselet = class {
5291
5708
  }
5292
5709
  parse(parser, _token, builder) {
5293
5710
  if (!hasInflationKeyword(parser)) {
5294
- parser.parseExpression(chunkPA4VC73I_cjs.BindingPower.Lowest, builder);
5711
+ parser.parseExpression(chunkJ45BCEZ4_cjs.BindingPower.Lowest, builder);
5295
5712
  return;
5296
5713
  }
5297
- parser.parseExpression(chunkPA4VC73I_cjs.BindingPower.Product, builder);
5714
+ parser.parseExpression(chunkJ45BCEZ4_cjs.BindingPower.Product, builder);
5298
5715
  if (this.variant === "what-was") {
5299
5716
  parser.consume("WORTH_IN");
5300
- parser.parseExpression(chunkPA4VC73I_cjs.BindingPower.Lowest, builder);
5717
+ parser.parseExpression(chunkJ45BCEZ4_cjs.BindingPower.Lowest, builder);
5301
5718
  builder.emitOpcode(50 /* CALL_PLUGIN */);
5302
5719
  builder.emitIndex(INFLATION_TO_YEAR_FROM_PRESENT_IDX);
5303
5720
  builder.emitIndex(2);
@@ -5306,7 +5723,7 @@ var InflationQueryParselet = class {
5306
5723
  const next = parser.peek();
5307
5724
  if (next?.type === "FROM") {
5308
5725
  parser.consume();
5309
- parser.parseExpression(chunkPA4VC73I_cjs.BindingPower.Lowest, builder);
5726
+ parser.parseExpression(chunkJ45BCEZ4_cjs.BindingPower.Lowest, builder);
5310
5727
  builder.emitOpcode(50 /* CALL_PLUGIN */);
5311
5728
  builder.emitIndex(INFLATION_FROM_YEAR_TO_PRESENT_IDX);
5312
5729
  builder.emitIndex(2);
@@ -5314,16 +5731,16 @@ var InflationQueryParselet = class {
5314
5731
  }
5315
5732
  if (next?.type === "IN") {
5316
5733
  parser.consume();
5317
- parser.parseExpression(chunkPA4VC73I_cjs.BindingPower.Lowest, builder);
5734
+ parser.parseExpression(chunkJ45BCEZ4_cjs.BindingPower.Lowest, builder);
5318
5735
  parser.consume("WORTH_IN");
5319
- parser.parseExpression(chunkPA4VC73I_cjs.BindingPower.Lowest, builder);
5736
+ parser.parseExpression(chunkJ45BCEZ4_cjs.BindingPower.Lowest, builder);
5320
5737
  builder.emitOpcode(51 /* CALL_BUILTIN */);
5321
5738
  builder.emitIndex(INFLATION_ADJUST_BUILTIN_IDX);
5322
5739
  builder.emitIndex(3);
5323
5740
  return;
5324
5741
  }
5325
5742
  const gotType = next ? next.type : "end of input";
5326
- throw chunk5KMIY374_cjs.ErrorFactory.parsing(
5743
+ throw chunkB4HBEFTB_cjs.ErrorFactory.parsing(
5327
5744
  "INFLATION_EXPECTED_FROM_OR_IN",
5328
5745
  `Expected "from <year>" or "in <year> worth in <year>" after "what is <amount>", got ${gotType}`
5329
5746
  );
@@ -5336,16 +5753,16 @@ var InflationFutureValueParselet = class {
5336
5753
  this.category = "Finance";
5337
5754
  }
5338
5755
  parse(parser, _token, builder) {
5339
- parser.parseExpression(chunkPA4VC73I_cjs.BindingPower.Product, builder);
5756
+ parser.parseExpression(chunkJ45BCEZ4_cjs.BindingPower.Product, builder);
5340
5757
  parser.consume("IN");
5341
- parser.parseExpression(chunkPA4VC73I_cjs.BindingPower.Lowest, builder);
5758
+ parser.parseExpression(chunkJ45BCEZ4_cjs.BindingPower.Lowest, builder);
5342
5759
  parser.consume("ASSUMING");
5343
- parser.parseExpression(chunkPA4VC73I_cjs.BindingPower.Lowest, builder);
5760
+ parser.parseExpression(chunkJ45BCEZ4_cjs.BindingPower.Lowest, builder);
5344
5761
  const inflationToken = parser.peek();
5345
5762
  const inflationWord = inflationToken && inflationToken.type === "IDENT" ? inflationToken.value.toLowerCase() : void 0;
5346
5763
  if (inflationWord !== "inflation") {
5347
5764
  const got = inflationToken ? inflationToken.value : "end of input";
5348
- throw chunk5KMIY374_cjs.ErrorFactory.parsing(
5765
+ throw chunkB4HBEFTB_cjs.ErrorFactory.parsing(
5349
5766
  "INFLATION_EXPECTED_INFLATION_WORD",
5350
5767
  `Expected the word "inflation" after "assuming <rate>%", got "${got}"`
5351
5768
  );
@@ -5389,13 +5806,138 @@ function inYearDollarsNormalizerRule(priority = 70) {
5389
5806
  if (word !== "dollars" && word !== "dollar") return null;
5390
5807
  return {
5391
5808
  consumed: 3,
5392
- replacement: [chunkBHTNFEGZ_cjs.createFusedToken("IN_YEAR_DOLLARS", yearToken.value, tokens.slice(pos, pos + 3))],
5809
+ replacement: [chunkC75AFGVD_cjs.createFusedToken("IN_YEAR_DOLLARS", yearToken.value, tokens.slice(pos, pos + 3))],
5393
5810
  ruleName: "finance:in-year-dollars"
5394
5811
  };
5395
5812
  }
5396
5813
  };
5397
5814
  }
5398
5815
 
5816
+ // src/packages/finance/normalizer/RecurringScheduleNormalizerRule.ts
5817
+ var RecurringScheduleErrorCodes = {
5818
+ /** `every 0 weeks` and the like: the interval a payment repeats on has to be a positive count, or the number of payments is undefined. */
5819
+ RECURRING_INTERVAL_NOT_POSITIVE: "RECURRING_INTERVAL_NOT_POSITIVE"
5820
+ };
5821
+ var PERIODS_PER_YEAR2 = {
5822
+ daily: 365,
5823
+ weekly: 52,
5824
+ monthly: 12,
5825
+ yearly: 1,
5826
+ // "annually" reads better than "yearly" before a duration and means the
5827
+ // same, so it is accepted as a spelling rather than a separate period.
5828
+ annually: 1
5829
+ };
5830
+ var UNIT_PER_YEAR = {
5831
+ day: 365,
5832
+ days: 365,
5833
+ week: 52,
5834
+ weeks: 52,
5835
+ month: 12,
5836
+ months: 12,
5837
+ year: 1,
5838
+ years: 1
5839
+ };
5840
+ var ARTICLES3 = /* @__PURE__ */ new Set(["a", "an"]);
5841
+ var VALUE_ENDERS = /* @__PURE__ */ new Set([
5842
+ "NUMBER",
5843
+ "FLOAT",
5844
+ "HEX",
5845
+ "BIGINT",
5846
+ "RPAREN",
5847
+ "RBRACKET",
5848
+ "IDENT",
5849
+ "PERCENT",
5850
+ "STRING"
5851
+ ]);
5852
+ function wordOf(token) {
5853
+ if (token === void 0) return "";
5854
+ return (token.text ?? token.value ?? "").toLowerCase();
5855
+ }
5856
+ function unitPerYear(token) {
5857
+ if (token === void 0 || token.type !== "UNIT") return void 0;
5858
+ return UNIT_PER_YEAR[wordOf(token)];
5859
+ }
5860
+ function readPeriod(tokens, i) {
5861
+ const head = tokens[i];
5862
+ if (head === void 0) return null;
5863
+ if (head.type === "IDENT") {
5864
+ const simple = PERIODS_PER_YEAR2[wordOf(head)];
5865
+ if (simple !== void 0) return { perYear: simple, consumed: 1 };
5866
+ if (wordOf(head) === "every") {
5867
+ const countToken = tokens[i + 1];
5868
+ if (countToken?.type !== "NUMBER") return null;
5869
+ const per = unitPerYear(tokens[i + 2]);
5870
+ if (per === void 0) return null;
5871
+ const n = Number((countToken.value ?? "").replace(/,/g, ""));
5872
+ if (!(n > 0)) {
5873
+ throw chunkB4HBEFTB_cjs.ErrorFactory.parsing({
5874
+ code: RecurringScheduleErrorCodes.RECURRING_INTERVAL_NOT_POSITIVE,
5875
+ message: `A recurring interval must be a positive number, not "${countToken.value}".`,
5876
+ expected: "a positive interval, e.g. every 2 weeks",
5877
+ found: `every ${countToken.value} ${wordOf(tokens[i + 2])}`
5878
+ });
5879
+ }
5880
+ return { perYear: per / n, consumed: 3 };
5881
+ }
5882
+ }
5883
+ return null;
5884
+ }
5885
+ function readDuration(tokens, i) {
5886
+ const first = tokens[i];
5887
+ if (first === void 0) return null;
5888
+ let count;
5889
+ let unitIndex;
5890
+ if (first.type === "NUMBER") {
5891
+ count = Number((first.value ?? "").replace(/,/g, ""));
5892
+ unitIndex = i + 1;
5893
+ } else if (first.type === "IDENT" && ARTICLES3.has(wordOf(first))) {
5894
+ count = 1;
5895
+ unitIndex = i + 1;
5896
+ } else {
5897
+ return null;
5898
+ }
5899
+ const per = unitPerYear(tokens[unitIndex]);
5900
+ if (per === void 0) return null;
5901
+ if (!Number.isFinite(count) || count < 0) return null;
5902
+ return { years: count / per, consumed: unitIndex - i + 1 };
5903
+ }
5904
+ function wholePayments(perYear, durationYears) {
5905
+ const raw = perYear * durationYears;
5906
+ const nearest = Math.round(raw);
5907
+ return Math.abs(raw - nearest) < 1e-9 ? nearest : Math.floor(raw);
5908
+ }
5909
+ function recurringScheduleNormalizerRule(priority = 78) {
5910
+ return {
5911
+ name: "finance:recurring-schedule",
5912
+ priority,
5913
+ match(tokens, pos) {
5914
+ const amount = tokens[pos];
5915
+ if (amount === void 0 || !VALUE_ENDERS.has(amount.type)) return null;
5916
+ const period = readPeriod(tokens, pos + 1);
5917
+ if (period === null) return null;
5918
+ const forIndex = pos + 1 + period.consumed;
5919
+ if (tokens[forIndex]?.type !== "FOR_DURATION") return null;
5920
+ const duration = readDuration(tokens, forIndex + 1);
5921
+ if (duration === null) return null;
5922
+ const count = wholePayments(period.perYear, duration.years);
5923
+ const consumed = forIndex + 1 + duration.consumed - pos;
5924
+ const source = tokens.slice(pos, pos + consumed);
5925
+ return {
5926
+ // The amount is re-emitted unchanged, then `* <count>`. The amount may
5927
+ // be the tail of a longer expression, whose earlier tokens are before
5928
+ // `pos` and pass through on their own.
5929
+ replacement: [
5930
+ amount,
5931
+ chunkC75AFGVD_cjs.createFusedToken("STAR", "*", source),
5932
+ chunkC75AFGVD_cjs.createFusedToken("NUMBER", String(count), source)
5933
+ ],
5934
+ consumed,
5935
+ ruleName: "finance:recurring-schedule"
5936
+ };
5937
+ }
5938
+ };
5939
+ }
5940
+
5399
5941
  // src/packages/finance/FinancePackage.ts
5400
5942
  var COMPOUND_FV = 51;
5401
5943
  var COMPOUND_INTEREST = 52;
@@ -5472,7 +6014,11 @@ var FINANCE_PACKAGE = {
5472
6014
  { tokenType: "IN_YEAR_DOLLARS", parselet: new InYearDollarsParselet() }
5473
6015
  ],
5474
6016
  normalizerRules: [
5475
- inYearDollarsNormalizerRule()
6017
+ inYearDollarsNormalizerRule(),
6018
+ // `450 monthly for 18 months` -> `450 * 18`. A recurring schedule totalled
6019
+ // as a plain multiplication, so a currency amount stays currency and an
6020
+ // exact one stays exact. See RecurringScheduleNormalizerRule.ts.
6021
+ recurringScheduleNormalizerRule()
5476
6022
  ],
5477
6023
  pluginFunctions: [
5478
6024
  { index: INFLATION_FROM_YEAR_TO_PRESENT_IDX, handler: inflationFromYearToPresentHandler },
@@ -5483,7 +6029,7 @@ var FINANCE_PACKAGE = {
5483
6029
 
5484
6030
  // src/resolvers/QueryResolver.ts
5485
6031
  function defaultOnError(namespace) {
5486
- return (query, error) => chunkJMXUNXQS_cjs.errorValue(
6032
+ return (query, error) => chunk2TZKENDH_cjs.errorValue(
5487
6033
  `${namespace.toUpperCase()}_QUERY_FAILED`,
5488
6034
  `Failed to resolve "${query}": ${error instanceof Error ? error.message : String(error)}`
5489
6035
  );
@@ -5538,7 +6084,7 @@ function createQueryResolver(opts) {
5538
6084
  i += 3;
5539
6085
  continue;
5540
6086
  }
5541
- i = chunk4B2CNWQU_cjs.nextInstruction(opcodes, i);
6087
+ i = chunkSSV46KFA_cjs.nextInstruction(opcodes, i);
5542
6088
  }
5543
6089
  return null;
5544
6090
  },
@@ -5549,7 +6095,7 @@ function createQueryResolver(opts) {
5549
6095
  const query = args[0]?.value;
5550
6096
  const cached = chunkZ2XP767T_cjs.getActiveQueryClient()?.getQueryData(queryKeyFor(query));
5551
6097
  if (cached !== void 0) return cached;
5552
- return chunkJMXUNXQS_cjs.errorValue(`${opts.namespace.toUpperCase()}_NOT_PREFLIGHTED`, `No cached result for "${query}"`);
6098
+ return chunk2TZKENDH_cjs.errorValue(`${opts.namespace.toUpperCase()}_NOT_PREFLIGHTED`, `No cached result for "${query}"`);
5553
6099
  };
5554
6100
  return { resolver, pluginFunction };
5555
6101
  }
@@ -5561,7 +6107,7 @@ function consumeCityName(parser, triggerText) {
5561
6107
  words.push(parser.consume().value);
5562
6108
  }
5563
6109
  if (words.length === 0) {
5564
- throw chunk5KMIY374_cjs.ErrorFactory.parsing(
6110
+ throw chunkB4HBEFTB_cjs.ErrorFactory.parsing(
5565
6111
  "WEATHER_EXPECTED_CITY",
5566
6112
  `Expected a city name after "${triggerText}" (e.g. "${triggerText} London")`
5567
6113
  );
@@ -5632,12 +6178,12 @@ var WeatherErrorCodes = {
5632
6178
  };
5633
6179
  var GEOCODING_API_URL = "https://geocoding-api.open-meteo.com/v1/search";
5634
6180
  var FORECAST_API_URL = "https://api.open-meteo.com/v1/forecast";
5635
- var FETCH_TIMEOUT_MS = 1e4;
6181
+ var FETCH_TIMEOUT_MS2 = 1e4;
5636
6182
  async function geocodeCity(city, signal) {
5637
6183
  const url = `${GEOCODING_API_URL}?name=${encodeURIComponent(city)}&count=1&language=en&format=json`;
5638
6184
  const response = await fetch(url, { signal });
5639
6185
  if (!response.ok) {
5640
- throw chunk5KMIY374_cjs.ErrorFactory.external(
6186
+ throw chunkB4HBEFTB_cjs.ErrorFactory.external(
5641
6187
  WeatherErrorCodes.GEOCODING_API_ERROR,
5642
6188
  `Open-Meteo geocoding API returned ${response.status} for "${city}"`,
5643
6189
  { city, status: response.status }
@@ -5646,7 +6192,7 @@ async function geocodeCity(city, signal) {
5646
6192
  const json = await response.json();
5647
6193
  const first = json?.results?.[0];
5648
6194
  if (!first) {
5649
- throw chunk5KMIY374_cjs.ErrorFactory.validation(
6195
+ throw chunkB4HBEFTB_cjs.ErrorFactory.validation(
5650
6196
  WeatherErrorCodes.CITY_NOT_FOUND,
5651
6197
  `No location found for "${city}"`,
5652
6198
  { city }
@@ -5665,7 +6211,7 @@ async function fetchForecast(lat, lon, signal) {
5665
6211
  });
5666
6212
  const response = await fetch(`${FORECAST_API_URL}?${params.toString()}`, { signal });
5667
6213
  if (!response.ok) {
5668
- throw chunk5KMIY374_cjs.ErrorFactory.external(
6214
+ throw chunkB4HBEFTB_cjs.ErrorFactory.external(
5669
6215
  WeatherErrorCodes.FORECAST_API_ERROR,
5670
6216
  `Open-Meteo forecast API returned ${response.status}`,
5671
6217
  { status: response.status }
@@ -5675,7 +6221,7 @@ async function fetchForecast(lat, lon, signal) {
5675
6221
  const current = json?.current;
5676
6222
  const daily = json?.daily;
5677
6223
  if (!current || !daily) {
5678
- throw chunk5KMIY374_cjs.ErrorFactory.external(
6224
+ throw chunkB4HBEFTB_cjs.ErrorFactory.external(
5679
6225
  WeatherErrorCodes.FORECAST_RESPONSE_MALFORMED,
5680
6226
  "Open-Meteo forecast response missing current/daily blocks"
5681
6227
  );
@@ -5697,7 +6243,7 @@ function fetchCityWeather(city, signal) {
5697
6243
  if (cached && cached.expiresAt > now) {
5698
6244
  return cached.promise;
5699
6245
  }
5700
- const { signal: fetchSignal, cleanup } = chunkVEKIWQ3F_cjs.createTimeoutSignal(signal, FETCH_TIMEOUT_MS, "Open-Meteo weather query");
6246
+ const { signal: fetchSignal, cleanup } = chunkVEKIWQ3F_cjs.createTimeoutSignal(signal, FETCH_TIMEOUT_MS2, "Open-Meteo weather query");
5701
6247
  const promise = (async () => {
5702
6248
  try {
5703
6249
  const geo = await geocodeCity(city, fetchSignal);
@@ -5726,7 +6272,7 @@ function fetchCityWeather(city, signal) {
5726
6272
  }
5727
6273
 
5728
6274
  // src/packages/weather/WeatherPackage.ts
5729
- var WEATHER_FN_IDX = chunk6WFMPTGB_cjs.allocatePluginFunctionIndex();
6275
+ var WEATHER_FN_IDX = chunk7ZX6B7SY_cjs.allocatePluginFunctionIndex();
5730
6276
  var { resolver: weatherResolver, pluginFunction: weatherPluginFunction } = createQueryResolver({
5731
6277
  namespace: "weather",
5732
6278
  pluginFunctionIndex: WEATHER_FN_IDX,
@@ -5739,17 +6285,17 @@ var { resolver: weatherResolver, pluginFunction: weatherPluginFunction } = creat
5739
6285
  const data = await fetchCityWeather(city, signal);
5740
6286
  switch (kind) {
5741
6287
  case "current":
5742
- return chunkJMXUNXQS_cjs.stringValue(`${Math.round(data.temperature)}\xB0C, ${data.description}`);
6288
+ return chunk2TZKENDH_cjs.stringValue(`${Math.round(data.temperature)}\xB0C, ${data.description}`);
5743
6289
  case "temperature":
5744
- return chunkJMXUNXQS_cjs.uomValue(data.temperature, "C");
6290
+ return chunk2TZKENDH_cjs.uomValue(data.temperature, "C");
5745
6291
  case "feelslike":
5746
- return chunkJMXUNXQS_cjs.uomValue(data.apparentTemperature, "C");
6292
+ return chunk2TZKENDH_cjs.uomValue(data.apparentTemperature, "C");
5747
6293
  case "high":
5748
- return chunkJMXUNXQS_cjs.uomValue(data.high, "C");
6294
+ return chunk2TZKENDH_cjs.uomValue(data.high, "C");
5749
6295
  case "low":
5750
- return chunkJMXUNXQS_cjs.uomValue(data.low, "C");
6296
+ return chunk2TZKENDH_cjs.uomValue(data.low, "C");
5751
6297
  default:
5752
- throw chunk5KMIY374_cjs.ErrorFactory.internal(
6298
+ throw chunkB4HBEFTB_cjs.ErrorFactory.internal(
5753
6299
  WeatherErrorCodes.UNKNOWN_QUERY_KIND,
5754
6300
  `Unknown weather query kind "${kind}"`,
5755
6301
  { kind }
@@ -5806,22 +6352,22 @@ var MONTH_NAMES2 = {
5806
6352
  dec: 11,
5807
6353
  december: 11
5808
6354
  };
5809
- function pad2(n) {
6355
+ function pad22(n) {
5810
6356
  return n < 10 ? `0${n}` : String(n);
5811
6357
  }
5812
6358
  function validateAndFormat(year, monthIndex, day, raw) {
5813
6359
  const d = new Date(year, monthIndex, day);
5814
6360
  if (d.getFullYear() !== year || d.getMonth() !== monthIndex || d.getDate() !== day) {
5815
- throw chunk5KMIY374_cjs.ErrorFactory.parsing(
6361
+ throw chunkB4HBEFTB_cjs.ErrorFactory.parsing(
5816
6362
  "STOCKS_INVALID_DATE",
5817
6363
  `"${raw}" is not a valid calendar date`
5818
6364
  );
5819
6365
  }
5820
- return { isoDate: `${year}-${pad2(monthIndex + 1)}-${pad2(day)}`, raw };
6366
+ return { isoDate: `${year}-${pad22(monthIndex + 1)}-${pad22(day)}`, raw };
5821
6367
  }
5822
6368
  function requireYear(text, raw) {
5823
6369
  if (!/^\d{4}$/.test(text)) {
5824
- throw chunk5KMIY374_cjs.ErrorFactory.parsing(
6370
+ throw chunkB4HBEFTB_cjs.ErrorFactory.parsing(
5825
6371
  "STOCKS_INVALID_DATE",
5826
6372
  `Expected a 4-digit year in "${raw}", got "${text}"`
5827
6373
  );
@@ -5836,7 +6382,7 @@ function tryParseDatePhrase(parser) {
5836
6382
  const date = new Date(Number(first.value));
5837
6383
  if (Number.isNaN(date.getTime())) return null;
5838
6384
  return {
5839
- isoDate: `${date.getFullYear()}-${pad2(date.getMonth() + 1)}-${pad2(date.getDate())}`,
6385
+ isoDate: `${date.getFullYear()}-${pad22(date.getMonth() + 1)}-${pad22(date.getDate())}`,
5840
6386
  raw: first.text ?? first.value
5841
6387
  };
5842
6388
  }
@@ -5878,7 +6424,7 @@ function tryParseDatePhrase(parser) {
5878
6424
  const year = requireYear(yearTok.value, raw);
5879
6425
  return validateAndFormat(year, monthIndex, parseInt(g1.value, 10), raw);
5880
6426
  }
5881
- throw chunk5KMIY374_cjs.ErrorFactory.parsing(
6427
+ throw chunkB4HBEFTB_cjs.ErrorFactory.parsing(
5882
6428
  "STOCKS_INVALID_DATE",
5883
6429
  `Expected a date after "on" (e.g. "April 12, 2005" or "4/12/2005"), got "${g1.value}"`
5884
6430
  );
@@ -5965,7 +6511,7 @@ var MAJOR_TICKERS = /* @__PURE__ */ new Set([
5965
6511
 
5966
6512
  // src/packages/stocks/normalizer/StockTickerNormalizerRule.ts
5967
6513
  var STOCK_TICKER_TYPE = "STOCK_TICKER";
5968
- var STOCK_TICKER_TYPE_ID = chunk5LI5EPGJ_cjs.tokenTypeId(STOCK_TICKER_TYPE);
6514
+ var STOCK_TICKER_TYPE_ID = chunkQFTDTX6K_cjs.tokenTypeId(STOCK_TICKER_TYPE);
5969
6515
  function stockTickerNormalizerRule() {
5970
6516
  return {
5971
6517
  name: "stocks:bare-ticker",
@@ -5975,7 +6521,7 @@ function stockTickerNormalizerRule() {
5975
6521
  if (!token || token.type !== "IDENT") return null;
5976
6522
  if (!MAJOR_TICKERS.has(token.value)) return null;
5977
6523
  if (pos > 0 && tokens[pos - 1].type === "COLON") return null;
5978
- const fusedToken = new chunkYWQ6V4ZN_cjs.LexerToken(
6524
+ const fusedToken = new chunkYTIYVHL7_cjs.LexerToken(
5979
6525
  STOCK_TICKER_TYPE,
5980
6526
  STOCK_TICKER_TYPE_ID,
5981
6527
  token.value,
@@ -6001,7 +6547,7 @@ function tryParseDateSuffix(parser) {
6001
6547
  parser.consume();
6002
6548
  const onTok = parser.peek();
6003
6549
  if (!onTok || onTok.type !== "IDENT" || onTok.value.toLowerCase() !== "on") {
6004
- throw chunk5KMIY374_cjs.ErrorFactory.parsing(
6550
+ throw chunkB4HBEFTB_cjs.ErrorFactory.parsing(
6005
6551
  "STOCKS_EXPECTED_ON",
6006
6552
  `Expected "on <date>" after "${lower}" (e.g. "AAPL ${lower} on April 12, 2005")`
6007
6553
  );
@@ -6014,7 +6560,7 @@ function tryParseDateSuffix(parser) {
6014
6560
  }
6015
6561
  const date = tryParseDatePhrase(parser);
6016
6562
  if (!date) {
6017
- throw chunk5KMIY374_cjs.ErrorFactory.parsing(
6563
+ throw chunkB4HBEFTB_cjs.ErrorFactory.parsing(
6018
6564
  "STOCKS_EXPECTED_DATE",
6019
6565
  `Expected a date after "on" (e.g. "April 12, 2005" or "4/12/2005")`
6020
6566
  );
@@ -6044,7 +6590,7 @@ function finishStockExpression(parser, builder, ticker, currentFnIdx, historical
6044
6590
  const suffix = tryParseDateSuffix(parser);
6045
6591
  if (sawStar && !suffix) {
6046
6592
  emitStockQuery(builder, ticker, null, currentFnIdx, historicalFnIdx);
6047
- parser.parseExpression(chunkPA4VC73I_cjs.BindingPower.Product, builder);
6593
+ parser.parseExpression(chunkJ45BCEZ4_cjs.BindingPower.Product, builder);
6048
6594
  builder.emitOpcode(22 /* MUL */);
6049
6595
  return;
6050
6596
  }
@@ -6062,7 +6608,7 @@ function stockFnParselet(currentFnIdx, historicalFnIdx) {
6062
6608
  } else if (tickerToken.type === "IDENT" || tickerToken.type === STOCK_TICKER_TYPE) {
6063
6609
  ticker = tickerToken.value.toUpperCase();
6064
6610
  } else {
6065
- throw chunk5KMIY374_cjs.ErrorFactory.parsing(
6611
+ throw chunkB4HBEFTB_cjs.ErrorFactory.parsing(
6066
6612
  "STOCKS_INVALID_TICKER",
6067
6613
  `Expected a ticker symbol inside stock(...), got "${tickerToken.value}" (${tickerToken.type})`
6068
6614
  );
@@ -6084,10 +6630,10 @@ function bareStockTickerParselet(currentFnIdx, historicalFnIdx) {
6084
6630
 
6085
6631
  // src/packages/stocks/StocksPackage.ts
6086
6632
  function createStocksPackage(config = {}) {
6087
- const currentFnIdx = chunk6WFMPTGB_cjs.allocatePluginFunctionIndex();
6088
- const historicalFnIdx = chunk6WFMPTGB_cjs.allocatePluginFunctionIndex();
6633
+ const currentFnIdx = chunk7ZX6B7SY_cjs.allocatePluginFunctionIndex();
6634
+ const historicalFnIdx = chunk7ZX6B7SY_cjs.allocatePluginFunctionIndex();
6089
6635
  function notConfigured(what) {
6090
- return chunkJMXUNXQS_cjs.errorValue(
6636
+ return chunk2TZKENDH_cjs.errorValue(
6091
6637
  "STOCKS_NOT_CONFIGURED",
6092
6638
  `Stock data provider not configured. Supply ${what} via createStocksPackage({ ... }); see the StocksPackage doc comment.`
6093
6639
  );
@@ -6100,7 +6646,7 @@ function createStocksPackage(config = {}) {
6100
6646
  fetchQuery: async (ticker, signal) => {
6101
6647
  if (!config.fetchQuote) return notConfigured("fetchQuote");
6102
6648
  const quote = await config.fetchQuote(ticker, signal);
6103
- return chunkJMXUNXQS_cjs.uomValue(quote.price, quote.currency ?? "USD");
6649
+ return chunk2TZKENDH_cjs.uomValue(quote.price, quote.currency ?? "USD");
6104
6650
  }
6105
6651
  });
6106
6652
  const { resolver: historicalResolver, pluginFunction: historicalPluginFunction } = createQueryResolver({
@@ -6112,8 +6658,8 @@ function createStocksPackage(config = {}) {
6112
6658
  const [field, ticker, isoDate] = query.split(":");
6113
6659
  if (!config.fetchHistoricalQuote) return notConfigured("fetchHistoricalQuote");
6114
6660
  const quote = await config.fetchHistoricalQuote(ticker, isoDate, signal);
6115
- if (field === "volume") return chunkJMXUNXQS_cjs.numberValue(quote.volume ?? 0);
6116
- return chunkJMXUNXQS_cjs.uomValue(quote.close, quote.currency ?? "USD");
6661
+ if (field === "volume") return chunk2TZKENDH_cjs.numberValue(quote.volume ?? 0);
6662
+ return chunk2TZKENDH_cjs.uomValue(quote.close, quote.currency ?? "USD");
6117
6663
  }
6118
6664
  });
6119
6665
  const pkg = {
@@ -6160,20 +6706,20 @@ function knowledgeQueryParselet(pluginFnIdx) {
6160
6706
 
6161
6707
  // src/packages/knowledge/KnowledgePackage.ts
6162
6708
  function createKnowledgePackage(config = {}) {
6163
- const fnIdx = chunk6WFMPTGB_cjs.allocatePluginFunctionIndex();
6709
+ const fnIdx = chunk7ZX6B7SY_cjs.allocatePluginFunctionIndex();
6164
6710
  const { resolver, pluginFunction } = createQueryResolver({
6165
6711
  namespace: "knowledge",
6166
6712
  pluginFunctionIndex: fnIdx,
6167
6713
  staleTimeMs: config.staleTimeMs ?? 5 * 60 * 1e3,
6168
6714
  fetchQuery: async (query, signal) => {
6169
6715
  if (!config.answerQuery) {
6170
- return chunkJMXUNXQS_cjs.errorValue(
6716
+ return chunk2TZKENDH_cjs.errorValue(
6171
6717
  "KNOWLEDGE_NOT_CONFIGURED",
6172
6718
  `Knowledge answer provider not configured. Supply answerQuery via createKnowledgePackage({ ... }); see the KnowledgePackage doc comment.`
6173
6719
  );
6174
6720
  }
6175
6721
  const answer = await config.answerQuery(query, signal);
6176
- return chunkJMXUNXQS_cjs.stringValue(answer);
6722
+ return chunk2TZKENDH_cjs.stringValue(answer);
6177
6723
  }
6178
6724
  });
6179
6725
  return {
@@ -6200,23 +6746,23 @@ function createKnowledgePackage(config = {}) {
6200
6746
  // src/packages/lines/LinesPluginFunctions.ts
6201
6747
  function checkLineValue(v, lineNumber) {
6202
6748
  if (v === void 0) {
6203
- return chunkJMXUNXQS_cjs.errorValue("LINE_NOT_YET_EVALUATED", `Line ${lineNumber} has not been evaluated yet (forward reference, or out of range)`);
6749
+ return chunk2TZKENDH_cjs.errorValue("LINE_NOT_YET_EVALUATED", `Line ${lineNumber} has not been evaluated yet (forward reference, or out of range)`);
6204
6750
  }
6205
6751
  if (v.type === 12 /* Pending */) {
6206
- return chunkJMXUNXQS_cjs.errorValue("LINE_RESULT_PENDING", `Line ${lineNumber}'s result is still resolving`);
6752
+ return chunk2TZKENDH_cjs.errorValue("LINE_RESULT_PENDING", `Line ${lineNumber}'s result is still resolving`);
6207
6753
  }
6208
6754
  if (v.type === 13 /* Error */) {
6209
- return chunkJMXUNXQS_cjs.errorValue("LINE_RESULT_ERROR", `Line ${lineNumber} has an error`);
6755
+ return chunk2TZKENDH_cjs.errorValue("LINE_RESULT_ERROR", `Line ${lineNumber} has an error`);
6210
6756
  }
6211
6757
  return null;
6212
6758
  }
6213
6759
  function requireContext(context) {
6214
6760
  if (!context?.getLineResult) {
6215
- return chunkJMXUNXQS_cjs.errorValue("LINE_REF_NO_DOCUMENT", "Cross-line references require a real document \u2014 not available outside one (e.g. evaluateExpression()'s single-expression path)");
6761
+ return chunk2TZKENDH_cjs.errorValue("LINE_REF_NO_DOCUMENT", "Cross-line references require a real document \u2014 not available outside one (e.g. evaluateExpression()'s single-expression path)");
6216
6762
  }
6217
6763
  return null;
6218
6764
  }
6219
- var PREV_FN_IDX = chunk6WFMPTGB_cjs.allocatePluginFunctionIndex();
6765
+ var PREV_FN_IDX = chunk7ZX6B7SY_cjs.allocatePluginFunctionIndex();
6220
6766
  function prevHandler(_args, context) {
6221
6767
  const ctxError = requireContext(context);
6222
6768
  if (ctxError) return ctxError;
@@ -6225,7 +6771,7 @@ function prevHandler(_args, context) {
6225
6771
  const err = checkLineValue(v, targetLine);
6226
6772
  return err ?? v;
6227
6773
  }
6228
- var LINE_REF_FN_IDX = chunk6WFMPTGB_cjs.allocatePluginFunctionIndex();
6774
+ var LINE_REF_FN_IDX = chunk7ZX6B7SY_cjs.allocatePluginFunctionIndex();
6229
6775
  function lineRefHandler(args, context) {
6230
6776
  const ctxError = requireContext(context);
6231
6777
  if (ctxError) return ctxError;
@@ -6245,28 +6791,28 @@ function aggregateRange(from, to, context, isAverage) {
6245
6791
  const err = checkLineValue(v, n);
6246
6792
  if (err) return err;
6247
6793
  if (v.type !== 0 /* Number */ && v.type !== 6 /* Uom */) {
6248
- return chunkJMXUNXQS_cjs.errorValue("LINE_RANGE_NON_NUMERIC", `Line ${n} is not a plain number or unit value \u2014 cannot include it in a sum/total/average range`);
6794
+ return chunk2TZKENDH_cjs.errorValue("LINE_RANGE_NON_NUMERIC", `Line ${n} is not a plain number or unit value \u2014 cannot include it in a sum/total/average range`);
6249
6795
  }
6250
6796
  if (v.type === 6 /* Uom */) {
6251
6797
  sawUom = true;
6252
6798
  if (commonUnit === void 0) commonUnit = v.unit;
6253
6799
  else if (v.unit !== commonUnit) {
6254
- return chunkJMXUNXQS_cjs.errorValue("LINE_RANGE_MIXED_UNITS", `Line ${n}'s unit (${v.unit}) doesn't match the range's first unit (${commonUnit}) \u2014 aggregating mixed units would silently misrepresent the result`);
6800
+ return chunk2TZKENDH_cjs.errorValue("LINE_RANGE_MIXED_UNITS", `Line ${n}'s unit (${v.unit}) doesn't match the range's first unit (${commonUnit}) \u2014 aggregating mixed units would silently misrepresent the result`);
6255
6801
  }
6256
6802
  }
6257
6803
  total += v.toNumber();
6258
6804
  count++;
6259
6805
  }
6260
6806
  const result = isAverage ? total / count : total;
6261
- return sawUom ? chunkJMXUNXQS_cjs.uomValue(result, commonUnit) : chunkJMXUNXQS_cjs.numberValue(result);
6807
+ return sawUom ? chunk2TZKENDH_cjs.uomValue(result, commonUnit) : chunk2TZKENDH_cjs.numberValue(result);
6262
6808
  }
6263
- var SUM_RANGE_FN_IDX = chunk6WFMPTGB_cjs.allocatePluginFunctionIndex();
6809
+ var SUM_RANGE_FN_IDX = chunk7ZX6B7SY_cjs.allocatePluginFunctionIndex();
6264
6810
  function sumRangeHandler(args, context) {
6265
6811
  const ctxError = requireContext(context);
6266
6812
  if (ctxError) return ctxError;
6267
6813
  return aggregateRange(args[0].toNumber(), args[1].toNumber(), context, false);
6268
6814
  }
6269
- var AVERAGE_RANGE_FN_IDX = chunk6WFMPTGB_cjs.allocatePluginFunctionIndex();
6815
+ var AVERAGE_RANGE_FN_IDX = chunk7ZX6B7SY_cjs.allocatePluginFunctionIndex();
6270
6816
  function averageRangeHandler(args, context) {
6271
6817
  const ctxError = requireContext(context);
6272
6818
  if (ctxError) return ctxError;
@@ -6275,7 +6821,7 @@ function averageRangeHandler(args, context) {
6275
6821
  function aggregateAbove(context, isAverage) {
6276
6822
  const boundaryCheck = context.isLineBoundary;
6277
6823
  if (!boundaryCheck) {
6278
- return chunkJMXUNXQS_cjs.errorValue("LINE_REF_NO_DOCUMENT", '"above" aggregation requires a real document');
6824
+ return chunk2TZKENDH_cjs.errorValue("LINE_REF_NO_DOCUMENT", '"above" aggregation requires a real document');
6279
6825
  }
6280
6826
  let total = 0;
6281
6827
  let count = 0;
@@ -6287,29 +6833,29 @@ function aggregateAbove(context, isAverage) {
6287
6833
  const err = checkLineValue(v, n);
6288
6834
  if (err) return err;
6289
6835
  if (v.type !== 0 /* Number */ && v.type !== 6 /* Uom */) {
6290
- return chunkJMXUNXQS_cjs.errorValue("LINE_RANGE_NON_NUMERIC", `Line ${n} is not a plain number or unit value \u2014 cannot include it in "above" aggregation`);
6836
+ return chunk2TZKENDH_cjs.errorValue("LINE_RANGE_NON_NUMERIC", `Line ${n} is not a plain number or unit value \u2014 cannot include it in "above" aggregation`);
6291
6837
  }
6292
6838
  if (v.type === 6 /* Uom */) {
6293
6839
  sawUom = true;
6294
6840
  if (commonUnit === void 0) commonUnit = v.unit;
6295
6841
  else if (v.unit !== commonUnit) {
6296
- return chunkJMXUNXQS_cjs.errorValue("LINE_RANGE_MIXED_UNITS", `Line ${n}'s unit (${v.unit}) doesn't match the range's unit (${commonUnit})`);
6842
+ return chunk2TZKENDH_cjs.errorValue("LINE_RANGE_MIXED_UNITS", `Line ${n}'s unit (${v.unit}) doesn't match the range's unit (${commonUnit})`);
6297
6843
  }
6298
6844
  }
6299
6845
  total += v.toNumber();
6300
6846
  count++;
6301
6847
  }
6302
- if (count === 0) return chunkJMXUNXQS_cjs.errorValue("LINE_RANGE_EMPTY", "No lines above to aggregate (hit the top of the document, a blank line, or a heading immediately)");
6848
+ if (count === 0) return chunk2TZKENDH_cjs.errorValue("LINE_RANGE_EMPTY", "No lines above to aggregate (hit the top of the document, a blank line, or a heading immediately)");
6303
6849
  const result = isAverage ? total / count : total;
6304
- return sawUom ? chunkJMXUNXQS_cjs.uomValue(result, commonUnit) : chunkJMXUNXQS_cjs.numberValue(result);
6850
+ return sawUom ? chunk2TZKENDH_cjs.uomValue(result, commonUnit) : chunk2TZKENDH_cjs.numberValue(result);
6305
6851
  }
6306
- var TOTAL_ABOVE_FN_IDX = chunk6WFMPTGB_cjs.allocatePluginFunctionIndex();
6852
+ var TOTAL_ABOVE_FN_IDX = chunk7ZX6B7SY_cjs.allocatePluginFunctionIndex();
6307
6853
  function totalAboveHandler(_args, context) {
6308
6854
  const ctxError = requireContext(context);
6309
6855
  if (ctxError) return ctxError;
6310
6856
  return aggregateAbove(context, false);
6311
6857
  }
6312
- var AVERAGE_ABOVE_FN_IDX = chunk6WFMPTGB_cjs.allocatePluginFunctionIndex();
6858
+ var AVERAGE_ABOVE_FN_IDX = chunk7ZX6B7SY_cjs.allocatePluginFunctionIndex();
6313
6859
  function averageAboveHandler(_args, context) {
6314
6860
  const ctxError = requireContext(context);
6315
6861
  if (ctxError) return ctxError;
@@ -6381,9 +6927,9 @@ var AboveAggregateParselet = class {
6381
6927
  };
6382
6928
 
6383
6929
  // src/packages/lines/normalizer/LineRefNormalizerRule.ts
6384
- var LINE_REF_TYPE_ID = chunk5LI5EPGJ_cjs.tokenTypeId("LINE_REF");
6385
- var SUM_RANGE_CALL_TYPE_ID = chunk5LI5EPGJ_cjs.tokenTypeId("SUM_RANGE_CALL");
6386
- var AVERAGE_RANGE_CALL_TYPE_ID = chunk5LI5EPGJ_cjs.tokenTypeId("AVERAGE_RANGE_CALL");
6930
+ var LINE_REF_TYPE_ID = chunkQFTDTX6K_cjs.tokenTypeId("LINE_REF");
6931
+ var SUM_RANGE_CALL_TYPE_ID = chunkQFTDTX6K_cjs.tokenTypeId("SUM_RANGE_CALL");
6932
+ var AVERAGE_RANGE_CALL_TYPE_ID = chunkQFTDTX6K_cjs.tokenTypeId("AVERAGE_RANGE_CALL");
6387
6933
  function lineRefNormalizerRule() {
6388
6934
  return {
6389
6935
  name: "lines:line-ref",
@@ -6420,7 +6966,7 @@ function lineRefNormalizerRule() {
6420
6966
  };
6421
6967
  }
6422
6968
  function makeLineRefToken(lineNumberText, sourceToken) {
6423
- return new chunkYWQ6V4ZN_cjs.LexerToken(
6969
+ return new chunkYTIYVHL7_cjs.LexerToken(
6424
6970
  "LINE_REF",
6425
6971
  LINE_REF_TYPE_ID,
6426
6972
  lineNumberText,
@@ -6446,7 +6992,7 @@ function rangeCallNormalizerRule() {
6446
6992
  return {
6447
6993
  consumed: 1,
6448
6994
  replacement: [
6449
- new chunkYWQ6V4ZN_cjs.LexerToken(tokenType, typeId, token.value, token.value, token.offset, 0, token.line, token.col)
6995
+ new chunkYTIYVHL7_cjs.LexerToken(tokenType, typeId, token.value, token.value, token.offset, 0, token.line, token.col)
6450
6996
  ],
6451
6997
  ruleName: "lines:range-call"
6452
6998
  };
@@ -6485,6 +7031,927 @@ var LINES_PACKAGE = {
6485
7031
  ]
6486
7032
  };
6487
7033
 
7034
+ // src/packages/tables/parselets/ColumnAggregateParselet.ts
7035
+ var ColumnAggregateParselet = class {
7036
+ constructor(pluginFnIndex) {
7037
+ this.pluginFnIndex = pluginFnIndex;
7038
+ this.category = "Tables";
7039
+ }
7040
+ parse(parser, _token, builder) {
7041
+ const next = parser.peek();
7042
+ if (!next || next.type !== "STRING") {
7043
+ throw chunkB4HBEFTB_cjs.ErrorFactory.parsing(
7044
+ "TABLE_COLUMN_NAME_EXPECTED",
7045
+ `Expected a quoted column name after "... of column" but got ${next ? `"${next.value}"` : "end of input"}`
7046
+ );
7047
+ }
7048
+ const nameToken = parser.consume("STRING");
7049
+ while (true) {
7050
+ const t = parser.peek();
7051
+ if (!t) break;
7052
+ if (t.type === "IN") {
7053
+ parser.consume();
7054
+ continue;
7055
+ }
7056
+ if (t.type === "IDENT" && ADDRESS_WORDS.has(t.value.toLowerCase())) {
7057
+ parser.consume();
7058
+ continue;
7059
+ }
7060
+ break;
7061
+ }
7062
+ builder.emitOpcode(13 /* PUSH_STRING */);
7063
+ builder.emitString(nameToken.value);
7064
+ builder.emitOpcode(50 /* CALL_PLUGIN */);
7065
+ builder.emitIndex(this.pluginFnIndex);
7066
+ builder.emitIndex(1);
7067
+ }
7068
+ };
7069
+ var ADDRESS_WORDS = /* @__PURE__ */ new Set(["the", "table", "above"]);
7070
+
7071
+ // src/packages/tables/TableReader.ts
7072
+ function isTableRow(line) {
7073
+ return line.trimStart().startsWith("|");
7074
+ }
7075
+ function isSeparatorRow(line) {
7076
+ const s = line.trim();
7077
+ if (s.length === 0) return false;
7078
+ if (!s.includes("-") || !s.includes("|")) return false;
7079
+ return /^[|\s:-]+$/.test(s);
7080
+ }
7081
+ function splitTableRow(line) {
7082
+ let s = line.trim();
7083
+ if (s.startsWith("|")) s = s.slice(1);
7084
+ if (s.endsWith("|") && !s.endsWith("\\|")) s = s.slice(0, -1);
7085
+ const cells = [];
7086
+ let cell = "";
7087
+ for (let i = 0; i < s.length; i++) {
7088
+ const ch = s[i];
7089
+ if (ch === "\\" && i + 1 < s.length && s[i + 1] === "|") {
7090
+ cell += "|";
7091
+ i++;
7092
+ continue;
7093
+ }
7094
+ if (ch === "|") {
7095
+ cells.push(cell.trim());
7096
+ cell = "";
7097
+ continue;
7098
+ }
7099
+ cell += ch;
7100
+ }
7101
+ cells.push(cell.trim());
7102
+ return cells;
7103
+ }
7104
+ function parseNumericCell(cell) {
7105
+ if (cell === void 0) return null;
7106
+ const trimmed = cell.trim();
7107
+ if (trimmed.length === 0) return null;
7108
+ const cleaned = trimmed.replace(/,/g, "");
7109
+ if (!/^[+-]?(\d+(\.\d+)?|\.\d+)$/.test(cleaned)) return null;
7110
+ const n = Number.parseFloat(cleaned);
7111
+ return Number.isFinite(n) ? n : null;
7112
+ }
7113
+ function findTableAbove(getLineText, fromLine) {
7114
+ let bottom = -1;
7115
+ for (let n = fromLine - 1; n >= 1; n--) {
7116
+ const text = getLineText(n);
7117
+ if (text === void 0) break;
7118
+ if (isTableRow(text)) {
7119
+ bottom = n;
7120
+ break;
7121
+ }
7122
+ }
7123
+ if (bottom === -1) return null;
7124
+ let top = bottom;
7125
+ while (top - 1 >= 1) {
7126
+ const text = getLineText(top - 1);
7127
+ if (text === void 0 || !isTableRow(text)) break;
7128
+ top--;
7129
+ }
7130
+ let separator = -1;
7131
+ for (let n = top; n <= bottom; n++) {
7132
+ if (isSeparatorRow(getLineText(n))) {
7133
+ separator = n;
7134
+ break;
7135
+ }
7136
+ }
7137
+ if (separator === -1 || separator === top) return null;
7138
+ const header = splitTableRow(getLineText(separator - 1));
7139
+ const rows = [];
7140
+ for (let n = separator + 1; n <= bottom; n++) {
7141
+ rows.push(splitTableRow(getLineText(n)));
7142
+ }
7143
+ return { header, rows };
7144
+ }
7145
+ function columnIndex(header, name) {
7146
+ const target = name.trim().toLowerCase();
7147
+ for (let i = 0; i < header.length; i++) {
7148
+ if (header[i].trim().toLowerCase() === target) return i;
7149
+ }
7150
+ return -1;
7151
+ }
7152
+ function numericColumn(table, colIndex) {
7153
+ const out = [];
7154
+ for (const row of table.rows) {
7155
+ const value = parseNumericCell(row[colIndex]);
7156
+ if (value !== null) out.push(value);
7157
+ }
7158
+ return out;
7159
+ }
7160
+
7161
+ // src/packages/tables/TablesPluginFunctions.ts
7162
+ var TablesErrorCodes = {
7163
+ /** Cross-line table reads need a real document, absent on the single-expression path. */
7164
+ TABLE_NO_DOCUMENT: "TABLE_NO_DOCUMENT",
7165
+ /** No markdown table was found above the query line. */
7166
+ TABLE_NOT_FOUND: "TABLE_NOT_FOUND",
7167
+ /** The named column is not one of the table's headers. */
7168
+ TABLE_COLUMN_NOT_FOUND: "TABLE_COLUMN_NOT_FOUND",
7169
+ /** The column held no plain-number cells to aggregate. */
7170
+ TABLE_COLUMN_NO_NUMERIC_CELLS: "TABLE_COLUMN_NO_NUMERIC_CELLS"
7171
+ };
7172
+ function reduce(op, cells) {
7173
+ switch (op) {
7174
+ case "sum":
7175
+ return cells.reduce((acc, n) => acc + n, 0);
7176
+ case "average":
7177
+ return cells.reduce((acc, n) => acc + n, 0) / cells.length;
7178
+ case "min":
7179
+ return Math.min(...cells);
7180
+ case "max":
7181
+ return Math.max(...cells);
7182
+ case "count":
7183
+ return cells.length;
7184
+ case "median": {
7185
+ const sorted = [...cells].sort((a, b) => a - b);
7186
+ const mid = Math.floor(sorted.length / 2);
7187
+ return sorted.length % 2 === 0 ? (sorted[mid - 1] + sorted[mid]) / 2 : sorted[mid];
7188
+ }
7189
+ }
7190
+ }
7191
+ function aggregateColumn(op, columnName2, context) {
7192
+ const getLineText = context?.getLineText;
7193
+ if (!getLineText || context.lineIndex < 1) {
7194
+ return chunk2TZKENDH_cjs.errorValue(
7195
+ TablesErrorCodes.TABLE_NO_DOCUMENT,
7196
+ "Reading a table column needs a real document, which the single-expression path (evaluateExpression) does not have"
7197
+ );
7198
+ }
7199
+ const table = findTableAbove(getLineText, context.lineIndex);
7200
+ if (!table) {
7201
+ return chunk2TZKENDH_cjs.errorValue(
7202
+ TablesErrorCodes.TABLE_NOT_FOUND,
7203
+ 'No markdown table found above this line (a table row starts with "|", and needs a "|---|" separator under its header)'
7204
+ );
7205
+ }
7206
+ const index = columnIndex(table.header, columnName2);
7207
+ if (index === -1) {
7208
+ const names = table.header.map((h) => `"${h}"`).join(", ");
7209
+ return chunk2TZKENDH_cjs.errorValue(
7210
+ TablesErrorCodes.TABLE_COLUMN_NOT_FOUND,
7211
+ `The table above has no column named "${columnName2}". Its columns are: ${names}`
7212
+ );
7213
+ }
7214
+ const cells = numericColumn(table, index);
7215
+ if (op === "count") return chunk2TZKENDH_cjs.numberValue(cells.length);
7216
+ if (cells.length === 0) {
7217
+ return chunk2TZKENDH_cjs.errorValue(
7218
+ TablesErrorCodes.TABLE_COLUMN_NO_NUMERIC_CELLS,
7219
+ `Column "${columnName2}" has no plain-number cells to aggregate (currency and unit cells are not read yet)`
7220
+ );
7221
+ }
7222
+ return chunk2TZKENDH_cjs.numberValue(reduce(op, cells));
7223
+ }
7224
+ function columnName(args) {
7225
+ const value = args[0];
7226
+ if (value && value.type === 3 /* String */ && typeof value.value === "string") {
7227
+ return value.value;
7228
+ }
7229
+ return String(value?.value ?? "");
7230
+ }
7231
+ var TABLE_COLUMN_SUM_FN_IDX = chunk7ZX6B7SY_cjs.allocatePluginFunctionIndex();
7232
+ function tableColumnSumHandler(args, context) {
7233
+ return aggregateColumn("sum", columnName(args), context);
7234
+ }
7235
+ var TABLE_COLUMN_AVERAGE_FN_IDX = chunk7ZX6B7SY_cjs.allocatePluginFunctionIndex();
7236
+ function tableColumnAverageHandler(args, context) {
7237
+ return aggregateColumn("average", columnName(args), context);
7238
+ }
7239
+ var TABLE_COLUMN_MIN_FN_IDX = chunk7ZX6B7SY_cjs.allocatePluginFunctionIndex();
7240
+ function tableColumnMinHandler(args, context) {
7241
+ return aggregateColumn("min", columnName(args), context);
7242
+ }
7243
+ var TABLE_COLUMN_MAX_FN_IDX = chunk7ZX6B7SY_cjs.allocatePluginFunctionIndex();
7244
+ function tableColumnMaxHandler(args, context) {
7245
+ return aggregateColumn("max", columnName(args), context);
7246
+ }
7247
+ var TABLE_COLUMN_COUNT_FN_IDX = chunk7ZX6B7SY_cjs.allocatePluginFunctionIndex();
7248
+ function tableColumnCountHandler(args, context) {
7249
+ return aggregateColumn("count", columnName(args), context);
7250
+ }
7251
+ var TABLE_COLUMN_MEDIAN_FN_IDX = chunk7ZX6B7SY_cjs.allocatePluginFunctionIndex();
7252
+ function tableColumnMedianHandler(args, context) {
7253
+ return aggregateColumn("median", columnName(args), context);
7254
+ }
7255
+
7256
+ // src/packages/tables/TablesPackage.ts
7257
+ var TABLES_PACKAGE = {
7258
+ name: "solve-tables",
7259
+ phrases: {
7260
+ "sum of column": "TABLE_COLUMN_SUM",
7261
+ "total of column": "TABLE_COLUMN_SUM",
7262
+ "average of column": "TABLE_COLUMN_AVERAGE",
7263
+ "mean of column": "TABLE_COLUMN_AVERAGE",
7264
+ "min of column": "TABLE_COLUMN_MIN",
7265
+ "minimum of column": "TABLE_COLUMN_MIN",
7266
+ "max of column": "TABLE_COLUMN_MAX",
7267
+ "maximum of column": "TABLE_COLUMN_MAX",
7268
+ "count of column": "TABLE_COLUMN_COUNT",
7269
+ "median of column": "TABLE_COLUMN_MEDIAN"
7270
+ },
7271
+ prefixParselets: [
7272
+ { tokenType: "TABLE_COLUMN_SUM", parselet: new ColumnAggregateParselet(TABLE_COLUMN_SUM_FN_IDX) },
7273
+ { tokenType: "TABLE_COLUMN_AVERAGE", parselet: new ColumnAggregateParselet(TABLE_COLUMN_AVERAGE_FN_IDX) },
7274
+ { tokenType: "TABLE_COLUMN_MIN", parselet: new ColumnAggregateParselet(TABLE_COLUMN_MIN_FN_IDX) },
7275
+ { tokenType: "TABLE_COLUMN_MAX", parselet: new ColumnAggregateParselet(TABLE_COLUMN_MAX_FN_IDX) },
7276
+ { tokenType: "TABLE_COLUMN_COUNT", parselet: new ColumnAggregateParselet(TABLE_COLUMN_COUNT_FN_IDX) },
7277
+ { tokenType: "TABLE_COLUMN_MEDIAN", parselet: new ColumnAggregateParselet(TABLE_COLUMN_MEDIAN_FN_IDX) }
7278
+ ],
7279
+ pluginFunctions: [
7280
+ { index: TABLE_COLUMN_SUM_FN_IDX, handler: tableColumnSumHandler },
7281
+ { index: TABLE_COLUMN_AVERAGE_FN_IDX, handler: tableColumnAverageHandler },
7282
+ { index: TABLE_COLUMN_MIN_FN_IDX, handler: tableColumnMinHandler },
7283
+ { index: TABLE_COLUMN_MAX_FN_IDX, handler: tableColumnMaxHandler },
7284
+ { index: TABLE_COLUMN_COUNT_FN_IDX, handler: tableColumnCountHandler },
7285
+ { index: TABLE_COLUMN_MEDIAN_FN_IDX, handler: tableColumnMedianHandler }
7286
+ ]
7287
+ };
7288
+
7289
+ // src/packages/uncertainty/parselets/UncertaintyParselet.ts
7290
+ var UncertaintyParselet = class {
7291
+ constructor() {
7292
+ this.category = "operator";
7293
+ this.bindingPower = chunkJ45BCEZ4_cjs.BindingPower.Uncertainty;
7294
+ }
7295
+ parse(parser, _left, _token, builder) {
7296
+ parser.parseExpression(this.bindingPower, builder);
7297
+ builder.emitOpcode(28 /* MAKE_UNCERTAIN */);
7298
+ }
7299
+ };
7300
+
7301
+ // src/packages/uncertainty/normalizer/AsciiPlusMinusNormalizerRule.ts
7302
+ function asciiPlusMinusNormalizerRule(priority = 90) {
7303
+ return {
7304
+ name: "uncertainty:ascii-plus-minus",
7305
+ priority,
7306
+ match(tokens, pos) {
7307
+ const plus = tokens[pos];
7308
+ if (plus?.type !== "PLUS") return null;
7309
+ const slash = tokens[pos + 1];
7310
+ if (slash?.type !== "SLASH") return null;
7311
+ const minus = tokens[pos + 2];
7312
+ if (minus?.type !== "MINUS") return null;
7313
+ return {
7314
+ consumed: 3,
7315
+ replacement: [chunkC75AFGVD_cjs.createFusedToken("PLUS_MINUS", "\xB1", [plus, slash, minus])],
7316
+ ruleName: "uncertainty:ascii-plus-minus"
7317
+ };
7318
+ }
7319
+ };
7320
+ }
7321
+
7322
+ // src/packages/uncertainty/UncertaintyPackage.ts
7323
+ var UNCERTAINTY_PACKAGE = {
7324
+ name: "solve-uncertainty",
7325
+ infixParselets: [
7326
+ { tokenType: "PLUS_MINUS", parselet: new UncertaintyParselet() }
7327
+ ],
7328
+ normalizerRules: [asciiPlusMinusNormalizerRule()]
7329
+ // PLUS_MINUS is a core lexer token, so its highlight category lives in the
7330
+ // built-in language/TokenCategoryMap.ts rather than being contributed here.
7331
+ };
7332
+
7333
+ // src/packages/goalseek/GoalSeekPluginFunctions.ts
7334
+ var SEARCH_LOWER_BOUND = 1e-9;
7335
+ var SEARCH_UPPER_BOUND = 1e9;
7336
+ var FALLBACK_MAX_ITERATIONS = 100;
7337
+ var ABSOLUTE_VALUE_TOLERANCE = 1e-9;
7338
+ var RELATIVE_VALUE_TOLERANCE = 1e-9;
7339
+ var INTERVAL_COLLAPSE_WIDTH = 1e-12;
7340
+ var REAL_ROOT_IMAGINARY_TOLERANCE = 1e-9;
7341
+ var GOAL_SEEK_FN_IDX = chunk7ZX6B7SY_cjs.allocatePluginFunctionIndex();
7342
+ function goalSeekHandler(args, context) {
7343
+ const probe = context?.evaluateLineWithBinding;
7344
+ const getLineReads = context?.getLineReads;
7345
+ if (!probe || !getLineReads) {
7346
+ return chunk2TZKENDH_cjs.errorValue(
7347
+ "GOAL_SEEK_NO_DOCUMENT",
7348
+ "Goal seek only works inside a document, since it re-runs another line. The single-expression entry point has no document to solve against."
7349
+ );
7350
+ }
7351
+ const targetLine = args[0].toNumber();
7352
+ const varName = typeof args[1].value === "string" ? args[1].value : String(args[1].value);
7353
+ const targetArg = args[2];
7354
+ if (targetArg.type !== 0 /* Number */ && targetArg.type !== 6 /* Uom */) {
7355
+ return chunk2TZKENDH_cjs.errorValue("GOAL_SEEK_TARGET_NOT_NUMERIC", `Goal seek's target must be a number, as in "solve line ${targetLine} for ${varName} = 900".`);
7356
+ }
7357
+ const target = targetArg.toNumber();
7358
+ if (!Number.isFinite(target)) {
7359
+ return chunk2TZKENDH_cjs.errorValue("GOAL_SEEK_TARGET_NOT_NUMERIC", "Goal seek's target is not a finite number.");
7360
+ }
7361
+ const reads = getLineReads(targetLine);
7362
+ if (reads === void 0) {
7363
+ return chunk2TZKENDH_cjs.errorValue("GOAL_SEEK_LINE_NOT_READY", `Line ${targetLine} has no evaluated expression to solve (forward reference, out of range, or not an expression).`);
7364
+ }
7365
+ if (!reads.includes(varName)) {
7366
+ return chunk2TZKENDH_cjs.errorValue("GOAL_SEEK_VARIABLE_NOT_USED", `Line ${targetLine} does not use ${varName}, so changing ${varName} cannot move its result toward ${target}.`);
7367
+ }
7368
+ const symbolic = probe(targetLine, varName, chunk2TZKENDH_cjs.symbolicValue(chunkHIQ5HSZL_cjs.varNode(varName)), true);
7369
+ if (symbolic.type === 9 /* Symbolic */) {
7370
+ const exact = solveClosedForm(symbolic.value, target, varName);
7371
+ if (exact !== null) return chunk2TZKENDH_cjs.numberValue(exact);
7372
+ }
7373
+ const maxIterations = context.goalSeekMaxIterations ?? FALLBACK_MAX_ITERATIONS;
7374
+ return bisect(probe, targetLine, varName, target, maxIterations);
7375
+ }
7376
+ function solveClosedForm(lhs, target, variable) {
7377
+ const rhs = chunkHIQ5HSZL_cjs.constNode(chunkHIQ5HSZL_cjs.rationalFromNumber(target));
7378
+ let outcome;
7379
+ try {
7380
+ outcome = chunk7ZX6B7SY_cjs.solveForVariable(lhs, rhs, variable);
7381
+ } catch {
7382
+ return null;
7383
+ }
7384
+ if (outcome.kind !== "roots") return null;
7385
+ const candidates = [];
7386
+ for (const node of outcome.exact) {
7387
+ const simplified = chunkHIQ5HSZL_cjs.simplifySymbolic(node);
7388
+ if (simplified.kind === "const") {
7389
+ const value = chunkHIQ5HSZL_cjs.rationalToNumber(simplified.value);
7390
+ if (Number.isFinite(value)) candidates.push(value);
7391
+ }
7392
+ }
7393
+ for (const root of outcome.approximate) {
7394
+ if (Math.abs(root.im) <= REAL_ROOT_IMAGINARY_TOLERANCE && Number.isFinite(root.re)) candidates.push(root.re);
7395
+ }
7396
+ if (candidates.length === 0) return null;
7397
+ const inDomain = candidates.filter((c) => c >= SEARCH_LOWER_BOUND && c <= SEARCH_UPPER_BOUND);
7398
+ const pool = inDomain.length > 0 ? inDomain : candidates;
7399
+ pool.sort((a, b) => Math.abs(a) - Math.abs(b));
7400
+ return pool[0];
7401
+ }
7402
+ function bisect(probe, targetLine, variable, target, maxIterations) {
7403
+ const sample = (candidate) => {
7404
+ const result = probe(targetLine, variable, chunk2TZKENDH_cjs.numberValue(candidate), false);
7405
+ if (result.type === 13 /* Error */) return { error: result };
7406
+ if (result.type !== 0 /* Number */ && result.type !== 6 /* Uom */) {
7407
+ return { error: chunk2TZKENDH_cjs.errorValue("GOAL_SEEK_TARGET_NOT_NUMERIC", `Line ${targetLine} did not produce a number when ${variable} was set to ${candidate}, so goal seek cannot compare it to the target.`) };
7408
+ }
7409
+ const numeric = result.toNumber();
7410
+ if (!Number.isFinite(numeric)) return { error: chunk2TZKENDH_cjs.errorValue("GOAL_SEEK_NON_FINITE", `Line ${targetLine}'s result is not finite when ${variable} is ${candidate}.`) };
7411
+ return { value: numeric };
7412
+ };
7413
+ let lo = SEARCH_LOWER_BOUND;
7414
+ let hi = SEARCH_UPPER_BOUND;
7415
+ const loSample = sample(lo);
7416
+ if ("error" in loSample) return loSample.error;
7417
+ const hiSample = sample(hi);
7418
+ if ("error" in hiSample) return hiSample.error;
7419
+ const tolerance = Math.max(ABSOLUTE_VALUE_TOLERANCE, Math.abs(target) * RELATIVE_VALUE_TOLERANCE);
7420
+ let flo = loSample.value - target;
7421
+ const fhi = hiSample.value - target;
7422
+ if (Math.abs(flo) <= tolerance) return chunk2TZKENDH_cjs.numberValue(lo);
7423
+ if (Math.abs(fhi) <= tolerance) return chunk2TZKENDH_cjs.numberValue(hi);
7424
+ if (Math.sign(flo) === Math.sign(fhi)) {
7425
+ return chunk2TZKENDH_cjs.errorValue(
7426
+ "GOAL_SEEK_NO_SOLUTION",
7427
+ `No value of ${variable} between ${lo} and ${hi} makes line ${targetLine} equal ${target}: across that whole range its result stays on one side of the target.`
7428
+ );
7429
+ }
7430
+ for (let step = 0; step < maxIterations; step++) {
7431
+ const mid = (lo + hi) / 2;
7432
+ const midSample = sample(mid);
7433
+ if ("error" in midSample) return midSample.error;
7434
+ const fmid = midSample.value - target;
7435
+ if (Math.abs(fmid) <= tolerance) return chunk2TZKENDH_cjs.numberValue(mid);
7436
+ if (hi - lo <= INTERVAL_COLLAPSE_WIDTH) {
7437
+ return chunk2TZKENDH_cjs.errorValue(
7438
+ "GOAL_SEEK_DID_NOT_CONVERGE",
7439
+ `Goal seek narrowed ${variable} to a single point near ${mid} without line ${targetLine} reaching ${target}: the relationship jumps across the target rather than passing through it.`
7440
+ );
7441
+ }
7442
+ if (Math.sign(fmid) === Math.sign(flo)) {
7443
+ lo = mid;
7444
+ flo = fmid;
7445
+ } else {
7446
+ hi = mid;
7447
+ }
7448
+ }
7449
+ return chunk2TZKENDH_cjs.errorValue(
7450
+ "GOAL_SEEK_DID_NOT_CONVERGE",
7451
+ `Goal seek did not bring line ${targetLine} within tolerance of ${target} by varying ${variable} in ${maxIterations} steps.`
7452
+ );
7453
+ }
7454
+
7455
+ // src/packages/goalseek/parselets/GoalSeekParselet.ts
7456
+ var GoalSeekParselet = class {
7457
+ constructor() {
7458
+ this.category = "GoalSeek";
7459
+ }
7460
+ parse(parser, _token, builder) {
7461
+ const lineRef = parser.peek();
7462
+ if (!lineRef || lineRef.type !== "LINE_REF") {
7463
+ throw chunkB4HBEFTB_cjs.ErrorFactory.parsing(
7464
+ "GOAL_SEEK_SYNTAX",
7465
+ `Goal seek reads "solve line N for <var> = <target>", for example "solve line 4 for rate = 900".`,
7466
+ { found: lineRef?.type ?? "end of input" }
7467
+ );
7468
+ }
7469
+ parser.consume();
7470
+ const targetLine = parseInt(lineRef.value, 10);
7471
+ const connective = parser.peek();
7472
+ const isFor = !!connective && (connective.type === "FOR_DURATION" || connective.type === "IDENT" && connective.value.toLowerCase() === "for");
7473
+ if (!isFor) {
7474
+ throw chunkB4HBEFTB_cjs.ErrorFactory.parsing(
7475
+ "GOAL_SEEK_SYNTAX",
7476
+ `Goal seek reads "solve line N for <var> = <target>". Expected "for" after the line reference.`,
7477
+ { found: connective?.type ?? "end of input" }
7478
+ );
7479
+ }
7480
+ parser.consume();
7481
+ const variable = parser.peek();
7482
+ if (!variable || variable.type !== "IDENT" && variable.type !== "UNIT") {
7483
+ throw chunkB4HBEFTB_cjs.ErrorFactory.parsing(
7484
+ "GOAL_SEEK_REQUIRES_VARIABLE_NAME",
7485
+ `Goal seek needs the name of the variable to vary here, as in "solve line 4 for rate = 900".`,
7486
+ { found: variable?.type ?? "end of input" }
7487
+ );
7488
+ }
7489
+ parser.consume();
7490
+ parser.consume("EQUALS");
7491
+ builder.emitOpcode(10 /* PUSH_NUMBER */);
7492
+ builder.emitNumber(targetLine);
7493
+ builder.emitOpcode(13 /* PUSH_STRING */);
7494
+ builder.emitString(variable.value);
7495
+ parser.parseExpression(chunkJ45BCEZ4_cjs.BindingPower.Lowest, builder);
7496
+ builder.emitOpcode(50 /* CALL_PLUGIN */);
7497
+ builder.emitIndex(GOAL_SEEK_FN_IDX);
7498
+ builder.emitIndex(3);
7499
+ }
7500
+ };
7501
+
7502
+ // src/packages/goalseek/normalizer/GoalSeekNormalizerRule.ts
7503
+ var GOAL_SEEK_TYPE_ID = chunkQFTDTX6K_cjs.tokenTypeId("GOAL_SEEK");
7504
+ function goalSeekNormalizerRule(priority = 75) {
7505
+ return {
7506
+ name: "goalseek:solve-line",
7507
+ priority,
7508
+ match(tokens, pos) {
7509
+ if (pos > 0 && tokens[pos - 1].type === "COLON") return null;
7510
+ const token = tokens[pos];
7511
+ if (!token || token.type !== "IDENT" || token.value.toLowerCase() !== "solve") return null;
7512
+ if (tokens[pos + 1]?.type !== "LINE_REF") return null;
7513
+ return {
7514
+ consumed: 1,
7515
+ replacement: [
7516
+ new chunkYTIYVHL7_cjs.LexerToken("GOAL_SEEK", GOAL_SEEK_TYPE_ID, token.value, token.value, token.offset, 0, token.line, token.col)
7517
+ ],
7518
+ ruleName: "goalseek:solve-line"
7519
+ };
7520
+ }
7521
+ };
7522
+ }
7523
+
7524
+ // src/packages/goalseek/GoalSeekPackage.ts
7525
+ var GOALSEEK_PACKAGE = {
7526
+ name: "solve-goalseek",
7527
+ normalizerRules: [goalSeekNormalizerRule()],
7528
+ prefixParselets: [{ tokenType: "GOAL_SEEK", parselet: new GoalSeekParselet() }],
7529
+ pluginFunctions: [{ index: GOAL_SEEK_FN_IDX, handler: goalSeekHandler }]
7530
+ // GOAL_SEEK's highlight category lives in the static TokenCategoryMap
7531
+ // alongside every other token declared in Token.ts's TokenTypes (the same
7532
+ // place the lines package's LINE_REF etc. are categorized), so it is not
7533
+ // repeated here.
7534
+ };
7535
+
7536
+ // src/packages/colour/ColourPluginFunctions.ts
7537
+ var ColourErrorCodes = {
7538
+ /** A string was not a hex colour or a known CSS colour name. */
7539
+ COLOUR_INVALID: "COLOUR_INVALID",
7540
+ /** A function expected a colour argument and got something else. */
7541
+ COLOUR_EXPECTED_COLOUR: "COLOUR_EXPECTED_COLOUR",
7542
+ /** A function was called with the wrong number or type of arguments. */
7543
+ COLOUR_BAD_ARGUMENTS: "COLOUR_BAD_ARGUMENTS"
7544
+ };
7545
+ function colourOf(v) {
7546
+ return v && v.type === 14 /* Colour */ ? v.value : null;
7547
+ }
7548
+ function readAmount(v) {
7549
+ if (!v) return 0;
7550
+ const n = v.toNumber();
7551
+ if (v.type === 5 /* Percentage */) return n;
7552
+ return n > 1 ? n / 100 : n;
7553
+ }
7554
+ function badArgs(fn, expected) {
7555
+ return chunk2TZKENDH_cjs.errorValue(ColourErrorCodes.COLOUR_BAD_ARGUMENTS, `${fn}(...) expects ${expected}`);
7556
+ }
7557
+ function notAColour(fn) {
7558
+ return chunk2TZKENDH_cjs.errorValue(ColourErrorCodes.COLOUR_EXPECTED_COLOUR, `${fn}(...) expects a colour as its first argument`);
7559
+ }
7560
+ var COLOUR_PARSE_FN_IDX = chunk7ZX6B7SY_cjs.allocatePluginFunctionIndex();
7561
+ function colourParseHandler(args) {
7562
+ const arg = args[0];
7563
+ if (!arg || arg.type !== 3 /* String */ || typeof arg.value !== "string") {
7564
+ return badArgs("color", 'a string, e.g. color("#ff0000") or color("red")');
7565
+ }
7566
+ const parsed = chunkALYRJ72W_cjs.parseColour(arg.value);
7567
+ if (!parsed) {
7568
+ return chunk2TZKENDH_cjs.errorValue(
7569
+ ColourErrorCodes.COLOUR_INVALID,
7570
+ `"${arg.value}" is not a hex colour (#rgb, #rgba, #rrggbb, #rrggbbaa) or a CSS colour name`
7571
+ );
7572
+ }
7573
+ return chunk2TZKENDH_cjs.colourValue(parsed);
7574
+ }
7575
+ var COLOUR_RGB_FN_IDX = chunk7ZX6B7SY_cjs.allocatePluginFunctionIndex();
7576
+ function rgbHandler(args) {
7577
+ if (args.length !== 3) return badArgs("rgb", "three numbers: rgb(red, green, blue)");
7578
+ return chunk2TZKENDH_cjs.colourValue({
7579
+ r: chunkALYRJ72W_cjs.clamp255(args[0].toNumber()),
7580
+ g: chunkALYRJ72W_cjs.clamp255(args[1].toNumber()),
7581
+ b: chunkALYRJ72W_cjs.clamp255(args[2].toNumber()),
7582
+ a: 1,
7583
+ format: "rgb"
7584
+ });
7585
+ }
7586
+ var COLOUR_RGBA_FN_IDX = chunk7ZX6B7SY_cjs.allocatePluginFunctionIndex();
7587
+ function rgbaHandler(args) {
7588
+ if (args.length !== 4) return badArgs("rgba", "four numbers: rgba(red, green, blue, alpha)");
7589
+ return chunk2TZKENDH_cjs.colourValue({
7590
+ r: chunkALYRJ72W_cjs.clamp255(args[0].toNumber()),
7591
+ g: chunkALYRJ72W_cjs.clamp255(args[1].toNumber()),
7592
+ b: chunkALYRJ72W_cjs.clamp255(args[2].toNumber()),
7593
+ a: chunkALYRJ72W_cjs.clamp01(readAmount(args[3])),
7594
+ format: "rgba"
7595
+ });
7596
+ }
7597
+ var COLOUR_HSL_FN_IDX = chunk7ZX6B7SY_cjs.allocatePluginFunctionIndex();
7598
+ function hslHandler(args) {
7599
+ if (args.length !== 3) return badArgs("hsl", "three values: hsl(hue, saturation%, lightness%)");
7600
+ const { r, g, b } = chunkALYRJ72W_cjs.hslToRgb(args[0].toNumber(), readAmount(args[1]), readAmount(args[2]));
7601
+ return chunk2TZKENDH_cjs.colourValue({ r, g, b, a: 1, format: "hsl" });
7602
+ }
7603
+ var COLOUR_HSLA_FN_IDX = chunk7ZX6B7SY_cjs.allocatePluginFunctionIndex();
7604
+ function hslaHandler(args) {
7605
+ if (args.length !== 4) return badArgs("hsla", "four values: hsla(hue, saturation%, lightness%, alpha)");
7606
+ const { r, g, b } = chunkALYRJ72W_cjs.hslToRgb(args[0].toNumber(), readAmount(args[1]), readAmount(args[2]));
7607
+ return chunk2TZKENDH_cjs.colourValue({ r, g, b, a: chunkALYRJ72W_cjs.clamp01(readAmount(args[3])), format: "hsla" });
7608
+ }
7609
+ function adjuster(name, op) {
7610
+ return (args) => {
7611
+ const c = colourOf(args[0]);
7612
+ if (!c) return notAColour(name);
7613
+ if (args.length < 2) return badArgs(name, `a colour and an amount, e.g. ${name}(#3366cc, 20%)`);
7614
+ return chunk2TZKENDH_cjs.colourValue(op(c, readAmount(args[1])));
7615
+ };
7616
+ }
7617
+ var COLOUR_LIGHTEN_FN_IDX = chunk7ZX6B7SY_cjs.allocatePluginFunctionIndex();
7618
+ var lightenHandler = adjuster("lighten", chunkALYRJ72W_cjs.lighten);
7619
+ var COLOUR_DARKEN_FN_IDX = chunk7ZX6B7SY_cjs.allocatePluginFunctionIndex();
7620
+ var darkenHandler = adjuster("darken", chunkALYRJ72W_cjs.darken);
7621
+ var COLOUR_SATURATE_FN_IDX = chunk7ZX6B7SY_cjs.allocatePluginFunctionIndex();
7622
+ var saturateHandler = adjuster("saturate", chunkALYRJ72W_cjs.saturate);
7623
+ var COLOUR_DESATURATE_FN_IDX = chunk7ZX6B7SY_cjs.allocatePluginFunctionIndex();
7624
+ var desaturateHandler = adjuster("desaturate", chunkALYRJ72W_cjs.desaturate);
7625
+ var COLOUR_ROTATE_FN_IDX = chunk7ZX6B7SY_cjs.allocatePluginFunctionIndex();
7626
+ function rotateHandler(args) {
7627
+ const c = colourOf(args[0]);
7628
+ if (!c) return notAColour("rotate");
7629
+ if (args.length < 2) return badArgs("rotate", "a colour and a hue angle in degrees, e.g. rotate(#ff0000, 90)");
7630
+ return chunk2TZKENDH_cjs.colourValue(chunkALYRJ72W_cjs.rotateHue(c, args[1].toNumber()));
7631
+ }
7632
+ var COLOUR_COMPLEMENT_FN_IDX = chunk7ZX6B7SY_cjs.allocatePluginFunctionIndex();
7633
+ function complementHandler(args) {
7634
+ const c = colourOf(args[0]);
7635
+ if (!c) return notAColour("complement");
7636
+ return chunk2TZKENDH_cjs.colourValue(chunkALYRJ72W_cjs.complement(c));
7637
+ }
7638
+ var COLOUR_GRAYSCALE_FN_IDX = chunk7ZX6B7SY_cjs.allocatePluginFunctionIndex();
7639
+ function grayscaleHandler(args) {
7640
+ const c = colourOf(args[0]);
7641
+ if (!c) return notAColour("grayscale");
7642
+ return chunk2TZKENDH_cjs.colourValue(chunkALYRJ72W_cjs.grayscale(c));
7643
+ }
7644
+ var COLOUR_INVERT_FN_IDX = chunk7ZX6B7SY_cjs.allocatePluginFunctionIndex();
7645
+ function invertHandler(args) {
7646
+ const c = colourOf(args[0]);
7647
+ if (!c) return notAColour("invert");
7648
+ return chunk2TZKENDH_cjs.colourValue(chunkALYRJ72W_cjs.invert(c, args.length > 1 ? readAmount(args[1]) : 1));
7649
+ }
7650
+ var COLOUR_MIX_FN_IDX = chunk7ZX6B7SY_cjs.allocatePluginFunctionIndex();
7651
+ function mixHandler(args) {
7652
+ const a = colourOf(args[0]);
7653
+ const b = colourOf(args[1]);
7654
+ if (!a || !b) return chunk2TZKENDH_cjs.errorValue(ColourErrorCodes.COLOUR_EXPECTED_COLOUR, "mix(...) expects two colours to blend");
7655
+ return chunk2TZKENDH_cjs.colourValue(chunkALYRJ72W_cjs.mix(a, b, args.length > 2 ? readAmount(args[2]) : 0.5));
7656
+ }
7657
+ var COLOUR_ALPHA_FN_IDX = chunk7ZX6B7SY_cjs.allocatePluginFunctionIndex();
7658
+ function alphaHandler(args) {
7659
+ const c = colourOf(args[0]);
7660
+ if (!c) return notAColour("alpha");
7661
+ if (args.length < 2) return chunk2TZKENDH_cjs.numberValue(c.a);
7662
+ return chunk2TZKENDH_cjs.colourValue(chunkALYRJ72W_cjs.withAlpha(c, readAmount(args[1])));
7663
+ }
7664
+ var COLOUR_CONTRAST_FN_IDX = chunk7ZX6B7SY_cjs.allocatePluginFunctionIndex();
7665
+ function contrastHandler(args) {
7666
+ const a = colourOf(args[0]);
7667
+ const b = colourOf(args[1]);
7668
+ if (!a || !b) return chunk2TZKENDH_cjs.errorValue(ColourErrorCodes.COLOUR_EXPECTED_COLOUR, "contrast(...) expects two colours");
7669
+ return chunk2TZKENDH_cjs.numberValue(chunkALYRJ72W_cjs.contrastRatio(a, b));
7670
+ }
7671
+ var COLOUR_LUMINANCE_FN_IDX = chunk7ZX6B7SY_cjs.allocatePluginFunctionIndex();
7672
+ function luminanceHandler(args) {
7673
+ const c = colourOf(args[0]);
7674
+ if (!c) return notAColour("luminance");
7675
+ return chunk2TZKENDH_cjs.numberValue(chunkALYRJ72W_cjs.relativeLuminance(c));
7676
+ }
7677
+ function extractor(name, get) {
7678
+ return (args) => {
7679
+ const c = colourOf(args[0]);
7680
+ if (!c) return notAColour(name);
7681
+ return chunk2TZKENDH_cjs.numberValue(get(c));
7682
+ };
7683
+ }
7684
+ var COLOUR_RED_FN_IDX = chunk7ZX6B7SY_cjs.allocatePluginFunctionIndex();
7685
+ var redHandler = extractor("red", (c) => c.r);
7686
+ var COLOUR_GREEN_FN_IDX = chunk7ZX6B7SY_cjs.allocatePluginFunctionIndex();
7687
+ var greenHandler = extractor("green", (c) => c.g);
7688
+ var COLOUR_BLUE_FN_IDX = chunk7ZX6B7SY_cjs.allocatePluginFunctionIndex();
7689
+ var blueHandler = extractor("blue", (c) => c.b);
7690
+ var COLOUR_HUE_FN_IDX = chunk7ZX6B7SY_cjs.allocatePluginFunctionIndex();
7691
+ var hueHandler = extractor("hue", (c) => chunkALYRJ72W_cjs.rgbToHsl(c.r, c.g, c.b).h);
7692
+ var COLOUR_SATURATION_FN_IDX = chunk7ZX6B7SY_cjs.allocatePluginFunctionIndex();
7693
+ var saturationHandler = extractor("saturation", (c) => chunkALYRJ72W_cjs.rgbToHsl(c.r, c.g, c.b).s * 100);
7694
+ var COLOUR_LIGHTNESS_FN_IDX = chunk7ZX6B7SY_cjs.allocatePluginFunctionIndex();
7695
+ var lightnessHandler = extractor("lightness", (c) => chunkALYRJ72W_cjs.rgbToHsl(c.r, c.g, c.b).l * 100);
7696
+ var COLOUR_HSV_FN_IDX = chunk7ZX6B7SY_cjs.allocatePluginFunctionIndex();
7697
+ function hsvHandler(args) {
7698
+ if (args.length !== 3) return badArgs("hsv", "three values: hsv(hue, saturation%, value%)");
7699
+ const { r, g, b } = chunkALYRJ72W_cjs.hsvToRgb(args[0].toNumber(), readAmount(args[1]), readAmount(args[2]));
7700
+ return chunk2TZKENDH_cjs.colourValue({ r, g, b, a: 1, format: "hex" });
7701
+ }
7702
+ var COLOUR_HSVA_FN_IDX = chunk7ZX6B7SY_cjs.allocatePluginFunctionIndex();
7703
+ function hsvaHandler(args) {
7704
+ if (args.length !== 4) return badArgs("hsva", "four values: hsva(hue, saturation%, value%, alpha)");
7705
+ const { r, g, b } = chunkALYRJ72W_cjs.hsvToRgb(args[0].toNumber(), readAmount(args[1]), readAmount(args[2]));
7706
+ return chunk2TZKENDH_cjs.colourValue({ r, g, b, a: chunkALYRJ72W_cjs.clamp01(readAmount(args[3])), format: "hex" });
7707
+ }
7708
+ var COLOUR_HWB_FN_IDX = chunk7ZX6B7SY_cjs.allocatePluginFunctionIndex();
7709
+ function hwbHandler(args) {
7710
+ if (args.length !== 3) return badArgs("hwb", "three values: hwb(hue, whiteness%, blackness%)");
7711
+ const { r, g, b } = chunkALYRJ72W_cjs.hwbToRgb(args[0].toNumber(), readAmount(args[1]), readAmount(args[2]));
7712
+ return chunk2TZKENDH_cjs.colourValue({ r, g, b, a: 1, format: "hex" });
7713
+ }
7714
+ var COLOUR_TINT_FN_IDX = chunk7ZX6B7SY_cjs.allocatePluginFunctionIndex();
7715
+ var tintHandler = adjuster("tint", chunkALYRJ72W_cjs.tint);
7716
+ var COLOUR_SHADE_FN_IDX = chunk7ZX6B7SY_cjs.allocatePluginFunctionIndex();
7717
+ var shadeHandler = adjuster("shade", chunkALYRJ72W_cjs.shade);
7718
+ var COLOUR_TONE_FN_IDX = chunk7ZX6B7SY_cjs.allocatePluginFunctionIndex();
7719
+ var toneHandler = adjuster("tone", chunkALYRJ72W_cjs.tone);
7720
+ var COLOUR_ISDARK_FN_IDX = chunk7ZX6B7SY_cjs.allocatePluginFunctionIndex();
7721
+ function isdarkHandler(args) {
7722
+ const c = colourOf(args[0]);
7723
+ if (!c) return notAColour("isdark");
7724
+ return chunk2TZKENDH_cjs.boolValue(chunkALYRJ72W_cjs.isDark(c));
7725
+ }
7726
+ var COLOUR_ISLIGHT_FN_IDX = chunk7ZX6B7SY_cjs.allocatePluginFunctionIndex();
7727
+ function islightHandler(args) {
7728
+ const c = colourOf(args[0]);
7729
+ if (!c) return notAColour("islight");
7730
+ return chunk2TZKENDH_cjs.boolValue(!chunkALYRJ72W_cjs.isDark(c));
7731
+ }
7732
+ var COLOUR_READABLE_FN_IDX = chunk7ZX6B7SY_cjs.allocatePluginFunctionIndex();
7733
+ function readableHandler(args) {
7734
+ const c = colourOf(args[0]);
7735
+ if (!c) return notAColour("readable");
7736
+ return chunk2TZKENDH_cjs.colourValue(chunkALYRJ72W_cjs.readableColour(c));
7737
+ }
7738
+ function readWcagThreshold(v) {
7739
+ if (!v) return chunkALYRJ72W_cjs.WCAG_THRESHOLDS.aa;
7740
+ if (v.type === 0 /* Number */) return v.toNumber();
7741
+ if (v.type === 3 /* String */) {
7742
+ const key = v.value.trim().toLowerCase();
7743
+ if (key === "aaa") return chunkALYRJ72W_cjs.WCAG_THRESHOLDS.aaa;
7744
+ if (key === "aa large" || key === "aa-large" || key === "large") return chunkALYRJ72W_cjs.WCAG_THRESHOLDS.aaLarge;
7745
+ if (key === "aaa large" || key === "aaa-large") return chunkALYRJ72W_cjs.WCAG_THRESHOLDS.aaaLarge;
7746
+ }
7747
+ return chunkALYRJ72W_cjs.WCAG_THRESHOLDS.aa;
7748
+ }
7749
+ var COLOUR_CONTRAST_COMPLIANT_FN_IDX = chunk7ZX6B7SY_cjs.allocatePluginFunctionIndex();
7750
+ function contrastCompliantHandler(args) {
7751
+ const a = colourOf(args[0]);
7752
+ const b = colourOf(args[1]);
7753
+ if (!a || !b) {
7754
+ return chunk2TZKENDH_cjs.errorValue(ColourErrorCodes.COLOUR_EXPECTED_COLOUR, "iscontrastcompliant(...) expects two colours");
7755
+ }
7756
+ return chunk2TZKENDH_cjs.boolValue(chunkALYRJ72W_cjs.contrastRatio(a, b) >= readWcagThreshold(args[2]));
7757
+ }
7758
+ var COLOUR_WCAG_LEVEL_FN_IDX = chunk7ZX6B7SY_cjs.allocatePluginFunctionIndex();
7759
+ function wcagLevelHandler(args) {
7760
+ const a = colourOf(args[0]);
7761
+ const b = colourOf(args[1]);
7762
+ if (!a || !b) return chunk2TZKENDH_cjs.errorValue(ColourErrorCodes.COLOUR_EXPECTED_COLOUR, "wcaglevel(...) expects two colours");
7763
+ return chunk2TZKENDH_cjs.stringValue(chunkALYRJ72W_cjs.wcagLevel(a, b));
7764
+ }
7765
+ var COLOUR_FUNCTION_INDEX = {
7766
+ color: COLOUR_PARSE_FN_IDX,
7767
+ colour: COLOUR_PARSE_FN_IDX,
7768
+ rgb: COLOUR_RGB_FN_IDX,
7769
+ rgba: COLOUR_RGBA_FN_IDX,
7770
+ hsl: COLOUR_HSL_FN_IDX,
7771
+ hsla: COLOUR_HSLA_FN_IDX,
7772
+ lighten: COLOUR_LIGHTEN_FN_IDX,
7773
+ darken: COLOUR_DARKEN_FN_IDX,
7774
+ saturate: COLOUR_SATURATE_FN_IDX,
7775
+ desaturate: COLOUR_DESATURATE_FN_IDX,
7776
+ desat: COLOUR_DESATURATE_FN_IDX,
7777
+ rotate: COLOUR_ROTATE_FN_IDX,
7778
+ spin: COLOUR_ROTATE_FN_IDX,
7779
+ adjusthue: COLOUR_ROTATE_FN_IDX,
7780
+ complement: COLOUR_COMPLEMENT_FN_IDX,
7781
+ grayscale: COLOUR_GRAYSCALE_FN_IDX,
7782
+ greyscale: COLOUR_GRAYSCALE_FN_IDX,
7783
+ invert: COLOUR_INVERT_FN_IDX,
7784
+ mix: COLOUR_MIX_FN_IDX,
7785
+ alpha: COLOUR_ALPHA_FN_IDX,
7786
+ opacity: COLOUR_ALPHA_FN_IDX,
7787
+ fade: COLOUR_ALPHA_FN_IDX,
7788
+ contrast: COLOUR_CONTRAST_FN_IDX,
7789
+ luminance: COLOUR_LUMINANCE_FN_IDX,
7790
+ red: COLOUR_RED_FN_IDX,
7791
+ green: COLOUR_GREEN_FN_IDX,
7792
+ blue: COLOUR_BLUE_FN_IDX,
7793
+ hue: COLOUR_HUE_FN_IDX,
7794
+ saturation: COLOUR_SATURATION_FN_IDX,
7795
+ lightness: COLOUR_LIGHTNESS_FN_IDX,
7796
+ hsv: COLOUR_HSV_FN_IDX,
7797
+ hsb: COLOUR_HSV_FN_IDX,
7798
+ hsva: COLOUR_HSVA_FN_IDX,
7799
+ hwb: COLOUR_HWB_FN_IDX,
7800
+ tint: COLOUR_TINT_FN_IDX,
7801
+ shade: COLOUR_SHADE_FN_IDX,
7802
+ tone: COLOUR_TONE_FN_IDX,
7803
+ negate: COLOUR_INVERT_FN_IDX,
7804
+ isdark: COLOUR_ISDARK_FN_IDX,
7805
+ islight: COLOUR_ISLIGHT_FN_IDX,
7806
+ readable: COLOUR_READABLE_FN_IDX,
7807
+ contrastcolor: COLOUR_READABLE_FN_IDX,
7808
+ contrastcolour: COLOUR_READABLE_FN_IDX,
7809
+ iscontrastcompliant: COLOUR_CONTRAST_COMPLIANT_FN_IDX,
7810
+ wcaglevel: COLOUR_WCAG_LEVEL_FN_IDX,
7811
+ wcag: COLOUR_WCAG_LEVEL_FN_IDX
7812
+ };
7813
+ var COLOUR_PLUGIN_FUNCTIONS = [
7814
+ { index: COLOUR_PARSE_FN_IDX, handler: colourParseHandler },
7815
+ { index: COLOUR_RGB_FN_IDX, handler: rgbHandler },
7816
+ { index: COLOUR_RGBA_FN_IDX, handler: rgbaHandler },
7817
+ { index: COLOUR_HSL_FN_IDX, handler: hslHandler },
7818
+ { index: COLOUR_HSLA_FN_IDX, handler: hslaHandler },
7819
+ { index: COLOUR_LIGHTEN_FN_IDX, handler: lightenHandler },
7820
+ { index: COLOUR_DARKEN_FN_IDX, handler: darkenHandler },
7821
+ { index: COLOUR_SATURATE_FN_IDX, handler: saturateHandler },
7822
+ { index: COLOUR_DESATURATE_FN_IDX, handler: desaturateHandler },
7823
+ { index: COLOUR_ROTATE_FN_IDX, handler: rotateHandler },
7824
+ { index: COLOUR_COMPLEMENT_FN_IDX, handler: complementHandler },
7825
+ { index: COLOUR_GRAYSCALE_FN_IDX, handler: grayscaleHandler },
7826
+ { index: COLOUR_INVERT_FN_IDX, handler: invertHandler },
7827
+ { index: COLOUR_MIX_FN_IDX, handler: mixHandler },
7828
+ { index: COLOUR_ALPHA_FN_IDX, handler: alphaHandler },
7829
+ { index: COLOUR_CONTRAST_FN_IDX, handler: contrastHandler },
7830
+ { index: COLOUR_LUMINANCE_FN_IDX, handler: luminanceHandler },
7831
+ { index: COLOUR_RED_FN_IDX, handler: redHandler },
7832
+ { index: COLOUR_GREEN_FN_IDX, handler: greenHandler },
7833
+ { index: COLOUR_BLUE_FN_IDX, handler: blueHandler },
7834
+ { index: COLOUR_HUE_FN_IDX, handler: hueHandler },
7835
+ { index: COLOUR_SATURATION_FN_IDX, handler: saturationHandler },
7836
+ { index: COLOUR_LIGHTNESS_FN_IDX, handler: lightnessHandler },
7837
+ { index: COLOUR_HSV_FN_IDX, handler: hsvHandler },
7838
+ { index: COLOUR_HSVA_FN_IDX, handler: hsvaHandler },
7839
+ { index: COLOUR_HWB_FN_IDX, handler: hwbHandler },
7840
+ { index: COLOUR_TINT_FN_IDX, handler: tintHandler },
7841
+ { index: COLOUR_SHADE_FN_IDX, handler: shadeHandler },
7842
+ { index: COLOUR_TONE_FN_IDX, handler: toneHandler },
7843
+ { index: COLOUR_ISDARK_FN_IDX, handler: isdarkHandler },
7844
+ { index: COLOUR_ISLIGHT_FN_IDX, handler: islightHandler },
7845
+ { index: COLOUR_READABLE_FN_IDX, handler: readableHandler },
7846
+ { index: COLOUR_CONTRAST_COMPLIANT_FN_IDX, handler: contrastCompliantHandler },
7847
+ { index: COLOUR_WCAG_LEVEL_FN_IDX, handler: wcagLevelHandler }
7848
+ ];
7849
+
7850
+ // src/packages/colour/parselets/HexColourParselet.ts
7851
+ var HexColourParselet = class {
7852
+ constructor() {
7853
+ this.category = "Colour";
7854
+ }
7855
+ parse(_parser, token, builder) {
7856
+ builder.emitOpcode(13 /* PUSH_STRING */);
7857
+ builder.emitString(token.value);
7858
+ builder.emitOpcode(50 /* CALL_PLUGIN */);
7859
+ builder.emitIndex(COLOUR_PARSE_FN_IDX);
7860
+ builder.emitIndex(1);
7861
+ }
7862
+ };
7863
+
7864
+ // src/packages/colour/parselets/ColourCallParselet.ts
7865
+ var ColourCallParselet = class {
7866
+ constructor() {
7867
+ this.category = "Colour";
7868
+ }
7869
+ parse(parser, token, builder) {
7870
+ const name = token.value.toLowerCase();
7871
+ const fnIdx = COLOUR_FUNCTION_INDEX[name];
7872
+ if (fnIdx === void 0) {
7873
+ throw chunkB4HBEFTB_cjs.ErrorFactory.execution("UNKNOWN_COLOUR_FUNCTION", `Unknown colour function: ${name}`, {
7874
+ functionName: name
7875
+ });
7876
+ }
7877
+ parser.consume("LPAREN");
7878
+ let argCount = 0;
7879
+ if (parser.peek()?.type !== "RPAREN") {
7880
+ parser.parseExpression(chunkJ45BCEZ4_cjs.BindingPower.Lowest, builder);
7881
+ argCount++;
7882
+ while (parser.match("COMMA")) {
7883
+ parser.parseExpression(chunkJ45BCEZ4_cjs.BindingPower.Lowest, builder);
7884
+ argCount++;
7885
+ }
7886
+ }
7887
+ parser.consume("RPAREN");
7888
+ builder.emitOpcode(50 /* CALL_PLUGIN */);
7889
+ builder.emitIndex(fnIdx);
7890
+ builder.emitIndex(argCount);
7891
+ }
7892
+ };
7893
+
7894
+ // src/packages/colour/normalizer/ColourCallNormalizerRule.ts
7895
+ function colourCallNormalizerRule(priority = 80) {
7896
+ return {
7897
+ name: "colour:call",
7898
+ priority,
7899
+ match(tokens, pos) {
7900
+ const token = tokens[pos];
7901
+ if (!token || token.type !== "IDENT") return null;
7902
+ const word = token.value.toLowerCase();
7903
+ if (COLOUR_FUNCTION_INDEX[word] === void 0) return null;
7904
+ if (tokens[pos + 1]?.type !== "LPAREN") return null;
7905
+ return {
7906
+ consumed: 1,
7907
+ replacement: [
7908
+ new chunkYTIYVHL7_cjs.LexerToken(
7909
+ "COLOUR_CALL",
7910
+ chunkQFTDTX6K_cjs.tokenTypeId("COLOUR_CALL"),
7911
+ token.value,
7912
+ token.value,
7913
+ token.offset,
7914
+ 0,
7915
+ token.line,
7916
+ token.col
7917
+ )
7918
+ ],
7919
+ ruleName: "colour:call"
7920
+ };
7921
+ }
7922
+ };
7923
+ }
7924
+
7925
+ // src/packages/colour/ColourPackage.ts
7926
+ var retag = (format) => (v) => {
7927
+ if (v.type !== 14 /* Colour */) return v;
7928
+ const c = v.value;
7929
+ return chunk2TZKENDH_cjs.colourValue({ r: c.r, g: c.g, b: c.b, a: c.a, format });
7930
+ };
7931
+ var completionItems = Object.keys(COLOUR_FUNCTION_INDEX).map((label) => ({
7932
+ label,
7933
+ category: "function",
7934
+ detail: "colour"
7935
+ }));
7936
+ var COLOUR_PACKAGE = {
7937
+ name: "solve-colour",
7938
+ prefixParselets: [
7939
+ { tokenType: "HEX_COLOUR", parselet: new HexColourParselet() },
7940
+ { tokenType: "COLOUR_CALL", parselet: new ColourCallParselet() }
7941
+ ],
7942
+ normalizerRules: [colourCallNormalizerRule()],
7943
+ pluginFunctions: COLOUR_PLUGIN_FUNCTIONS.map((f) => ({ index: f.index, handler: f.handler })),
7944
+ asConverters: {
7945
+ rgb: retag("rgb"),
7946
+ rgba: retag("rgba"),
7947
+ hsl: retag("hsl"),
7948
+ hsla: retag("hsla")
7949
+ },
7950
+ // HEX_COLOUR/COLOUR_CALL categories live in the core TokenCategoryMap (both
7951
+ // are declared TokenTypes), so no per-package tokenCategories are needed.
7952
+ completionItems
7953
+ };
7954
+
6488
7955
  // src/packages/builtins.ts
6489
7956
  var BUILTIN_PACKAGES = [
6490
7957
  ARITHMETIC_PACKAGE,
@@ -6505,8 +7972,14 @@ var BUILTIN_PACKAGES = [
6505
7972
  CONVERTERS_PACKAGE,
6506
7973
  MATHPHRASES_PACKAGE,
6507
7974
  FINANCE_PACKAGE,
7975
+ UNCERTAINTY_PACKAGE,
6508
7976
  WEATHER_PACKAGE,
6509
- LINES_PACKAGE
7977
+ LINES_PACKAGE,
7978
+ // After LINES_PACKAGE: goal seek's normalizer reads a LINE_REF the lines
7979
+ // rule mints, and its parselet targets a line reference.
7980
+ GOALSEEK_PACKAGE,
7981
+ TABLES_PACKAGE,
7982
+ COLOUR_PACKAGE
6510
7983
  ];
6511
7984
 
6512
7985
  exports.ARITHMETIC_PACKAGE = ARITHMETIC_PACKAGE;
@@ -6525,13 +7998,15 @@ exports.MATHPHRASES_PACKAGE = MATHPHRASES_PACKAGE;
6525
7998
  exports.MATRIX_PACKAGE = MATRIX_PACKAGE;
6526
7999
  exports.PERCENTAGE_PACKAGE = PERCENTAGE_PACKAGE;
6527
8000
  exports.SYMBOLIC_PACKAGE = SYMBOLIC_PACKAGE;
8001
+ exports.TABLES_PACKAGE = TABLES_PACKAGE;
6528
8002
  exports.TIME_PACKAGE = TIME_PACKAGE;
6529
8003
  exports.UOM_PACKAGE = UOM_PACKAGE;
6530
8004
  exports.VARIABLES_PACKAGE = VARIABLES_PACKAGE;
6531
8005
  exports.VECTOR_PACKAGE = VECTOR_PACKAGE;
6532
8006
  exports.WEATHER_PACKAGE = WEATHER_PACKAGE;
6533
8007
  exports.containsSymbolicCall = containsSymbolicCall;
8008
+ exports.createCurrencyPackage = createCurrencyPackage;
6534
8009
  exports.createKnowledgePackage = createKnowledgePackage;
6535
8010
  exports.createStocksPackage = createStocksPackage;
6536
- //# sourceMappingURL=chunk-3AFRJYP4.cjs.map
6537
- //# sourceMappingURL=chunk-3AFRJYP4.cjs.map
8011
+ //# sourceMappingURL=chunk-OTN6SZOD.cjs.map
8012
+ //# sourceMappingURL=chunk-OTN6SZOD.cjs.map