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,28 +1,32 @@
1
- import { BindingPower, BytecodeBuilder, bigIntLiteralDigits } from './chunk-526PMQOA.js';
2
- import { createFusedToken, isInsideRangeContext } from './chunk-YPPPYLSR.js';
1
+ import { BindingPower, BytecodeBuilder, bigIntLiteralDigits } from './chunk-ENKKJYD3.js';
2
+ import { createFusedToken, isInsideRangeContext } from './chunk-JJIXHXFQ.js';
3
3
  import { stripQuotes } from './chunk-3BOK2LDK.js';
4
- import { CurrencyAsyncResolver, nextInstruction } from './chunk-KV7UW6T6.js';
4
+ import { CurrencyAsyncResolver, nextInstruction } from './chunk-7KYSUDQO.js';
5
5
  import { getActiveQueryClient } from './chunk-NLNDMWZC.js';
6
- import { allocatePluginFunctionIndex, sharedGlobalVariableStore, adjustForInflation, CPI_MIN_YEAR, CPI_MAX_YEAR } from './chunk-Y7FT4IQT.js';
6
+ import { allocatePluginFunctionIndex, sharedGlobalVariableStore, adjustForInflation, solveForVariable, CPI_MIN_YEAR, CPI_MAX_YEAR } from './chunk-NKW7LKYU.js';
7
+ import { sharedCurrencyExchange } from './chunk-Z7XGGLI2.js';
7
8
  import { createTimeoutSignal } from './chunk-Z4X2RWOF.js';
8
- import { resolveCurrencyAlias } from './chunk-O3ANBHSA.js';
9
- import { errorValue, numberValue, stringValue, boolValue, uomValue, datetimeValue, timecodeUnit, ValueType } from './chunk-GQCOSXMG.js';
10
- import { getMeasure, convertUnit } from './chunk-R3PY4G7J.js';
11
- import { LexerToken } from './chunk-QY25VWBF.js';
12
- import { tokenTypeId } from './chunk-VB37OC6I.js';
9
+ import { parseColour, clamp255, clamp01, hslToRgb, rotateHue, complement, grayscale, invert, mix, withAlpha, contrastRatio, relativeLuminance, hsvToRgb, hwbToRgb, isDark, readableColour, wcagLevel, WCAG_THRESHOLDS, lighten, darken, saturate, desaturate, rgbToHsl, tint, shade, tone } from './chunk-5HRB36DK.js';
10
+ import { resolveCurrencyAlias, varNode, constNode, rationalFromNumber, simplifySymbolic, rationalToNumber } from './chunk-NNQ2TYDF.js';
11
+ import { errorValue, colourValue, numberValue, boolValue, stringValue, faultedOperand, uomValue, datetimeValue, timecodeUnit, symbolicValue, ValueType } from './chunk-267JPOTF.js';
12
+ import { getMeasure, convertUnit } from './chunk-MVTOCRV2.js';
13
+ import { LexerToken } from './chunk-GQMUHVE3.js';
14
+ import { tokenTypeId } from './chunk-4D6NIHE2.js';
13
15
  import { getLocale } from './chunk-XBTEO4OB.js';
14
16
  import { isKnownUnit } from './chunk-SBUFNJVE.js';
15
17
  import { UNIT_TABLE } from './chunk-V4XG5QF6.js';
16
- import { ErrorFactory } from './chunk-3D7V24DG.js';
18
+ import { ErrorFactory } from './chunk-ERCOHGXD.js';
17
19
 
18
20
  // src/packages/arithmetic/parselets/NumberParselet.ts
19
21
  var CHAINED_DOT_THOUSANDS_GROUPS = /^\d{1,3}(\.\d{3}){2,}$/;
22
+ var PLAIN_DECIMAL = /^\d*\.\d*$/;
20
23
  var NumberParselet = class {
21
24
  constructor() {
22
25
  this.category = "Arithmetic";
23
26
  }
24
27
  parse(parser, token, builder) {
25
28
  let v;
29
+ let decimalText = null;
26
30
  const raw = token.value;
27
31
  if (raw.startsWith("0x") || raw.startsWith("0X")) {
28
32
  v = parseInt(raw, 16);
@@ -53,6 +57,12 @@ var NumberParselet = class {
53
57
  normalized = normalized.replace(decimalSep, ".");
54
58
  }
55
59
  v = parseFloat(normalized);
60
+ if (PLAIN_DECIMAL.test(normalized)) decimalText = normalized;
61
+ }
62
+ if (decimalText !== null) {
63
+ builder.emitOpcode(16 /* PUSH_DECIMAL */);
64
+ builder.emitString(decimalText);
65
+ return;
56
66
  }
57
67
  builder.emitOpcode(10 /* PUSH_NUMBER */);
58
68
  builder.emitNumber(v);
@@ -139,7 +149,7 @@ var WORD_MAGNITUDE = {
139
149
  trillions: 12,
140
150
  tn: 12
141
151
  };
142
- var PLAIN_DECIMAL = /^\d+(\.\d+)?$/;
152
+ var PLAIN_DECIMAL2 = /^\d+(\.\d+)?$/;
143
153
  function scaleDecimalString(raw, magnitude) {
144
154
  const dotIdx = raw.indexOf(".");
145
155
  const intPart = dotIdx === -1 ? raw : raw.slice(0, dotIdx);
@@ -158,7 +168,7 @@ function largeNumberSuffixNormalizerRule(priority = 65) {
158
168
  match(tokens, pos) {
159
169
  const numberToken = tokens[pos];
160
170
  if (numberToken.type !== "NUMBER") return null;
161
- if (!PLAIN_DECIMAL.test(numberToken.text)) return null;
171
+ if (!PLAIN_DECIMAL2.test(numberToken.text)) return null;
162
172
  const suffixToken = tokens[pos + 1];
163
173
  if (!suffixToken) return null;
164
174
  if (suffixToken.type !== "IDENT" && suffixToken.type !== "UNIT") return null;
@@ -509,6 +519,119 @@ var PercentageChangeParselet = class {
509
519
  }
510
520
  };
511
521
 
522
+ // src/packages/percentage/parselets/UpDownParselet.ts
523
+ var COUNT_WORDS = {
524
+ one: 1,
525
+ two: 2,
526
+ three: 3,
527
+ four: 4,
528
+ five: 5,
529
+ six: 6,
530
+ seven: 7,
531
+ eight: 8,
532
+ nine: 9,
533
+ ten: 10,
534
+ eleven: 11,
535
+ twelve: 12
536
+ };
537
+ function resolveCount(token) {
538
+ if (token.type === "NUMBER") {
539
+ const n = Number(token.value);
540
+ return Number.isInteger(n) && n >= 0 ? n : null;
541
+ }
542
+ if (token.type === "IDENT") {
543
+ return COUNT_WORDS[token.value.toLowerCase()] ?? null;
544
+ }
545
+ return null;
546
+ }
547
+ var UpDownParselet = class {
548
+ /** @param sign `1` for `up` (grow), `-1` for `down` (shrink). */
549
+ constructor(sign) {
550
+ this.sign = sign;
551
+ this.category = "Percentage";
552
+ this.bindingPower = BindingPower.Conditional;
553
+ }
554
+ parse(parser, _left, _token, builder) {
555
+ parser.parseExpression(this.bindingPower, builder);
556
+ builder.emitOpcode(10 /* PUSH_NUMBER */);
557
+ builder.emitNumber(1);
558
+ builder.emitOpcode(2 /* SWAP */);
559
+ builder.emitOpcode(this.sign > 0 ? 20 /* ADD */ : 21 /* SUB */);
560
+ const repeat = this.tryConsumeRepeat(parser);
561
+ if (repeat !== null) {
562
+ builder.emitOpcode(10 /* PUSH_NUMBER */);
563
+ builder.emitNumber(repeat);
564
+ builder.emitOpcode(25 /* EXP */);
565
+ }
566
+ builder.emitOpcode(22 /* MUL */);
567
+ this.consumeChainThen(parser);
568
+ }
569
+ /**
570
+ * Consume a trailing `N times` (`3 times`, `three times`) if present,
571
+ * returning the count, or `null` when there is no repeat here. The word
572
+ * "times" lexes as the multiply token but keeps its source text, which is
573
+ * what tells it apart from a literal `*`.
574
+ */
575
+ tryConsumeRepeat(parser) {
576
+ const countTok = parser.peek();
577
+ const timesTok = parser.peekAt(1);
578
+ if (!countTok || !timesTok) return null;
579
+ if (timesTok.type !== "STAR" || timesTok.value.toLowerCase() !== "times") return null;
580
+ const count = resolveCount(countTok);
581
+ if (count === null) return null;
582
+ parser.consume();
583
+ parser.consume();
584
+ return count;
585
+ }
586
+ /** Drop a connective `then` that sits directly before the next step. */
587
+ consumeChainThen(parser) {
588
+ if (parser.peek()?.type !== "THEN") return;
589
+ const next = parser.peekAt(1)?.type;
590
+ if (next === "PCT_UP" || next === "PCT_DOWN") {
591
+ parser.consume();
592
+ }
593
+ }
594
+ };
595
+
596
+ // src/packages/percentage/normalizer/PercentUpDownNormalizerRule.ts
597
+ function stepTokenAt(tokens, i) {
598
+ const word = tokens[i];
599
+ if (word?.type !== "IDENT") return null;
600
+ const text = (word.text ?? word.value ?? "").toLowerCase();
601
+ if (text !== "up" && text !== "down") return null;
602
+ if (tokens[i + 1]?.type !== "NUMBER") return null;
603
+ if (tokens[i + 2]?.type !== "PERCENT") return null;
604
+ return createFusedToken(text === "up" ? "PCT_UP" : "PCT_DOWN", text, [word]);
605
+ }
606
+ function percentUpDownNormalizerRule(priority = 68) {
607
+ return {
608
+ name: "percentage:up-down",
609
+ priority,
610
+ match(tokens, pos) {
611
+ const here = tokens[pos];
612
+ if (here && (here.type === "NUMBER" || here.type === "RPAREN")) {
613
+ const step2 = stepTokenAt(tokens, pos + 1);
614
+ if (step2) {
615
+ return {
616
+ consumed: 2,
617
+ replacement: [here, step2],
618
+ ruleName: "percentage:up-down"
619
+ };
620
+ }
621
+ }
622
+ const step = stepTokenAt(tokens, pos);
623
+ if (step) {
624
+ return {
625
+ consumed: 1,
626
+ replacement: [step],
627
+ ruleName: "percentage:up-down"
628
+ };
629
+ }
630
+ return null;
631
+ }
632
+ };
633
+ }
634
+
512
635
  // src/packages/percentage/PercentagePackage.ts
513
636
  var PERCENTAGE_PACKAGE = {
514
637
  name: "solve-percentage",
@@ -536,14 +659,18 @@ var PERCENTAGE_PACKAGE = {
536
659
  { tokenType: "OFF_WHAT_IS", parselet: new OnOffWhatIsParselet(-1) },
537
660
  { tokenType: "TO", parselet: new PercentageChangeParselet() },
538
661
  { tokenType: "INCREASE_BY", parselet: new IncreaseByParselet(1) },
539
- { tokenType: "DECREASE_BY", parselet: new IncreaseByParselet(-1) }
662
+ { tokenType: "DECREASE_BY", parselet: new IncreaseByParselet(-1) },
663
+ // Successive change: "120 up 10% then down 10%" is 118.80, not 120.
664
+ { tokenType: "PCT_UP", parselet: new UpDownParselet(1) },
665
+ { tokenType: "PCT_DOWN", parselet: new UpDownParselet(-1) }
540
666
  ],
541
667
  prefixParselets: [
542
668
  { tokenType: "INCREASE", parselet: new IncreaseDecreaseParselet(1) },
543
669
  { tokenType: "DECREASE", parselet: new IncreaseDecreaseParselet(-1) }
544
670
  ],
545
671
  normalizerRules: [
546
- percentOnOffNormalizerRule()
672
+ percentOnOffNormalizerRule(),
673
+ percentUpDownNormalizerRule()
547
674
  ]
548
675
  };
549
676
 
@@ -945,6 +1072,34 @@ var WorkdaysInParselet = class {
945
1072
  }
946
1073
  };
947
1074
 
1075
+ // src/packages/datetime/parselets/WorkdayOffsetParselet.ts
1076
+ var WorkdayOffsetParselet = class {
1077
+ /** @param direction "forward" for after/from, "backward" for before. */
1078
+ constructor(direction) {
1079
+ this.direction = direction;
1080
+ this.category = "Date/Time";
1081
+ this.bindingPower = BindingPower.Conditional;
1082
+ }
1083
+ parse(parser, _left, _token, builder) {
1084
+ parser.parseExpression(BindingPower.Sum, builder);
1085
+ builder.emitOpcode(96 /* DATE_WORKDAY_OFFSET */);
1086
+ builder.emitByte(this.direction === "backward" ? 1 : 0);
1087
+ }
1088
+ };
1089
+
1090
+ // src/packages/datetime/parselets/WorkdaysBetweenParselet.ts
1091
+ var WorkdaysBetweenParselet = class {
1092
+ constructor() {
1093
+ this.category = "Date/Time";
1094
+ }
1095
+ parse(parser, _token, builder) {
1096
+ parser.parseExpression(BindingPower.Product, builder);
1097
+ parser.consume("AND_CONJ");
1098
+ parser.parseExpression(BindingPower.Lowest, builder);
1099
+ builder.emitOpcode(97 /* DATE_WORKDAYS_BETWEEN */);
1100
+ }
1101
+ };
1102
+
948
1103
  // src/packages/datetime/parselets/DateFieldQueryParselet.ts
949
1104
  var DateFieldQueryParselet = class {
950
1105
  constructor(functionIndex, mode) {
@@ -1499,6 +1654,26 @@ var DATETIME_PACKAGE = {
1499
1654
  name: "solve-datetime",
1500
1655
  phrases: {
1501
1656
  "workdays in": "WORKDAYS_IN",
1657
+ // Business-day arithmetic in words. "after"/"from" count forward, "before"
1658
+ // back; the count is the left operand, so these fuse as INFIX tokens (see
1659
+ // WorkdayOffsetParselet). Both "working" and "business" days, singular and
1660
+ // plural, so "1 working day after" and "5 business days from" both read.
1661
+ "working days after": "WORKDAYS_AFTER",
1662
+ "business days after": "WORKDAYS_AFTER",
1663
+ "working day after": "WORKDAYS_AFTER",
1664
+ "business day after": "WORKDAYS_AFTER",
1665
+ "working days from": "WORKDAYS_AFTER",
1666
+ "business days from": "WORKDAYS_AFTER",
1667
+ "working day from": "WORKDAYS_AFTER",
1668
+ "business day from": "WORKDAYS_AFTER",
1669
+ "working days before": "WORKDAYS_BEFORE",
1670
+ "business days before": "WORKDAYS_BEFORE",
1671
+ "working day before": "WORKDAYS_BEFORE",
1672
+ "business day before": "WORKDAYS_BEFORE",
1673
+ // The count form (prefix). Fusing the whole "working days between" keeps
1674
+ // the bare `days between` rule from claiming the "days" first.
1675
+ "working days between": "WORKDAYS_BETWEEN",
1676
+ "business days between": "WORKDAYS_BETWEEN",
1502
1677
  "day of the week on": "WEEKDAY_ON",
1503
1678
  "weekday on": "WEEKDAY_ON",
1504
1679
  "current timestamp": "CURRENT_TIMESTAMP",
@@ -1546,6 +1721,7 @@ var DATETIME_PACKAGE = {
1546
1721
  { tokenType: "UNTIL_UNIT", parselet: new UntilSinceParselet("until") },
1547
1722
  { tokenType: "SINCE_UNIT", parselet: new UntilSinceParselet("since") },
1548
1723
  { tokenType: "WORKDAYS_IN", parselet: new WorkdaysInParselet() },
1724
+ { tokenType: "WORKDAYS_BETWEEN", parselet: new WorkdaysBetweenParselet() },
1549
1725
  { tokenType: "WEEKDAY_ON", parselet: new DateFieldQueryParselet(WEEKDAY_ON_FN_IDX, "on") },
1550
1726
  { tokenType: "WEEKDAY_IN", parselet: new DateFieldQueryParselet(WEEKDAY_ON_FN_IDX, "in") },
1551
1727
  { tokenType: "MONTH_ON", parselet: new DateFieldQueryParselet(MONTH_ON_FN_IDX, "on") },
@@ -1557,6 +1733,8 @@ var DATETIME_PACKAGE = {
1557
1733
  { tokenType: "DATETIME_LITERAL", parselet: new DateLiteralParselet() }
1558
1734
  ],
1559
1735
  infixParselets: [
1736
+ { tokenType: "WORKDAYS_AFTER", parselet: new WorkdayOffsetParselet("forward") },
1737
+ { tokenType: "WORKDAYS_BEFORE", parselet: new WorkdayOffsetParselet("backward") },
1560
1738
  { tokenType: "TO_DATE", parselet: new ToDateParselet() },
1561
1739
  { tokenType: "TO_TIMESTAMP", parselet: new ToTimestampParselet() },
1562
1740
  { tokenType: "IS_WEEKEND", parselet: new DayTypePredicateParselet(IS_WEEKEND_FN_IDX) },
@@ -2767,6 +2945,159 @@ var VARIABLES_PACKAGE = {
2767
2945
  ]
2768
2946
  };
2769
2947
 
2948
+ // src/uom/HistoricalCurrency.ts
2949
+ var HISTORICAL_CURRENCY_NS = "currency-historical";
2950
+ var HISTORICAL_CURRENCY_FN_IDX = allocatePluginFunctionIndex();
2951
+ var HistoricalCurrencyErrorCodes = {
2952
+ /** No {@link HistoricalRateProvider} was supplied, so `on <date>` conversions cannot be answered. NOT a fall back to today's rate. */
2953
+ NOT_CONFIGURED: "HISTORICAL_RATES_NOT_CONFIGURED",
2954
+ /** The host provider threw or timed out for one pair/date. Transient, evicted after a cooldown so a retry can happen. */
2955
+ QUERY_FAILED: "HISTORICAL_RATE_QUERY_FAILED",
2956
+ /** The VM reached the conversion before preflight cached its rate, a "shouldn't happen" invariant break, not a user error. */
2957
+ NOT_PREFLIGHTED: "HISTORICAL_RATE_NOT_PREFLIGHTED",
2958
+ /** The amount being converted was not a currency Value (bad bytecode, or a non-currency left operand). */
2959
+ INVALID_OPERAND: "HISTORICAL_CURRENCY_INVALID_OPERAND"
2960
+ };
2961
+ var FAILURE_COOLDOWN_MS = 3e4;
2962
+ var HISTORICAL_RATE_STALE_TIME_MS = Infinity;
2963
+ var FETCH_TIMEOUT_MS = 1e4;
2964
+ function historicalRateQueryKey(from, to, isoDate) {
2965
+ return [HISTORICAL_CURRENCY_NS, `${from.toUpperCase()}:${to.toUpperCase()}:${isoDate}`];
2966
+ }
2967
+ function pad2(n) {
2968
+ return n < 10 ? `0${n}` : String(n);
2969
+ }
2970
+ function tryConsumeCurrencyOnDate(parser, to, from) {
2971
+ if (!sharedCurrencyExchange.isCurrency(to)) return null;
2972
+ if (from !== void 0 && !sharedCurrencyExchange.isCurrency(from)) return null;
2973
+ const onToken = parser.peek();
2974
+ if (!onToken || onToken.type !== "IDENT" || onToken.value.toLowerCase() !== "on") return null;
2975
+ const dateToken = parser.peekAt(1);
2976
+ if (!dateToken || dateToken.type !== "DATETIME_LITERAL") return null;
2977
+ parser.consume();
2978
+ parser.consume();
2979
+ const date = new Date(Number(dateToken.value));
2980
+ return `${date.getFullYear()}-${pad2(date.getMonth() + 1)}-${pad2(date.getDate())}`;
2981
+ }
2982
+ async function fetchHistoricalRate(provider, from, to, isoDate, signal, queryClient) {
2983
+ const fromUpper = from.toUpperCase();
2984
+ const toUpper = to.toUpperCase();
2985
+ if (!provider) {
2986
+ return errorValue(
2987
+ HistoricalCurrencyErrorCodes.NOT_CONFIGURED,
2988
+ `Historical exchange rates are not configured. Supply historicalRateProvider via createCurrencyPackage({ ... }) to convert ${fromUpper} to ${toUpper} on ${isoDate}.`
2989
+ );
2990
+ }
2991
+ const { signal: fetchSignal, cleanup } = createTimeoutSignal(signal, FETCH_TIMEOUT_MS, "Historical currency rate fetch");
2992
+ try {
2993
+ const rate = await provider(fromUpper, toUpper, isoDate, fetchSignal);
2994
+ return numberValue(rate);
2995
+ } catch (error) {
2996
+ const failedValue = errorValue(
2997
+ HistoricalCurrencyErrorCodes.QUERY_FAILED,
2998
+ `Failed to resolve the historical rate for ${fromUpper} to ${toUpper} on ${isoDate}: ${error instanceof Error ? error.message : String(error)}`
2999
+ );
3000
+ const key = historicalRateQueryKey(from, to, isoDate);
3001
+ const cooldownTimer = setTimeout(() => {
3002
+ if (queryClient.getQueryData(key) === failedValue) {
3003
+ queryClient.removeQueries({ queryKey: key, exact: true });
3004
+ }
3005
+ }, FAILURE_COOLDOWN_MS);
3006
+ cooldownTimer.unref?.();
3007
+ return failedValue;
3008
+ } finally {
3009
+ cleanup();
3010
+ }
3011
+ }
3012
+ function createHistoricalCurrencyPluginFunction(provider) {
3013
+ return (args) => {
3014
+ const amount = args[0];
3015
+ const toArg = args[1];
3016
+ const dateArg = args[2];
3017
+ const fault = faultedOperand(amount);
3018
+ if (fault) return fault;
3019
+ if (amount.type !== 6 /* Uom */ || amount.unit === void 0 || !sharedCurrencyExchange.isCurrency(amount.unit)) {
3020
+ return errorValue(
3021
+ HistoricalCurrencyErrorCodes.INVALID_OPERAND,
3022
+ `Historical conversion needs an amount in a currency, for example "100 USD in GBP on 2024-01-15".`
3023
+ );
3024
+ }
3025
+ const from = amount.unit;
3026
+ const to = String(toArg.value);
3027
+ const isoDate = String(dateArg.value);
3028
+ if (from.toUpperCase() === to.toUpperCase()) {
3029
+ return uomValue(amount.toNumber(), to);
3030
+ }
3031
+ const queryClient = getActiveQueryClient();
3032
+ const key = historicalRateQueryKey(from, to, isoDate);
3033
+ const cached = queryClient?.getQueryData(key);
3034
+ if (cached === void 0) {
3035
+ if (!queryClient) {
3036
+ return errorValue(
3037
+ HistoricalCurrencyErrorCodes.NOT_PREFLIGHTED,
3038
+ `No historical rate resolved for ${from.toUpperCase()} to ${to.toUpperCase()} on ${isoDate}.`
3039
+ );
3040
+ }
3041
+ return queryClient.fetchQuery({
3042
+ queryKey: key,
3043
+ queryFn: ({ signal }) => fetchHistoricalRate(provider, from, to, isoDate, signal, queryClient),
3044
+ staleTime: HISTORICAL_RATE_STALE_TIME_MS
3045
+ });
3046
+ }
3047
+ if (cached.type === 13 /* Error */) return cached;
3048
+ const rate = cached.value;
3049
+ return uomValue(amount.toNumber() * rate, to);
3050
+ };
3051
+ }
3052
+ function createHistoricalCurrencyResolver(provider) {
3053
+ return {
3054
+ namespace: HISTORICAL_CURRENCY_NS,
3055
+ preflight(_tokens, bytecode, packageId, signal, queryClient) {
3056
+ const { opcodes, strings } = bytecode;
3057
+ const len = opcodes.length;
3058
+ let i = 0;
3059
+ let stringIdxs = [];
3060
+ while (i < len) {
3061
+ const op = opcodes[i];
3062
+ if (op === 13 /* PUSH_STRING */) {
3063
+ stringIdxs.push(opcodes[i + 1]);
3064
+ } else if (op === 50 /* CALL_PLUGIN */) {
3065
+ const fnIdx = opcodes[i + 1];
3066
+ const argCount = opcodes[i + 2];
3067
+ if (fnIdx === HISTORICAL_CURRENCY_FN_IDX && argCount === 3 && stringIdxs.length >= 3) {
3068
+ const isoDate = strings[stringIdxs[stringIdxs.length - 1]];
3069
+ const to = strings[stringIdxs[stringIdxs.length - 2]];
3070
+ const sources = /* @__PURE__ */ new Set();
3071
+ for (let k = 0; k < stringIdxs.length - 2; k++) {
3072
+ const s = strings[stringIdxs[k]];
3073
+ if (sharedCurrencyExchange.isCurrency(s)) sources.add(s.toUpperCase());
3074
+ }
3075
+ if (sources.size === 1) {
3076
+ const from = sources.values().next().value;
3077
+ if (from !== to.toUpperCase()) {
3078
+ const key = historicalRateQueryKey(from, to, isoDate);
3079
+ if (queryClient.getQueryData(key) === void 0) {
3080
+ const resolver = queryClient.fetchQuery({
3081
+ queryKey: key,
3082
+ queryFn: ({ signal: qSignal }) => fetchHistoricalRate(provider, from, to, isoDate, qSignal, queryClient),
3083
+ staleTime: HISTORICAL_RATE_STALE_TIME_MS
3084
+ });
3085
+ return { queryKey: key.join(":"), resolver, packageId, signal, metadata: { from, to, isoDate } };
3086
+ }
3087
+ }
3088
+ }
3089
+ }
3090
+ stringIdxs = [];
3091
+ }
3092
+ i = nextInstruction(opcodes, i);
3093
+ }
3094
+ return null;
3095
+ },
3096
+ destroy() {
3097
+ }
3098
+ };
3099
+ }
3100
+
2770
3101
  // src/packages/uom/parselets/UomLiteralParselet.ts
2771
3102
  function resolveUnitAlias(rawUnit) {
2772
3103
  return resolveCurrencyAlias(rawUnit) ?? rawUnit;
@@ -2780,13 +3111,27 @@ var UomLiteralParselet = class {
2780
3111
  const unit = resolveUnitAlias(token.value);
2781
3112
  builder.emitOpcode(13 /* PUSH_STRING */);
2782
3113
  builder.emitString(unit);
2783
- if (parser.peek()?.type === "TO" || parser.peek()?.type === "IN") {
3114
+ const boundInsideProduct = parser.infixMinBindingPower >= BindingPower.Product;
3115
+ if (!boundInsideProduct && (parser.peek()?.type === "TO" || parser.peek()?.type === "IN")) {
2784
3116
  parser.consume();
2785
3117
  const targetToken = parser.peek();
2786
3118
  if (targetToken?.type === "UNIT" || targetToken?.type === "IN") {
2787
3119
  parser.consume();
3120
+ const targetUnit = resolveUnitAlias(targetToken.value);
3121
+ const isoDate = tryConsumeCurrencyOnDate(parser, targetUnit, unit);
3122
+ if (isoDate !== null) {
3123
+ builder.emitOpcode(80 /* UOM_CONVERT */);
3124
+ builder.emitOpcode(13 /* PUSH_STRING */);
3125
+ builder.emitString(targetUnit);
3126
+ builder.emitOpcode(13 /* PUSH_STRING */);
3127
+ builder.emitString(isoDate);
3128
+ builder.emitOpcode(50 /* CALL_PLUGIN */);
3129
+ builder.emitIndex(HISTORICAL_CURRENCY_FN_IDX);
3130
+ builder.emitIndex(3);
3131
+ return;
3132
+ }
2788
3133
  builder.emitOpcode(13 /* PUSH_STRING */);
2789
- builder.emitString(resolveUnitAlias(targetToken.value));
3134
+ builder.emitString(targetUnit);
2790
3135
  builder.emitOpcode(81 /* UOM_CONVERT_TO */);
2791
3136
  return;
2792
3137
  }
@@ -3406,6 +3751,28 @@ function multiWordUnitNormalizerRule(priority = 78) {
3406
3751
  };
3407
3752
  }
3408
3753
 
3754
+ // src/packages/uom/normalizer/CompoundUnitNormalizerRule.ts
3755
+ function compoundUnitNormalizerRule(priority = 77) {
3756
+ return {
3757
+ name: "uom:compound-unit",
3758
+ priority,
3759
+ match(tokens, pos) {
3760
+ const numerator = tokens[pos];
3761
+ const slash = tokens[pos + 1];
3762
+ const denominator = tokens[pos + 2];
3763
+ if (numerator?.type !== "UNIT") return null;
3764
+ if (slash?.type !== "SLASH") return null;
3765
+ if (denominator?.type !== "UNIT") return null;
3766
+ const spelling = `${numerator.value}/${denominator.value}`;
3767
+ return {
3768
+ consumed: 3,
3769
+ replacement: [createFusedToken("UNIT", spelling, [numerator, slash, denominator])],
3770
+ ruleName: "uom:compound-unit"
3771
+ };
3772
+ }
3773
+ };
3774
+ }
3775
+
3409
3776
  // src/packages/uom/UomPackage.ts
3410
3777
  var UOM_PACKAGE = {
3411
3778
  name: "solve-uom",
@@ -3424,6 +3791,9 @@ var UOM_PACKAGE = {
3424
3791
  // Highest priority of the unit rules: a two-word spelling has to become one
3425
3792
  // token before anything else reads the first word as a unit on its own.
3426
3793
  multiWordUnitNormalizerRule(),
3794
+ // Above the bare-denominator rule (75): a slash-notation compound unit like
3795
+ // `km/h` is claimed whole before the slash is read as a bare denominator.
3796
+ compoundUnitNormalizerRule(),
3427
3797
  uomPossibilitiesNormalizerRule(),
3428
3798
  compoundQuantityNormalizerRule(),
3429
3799
  twoUnitConversionNormalizerRule(),
@@ -3466,32 +3836,59 @@ var InParselet = class {
3466
3836
  const targetToken = parser.peek();
3467
3837
  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")) {
3468
3838
  parser.consume();
3839
+ const targetUnit = resolveCurrencyAlias(targetToken.value) ?? targetToken.value;
3840
+ const isoDate = tryConsumeCurrencyOnDate(parser, targetUnit);
3841
+ if (isoDate !== null) {
3842
+ builder.emitOpcode(13 /* PUSH_STRING */);
3843
+ builder.emitString(targetUnit);
3844
+ builder.emitOpcode(13 /* PUSH_STRING */);
3845
+ builder.emitString(isoDate);
3846
+ builder.emitOpcode(50 /* CALL_PLUGIN */);
3847
+ builder.emitIndex(HISTORICAL_CURRENCY_FN_IDX);
3848
+ builder.emitIndex(3);
3849
+ return;
3850
+ }
3469
3851
  builder.emitOpcode(13 /* PUSH_STRING */);
3470
- builder.emitString(resolveCurrencyAlias(targetToken.value) ?? targetToken.value);
3852
+ builder.emitString(targetUnit);
3471
3853
  builder.emitOpcode(84 /* UOM_CONVERT_IN */);
3472
3854
  }
3473
3855
  }
3474
3856
  };
3475
3857
 
3476
3858
  // src/packages/currency/CurrencyPackage.ts
3477
- var CURRENCY_PACKAGE = {
3478
- name: "solve-currency",
3479
- asyncResolvers: [new CurrencyAsyncResolver()],
3480
- prefixParselets: [
3481
- { tokenType: "DOLLAR", parselet: new CurrencySymbolParselet() },
3482
- { tokenType: "POUND", parselet: new CurrencySymbolParselet() },
3483
- { tokenType: "EURO", parselet: new CurrencySymbolParselet() },
3484
- { tokenType: "YEN", parselet: new CurrencySymbolParselet() },
3485
- { tokenType: "RUBLE", parselet: new CurrencySymbolParselet() },
3486
- { tokenType: "WON", parselet: new CurrencySymbolParselet() },
3487
- // Every currency symbol added after the original six above shares this
3488
- // one generic token type. See Token.ts's CURRENCY_SYMBOL doc comment.
3489
- { tokenType: "CURRENCY_SYMBOL", parselet: new CurrencySymbolParselet() }
3490
- ],
3491
- infixParselets: [
3492
- { tokenType: "IN", parselet: new InParselet() }
3493
- ]
3494
- };
3859
+ function createCurrencyPackage(config = {}) {
3860
+ return {
3861
+ name: "solve-currency",
3862
+ asyncResolvers: [
3863
+ new CurrencyAsyncResolver(),
3864
+ createHistoricalCurrencyResolver(config.historicalRateProvider)
3865
+ ],
3866
+ prefixParselets: [
3867
+ { tokenType: "DOLLAR", parselet: new CurrencySymbolParselet() },
3868
+ { tokenType: "POUND", parselet: new CurrencySymbolParselet() },
3869
+ { tokenType: "EURO", parselet: new CurrencySymbolParselet() },
3870
+ { tokenType: "YEN", parselet: new CurrencySymbolParselet() },
3871
+ { tokenType: "RUBLE", parselet: new CurrencySymbolParselet() },
3872
+ { tokenType: "WON", parselet: new CurrencySymbolParselet() },
3873
+ // Every currency symbol added after the original six above shares this
3874
+ // one generic token type. See Token.ts's CURRENCY_SYMBOL doc comment.
3875
+ { tokenType: "CURRENCY_SYMBOL", parselet: new CurrencySymbolParselet() }
3876
+ ],
3877
+ infixParselets: [
3878
+ { tokenType: "IN", parselet: new InParselet() }
3879
+ ],
3880
+ pluginFunctions: [
3881
+ // Historical conversions (`<money> in <currency> on <date>`) compile to a
3882
+ // CALL_PLUGIN at this shared index (see uom/HistoricalCurrency.ts). One
3883
+ // index serves every pair and date, the query is the amount plus the
3884
+ // target and date strings. The handler carries the same provider as the
3885
+ // resolver, so a source currency known only at runtime (`x in GBP on
3886
+ // <date>`) can fetch the rate the bytecode scan could not preflight.
3887
+ { index: HISTORICAL_CURRENCY_FN_IDX, handler: createHistoricalCurrencyPluginFunction(config.historicalRateProvider) }
3888
+ ]
3889
+ };
3890
+ }
3891
+ var CURRENCY_PACKAGE = createCurrencyPackage();
3495
3892
 
3496
3893
  // src/packages/vector/parselets/VectorParselet.ts
3497
3894
  var VectorParselet = class {
@@ -5160,6 +5557,34 @@ var AnnualReturnParselet = class {
5160
5557
  }
5161
5558
  };
5162
5559
 
5560
+ // src/packages/finance/parselets/TermRateClauses.ts
5561
+ function parseTermAndRate(parser, builder, termWords) {
5562
+ if (matchTerm(parser, termWords)) {
5563
+ parser.parseExpression(BindingPower.Lowest, builder);
5564
+ consumeRate(parser);
5565
+ parser.parseExpression(BindingPower.Lowest, builder);
5566
+ return { swap: true };
5567
+ }
5568
+ consumeRate(parser);
5569
+ parser.parseExpression(BindingPower.Lowest, builder);
5570
+ consumeTerm(parser, termWords);
5571
+ parser.parseExpression(BindingPower.Lowest, builder);
5572
+ return { swap: false };
5573
+ }
5574
+ function matchTerm(parser, termWords) {
5575
+ for (const word of termWords) {
5576
+ if (parser.match(word)) return true;
5577
+ }
5578
+ return false;
5579
+ }
5580
+ function consumeTerm(parser, termWords) {
5581
+ if (matchTerm(parser, termWords)) return;
5582
+ parser.consume(termWords[0]);
5583
+ }
5584
+ function consumeRate(parser) {
5585
+ if (!parser.match("RATE_AT")) parser.consume("AT");
5586
+ }
5587
+
5163
5588
  // src/packages/finance/parselets/CompoundInterestParselet.ts
5164
5589
  var CompoundInterestParselet = class {
5165
5590
  /**
@@ -5174,14 +5599,9 @@ var CompoundInterestParselet = class {
5174
5599
  }
5175
5600
  parse(parser, _token, builder) {
5176
5601
  parser.parseExpression(BindingPower.Conditional, builder);
5177
- if (!parser.match("AFTER") && !parser.match("FOR_DURATION")) {
5178
- parser.consume("OVER");
5179
- }
5180
- parser.parseExpression(BindingPower.Lowest, builder);
5181
- if (!parser.match("RATE_AT")) parser.consume("AT");
5182
- parser.parseExpression(BindingPower.Lowest, builder);
5602
+ const { swap } = parseTermAndRate(parser, builder, ["AFTER", "FOR_DURATION", "OVER"]);
5183
5603
  const periods = readCompoundingInterval(parser);
5184
- builder.emitOpcode(2 /* SWAP */);
5604
+ if (swap) builder.emitOpcode(2 /* SWAP */);
5185
5605
  if (periods === 1) {
5186
5606
  builder.emitOpcode(51 /* CALL_BUILTIN */);
5187
5607
  builder.emitIndex(this.builtinIndex);
@@ -5205,11 +5625,8 @@ var LoanRepaymentParselet = class {
5205
5625
  }
5206
5626
  parse(parser, _token, builder) {
5207
5627
  parser.parseExpression(BindingPower.Lowest, builder);
5208
- parser.consume("OVER");
5209
- parser.parseExpression(BindingPower.Lowest, builder);
5210
- if (!parser.match("RATE_AT")) parser.consume("AT");
5211
- parser.parseExpression(BindingPower.Lowest, builder);
5212
- builder.emitOpcode(2 /* SWAP */);
5628
+ const { swap } = parseTermAndRate(parser, builder, ["OVER"]);
5629
+ if (swap) builder.emitOpcode(2 /* SWAP */);
5213
5630
  builder.emitOpcode(10 /* PUSH_NUMBER */);
5214
5631
  builder.emitNumber(this.periodsPerYear);
5215
5632
  builder.emitOpcode(51 /* CALL_BUILTIN */);
@@ -5394,6 +5811,131 @@ function inYearDollarsNormalizerRule(priority = 70) {
5394
5811
  };
5395
5812
  }
5396
5813
 
5814
+ // src/packages/finance/normalizer/RecurringScheduleNormalizerRule.ts
5815
+ var RecurringScheduleErrorCodes = {
5816
+ /** `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. */
5817
+ RECURRING_INTERVAL_NOT_POSITIVE: "RECURRING_INTERVAL_NOT_POSITIVE"
5818
+ };
5819
+ var PERIODS_PER_YEAR2 = {
5820
+ daily: 365,
5821
+ weekly: 52,
5822
+ monthly: 12,
5823
+ yearly: 1,
5824
+ // "annually" reads better than "yearly" before a duration and means the
5825
+ // same, so it is accepted as a spelling rather than a separate period.
5826
+ annually: 1
5827
+ };
5828
+ var UNIT_PER_YEAR = {
5829
+ day: 365,
5830
+ days: 365,
5831
+ week: 52,
5832
+ weeks: 52,
5833
+ month: 12,
5834
+ months: 12,
5835
+ year: 1,
5836
+ years: 1
5837
+ };
5838
+ var ARTICLES3 = /* @__PURE__ */ new Set(["a", "an"]);
5839
+ var VALUE_ENDERS = /* @__PURE__ */ new Set([
5840
+ "NUMBER",
5841
+ "FLOAT",
5842
+ "HEX",
5843
+ "BIGINT",
5844
+ "RPAREN",
5845
+ "RBRACKET",
5846
+ "IDENT",
5847
+ "PERCENT",
5848
+ "STRING"
5849
+ ]);
5850
+ function wordOf(token) {
5851
+ if (token === void 0) return "";
5852
+ return (token.text ?? token.value ?? "").toLowerCase();
5853
+ }
5854
+ function unitPerYear(token) {
5855
+ if (token === void 0 || token.type !== "UNIT") return void 0;
5856
+ return UNIT_PER_YEAR[wordOf(token)];
5857
+ }
5858
+ function readPeriod(tokens, i) {
5859
+ const head = tokens[i];
5860
+ if (head === void 0) return null;
5861
+ if (head.type === "IDENT") {
5862
+ const simple = PERIODS_PER_YEAR2[wordOf(head)];
5863
+ if (simple !== void 0) return { perYear: simple, consumed: 1 };
5864
+ if (wordOf(head) === "every") {
5865
+ const countToken = tokens[i + 1];
5866
+ if (countToken?.type !== "NUMBER") return null;
5867
+ const per = unitPerYear(tokens[i + 2]);
5868
+ if (per === void 0) return null;
5869
+ const n = Number((countToken.value ?? "").replace(/,/g, ""));
5870
+ if (!(n > 0)) {
5871
+ throw ErrorFactory.parsing({
5872
+ code: RecurringScheduleErrorCodes.RECURRING_INTERVAL_NOT_POSITIVE,
5873
+ message: `A recurring interval must be a positive number, not "${countToken.value}".`,
5874
+ expected: "a positive interval, e.g. every 2 weeks",
5875
+ found: `every ${countToken.value} ${wordOf(tokens[i + 2])}`
5876
+ });
5877
+ }
5878
+ return { perYear: per / n, consumed: 3 };
5879
+ }
5880
+ }
5881
+ return null;
5882
+ }
5883
+ function readDuration(tokens, i) {
5884
+ const first = tokens[i];
5885
+ if (first === void 0) return null;
5886
+ let count;
5887
+ let unitIndex;
5888
+ if (first.type === "NUMBER") {
5889
+ count = Number((first.value ?? "").replace(/,/g, ""));
5890
+ unitIndex = i + 1;
5891
+ } else if (first.type === "IDENT" && ARTICLES3.has(wordOf(first))) {
5892
+ count = 1;
5893
+ unitIndex = i + 1;
5894
+ } else {
5895
+ return null;
5896
+ }
5897
+ const per = unitPerYear(tokens[unitIndex]);
5898
+ if (per === void 0) return null;
5899
+ if (!Number.isFinite(count) || count < 0) return null;
5900
+ return { years: count / per, consumed: unitIndex - i + 1 };
5901
+ }
5902
+ function wholePayments(perYear, durationYears) {
5903
+ const raw = perYear * durationYears;
5904
+ const nearest = Math.round(raw);
5905
+ return Math.abs(raw - nearest) < 1e-9 ? nearest : Math.floor(raw);
5906
+ }
5907
+ function recurringScheduleNormalizerRule(priority = 78) {
5908
+ return {
5909
+ name: "finance:recurring-schedule",
5910
+ priority,
5911
+ match(tokens, pos) {
5912
+ const amount = tokens[pos];
5913
+ if (amount === void 0 || !VALUE_ENDERS.has(amount.type)) return null;
5914
+ const period = readPeriod(tokens, pos + 1);
5915
+ if (period === null) return null;
5916
+ const forIndex = pos + 1 + period.consumed;
5917
+ if (tokens[forIndex]?.type !== "FOR_DURATION") return null;
5918
+ const duration = readDuration(tokens, forIndex + 1);
5919
+ if (duration === null) return null;
5920
+ const count = wholePayments(period.perYear, duration.years);
5921
+ const consumed = forIndex + 1 + duration.consumed - pos;
5922
+ const source = tokens.slice(pos, pos + consumed);
5923
+ return {
5924
+ // The amount is re-emitted unchanged, then `* <count>`. The amount may
5925
+ // be the tail of a longer expression, whose earlier tokens are before
5926
+ // `pos` and pass through on their own.
5927
+ replacement: [
5928
+ amount,
5929
+ createFusedToken("STAR", "*", source),
5930
+ createFusedToken("NUMBER", String(count), source)
5931
+ ],
5932
+ consumed,
5933
+ ruleName: "finance:recurring-schedule"
5934
+ };
5935
+ }
5936
+ };
5937
+ }
5938
+
5397
5939
  // src/packages/finance/FinancePackage.ts
5398
5940
  var COMPOUND_FV = 51;
5399
5941
  var COMPOUND_INTEREST = 52;
@@ -5470,7 +6012,11 @@ var FINANCE_PACKAGE = {
5470
6012
  { tokenType: "IN_YEAR_DOLLARS", parselet: new InYearDollarsParselet() }
5471
6013
  ],
5472
6014
  normalizerRules: [
5473
- inYearDollarsNormalizerRule()
6015
+ inYearDollarsNormalizerRule(),
6016
+ // `450 monthly for 18 months` -> `450 * 18`. A recurring schedule totalled
6017
+ // as a plain multiplication, so a currency amount stays currency and an
6018
+ // exact one stays exact. See RecurringScheduleNormalizerRule.ts.
6019
+ recurringScheduleNormalizerRule()
5474
6020
  ],
5475
6021
  pluginFunctions: [
5476
6022
  { index: INFLATION_FROM_YEAR_TO_PRESENT_IDX, handler: inflationFromYearToPresentHandler },
@@ -5630,7 +6176,7 @@ var WeatherErrorCodes = {
5630
6176
  };
5631
6177
  var GEOCODING_API_URL = "https://geocoding-api.open-meteo.com/v1/search";
5632
6178
  var FORECAST_API_URL = "https://api.open-meteo.com/v1/forecast";
5633
- var FETCH_TIMEOUT_MS = 1e4;
6179
+ var FETCH_TIMEOUT_MS2 = 1e4;
5634
6180
  async function geocodeCity(city, signal) {
5635
6181
  const url = `${GEOCODING_API_URL}?name=${encodeURIComponent(city)}&count=1&language=en&format=json`;
5636
6182
  const response = await fetch(url, { signal });
@@ -5695,7 +6241,7 @@ function fetchCityWeather(city, signal) {
5695
6241
  if (cached && cached.expiresAt > now) {
5696
6242
  return cached.promise;
5697
6243
  }
5698
- const { signal: fetchSignal, cleanup } = createTimeoutSignal(signal, FETCH_TIMEOUT_MS, "Open-Meteo weather query");
6244
+ const { signal: fetchSignal, cleanup } = createTimeoutSignal(signal, FETCH_TIMEOUT_MS2, "Open-Meteo weather query");
5699
6245
  const promise = (async () => {
5700
6246
  try {
5701
6247
  const geo = await geocodeCity(city, fetchSignal);
@@ -5804,7 +6350,7 @@ var MONTH_NAMES2 = {
5804
6350
  dec: 11,
5805
6351
  december: 11
5806
6352
  };
5807
- function pad2(n) {
6353
+ function pad22(n) {
5808
6354
  return n < 10 ? `0${n}` : String(n);
5809
6355
  }
5810
6356
  function validateAndFormat(year, monthIndex, day, raw) {
@@ -5815,7 +6361,7 @@ function validateAndFormat(year, monthIndex, day, raw) {
5815
6361
  `"${raw}" is not a valid calendar date`
5816
6362
  );
5817
6363
  }
5818
- return { isoDate: `${year}-${pad2(monthIndex + 1)}-${pad2(day)}`, raw };
6364
+ return { isoDate: `${year}-${pad22(monthIndex + 1)}-${pad22(day)}`, raw };
5819
6365
  }
5820
6366
  function requireYear(text, raw) {
5821
6367
  if (!/^\d{4}$/.test(text)) {
@@ -5834,7 +6380,7 @@ function tryParseDatePhrase(parser) {
5834
6380
  const date = new Date(Number(first.value));
5835
6381
  if (Number.isNaN(date.getTime())) return null;
5836
6382
  return {
5837
- isoDate: `${date.getFullYear()}-${pad2(date.getMonth() + 1)}-${pad2(date.getDate())}`,
6383
+ isoDate: `${date.getFullYear()}-${pad22(date.getMonth() + 1)}-${pad22(date.getDate())}`,
5838
6384
  raw: first.text ?? first.value
5839
6385
  };
5840
6386
  }
@@ -6483,6 +7029,927 @@ var LINES_PACKAGE = {
6483
7029
  ]
6484
7030
  };
6485
7031
 
7032
+ // src/packages/tables/parselets/ColumnAggregateParselet.ts
7033
+ var ColumnAggregateParselet = class {
7034
+ constructor(pluginFnIndex) {
7035
+ this.pluginFnIndex = pluginFnIndex;
7036
+ this.category = "Tables";
7037
+ }
7038
+ parse(parser, _token, builder) {
7039
+ const next = parser.peek();
7040
+ if (!next || next.type !== "STRING") {
7041
+ throw ErrorFactory.parsing(
7042
+ "TABLE_COLUMN_NAME_EXPECTED",
7043
+ `Expected a quoted column name after "... of column" but got ${next ? `"${next.value}"` : "end of input"}`
7044
+ );
7045
+ }
7046
+ const nameToken = parser.consume("STRING");
7047
+ while (true) {
7048
+ const t = parser.peek();
7049
+ if (!t) break;
7050
+ if (t.type === "IN") {
7051
+ parser.consume();
7052
+ continue;
7053
+ }
7054
+ if (t.type === "IDENT" && ADDRESS_WORDS.has(t.value.toLowerCase())) {
7055
+ parser.consume();
7056
+ continue;
7057
+ }
7058
+ break;
7059
+ }
7060
+ builder.emitOpcode(13 /* PUSH_STRING */);
7061
+ builder.emitString(nameToken.value);
7062
+ builder.emitOpcode(50 /* CALL_PLUGIN */);
7063
+ builder.emitIndex(this.pluginFnIndex);
7064
+ builder.emitIndex(1);
7065
+ }
7066
+ };
7067
+ var ADDRESS_WORDS = /* @__PURE__ */ new Set(["the", "table", "above"]);
7068
+
7069
+ // src/packages/tables/TableReader.ts
7070
+ function isTableRow(line) {
7071
+ return line.trimStart().startsWith("|");
7072
+ }
7073
+ function isSeparatorRow(line) {
7074
+ const s = line.trim();
7075
+ if (s.length === 0) return false;
7076
+ if (!s.includes("-") || !s.includes("|")) return false;
7077
+ return /^[|\s:-]+$/.test(s);
7078
+ }
7079
+ function splitTableRow(line) {
7080
+ let s = line.trim();
7081
+ if (s.startsWith("|")) s = s.slice(1);
7082
+ if (s.endsWith("|") && !s.endsWith("\\|")) s = s.slice(0, -1);
7083
+ const cells = [];
7084
+ let cell = "";
7085
+ for (let i = 0; i < s.length; i++) {
7086
+ const ch = s[i];
7087
+ if (ch === "\\" && i + 1 < s.length && s[i + 1] === "|") {
7088
+ cell += "|";
7089
+ i++;
7090
+ continue;
7091
+ }
7092
+ if (ch === "|") {
7093
+ cells.push(cell.trim());
7094
+ cell = "";
7095
+ continue;
7096
+ }
7097
+ cell += ch;
7098
+ }
7099
+ cells.push(cell.trim());
7100
+ return cells;
7101
+ }
7102
+ function parseNumericCell(cell) {
7103
+ if (cell === void 0) return null;
7104
+ const trimmed = cell.trim();
7105
+ if (trimmed.length === 0) return null;
7106
+ const cleaned = trimmed.replace(/,/g, "");
7107
+ if (!/^[+-]?(\d+(\.\d+)?|\.\d+)$/.test(cleaned)) return null;
7108
+ const n = Number.parseFloat(cleaned);
7109
+ return Number.isFinite(n) ? n : null;
7110
+ }
7111
+ function findTableAbove(getLineText, fromLine) {
7112
+ let bottom = -1;
7113
+ for (let n = fromLine - 1; n >= 1; n--) {
7114
+ const text = getLineText(n);
7115
+ if (text === void 0) break;
7116
+ if (isTableRow(text)) {
7117
+ bottom = n;
7118
+ break;
7119
+ }
7120
+ }
7121
+ if (bottom === -1) return null;
7122
+ let top = bottom;
7123
+ while (top - 1 >= 1) {
7124
+ const text = getLineText(top - 1);
7125
+ if (text === void 0 || !isTableRow(text)) break;
7126
+ top--;
7127
+ }
7128
+ let separator = -1;
7129
+ for (let n = top; n <= bottom; n++) {
7130
+ if (isSeparatorRow(getLineText(n))) {
7131
+ separator = n;
7132
+ break;
7133
+ }
7134
+ }
7135
+ if (separator === -1 || separator === top) return null;
7136
+ const header = splitTableRow(getLineText(separator - 1));
7137
+ const rows = [];
7138
+ for (let n = separator + 1; n <= bottom; n++) {
7139
+ rows.push(splitTableRow(getLineText(n)));
7140
+ }
7141
+ return { header, rows };
7142
+ }
7143
+ function columnIndex(header, name) {
7144
+ const target = name.trim().toLowerCase();
7145
+ for (let i = 0; i < header.length; i++) {
7146
+ if (header[i].trim().toLowerCase() === target) return i;
7147
+ }
7148
+ return -1;
7149
+ }
7150
+ function numericColumn(table, colIndex) {
7151
+ const out = [];
7152
+ for (const row of table.rows) {
7153
+ const value = parseNumericCell(row[colIndex]);
7154
+ if (value !== null) out.push(value);
7155
+ }
7156
+ return out;
7157
+ }
7158
+
7159
+ // src/packages/tables/TablesPluginFunctions.ts
7160
+ var TablesErrorCodes = {
7161
+ /** Cross-line table reads need a real document, absent on the single-expression path. */
7162
+ TABLE_NO_DOCUMENT: "TABLE_NO_DOCUMENT",
7163
+ /** No markdown table was found above the query line. */
7164
+ TABLE_NOT_FOUND: "TABLE_NOT_FOUND",
7165
+ /** The named column is not one of the table's headers. */
7166
+ TABLE_COLUMN_NOT_FOUND: "TABLE_COLUMN_NOT_FOUND",
7167
+ /** The column held no plain-number cells to aggregate. */
7168
+ TABLE_COLUMN_NO_NUMERIC_CELLS: "TABLE_COLUMN_NO_NUMERIC_CELLS"
7169
+ };
7170
+ function reduce(op, cells) {
7171
+ switch (op) {
7172
+ case "sum":
7173
+ return cells.reduce((acc, n) => acc + n, 0);
7174
+ case "average":
7175
+ return cells.reduce((acc, n) => acc + n, 0) / cells.length;
7176
+ case "min":
7177
+ return Math.min(...cells);
7178
+ case "max":
7179
+ return Math.max(...cells);
7180
+ case "count":
7181
+ return cells.length;
7182
+ case "median": {
7183
+ const sorted = [...cells].sort((a, b) => a - b);
7184
+ const mid = Math.floor(sorted.length / 2);
7185
+ return sorted.length % 2 === 0 ? (sorted[mid - 1] + sorted[mid]) / 2 : sorted[mid];
7186
+ }
7187
+ }
7188
+ }
7189
+ function aggregateColumn(op, columnName2, context) {
7190
+ const getLineText = context?.getLineText;
7191
+ if (!getLineText || context.lineIndex < 1) {
7192
+ return errorValue(
7193
+ TablesErrorCodes.TABLE_NO_DOCUMENT,
7194
+ "Reading a table column needs a real document, which the single-expression path (evaluateExpression) does not have"
7195
+ );
7196
+ }
7197
+ const table = findTableAbove(getLineText, context.lineIndex);
7198
+ if (!table) {
7199
+ return errorValue(
7200
+ TablesErrorCodes.TABLE_NOT_FOUND,
7201
+ 'No markdown table found above this line (a table row starts with "|", and needs a "|---|" separator under its header)'
7202
+ );
7203
+ }
7204
+ const index = columnIndex(table.header, columnName2);
7205
+ if (index === -1) {
7206
+ const names = table.header.map((h) => `"${h}"`).join(", ");
7207
+ return errorValue(
7208
+ TablesErrorCodes.TABLE_COLUMN_NOT_FOUND,
7209
+ `The table above has no column named "${columnName2}". Its columns are: ${names}`
7210
+ );
7211
+ }
7212
+ const cells = numericColumn(table, index);
7213
+ if (op === "count") return numberValue(cells.length);
7214
+ if (cells.length === 0) {
7215
+ return errorValue(
7216
+ TablesErrorCodes.TABLE_COLUMN_NO_NUMERIC_CELLS,
7217
+ `Column "${columnName2}" has no plain-number cells to aggregate (currency and unit cells are not read yet)`
7218
+ );
7219
+ }
7220
+ return numberValue(reduce(op, cells));
7221
+ }
7222
+ function columnName(args) {
7223
+ const value = args[0];
7224
+ if (value && value.type === 3 /* String */ && typeof value.value === "string") {
7225
+ return value.value;
7226
+ }
7227
+ return String(value?.value ?? "");
7228
+ }
7229
+ var TABLE_COLUMN_SUM_FN_IDX = allocatePluginFunctionIndex();
7230
+ function tableColumnSumHandler(args, context) {
7231
+ return aggregateColumn("sum", columnName(args), context);
7232
+ }
7233
+ var TABLE_COLUMN_AVERAGE_FN_IDX = allocatePluginFunctionIndex();
7234
+ function tableColumnAverageHandler(args, context) {
7235
+ return aggregateColumn("average", columnName(args), context);
7236
+ }
7237
+ var TABLE_COLUMN_MIN_FN_IDX = allocatePluginFunctionIndex();
7238
+ function tableColumnMinHandler(args, context) {
7239
+ return aggregateColumn("min", columnName(args), context);
7240
+ }
7241
+ var TABLE_COLUMN_MAX_FN_IDX = allocatePluginFunctionIndex();
7242
+ function tableColumnMaxHandler(args, context) {
7243
+ return aggregateColumn("max", columnName(args), context);
7244
+ }
7245
+ var TABLE_COLUMN_COUNT_FN_IDX = allocatePluginFunctionIndex();
7246
+ function tableColumnCountHandler(args, context) {
7247
+ return aggregateColumn("count", columnName(args), context);
7248
+ }
7249
+ var TABLE_COLUMN_MEDIAN_FN_IDX = allocatePluginFunctionIndex();
7250
+ function tableColumnMedianHandler(args, context) {
7251
+ return aggregateColumn("median", columnName(args), context);
7252
+ }
7253
+
7254
+ // src/packages/tables/TablesPackage.ts
7255
+ var TABLES_PACKAGE = {
7256
+ name: "solve-tables",
7257
+ phrases: {
7258
+ "sum of column": "TABLE_COLUMN_SUM",
7259
+ "total of column": "TABLE_COLUMN_SUM",
7260
+ "average of column": "TABLE_COLUMN_AVERAGE",
7261
+ "mean of column": "TABLE_COLUMN_AVERAGE",
7262
+ "min of column": "TABLE_COLUMN_MIN",
7263
+ "minimum of column": "TABLE_COLUMN_MIN",
7264
+ "max of column": "TABLE_COLUMN_MAX",
7265
+ "maximum of column": "TABLE_COLUMN_MAX",
7266
+ "count of column": "TABLE_COLUMN_COUNT",
7267
+ "median of column": "TABLE_COLUMN_MEDIAN"
7268
+ },
7269
+ prefixParselets: [
7270
+ { tokenType: "TABLE_COLUMN_SUM", parselet: new ColumnAggregateParselet(TABLE_COLUMN_SUM_FN_IDX) },
7271
+ { tokenType: "TABLE_COLUMN_AVERAGE", parselet: new ColumnAggregateParselet(TABLE_COLUMN_AVERAGE_FN_IDX) },
7272
+ { tokenType: "TABLE_COLUMN_MIN", parselet: new ColumnAggregateParselet(TABLE_COLUMN_MIN_FN_IDX) },
7273
+ { tokenType: "TABLE_COLUMN_MAX", parselet: new ColumnAggregateParselet(TABLE_COLUMN_MAX_FN_IDX) },
7274
+ { tokenType: "TABLE_COLUMN_COUNT", parselet: new ColumnAggregateParselet(TABLE_COLUMN_COUNT_FN_IDX) },
7275
+ { tokenType: "TABLE_COLUMN_MEDIAN", parselet: new ColumnAggregateParselet(TABLE_COLUMN_MEDIAN_FN_IDX) }
7276
+ ],
7277
+ pluginFunctions: [
7278
+ { index: TABLE_COLUMN_SUM_FN_IDX, handler: tableColumnSumHandler },
7279
+ { index: TABLE_COLUMN_AVERAGE_FN_IDX, handler: tableColumnAverageHandler },
7280
+ { index: TABLE_COLUMN_MIN_FN_IDX, handler: tableColumnMinHandler },
7281
+ { index: TABLE_COLUMN_MAX_FN_IDX, handler: tableColumnMaxHandler },
7282
+ { index: TABLE_COLUMN_COUNT_FN_IDX, handler: tableColumnCountHandler },
7283
+ { index: TABLE_COLUMN_MEDIAN_FN_IDX, handler: tableColumnMedianHandler }
7284
+ ]
7285
+ };
7286
+
7287
+ // src/packages/uncertainty/parselets/UncertaintyParselet.ts
7288
+ var UncertaintyParselet = class {
7289
+ constructor() {
7290
+ this.category = "operator";
7291
+ this.bindingPower = BindingPower.Uncertainty;
7292
+ }
7293
+ parse(parser, _left, _token, builder) {
7294
+ parser.parseExpression(this.bindingPower, builder);
7295
+ builder.emitOpcode(28 /* MAKE_UNCERTAIN */);
7296
+ }
7297
+ };
7298
+
7299
+ // src/packages/uncertainty/normalizer/AsciiPlusMinusNormalizerRule.ts
7300
+ function asciiPlusMinusNormalizerRule(priority = 90) {
7301
+ return {
7302
+ name: "uncertainty:ascii-plus-minus",
7303
+ priority,
7304
+ match(tokens, pos) {
7305
+ const plus = tokens[pos];
7306
+ if (plus?.type !== "PLUS") return null;
7307
+ const slash = tokens[pos + 1];
7308
+ if (slash?.type !== "SLASH") return null;
7309
+ const minus = tokens[pos + 2];
7310
+ if (minus?.type !== "MINUS") return null;
7311
+ return {
7312
+ consumed: 3,
7313
+ replacement: [createFusedToken("PLUS_MINUS", "\xB1", [plus, slash, minus])],
7314
+ ruleName: "uncertainty:ascii-plus-minus"
7315
+ };
7316
+ }
7317
+ };
7318
+ }
7319
+
7320
+ // src/packages/uncertainty/UncertaintyPackage.ts
7321
+ var UNCERTAINTY_PACKAGE = {
7322
+ name: "solve-uncertainty",
7323
+ infixParselets: [
7324
+ { tokenType: "PLUS_MINUS", parselet: new UncertaintyParselet() }
7325
+ ],
7326
+ normalizerRules: [asciiPlusMinusNormalizerRule()]
7327
+ // PLUS_MINUS is a core lexer token, so its highlight category lives in the
7328
+ // built-in language/TokenCategoryMap.ts rather than being contributed here.
7329
+ };
7330
+
7331
+ // src/packages/goalseek/GoalSeekPluginFunctions.ts
7332
+ var SEARCH_LOWER_BOUND = 1e-9;
7333
+ var SEARCH_UPPER_BOUND = 1e9;
7334
+ var FALLBACK_MAX_ITERATIONS = 100;
7335
+ var ABSOLUTE_VALUE_TOLERANCE = 1e-9;
7336
+ var RELATIVE_VALUE_TOLERANCE = 1e-9;
7337
+ var INTERVAL_COLLAPSE_WIDTH = 1e-12;
7338
+ var REAL_ROOT_IMAGINARY_TOLERANCE = 1e-9;
7339
+ var GOAL_SEEK_FN_IDX = allocatePluginFunctionIndex();
7340
+ function goalSeekHandler(args, context) {
7341
+ const probe = context?.evaluateLineWithBinding;
7342
+ const getLineReads = context?.getLineReads;
7343
+ if (!probe || !getLineReads) {
7344
+ return errorValue(
7345
+ "GOAL_SEEK_NO_DOCUMENT",
7346
+ "Goal seek only works inside a document, since it re-runs another line. The single-expression entry point has no document to solve against."
7347
+ );
7348
+ }
7349
+ const targetLine = args[0].toNumber();
7350
+ const varName = typeof args[1].value === "string" ? args[1].value : String(args[1].value);
7351
+ const targetArg = args[2];
7352
+ if (targetArg.type !== 0 /* Number */ && targetArg.type !== 6 /* Uom */) {
7353
+ return errorValue("GOAL_SEEK_TARGET_NOT_NUMERIC", `Goal seek's target must be a number, as in "solve line ${targetLine} for ${varName} = 900".`);
7354
+ }
7355
+ const target = targetArg.toNumber();
7356
+ if (!Number.isFinite(target)) {
7357
+ return errorValue("GOAL_SEEK_TARGET_NOT_NUMERIC", "Goal seek's target is not a finite number.");
7358
+ }
7359
+ const reads = getLineReads(targetLine);
7360
+ if (reads === void 0) {
7361
+ return errorValue("GOAL_SEEK_LINE_NOT_READY", `Line ${targetLine} has no evaluated expression to solve (forward reference, out of range, or not an expression).`);
7362
+ }
7363
+ if (!reads.includes(varName)) {
7364
+ return errorValue("GOAL_SEEK_VARIABLE_NOT_USED", `Line ${targetLine} does not use ${varName}, so changing ${varName} cannot move its result toward ${target}.`);
7365
+ }
7366
+ const symbolic = probe(targetLine, varName, symbolicValue(varNode(varName)), true);
7367
+ if (symbolic.type === 9 /* Symbolic */) {
7368
+ const exact = solveClosedForm(symbolic.value, target, varName);
7369
+ if (exact !== null) return numberValue(exact);
7370
+ }
7371
+ const maxIterations = context.goalSeekMaxIterations ?? FALLBACK_MAX_ITERATIONS;
7372
+ return bisect(probe, targetLine, varName, target, maxIterations);
7373
+ }
7374
+ function solveClosedForm(lhs, target, variable) {
7375
+ const rhs = constNode(rationalFromNumber(target));
7376
+ let outcome;
7377
+ try {
7378
+ outcome = solveForVariable(lhs, rhs, variable);
7379
+ } catch {
7380
+ return null;
7381
+ }
7382
+ if (outcome.kind !== "roots") return null;
7383
+ const candidates = [];
7384
+ for (const node of outcome.exact) {
7385
+ const simplified = simplifySymbolic(node);
7386
+ if (simplified.kind === "const") {
7387
+ const value = rationalToNumber(simplified.value);
7388
+ if (Number.isFinite(value)) candidates.push(value);
7389
+ }
7390
+ }
7391
+ for (const root of outcome.approximate) {
7392
+ if (Math.abs(root.im) <= REAL_ROOT_IMAGINARY_TOLERANCE && Number.isFinite(root.re)) candidates.push(root.re);
7393
+ }
7394
+ if (candidates.length === 0) return null;
7395
+ const inDomain = candidates.filter((c) => c >= SEARCH_LOWER_BOUND && c <= SEARCH_UPPER_BOUND);
7396
+ const pool = inDomain.length > 0 ? inDomain : candidates;
7397
+ pool.sort((a, b) => Math.abs(a) - Math.abs(b));
7398
+ return pool[0];
7399
+ }
7400
+ function bisect(probe, targetLine, variable, target, maxIterations) {
7401
+ const sample = (candidate) => {
7402
+ const result = probe(targetLine, variable, numberValue(candidate), false);
7403
+ if (result.type === 13 /* Error */) return { error: result };
7404
+ if (result.type !== 0 /* Number */ && result.type !== 6 /* Uom */) {
7405
+ return { error: 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.`) };
7406
+ }
7407
+ const numeric = result.toNumber();
7408
+ if (!Number.isFinite(numeric)) return { error: errorValue("GOAL_SEEK_NON_FINITE", `Line ${targetLine}'s result is not finite when ${variable} is ${candidate}.`) };
7409
+ return { value: numeric };
7410
+ };
7411
+ let lo = SEARCH_LOWER_BOUND;
7412
+ let hi = SEARCH_UPPER_BOUND;
7413
+ const loSample = sample(lo);
7414
+ if ("error" in loSample) return loSample.error;
7415
+ const hiSample = sample(hi);
7416
+ if ("error" in hiSample) return hiSample.error;
7417
+ const tolerance = Math.max(ABSOLUTE_VALUE_TOLERANCE, Math.abs(target) * RELATIVE_VALUE_TOLERANCE);
7418
+ let flo = loSample.value - target;
7419
+ const fhi = hiSample.value - target;
7420
+ if (Math.abs(flo) <= tolerance) return numberValue(lo);
7421
+ if (Math.abs(fhi) <= tolerance) return numberValue(hi);
7422
+ if (Math.sign(flo) === Math.sign(fhi)) {
7423
+ return errorValue(
7424
+ "GOAL_SEEK_NO_SOLUTION",
7425
+ `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.`
7426
+ );
7427
+ }
7428
+ for (let step = 0; step < maxIterations; step++) {
7429
+ const mid = (lo + hi) / 2;
7430
+ const midSample = sample(mid);
7431
+ if ("error" in midSample) return midSample.error;
7432
+ const fmid = midSample.value - target;
7433
+ if (Math.abs(fmid) <= tolerance) return numberValue(mid);
7434
+ if (hi - lo <= INTERVAL_COLLAPSE_WIDTH) {
7435
+ return errorValue(
7436
+ "GOAL_SEEK_DID_NOT_CONVERGE",
7437
+ `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.`
7438
+ );
7439
+ }
7440
+ if (Math.sign(fmid) === Math.sign(flo)) {
7441
+ lo = mid;
7442
+ flo = fmid;
7443
+ } else {
7444
+ hi = mid;
7445
+ }
7446
+ }
7447
+ return errorValue(
7448
+ "GOAL_SEEK_DID_NOT_CONVERGE",
7449
+ `Goal seek did not bring line ${targetLine} within tolerance of ${target} by varying ${variable} in ${maxIterations} steps.`
7450
+ );
7451
+ }
7452
+
7453
+ // src/packages/goalseek/parselets/GoalSeekParselet.ts
7454
+ var GoalSeekParselet = class {
7455
+ constructor() {
7456
+ this.category = "GoalSeek";
7457
+ }
7458
+ parse(parser, _token, builder) {
7459
+ const lineRef = parser.peek();
7460
+ if (!lineRef || lineRef.type !== "LINE_REF") {
7461
+ throw ErrorFactory.parsing(
7462
+ "GOAL_SEEK_SYNTAX",
7463
+ `Goal seek reads "solve line N for <var> = <target>", for example "solve line 4 for rate = 900".`,
7464
+ { found: lineRef?.type ?? "end of input" }
7465
+ );
7466
+ }
7467
+ parser.consume();
7468
+ const targetLine = parseInt(lineRef.value, 10);
7469
+ const connective = parser.peek();
7470
+ const isFor = !!connective && (connective.type === "FOR_DURATION" || connective.type === "IDENT" && connective.value.toLowerCase() === "for");
7471
+ if (!isFor) {
7472
+ throw ErrorFactory.parsing(
7473
+ "GOAL_SEEK_SYNTAX",
7474
+ `Goal seek reads "solve line N for <var> = <target>". Expected "for" after the line reference.`,
7475
+ { found: connective?.type ?? "end of input" }
7476
+ );
7477
+ }
7478
+ parser.consume();
7479
+ const variable = parser.peek();
7480
+ if (!variable || variable.type !== "IDENT" && variable.type !== "UNIT") {
7481
+ throw ErrorFactory.parsing(
7482
+ "GOAL_SEEK_REQUIRES_VARIABLE_NAME",
7483
+ `Goal seek needs the name of the variable to vary here, as in "solve line 4 for rate = 900".`,
7484
+ { found: variable?.type ?? "end of input" }
7485
+ );
7486
+ }
7487
+ parser.consume();
7488
+ parser.consume("EQUALS");
7489
+ builder.emitOpcode(10 /* PUSH_NUMBER */);
7490
+ builder.emitNumber(targetLine);
7491
+ builder.emitOpcode(13 /* PUSH_STRING */);
7492
+ builder.emitString(variable.value);
7493
+ parser.parseExpression(BindingPower.Lowest, builder);
7494
+ builder.emitOpcode(50 /* CALL_PLUGIN */);
7495
+ builder.emitIndex(GOAL_SEEK_FN_IDX);
7496
+ builder.emitIndex(3);
7497
+ }
7498
+ };
7499
+
7500
+ // src/packages/goalseek/normalizer/GoalSeekNormalizerRule.ts
7501
+ var GOAL_SEEK_TYPE_ID = tokenTypeId("GOAL_SEEK");
7502
+ function goalSeekNormalizerRule(priority = 75) {
7503
+ return {
7504
+ name: "goalseek:solve-line",
7505
+ priority,
7506
+ match(tokens, pos) {
7507
+ if (pos > 0 && tokens[pos - 1].type === "COLON") return null;
7508
+ const token = tokens[pos];
7509
+ if (!token || token.type !== "IDENT" || token.value.toLowerCase() !== "solve") return null;
7510
+ if (tokens[pos + 1]?.type !== "LINE_REF") return null;
7511
+ return {
7512
+ consumed: 1,
7513
+ replacement: [
7514
+ new LexerToken("GOAL_SEEK", GOAL_SEEK_TYPE_ID, token.value, token.value, token.offset, 0, token.line, token.col)
7515
+ ],
7516
+ ruleName: "goalseek:solve-line"
7517
+ };
7518
+ }
7519
+ };
7520
+ }
7521
+
7522
+ // src/packages/goalseek/GoalSeekPackage.ts
7523
+ var GOALSEEK_PACKAGE = {
7524
+ name: "solve-goalseek",
7525
+ normalizerRules: [goalSeekNormalizerRule()],
7526
+ prefixParselets: [{ tokenType: "GOAL_SEEK", parselet: new GoalSeekParselet() }],
7527
+ pluginFunctions: [{ index: GOAL_SEEK_FN_IDX, handler: goalSeekHandler }]
7528
+ // GOAL_SEEK's highlight category lives in the static TokenCategoryMap
7529
+ // alongside every other token declared in Token.ts's TokenTypes (the same
7530
+ // place the lines package's LINE_REF etc. are categorized), so it is not
7531
+ // repeated here.
7532
+ };
7533
+
7534
+ // src/packages/colour/ColourPluginFunctions.ts
7535
+ var ColourErrorCodes = {
7536
+ /** A string was not a hex colour or a known CSS colour name. */
7537
+ COLOUR_INVALID: "COLOUR_INVALID",
7538
+ /** A function expected a colour argument and got something else. */
7539
+ COLOUR_EXPECTED_COLOUR: "COLOUR_EXPECTED_COLOUR",
7540
+ /** A function was called with the wrong number or type of arguments. */
7541
+ COLOUR_BAD_ARGUMENTS: "COLOUR_BAD_ARGUMENTS"
7542
+ };
7543
+ function colourOf(v) {
7544
+ return v && v.type === 14 /* Colour */ ? v.value : null;
7545
+ }
7546
+ function readAmount(v) {
7547
+ if (!v) return 0;
7548
+ const n = v.toNumber();
7549
+ if (v.type === 5 /* Percentage */) return n;
7550
+ return n > 1 ? n / 100 : n;
7551
+ }
7552
+ function badArgs(fn, expected) {
7553
+ return errorValue(ColourErrorCodes.COLOUR_BAD_ARGUMENTS, `${fn}(...) expects ${expected}`);
7554
+ }
7555
+ function notAColour(fn) {
7556
+ return errorValue(ColourErrorCodes.COLOUR_EXPECTED_COLOUR, `${fn}(...) expects a colour as its first argument`);
7557
+ }
7558
+ var COLOUR_PARSE_FN_IDX = allocatePluginFunctionIndex();
7559
+ function colourParseHandler(args) {
7560
+ const arg = args[0];
7561
+ if (!arg || arg.type !== 3 /* String */ || typeof arg.value !== "string") {
7562
+ return badArgs("color", 'a string, e.g. color("#ff0000") or color("red")');
7563
+ }
7564
+ const parsed = parseColour(arg.value);
7565
+ if (!parsed) {
7566
+ return errorValue(
7567
+ ColourErrorCodes.COLOUR_INVALID,
7568
+ `"${arg.value}" is not a hex colour (#rgb, #rgba, #rrggbb, #rrggbbaa) or a CSS colour name`
7569
+ );
7570
+ }
7571
+ return colourValue(parsed);
7572
+ }
7573
+ var COLOUR_RGB_FN_IDX = allocatePluginFunctionIndex();
7574
+ function rgbHandler(args) {
7575
+ if (args.length !== 3) return badArgs("rgb", "three numbers: rgb(red, green, blue)");
7576
+ return colourValue({
7577
+ r: clamp255(args[0].toNumber()),
7578
+ g: clamp255(args[1].toNumber()),
7579
+ b: clamp255(args[2].toNumber()),
7580
+ a: 1,
7581
+ format: "rgb"
7582
+ });
7583
+ }
7584
+ var COLOUR_RGBA_FN_IDX = allocatePluginFunctionIndex();
7585
+ function rgbaHandler(args) {
7586
+ if (args.length !== 4) return badArgs("rgba", "four numbers: rgba(red, green, blue, alpha)");
7587
+ return colourValue({
7588
+ r: clamp255(args[0].toNumber()),
7589
+ g: clamp255(args[1].toNumber()),
7590
+ b: clamp255(args[2].toNumber()),
7591
+ a: clamp01(readAmount(args[3])),
7592
+ format: "rgba"
7593
+ });
7594
+ }
7595
+ var COLOUR_HSL_FN_IDX = allocatePluginFunctionIndex();
7596
+ function hslHandler(args) {
7597
+ if (args.length !== 3) return badArgs("hsl", "three values: hsl(hue, saturation%, lightness%)");
7598
+ const { r, g, b } = hslToRgb(args[0].toNumber(), readAmount(args[1]), readAmount(args[2]));
7599
+ return colourValue({ r, g, b, a: 1, format: "hsl" });
7600
+ }
7601
+ var COLOUR_HSLA_FN_IDX = allocatePluginFunctionIndex();
7602
+ function hslaHandler(args) {
7603
+ if (args.length !== 4) return badArgs("hsla", "four values: hsla(hue, saturation%, lightness%, alpha)");
7604
+ const { r, g, b } = hslToRgb(args[0].toNumber(), readAmount(args[1]), readAmount(args[2]));
7605
+ return colourValue({ r, g, b, a: clamp01(readAmount(args[3])), format: "hsla" });
7606
+ }
7607
+ function adjuster(name, op) {
7608
+ return (args) => {
7609
+ const c = colourOf(args[0]);
7610
+ if (!c) return notAColour(name);
7611
+ if (args.length < 2) return badArgs(name, `a colour and an amount, e.g. ${name}(#3366cc, 20%)`);
7612
+ return colourValue(op(c, readAmount(args[1])));
7613
+ };
7614
+ }
7615
+ var COLOUR_LIGHTEN_FN_IDX = allocatePluginFunctionIndex();
7616
+ var lightenHandler = adjuster("lighten", lighten);
7617
+ var COLOUR_DARKEN_FN_IDX = allocatePluginFunctionIndex();
7618
+ var darkenHandler = adjuster("darken", darken);
7619
+ var COLOUR_SATURATE_FN_IDX = allocatePluginFunctionIndex();
7620
+ var saturateHandler = adjuster("saturate", saturate);
7621
+ var COLOUR_DESATURATE_FN_IDX = allocatePluginFunctionIndex();
7622
+ var desaturateHandler = adjuster("desaturate", desaturate);
7623
+ var COLOUR_ROTATE_FN_IDX = allocatePluginFunctionIndex();
7624
+ function rotateHandler(args) {
7625
+ const c = colourOf(args[0]);
7626
+ if (!c) return notAColour("rotate");
7627
+ if (args.length < 2) return badArgs("rotate", "a colour and a hue angle in degrees, e.g. rotate(#ff0000, 90)");
7628
+ return colourValue(rotateHue(c, args[1].toNumber()));
7629
+ }
7630
+ var COLOUR_COMPLEMENT_FN_IDX = allocatePluginFunctionIndex();
7631
+ function complementHandler(args) {
7632
+ const c = colourOf(args[0]);
7633
+ if (!c) return notAColour("complement");
7634
+ return colourValue(complement(c));
7635
+ }
7636
+ var COLOUR_GRAYSCALE_FN_IDX = allocatePluginFunctionIndex();
7637
+ function grayscaleHandler(args) {
7638
+ const c = colourOf(args[0]);
7639
+ if (!c) return notAColour("grayscale");
7640
+ return colourValue(grayscale(c));
7641
+ }
7642
+ var COLOUR_INVERT_FN_IDX = allocatePluginFunctionIndex();
7643
+ function invertHandler(args) {
7644
+ const c = colourOf(args[0]);
7645
+ if (!c) return notAColour("invert");
7646
+ return colourValue(invert(c, args.length > 1 ? readAmount(args[1]) : 1));
7647
+ }
7648
+ var COLOUR_MIX_FN_IDX = allocatePluginFunctionIndex();
7649
+ function mixHandler(args) {
7650
+ const a = colourOf(args[0]);
7651
+ const b = colourOf(args[1]);
7652
+ if (!a || !b) return errorValue(ColourErrorCodes.COLOUR_EXPECTED_COLOUR, "mix(...) expects two colours to blend");
7653
+ return colourValue(mix(a, b, args.length > 2 ? readAmount(args[2]) : 0.5));
7654
+ }
7655
+ var COLOUR_ALPHA_FN_IDX = allocatePluginFunctionIndex();
7656
+ function alphaHandler(args) {
7657
+ const c = colourOf(args[0]);
7658
+ if (!c) return notAColour("alpha");
7659
+ if (args.length < 2) return numberValue(c.a);
7660
+ return colourValue(withAlpha(c, readAmount(args[1])));
7661
+ }
7662
+ var COLOUR_CONTRAST_FN_IDX = allocatePluginFunctionIndex();
7663
+ function contrastHandler(args) {
7664
+ const a = colourOf(args[0]);
7665
+ const b = colourOf(args[1]);
7666
+ if (!a || !b) return errorValue(ColourErrorCodes.COLOUR_EXPECTED_COLOUR, "contrast(...) expects two colours");
7667
+ return numberValue(contrastRatio(a, b));
7668
+ }
7669
+ var COLOUR_LUMINANCE_FN_IDX = allocatePluginFunctionIndex();
7670
+ function luminanceHandler(args) {
7671
+ const c = colourOf(args[0]);
7672
+ if (!c) return notAColour("luminance");
7673
+ return numberValue(relativeLuminance(c));
7674
+ }
7675
+ function extractor(name, get) {
7676
+ return (args) => {
7677
+ const c = colourOf(args[0]);
7678
+ if (!c) return notAColour(name);
7679
+ return numberValue(get(c));
7680
+ };
7681
+ }
7682
+ var COLOUR_RED_FN_IDX = allocatePluginFunctionIndex();
7683
+ var redHandler = extractor("red", (c) => c.r);
7684
+ var COLOUR_GREEN_FN_IDX = allocatePluginFunctionIndex();
7685
+ var greenHandler = extractor("green", (c) => c.g);
7686
+ var COLOUR_BLUE_FN_IDX = allocatePluginFunctionIndex();
7687
+ var blueHandler = extractor("blue", (c) => c.b);
7688
+ var COLOUR_HUE_FN_IDX = allocatePluginFunctionIndex();
7689
+ var hueHandler = extractor("hue", (c) => rgbToHsl(c.r, c.g, c.b).h);
7690
+ var COLOUR_SATURATION_FN_IDX = allocatePluginFunctionIndex();
7691
+ var saturationHandler = extractor("saturation", (c) => rgbToHsl(c.r, c.g, c.b).s * 100);
7692
+ var COLOUR_LIGHTNESS_FN_IDX = allocatePluginFunctionIndex();
7693
+ var lightnessHandler = extractor("lightness", (c) => rgbToHsl(c.r, c.g, c.b).l * 100);
7694
+ var COLOUR_HSV_FN_IDX = allocatePluginFunctionIndex();
7695
+ function hsvHandler(args) {
7696
+ if (args.length !== 3) return badArgs("hsv", "three values: hsv(hue, saturation%, value%)");
7697
+ const { r, g, b } = hsvToRgb(args[0].toNumber(), readAmount(args[1]), readAmount(args[2]));
7698
+ return colourValue({ r, g, b, a: 1, format: "hex" });
7699
+ }
7700
+ var COLOUR_HSVA_FN_IDX = allocatePluginFunctionIndex();
7701
+ function hsvaHandler(args) {
7702
+ if (args.length !== 4) return badArgs("hsva", "four values: hsva(hue, saturation%, value%, alpha)");
7703
+ const { r, g, b } = hsvToRgb(args[0].toNumber(), readAmount(args[1]), readAmount(args[2]));
7704
+ return colourValue({ r, g, b, a: clamp01(readAmount(args[3])), format: "hex" });
7705
+ }
7706
+ var COLOUR_HWB_FN_IDX = allocatePluginFunctionIndex();
7707
+ function hwbHandler(args) {
7708
+ if (args.length !== 3) return badArgs("hwb", "three values: hwb(hue, whiteness%, blackness%)");
7709
+ const { r, g, b } = hwbToRgb(args[0].toNumber(), readAmount(args[1]), readAmount(args[2]));
7710
+ return colourValue({ r, g, b, a: 1, format: "hex" });
7711
+ }
7712
+ var COLOUR_TINT_FN_IDX = allocatePluginFunctionIndex();
7713
+ var tintHandler = adjuster("tint", tint);
7714
+ var COLOUR_SHADE_FN_IDX = allocatePluginFunctionIndex();
7715
+ var shadeHandler = adjuster("shade", shade);
7716
+ var COLOUR_TONE_FN_IDX = allocatePluginFunctionIndex();
7717
+ var toneHandler = adjuster("tone", tone);
7718
+ var COLOUR_ISDARK_FN_IDX = allocatePluginFunctionIndex();
7719
+ function isdarkHandler(args) {
7720
+ const c = colourOf(args[0]);
7721
+ if (!c) return notAColour("isdark");
7722
+ return boolValue(isDark(c));
7723
+ }
7724
+ var COLOUR_ISLIGHT_FN_IDX = allocatePluginFunctionIndex();
7725
+ function islightHandler(args) {
7726
+ const c = colourOf(args[0]);
7727
+ if (!c) return notAColour("islight");
7728
+ return boolValue(!isDark(c));
7729
+ }
7730
+ var COLOUR_READABLE_FN_IDX = allocatePluginFunctionIndex();
7731
+ function readableHandler(args) {
7732
+ const c = colourOf(args[0]);
7733
+ if (!c) return notAColour("readable");
7734
+ return colourValue(readableColour(c));
7735
+ }
7736
+ function readWcagThreshold(v) {
7737
+ if (!v) return WCAG_THRESHOLDS.aa;
7738
+ if (v.type === 0 /* Number */) return v.toNumber();
7739
+ if (v.type === 3 /* String */) {
7740
+ const key = v.value.trim().toLowerCase();
7741
+ if (key === "aaa") return WCAG_THRESHOLDS.aaa;
7742
+ if (key === "aa large" || key === "aa-large" || key === "large") return WCAG_THRESHOLDS.aaLarge;
7743
+ if (key === "aaa large" || key === "aaa-large") return WCAG_THRESHOLDS.aaaLarge;
7744
+ }
7745
+ return WCAG_THRESHOLDS.aa;
7746
+ }
7747
+ var COLOUR_CONTRAST_COMPLIANT_FN_IDX = allocatePluginFunctionIndex();
7748
+ function contrastCompliantHandler(args) {
7749
+ const a = colourOf(args[0]);
7750
+ const b = colourOf(args[1]);
7751
+ if (!a || !b) {
7752
+ return errorValue(ColourErrorCodes.COLOUR_EXPECTED_COLOUR, "iscontrastcompliant(...) expects two colours");
7753
+ }
7754
+ return boolValue(contrastRatio(a, b) >= readWcagThreshold(args[2]));
7755
+ }
7756
+ var COLOUR_WCAG_LEVEL_FN_IDX = allocatePluginFunctionIndex();
7757
+ function wcagLevelHandler(args) {
7758
+ const a = colourOf(args[0]);
7759
+ const b = colourOf(args[1]);
7760
+ if (!a || !b) return errorValue(ColourErrorCodes.COLOUR_EXPECTED_COLOUR, "wcaglevel(...) expects two colours");
7761
+ return stringValue(wcagLevel(a, b));
7762
+ }
7763
+ var COLOUR_FUNCTION_INDEX = {
7764
+ color: COLOUR_PARSE_FN_IDX,
7765
+ colour: COLOUR_PARSE_FN_IDX,
7766
+ rgb: COLOUR_RGB_FN_IDX,
7767
+ rgba: COLOUR_RGBA_FN_IDX,
7768
+ hsl: COLOUR_HSL_FN_IDX,
7769
+ hsla: COLOUR_HSLA_FN_IDX,
7770
+ lighten: COLOUR_LIGHTEN_FN_IDX,
7771
+ darken: COLOUR_DARKEN_FN_IDX,
7772
+ saturate: COLOUR_SATURATE_FN_IDX,
7773
+ desaturate: COLOUR_DESATURATE_FN_IDX,
7774
+ desat: COLOUR_DESATURATE_FN_IDX,
7775
+ rotate: COLOUR_ROTATE_FN_IDX,
7776
+ spin: COLOUR_ROTATE_FN_IDX,
7777
+ adjusthue: COLOUR_ROTATE_FN_IDX,
7778
+ complement: COLOUR_COMPLEMENT_FN_IDX,
7779
+ grayscale: COLOUR_GRAYSCALE_FN_IDX,
7780
+ greyscale: COLOUR_GRAYSCALE_FN_IDX,
7781
+ invert: COLOUR_INVERT_FN_IDX,
7782
+ mix: COLOUR_MIX_FN_IDX,
7783
+ alpha: COLOUR_ALPHA_FN_IDX,
7784
+ opacity: COLOUR_ALPHA_FN_IDX,
7785
+ fade: COLOUR_ALPHA_FN_IDX,
7786
+ contrast: COLOUR_CONTRAST_FN_IDX,
7787
+ luminance: COLOUR_LUMINANCE_FN_IDX,
7788
+ red: COLOUR_RED_FN_IDX,
7789
+ green: COLOUR_GREEN_FN_IDX,
7790
+ blue: COLOUR_BLUE_FN_IDX,
7791
+ hue: COLOUR_HUE_FN_IDX,
7792
+ saturation: COLOUR_SATURATION_FN_IDX,
7793
+ lightness: COLOUR_LIGHTNESS_FN_IDX,
7794
+ hsv: COLOUR_HSV_FN_IDX,
7795
+ hsb: COLOUR_HSV_FN_IDX,
7796
+ hsva: COLOUR_HSVA_FN_IDX,
7797
+ hwb: COLOUR_HWB_FN_IDX,
7798
+ tint: COLOUR_TINT_FN_IDX,
7799
+ shade: COLOUR_SHADE_FN_IDX,
7800
+ tone: COLOUR_TONE_FN_IDX,
7801
+ negate: COLOUR_INVERT_FN_IDX,
7802
+ isdark: COLOUR_ISDARK_FN_IDX,
7803
+ islight: COLOUR_ISLIGHT_FN_IDX,
7804
+ readable: COLOUR_READABLE_FN_IDX,
7805
+ contrastcolor: COLOUR_READABLE_FN_IDX,
7806
+ contrastcolour: COLOUR_READABLE_FN_IDX,
7807
+ iscontrastcompliant: COLOUR_CONTRAST_COMPLIANT_FN_IDX,
7808
+ wcaglevel: COLOUR_WCAG_LEVEL_FN_IDX,
7809
+ wcag: COLOUR_WCAG_LEVEL_FN_IDX
7810
+ };
7811
+ var COLOUR_PLUGIN_FUNCTIONS = [
7812
+ { index: COLOUR_PARSE_FN_IDX, handler: colourParseHandler },
7813
+ { index: COLOUR_RGB_FN_IDX, handler: rgbHandler },
7814
+ { index: COLOUR_RGBA_FN_IDX, handler: rgbaHandler },
7815
+ { index: COLOUR_HSL_FN_IDX, handler: hslHandler },
7816
+ { index: COLOUR_HSLA_FN_IDX, handler: hslaHandler },
7817
+ { index: COLOUR_LIGHTEN_FN_IDX, handler: lightenHandler },
7818
+ { index: COLOUR_DARKEN_FN_IDX, handler: darkenHandler },
7819
+ { index: COLOUR_SATURATE_FN_IDX, handler: saturateHandler },
7820
+ { index: COLOUR_DESATURATE_FN_IDX, handler: desaturateHandler },
7821
+ { index: COLOUR_ROTATE_FN_IDX, handler: rotateHandler },
7822
+ { index: COLOUR_COMPLEMENT_FN_IDX, handler: complementHandler },
7823
+ { index: COLOUR_GRAYSCALE_FN_IDX, handler: grayscaleHandler },
7824
+ { index: COLOUR_INVERT_FN_IDX, handler: invertHandler },
7825
+ { index: COLOUR_MIX_FN_IDX, handler: mixHandler },
7826
+ { index: COLOUR_ALPHA_FN_IDX, handler: alphaHandler },
7827
+ { index: COLOUR_CONTRAST_FN_IDX, handler: contrastHandler },
7828
+ { index: COLOUR_LUMINANCE_FN_IDX, handler: luminanceHandler },
7829
+ { index: COLOUR_RED_FN_IDX, handler: redHandler },
7830
+ { index: COLOUR_GREEN_FN_IDX, handler: greenHandler },
7831
+ { index: COLOUR_BLUE_FN_IDX, handler: blueHandler },
7832
+ { index: COLOUR_HUE_FN_IDX, handler: hueHandler },
7833
+ { index: COLOUR_SATURATION_FN_IDX, handler: saturationHandler },
7834
+ { index: COLOUR_LIGHTNESS_FN_IDX, handler: lightnessHandler },
7835
+ { index: COLOUR_HSV_FN_IDX, handler: hsvHandler },
7836
+ { index: COLOUR_HSVA_FN_IDX, handler: hsvaHandler },
7837
+ { index: COLOUR_HWB_FN_IDX, handler: hwbHandler },
7838
+ { index: COLOUR_TINT_FN_IDX, handler: tintHandler },
7839
+ { index: COLOUR_SHADE_FN_IDX, handler: shadeHandler },
7840
+ { index: COLOUR_TONE_FN_IDX, handler: toneHandler },
7841
+ { index: COLOUR_ISDARK_FN_IDX, handler: isdarkHandler },
7842
+ { index: COLOUR_ISLIGHT_FN_IDX, handler: islightHandler },
7843
+ { index: COLOUR_READABLE_FN_IDX, handler: readableHandler },
7844
+ { index: COLOUR_CONTRAST_COMPLIANT_FN_IDX, handler: contrastCompliantHandler },
7845
+ { index: COLOUR_WCAG_LEVEL_FN_IDX, handler: wcagLevelHandler }
7846
+ ];
7847
+
7848
+ // src/packages/colour/parselets/HexColourParselet.ts
7849
+ var HexColourParselet = class {
7850
+ constructor() {
7851
+ this.category = "Colour";
7852
+ }
7853
+ parse(_parser, token, builder) {
7854
+ builder.emitOpcode(13 /* PUSH_STRING */);
7855
+ builder.emitString(token.value);
7856
+ builder.emitOpcode(50 /* CALL_PLUGIN */);
7857
+ builder.emitIndex(COLOUR_PARSE_FN_IDX);
7858
+ builder.emitIndex(1);
7859
+ }
7860
+ };
7861
+
7862
+ // src/packages/colour/parselets/ColourCallParselet.ts
7863
+ var ColourCallParselet = class {
7864
+ constructor() {
7865
+ this.category = "Colour";
7866
+ }
7867
+ parse(parser, token, builder) {
7868
+ const name = token.value.toLowerCase();
7869
+ const fnIdx = COLOUR_FUNCTION_INDEX[name];
7870
+ if (fnIdx === void 0) {
7871
+ throw ErrorFactory.execution("UNKNOWN_COLOUR_FUNCTION", `Unknown colour function: ${name}`, {
7872
+ functionName: name
7873
+ });
7874
+ }
7875
+ parser.consume("LPAREN");
7876
+ let argCount = 0;
7877
+ if (parser.peek()?.type !== "RPAREN") {
7878
+ parser.parseExpression(BindingPower.Lowest, builder);
7879
+ argCount++;
7880
+ while (parser.match("COMMA")) {
7881
+ parser.parseExpression(BindingPower.Lowest, builder);
7882
+ argCount++;
7883
+ }
7884
+ }
7885
+ parser.consume("RPAREN");
7886
+ builder.emitOpcode(50 /* CALL_PLUGIN */);
7887
+ builder.emitIndex(fnIdx);
7888
+ builder.emitIndex(argCount);
7889
+ }
7890
+ };
7891
+
7892
+ // src/packages/colour/normalizer/ColourCallNormalizerRule.ts
7893
+ function colourCallNormalizerRule(priority = 80) {
7894
+ return {
7895
+ name: "colour:call",
7896
+ priority,
7897
+ match(tokens, pos) {
7898
+ const token = tokens[pos];
7899
+ if (!token || token.type !== "IDENT") return null;
7900
+ const word = token.value.toLowerCase();
7901
+ if (COLOUR_FUNCTION_INDEX[word] === void 0) return null;
7902
+ if (tokens[pos + 1]?.type !== "LPAREN") return null;
7903
+ return {
7904
+ consumed: 1,
7905
+ replacement: [
7906
+ new LexerToken(
7907
+ "COLOUR_CALL",
7908
+ tokenTypeId("COLOUR_CALL"),
7909
+ token.value,
7910
+ token.value,
7911
+ token.offset,
7912
+ 0,
7913
+ token.line,
7914
+ token.col
7915
+ )
7916
+ ],
7917
+ ruleName: "colour:call"
7918
+ };
7919
+ }
7920
+ };
7921
+ }
7922
+
7923
+ // src/packages/colour/ColourPackage.ts
7924
+ var retag = (format) => (v) => {
7925
+ if (v.type !== 14 /* Colour */) return v;
7926
+ const c = v.value;
7927
+ return colourValue({ r: c.r, g: c.g, b: c.b, a: c.a, format });
7928
+ };
7929
+ var completionItems = Object.keys(COLOUR_FUNCTION_INDEX).map((label) => ({
7930
+ label,
7931
+ category: "function",
7932
+ detail: "colour"
7933
+ }));
7934
+ var COLOUR_PACKAGE = {
7935
+ name: "solve-colour",
7936
+ prefixParselets: [
7937
+ { tokenType: "HEX_COLOUR", parselet: new HexColourParselet() },
7938
+ { tokenType: "COLOUR_CALL", parselet: new ColourCallParselet() }
7939
+ ],
7940
+ normalizerRules: [colourCallNormalizerRule()],
7941
+ pluginFunctions: COLOUR_PLUGIN_FUNCTIONS.map((f) => ({ index: f.index, handler: f.handler })),
7942
+ asConverters: {
7943
+ rgb: retag("rgb"),
7944
+ rgba: retag("rgba"),
7945
+ hsl: retag("hsl"),
7946
+ hsla: retag("hsla")
7947
+ },
7948
+ // HEX_COLOUR/COLOUR_CALL categories live in the core TokenCategoryMap (both
7949
+ // are declared TokenTypes), so no per-package tokenCategories are needed.
7950
+ completionItems
7951
+ };
7952
+
6486
7953
  // src/packages/builtins.ts
6487
7954
  var BUILTIN_PACKAGES = [
6488
7955
  ARITHMETIC_PACKAGE,
@@ -6503,10 +7970,16 @@ var BUILTIN_PACKAGES = [
6503
7970
  CONVERTERS_PACKAGE,
6504
7971
  MATHPHRASES_PACKAGE,
6505
7972
  FINANCE_PACKAGE,
7973
+ UNCERTAINTY_PACKAGE,
6506
7974
  WEATHER_PACKAGE,
6507
- LINES_PACKAGE
7975
+ LINES_PACKAGE,
7976
+ // After LINES_PACKAGE: goal seek's normalizer reads a LINE_REF the lines
7977
+ // rule mints, and its parselet targets a line reference.
7978
+ GOALSEEK_PACKAGE,
7979
+ TABLES_PACKAGE,
7980
+ COLOUR_PACKAGE
6508
7981
  ];
6509
7982
 
6510
- export { ARITHMETIC_PACKAGE, BIGINT_PACKAGE, BUILTIN_PACKAGES, CONDITIONALS_PACKAGE, CONVERTERS_PACKAGE, CURRENCY_PACKAGE, DATETIME_PACKAGE, DICE_PACKAGE, FINANCE_PACKAGE, FUNCTION_PACKAGE, LINES_PACKAGE, MAPREDUCE_PACKAGE, MATHPHRASES_PACKAGE, MATRIX_PACKAGE, PERCENTAGE_PACKAGE, SYMBOLIC_PACKAGE, TIME_PACKAGE, UOM_PACKAGE, VARIABLES_PACKAGE, VECTOR_PACKAGE, WEATHER_PACKAGE, containsSymbolicCall, createKnowledgePackage, createStocksPackage };
6511
- //# sourceMappingURL=chunk-HMOISHXR.js.map
6512
- //# sourceMappingURL=chunk-HMOISHXR.js.map
7983
+ export { ARITHMETIC_PACKAGE, BIGINT_PACKAGE, BUILTIN_PACKAGES, CONDITIONALS_PACKAGE, CONVERTERS_PACKAGE, CURRENCY_PACKAGE, DATETIME_PACKAGE, DICE_PACKAGE, FINANCE_PACKAGE, FUNCTION_PACKAGE, LINES_PACKAGE, MAPREDUCE_PACKAGE, MATHPHRASES_PACKAGE, MATRIX_PACKAGE, PERCENTAGE_PACKAGE, SYMBOLIC_PACKAGE, TABLES_PACKAGE, TIME_PACKAGE, UOM_PACKAGE, VARIABLES_PACKAGE, VECTOR_PACKAGE, WEATHER_PACKAGE, containsSymbolicCall, createCurrencyPackage, createKnowledgePackage, createStocksPackage };
7984
+ //# sourceMappingURL=chunk-RRLHC37V.js.map
7985
+ //# sourceMappingURL=chunk-RRLHC37V.js.map