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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (171) hide show
  1. package/dist/{Lexer-D9l4Yrk2.d.ts → Lexer-Cfm79Dak.d.cts} +18 -2
  2. package/dist/{Lexer-BJdhlnej.d.cts → Lexer-W9MBOP0V.d.ts} +18 -2
  3. package/dist/{PackageRegistry-DaH4aIKP.d.ts → PackageRegistry-CjDt-Jy_.d.ts} +60 -4
  4. package/dist/{PackageRegistry-D-Tv_7ca.d.cts → PackageRegistry-pHtKythi.d.cts} +60 -4
  5. package/dist/{Parselet-CK8cQu2a.d.cts → Parselet-Cu0bLSis.d.cts} +1 -1
  6. package/dist/{Parselet-ConOIdRO.d.ts → Parselet-DEdF9I7n.d.ts} +1 -1
  7. package/dist/{Token-0jpvUdPY.d.cts → Token-BzG5G4ja.d.cts} +44 -0
  8. package/dist/{Token-0jpvUdPY.d.ts → Token-BzG5G4ja.d.ts} +44 -0
  9. package/dist/TokenNormalizer-DGVa24Q-.d.cts +377 -0
  10. package/dist/TokenNormalizer-t_GotBxr.d.ts +377 -0
  11. package/dist/{chunk-HWSZJQCI.js → chunk-2CS6OMZK.js} +37 -15
  12. package/dist/chunk-2CS6OMZK.js.map +1 -0
  13. package/dist/{chunk-XJCMXB2E.cjs → chunk-2MV4HBKC.cjs} +187 -61
  14. package/dist/chunk-2MV4HBKC.cjs.map +1 -0
  15. package/dist/{chunk-J73SJHR3.cjs → chunk-2NPS5DQ3.cjs} +558 -341
  16. package/dist/chunk-2NPS5DQ3.cjs.map +1 -0
  17. package/dist/{chunk-QWY3VEZN.js → chunk-3MNKQZ77.js} +65 -12
  18. package/dist/chunk-3MNKQZ77.js.map +1 -0
  19. package/dist/{chunk-RZCWSXTA.cjs → chunk-43G3JT2E.cjs} +1491 -218
  20. package/dist/chunk-43G3JT2E.cjs.map +1 -0
  21. package/dist/{chunk-WQTTOGXC.cjs → chunk-4AVD7NZW.cjs} +4 -4
  22. package/dist/{chunk-WQTTOGXC.cjs.map → chunk-4AVD7NZW.cjs.map} +1 -1
  23. package/dist/{chunk-HTXVVJRA.cjs → chunk-4CVLFLOB.cjs} +112 -2
  24. package/dist/chunk-4CVLFLOB.cjs.map +1 -0
  25. package/dist/{chunk-5YEMOYSE.js → chunk-6GCKCWLB.js} +9 -3
  26. package/dist/chunk-6GCKCWLB.js.map +1 -0
  27. package/dist/{chunk-EBSPLUW4.cjs → chunk-AHCLWAM5.cjs} +28 -10
  28. package/dist/chunk-AHCLWAM5.cjs.map +1 -0
  29. package/dist/{chunk-KVILKGMS.js → chunk-AJA6LUI7.js} +36 -2
  30. package/dist/chunk-AJA6LUI7.js.map +1 -0
  31. package/dist/{chunk-EHAHVROS.cjs → chunk-CLVQBF5C.cjs} +5 -5
  32. package/dist/{chunk-EHAHVROS.cjs.map → chunk-CLVQBF5C.cjs.map} +1 -1
  33. package/dist/{chunk-34RRD7PC.js → chunk-CYFK5SY2.js} +111 -3
  34. package/dist/chunk-CYFK5SY2.js.map +1 -0
  35. package/dist/{chunk-M5LX5AOO.js → chunk-DDQVZGLO.js} +25 -12
  36. package/dist/chunk-DDQVZGLO.js.map +1 -0
  37. package/dist/{chunk-LR7YASZF.cjs → chunk-EAAHVJ4P.cjs} +3 -3
  38. package/dist/chunk-EAAHVJ4P.cjs.map +1 -0
  39. package/dist/{chunk-3PPFLFH4.js → chunk-FIFQPKBA.js} +1417 -144
  40. package/dist/chunk-FIFQPKBA.js.map +1 -0
  41. package/dist/{chunk-RIN643A3.js → chunk-GPPLSM2Z.js} +61 -2
  42. package/dist/chunk-GPPLSM2Z.js.map +1 -0
  43. package/dist/{chunk-4QADQTWS.js → chunk-KHJUSHFU.js} +231 -14
  44. package/dist/chunk-KHJUSHFU.js.map +1 -0
  45. package/dist/{chunk-NMD5VRN4.cjs → chunk-M4F66R4O.cjs} +74 -73
  46. package/dist/chunk-M4F66R4O.cjs.map +1 -0
  47. package/dist/{chunk-6NTVRDQV.cjs → chunk-M4Q47KHF.cjs} +126 -73
  48. package/dist/chunk-M4Q47KHF.cjs.map +1 -0
  49. package/dist/{chunk-LIPPNDBE.js → chunk-M5E34VG5.js} +3 -3
  50. package/dist/{chunk-LIPPNDBE.js.map → chunk-M5E34VG5.js.map} +1 -1
  51. package/dist/{chunk-53B6KDDJ.cjs → chunk-MBNQVDVC.cjs} +38 -3
  52. package/dist/{chunk-53B6KDDJ.cjs.map → chunk-MBNQVDVC.cjs.map} +1 -1
  53. package/dist/{chunk-SDGRK7EP.js → chunk-MEOHSQEH.js} +6 -5
  54. package/dist/chunk-MEOHSQEH.js.map +1 -0
  55. package/dist/{chunk-NMCRQP3Z.cjs → chunk-MTX2KVU7.cjs} +71 -70
  56. package/dist/chunk-MTX2KVU7.cjs.map +1 -0
  57. package/dist/{chunk-QNJ4ACRT.cjs → chunk-NJTXJ5AG.cjs} +37 -15
  58. package/dist/chunk-NJTXJ5AG.cjs.map +1 -0
  59. package/dist/{chunk-C4XZV6E7.cjs → chunk-NZFKROS7.cjs} +26 -20
  60. package/dist/chunk-NZFKROS7.cjs.map +1 -0
  61. package/dist/{chunk-64W6GLLZ.js → chunk-OFXOTECC.js} +24 -6
  62. package/dist/chunk-OFXOTECC.js.map +1 -0
  63. package/dist/{chunk-3YNVWKR2.cjs → chunk-OUHT5Z36.cjs} +28 -5
  64. package/dist/chunk-OUHT5Z36.cjs.map +1 -0
  65. package/dist/{chunk-EIGTWK5N.js → chunk-OWRHDUJD.js} +3 -3
  66. package/dist/chunk-OWRHDUJD.js.map +1 -0
  67. package/dist/{chunk-4MG4XKO2.js → chunk-PCPX42KL.js} +38 -3
  68. package/dist/{chunk-4MG4XKO2.js.map → chunk-PCPX42KL.js.map} +1 -1
  69. package/dist/{chunk-GW32KPCU.cjs → chunk-R24DI24X.cjs} +61 -2
  70. package/dist/chunk-R24DI24X.cjs.map +1 -0
  71. package/dist/{chunk-XVWCOTR6.js → chunk-SFQWJMKT.js} +7 -6
  72. package/dist/chunk-SFQWJMKT.js.map +1 -0
  73. package/dist/{chunk-6BKTCEUP.cjs → chunk-TY3TLZAW.cjs} +36 -2
  74. package/dist/chunk-TY3TLZAW.cjs.map +1 -0
  75. package/dist/{chunk-DM3LMRBC.js → chunk-UKPGSAZW.js} +187 -61
  76. package/dist/chunk-UKPGSAZW.js.map +1 -0
  77. package/dist/{chunk-NH2O2AUR.js → chunk-WC5FFSHB.js} +26 -4
  78. package/dist/chunk-WC5FFSHB.js.map +1 -0
  79. package/dist/{chunk-CLL7RUQV.cjs → chunk-WWOLFXTX.cjs} +40 -18
  80. package/dist/chunk-WWOLFXTX.cjs.map +1 -0
  81. package/dist/{chunk-JBSYC7BB.cjs → chunk-X2BWAZAU.cjs} +66 -53
  82. package/dist/chunk-X2BWAZAU.cjs.map +1 -0
  83. package/dist/{chunk-GOLDJNMZ.js → chunk-XBTEO4OB.js} +28 -5
  84. package/dist/chunk-XBTEO4OB.js.map +1 -0
  85. package/dist/{chunk-EPOXXJBK.js → chunk-YU2CUNFO.js} +3 -3
  86. package/dist/{chunk-EPOXXJBK.js.map → chunk-YU2CUNFO.js.map} +1 -1
  87. package/dist/constants.cjs +4 -4
  88. package/dist/constants.js +1 -1
  89. package/dist/engine.cjs +29 -30
  90. package/dist/engine.d.cts +6 -6
  91. package/dist/engine.d.ts +6 -6
  92. package/dist/engine.js +19 -20
  93. package/dist/format.cjs +7 -8
  94. package/dist/format.cjs.map +1 -1
  95. package/dist/format.js +2 -3
  96. package/dist/format.js.map +1 -1
  97. package/dist/index.cjs +28 -29
  98. package/dist/index.cjs.map +1 -1
  99. package/dist/index.d.cts +6 -6
  100. package/dist/index.d.ts +6 -6
  101. package/dist/index.js +20 -21
  102. package/dist/index.js.map +1 -1
  103. package/dist/language.cjs +74 -12
  104. package/dist/language.cjs.map +1 -1
  105. package/dist/language.d.cts +6 -6
  106. package/dist/language.d.ts +6 -6
  107. package/dist/language.js +68 -6
  108. package/dist/language.js.map +1 -1
  109. package/dist/lexer.cjs +19 -19
  110. package/dist/lexer.d.cts +3 -3
  111. package/dist/lexer.d.ts +3 -3
  112. package/dist/lexer.js +7 -7
  113. package/dist/normalizer.cjs +12 -12
  114. package/dist/normalizer.d.cts +4 -210
  115. package/dist/normalizer.d.ts +4 -210
  116. package/dist/normalizer.js +6 -6
  117. package/dist/packages.cjs +36 -37
  118. package/dist/packages.d.cts +5 -47
  119. package/dist/packages.d.ts +5 -47
  120. package/dist/packages.js +13 -14
  121. package/dist/parser.cjs +12 -12
  122. package/dist/parser.d.cts +3 -2
  123. package/dist/parser.d.ts +3 -2
  124. package/dist/parser.js +4 -4
  125. package/dist/resolvers.d.cts +1 -1
  126. package/dist/resolvers.d.ts +1 -1
  127. package/dist/uom.cjs +14 -14
  128. package/dist/uom.d.cts +12 -4
  129. package/dist/uom.d.ts +12 -4
  130. package/dist/uom.js +4 -4
  131. package/dist/vm.cjs +17 -17
  132. package/dist/vm.js +6 -6
  133. package/package.json +2 -1
  134. package/dist/NormalizerRule-BrVoVjmP.d.cts +0 -163
  135. package/dist/NormalizerRule-CEjf1FyD.d.ts +0 -163
  136. package/dist/chunk-34RRD7PC.js.map +0 -1
  137. package/dist/chunk-3PPFLFH4.js.map +0 -1
  138. package/dist/chunk-3YNVWKR2.cjs.map +0 -1
  139. package/dist/chunk-4QADQTWS.js.map +0 -1
  140. package/dist/chunk-5YEMOYSE.js.map +0 -1
  141. package/dist/chunk-64W6GLLZ.js.map +0 -1
  142. package/dist/chunk-6BKTCEUP.cjs.map +0 -1
  143. package/dist/chunk-6NTVRDQV.cjs.map +0 -1
  144. package/dist/chunk-C4XZV6E7.cjs.map +0 -1
  145. package/dist/chunk-CLL7RUQV.cjs.map +0 -1
  146. package/dist/chunk-DM3LMRBC.js.map +0 -1
  147. package/dist/chunk-EBSPLUW4.cjs.map +0 -1
  148. package/dist/chunk-EIGTWK5N.js.map +0 -1
  149. package/dist/chunk-GOLDJNMZ.js.map +0 -1
  150. package/dist/chunk-GW32KPCU.cjs.map +0 -1
  151. package/dist/chunk-HTXVVJRA.cjs.map +0 -1
  152. package/dist/chunk-HWSZJQCI.js.map +0 -1
  153. package/dist/chunk-J73SJHR3.cjs.map +0 -1
  154. package/dist/chunk-JBSYC7BB.cjs.map +0 -1
  155. package/dist/chunk-KVILKGMS.js.map +0 -1
  156. package/dist/chunk-LR7YASZF.cjs.map +0 -1
  157. package/dist/chunk-M5LX5AOO.js.map +0 -1
  158. package/dist/chunk-NH2O2AUR.js.map +0 -1
  159. package/dist/chunk-NMCRQP3Z.cjs.map +0 -1
  160. package/dist/chunk-NMD5VRN4.cjs.map +0 -1
  161. package/dist/chunk-OT6OJY7C.cjs +0 -114
  162. package/dist/chunk-OT6OJY7C.cjs.map +0 -1
  163. package/dist/chunk-QNJ4ACRT.cjs.map +0 -1
  164. package/dist/chunk-QWY3VEZN.js.map +0 -1
  165. package/dist/chunk-RFYD5TJE.js +0 -111
  166. package/dist/chunk-RFYD5TJE.js.map +0 -1
  167. package/dist/chunk-RIN643A3.js.map +0 -1
  168. package/dist/chunk-RZCWSXTA.cjs.map +0 -1
  169. package/dist/chunk-SDGRK7EP.js.map +0 -1
  170. package/dist/chunk-XJCMXB2E.cjs.map +0 -1
  171. package/dist/chunk-XVWCOTR6.js.map +0 -1
@@ -1,19 +1,20 @@
1
1
  'use strict';
2
2
 
3
- var chunkC4XZV6E7_cjs = require('./chunk-C4XZV6E7.cjs');
4
- var chunkEBSPLUW4_cjs = require('./chunk-EBSPLUW4.cjs');
3
+ var chunkNZFKROS7_cjs = require('./chunk-NZFKROS7.cjs');
4
+ var chunkAHCLWAM5_cjs = require('./chunk-AHCLWAM5.cjs');
5
5
  var chunkN5YFR4UP_cjs = require('./chunk-N5YFR4UP.cjs');
6
- var chunkWQTTOGXC_cjs = require('./chunk-WQTTOGXC.cjs');
6
+ var chunk4AVD7NZW_cjs = require('./chunk-4AVD7NZW.cjs');
7
7
  var chunkZ2XP767T_cjs = require('./chunk-Z2XP767T.cjs');
8
- var chunkJ73SJHR3_cjs = require('./chunk-J73SJHR3.cjs');
8
+ var chunk2NPS5DQ3_cjs = require('./chunk-2NPS5DQ3.cjs');
9
9
  var chunkVEKIWQ3F_cjs = require('./chunk-VEKIWQ3F.cjs');
10
- var chunkOT6OJY7C_cjs = require('./chunk-OT6OJY7C.cjs');
10
+ var chunk4CVLFLOB_cjs = require('./chunk-4CVLFLOB.cjs');
11
11
  var chunkOC6LTXYS_cjs = require('./chunk-OC6LTXYS.cjs');
12
- var chunkQNJ4ACRT_cjs = require('./chunk-QNJ4ACRT.cjs');
13
- var chunkNMD5VRN4_cjs = require('./chunk-NMD5VRN4.cjs');
14
- var chunkGW32KPCU_cjs = require('./chunk-GW32KPCU.cjs');
15
- var chunk3YNVWKR2_cjs = require('./chunk-3YNVWKR2.cjs');
16
- var chunkEHAHVROS_cjs = require('./chunk-EHAHVROS.cjs');
12
+ var chunkNJTXJ5AG_cjs = require('./chunk-NJTXJ5AG.cjs');
13
+ var chunkM4F66R4O_cjs = require('./chunk-M4F66R4O.cjs');
14
+ var chunkR24DI24X_cjs = require('./chunk-R24DI24X.cjs');
15
+ var chunkOUHT5Z36_cjs = require('./chunk-OUHT5Z36.cjs');
16
+ var chunkCLVQBF5C_cjs = require('./chunk-CLVQBF5C.cjs');
17
+ var chunkMBNQVDVC_cjs = require('./chunk-MBNQVDVC.cjs');
17
18
  var chunkM4N5GA5Z_cjs = require('./chunk-M4N5GA5Z.cjs');
18
19
 
19
20
  // src/packages/arithmetic/parselets/NumberParselet.ts
@@ -43,7 +44,7 @@ var NumberParselet = class {
43
44
  } else if (CHAINED_DOT_THOUSANDS_GROUPS.test(raw)) {
44
45
  v = parseFloat(raw.split(".").join(""));
45
46
  } else {
46
- const locale = chunk3YNVWKR2_cjs.getLocale(parser.getLocaleCode());
47
+ const locale = chunkOUHT5Z36_cjs.getLocale(parser.getLocaleCode());
47
48
  const decimalSep = locale.display.decimalSeparator;
48
49
  const thousandsSep = locale.display.thousandsSeparator;
49
50
  let normalized = raw;
@@ -126,6 +127,19 @@ var SUFFIX_MAGNITUDE = {
126
127
  B: 9,
127
128
  T: 12
128
129
  };
130
+ var WORD_MAGNITUDE = {
131
+ thousand: 3,
132
+ thousands: 3,
133
+ million: 6,
134
+ millions: 6,
135
+ mn: 6,
136
+ billion: 9,
137
+ billions: 9,
138
+ bn: 9,
139
+ trillion: 12,
140
+ trillions: 12,
141
+ tn: 12
142
+ };
129
143
  var PLAIN_DECIMAL = /^\d+(\.\d+)?$/;
130
144
  function scaleDecimalString(raw, magnitude) {
131
145
  const dotIdx = raw.indexOf(".");
@@ -149,13 +163,13 @@ function largeNumberSuffixNormalizerRule(priority = 65) {
149
163
  const suffixToken = tokens[pos + 1];
150
164
  if (!suffixToken) return null;
151
165
  if (suffixToken.type !== "IDENT" && suffixToken.type !== "UNIT") return null;
152
- if (numberToken.offset + numberToken.text.length !== suffixToken.offset) return null;
153
- const magnitude = SUFFIX_MAGNITUDE[suffixToken.value];
166
+ const adjacent = numberToken.offset + numberToken.text.length === suffixToken.offset;
167
+ const magnitude = adjacent ? SUFFIX_MAGNITUDE[suffixToken.value] ?? WORD_MAGNITUDE[suffixToken.value.toLowerCase()] : WORD_MAGNITUDE[suffixToken.value.toLowerCase()];
154
168
  if (magnitude === void 0) return null;
155
169
  const scaled = scaleDecimalString(numberToken.text, magnitude);
156
170
  return {
157
171
  consumed: 2,
158
- replacement: [chunkEBSPLUW4_cjs.createFusedToken("NUMBER", scaled, tokens.slice(pos, pos + 2))],
172
+ replacement: [chunkAHCLWAM5_cjs.createFusedToken("NUMBER", scaled, tokens.slice(pos, pos + 2))],
159
173
  ruleName: "arithmetic:large-number-suffix"
160
174
  };
161
175
  }
@@ -178,21 +192,25 @@ var ARITHMETIC_PACKAGE = {
178
192
  { tokenType: "BIT_NOT", parselet: new PrefixOpParselet(36 /* BIT_NOT */) }
179
193
  ],
180
194
  infixParselets: [
181
- { tokenType: "PLUS", parselet: new BinaryOpParselet(chunkC4XZV6E7_cjs.BindingPower.Sum, 20 /* ADD */) },
182
- { tokenType: "MINUS", parselet: new BinaryOpParselet(chunkC4XZV6E7_cjs.BindingPower.Sum, 21 /* SUB */) },
183
- { tokenType: "STAR", parselet: new BinaryOpParselet(chunkC4XZV6E7_cjs.BindingPower.Product, 22 /* MUL */) },
184
- { tokenType: "SLASH", parselet: new BinaryOpParselet(chunkC4XZV6E7_cjs.BindingPower.Product, 23 /* DIV */) },
185
- { tokenType: "MOD", parselet: new BinaryOpParselet(chunkC4XZV6E7_cjs.BindingPower.Product, 24 /* MOD */) },
186
- { tokenType: "CARET", parselet: new BinaryOpParselet(chunkC4XZV6E7_cjs.BindingPower.Exponent, 25 /* EXP */) },
187
- { tokenType: "TIMES_BY", parselet: new BinaryOpParselet(chunkC4XZV6E7_cjs.BindingPower.Product, 22 /* MUL */) },
188
- { tokenType: "MULTIPLY_BY", parselet: new BinaryOpParselet(chunkC4XZV6E7_cjs.BindingPower.Product, 22 /* MUL */) },
189
- { tokenType: "DIVIDE_BY", parselet: new BinaryOpParselet(chunkC4XZV6E7_cjs.BindingPower.Product, 23 /* DIV */) },
190
- { tokenType: "LSHIFT", parselet: new BinaryOpParselet(chunkC4XZV6E7_cjs.BindingPower.Sum, 30 /* LSHIFT */) },
191
- { tokenType: "RSHIFT", parselet: new BinaryOpParselet(chunkC4XZV6E7_cjs.BindingPower.Sum, 31 /* RSHIFT */) },
192
- { tokenType: "URSHIFT", parselet: new BinaryOpParselet(chunkC4XZV6E7_cjs.BindingPower.Sum, 32 /* URSHIFT */) },
193
- { tokenType: "BIT_AND", parselet: new BinaryOpParselet(chunkC4XZV6E7_cjs.BindingPower.Product, 33 /* BIT_AND */) },
194
- { tokenType: "BIT_OR", parselet: new BinaryOpParselet(chunkC4XZV6E7_cjs.BindingPower.Sum, 34 /* BIT_OR */) },
195
- { tokenType: "BIT_XOR", parselet: new BinaryOpParselet(chunkC4XZV6E7_cjs.BindingPower.BitwiseXor, 35 /* BIT_XOR */) }
195
+ { tokenType: "PLUS", parselet: new BinaryOpParselet(chunkNZFKROS7_cjs.BindingPower.Sum, 20 /* ADD */) },
196
+ // The word "and", which adds exactly like "+" but at a looser binding
197
+ // power so a phrase parselet can use it as a list separator. See
198
+ // Token.ts's AND_CONJ comment for why it is not simply mapped to PLUS.
199
+ { tokenType: "AND_CONJ", parselet: new BinaryOpParselet(chunkNZFKROS7_cjs.BindingPower.Conjunction, 20 /* ADD */) },
200
+ { tokenType: "MINUS", parselet: new BinaryOpParselet(chunkNZFKROS7_cjs.BindingPower.Sum, 21 /* SUB */) },
201
+ { tokenType: "STAR", parselet: new BinaryOpParselet(chunkNZFKROS7_cjs.BindingPower.Product, 22 /* MUL */) },
202
+ { tokenType: "SLASH", parselet: new BinaryOpParselet(chunkNZFKROS7_cjs.BindingPower.Product, 23 /* DIV */) },
203
+ { tokenType: "MOD", parselet: new BinaryOpParselet(chunkNZFKROS7_cjs.BindingPower.Product, 24 /* MOD */) },
204
+ { tokenType: "CARET", parselet: new BinaryOpParselet(chunkNZFKROS7_cjs.BindingPower.Exponent, 25 /* EXP */) },
205
+ { tokenType: "TIMES_BY", parselet: new BinaryOpParselet(chunkNZFKROS7_cjs.BindingPower.Product, 22 /* MUL */) },
206
+ { tokenType: "MULTIPLY_BY", parselet: new BinaryOpParselet(chunkNZFKROS7_cjs.BindingPower.Product, 22 /* MUL */) },
207
+ { tokenType: "DIVIDE_BY", parselet: new BinaryOpParselet(chunkNZFKROS7_cjs.BindingPower.Product, 23 /* DIV */) },
208
+ { tokenType: "LSHIFT", parselet: new BinaryOpParselet(chunkNZFKROS7_cjs.BindingPower.Sum, 30 /* LSHIFT */) },
209
+ { tokenType: "RSHIFT", parselet: new BinaryOpParselet(chunkNZFKROS7_cjs.BindingPower.Sum, 31 /* RSHIFT */) },
210
+ { tokenType: "URSHIFT", parselet: new BinaryOpParselet(chunkNZFKROS7_cjs.BindingPower.Sum, 32 /* URSHIFT */) },
211
+ { tokenType: "BIT_AND", parselet: new BinaryOpParselet(chunkNZFKROS7_cjs.BindingPower.Product, 33 /* BIT_AND */) },
212
+ { tokenType: "BIT_OR", parselet: new BinaryOpParselet(chunkNZFKROS7_cjs.BindingPower.Sum, 34 /* BIT_OR */) },
213
+ { tokenType: "BIT_XOR", parselet: new BinaryOpParselet(chunkNZFKROS7_cjs.BindingPower.BitwiseXor, 35 /* BIT_XOR */) }
196
214
  ],
197
215
  normalizerRules: [largeNumberSuffixNormalizerRule()]
198
216
  };
@@ -201,20 +219,178 @@ var ARITHMETIC_PACKAGE = {
201
219
  var PercentParselet = class {
202
220
  constructor() {
203
221
  this.category = "Percentage";
204
- this.bindingPower = chunkC4XZV6E7_cjs.BindingPower.Prefix;
222
+ this.bindingPower = chunkNZFKROS7_cjs.BindingPower.Prefix;
205
223
  }
206
224
  parse(parser, left, token, builder) {
207
225
  builder.emitOpcode(10 /* PUSH_NUMBER */);
208
226
  builder.emitNumber(100);
209
227
  builder.emitOpcode(23 /* DIV */);
228
+ builder.emitOpcode(74 /* TO_PERCENTAGE */);
229
+ }
230
+ };
231
+
232
+ // src/packages/percentage/parselets/IsWhatParselet.ts
233
+ var IsWhatParselet = class {
234
+ constructor() {
235
+ this.category = "Percentage";
236
+ // Below arithmetic, so the whole expression to the left is the value being
237
+ // asked about: `10 + 10 is what % of 200` asks about 20.
238
+ this.bindingPower = chunkNZFKROS7_cjs.BindingPower.Conditional;
239
+ }
240
+ parse(parser, _left, _token, builder) {
241
+ if (nextWordIs(parser, "what")) {
242
+ parser.consume();
243
+ this.parseWhatRate(parser, builder);
244
+ return;
245
+ }
246
+ this.parseWhatBase(parser, builder);
247
+ }
248
+ /**
249
+ * `<part> is what % of|off|on <base>`, solving for the rate.
250
+ */
251
+ parseWhatRate(parser, builder) {
252
+ if (!parser.match("PERCENT")) {
253
+ throw chunkM4N5GA5Z_cjs.ErrorFactory.parsing(
254
+ "IS_WHAT_EXPECTED_PERCENT",
255
+ 'Expected "%" after "is what", as in "20 is what % of 200"'
256
+ );
257
+ }
258
+ const preposition = readPreposition(parser);
259
+ if (preposition === null) {
260
+ builder.emitOpcode(74 /* TO_PERCENTAGE */);
261
+ return;
262
+ }
263
+ parser.parseExpression(chunkNZFKROS7_cjs.BindingPower.Conditional, builder);
264
+ builder.emitOpcode(23 /* DIV */);
265
+ if (preposition === "off") {
266
+ builder.emitOpcode(10 /* PUSH_NUMBER */);
267
+ builder.emitNumber(1);
268
+ builder.emitOpcode(2 /* SWAP */);
269
+ builder.emitOpcode(21 /* SUB */);
270
+ } else if (preposition === "on") {
271
+ builder.emitOpcode(10 /* PUSH_NUMBER */);
272
+ builder.emitNumber(1);
273
+ builder.emitOpcode(21 /* SUB */);
274
+ }
275
+ builder.emitOpcode(74 /* TO_PERCENTAGE */);
276
+ }
277
+ /**
278
+ * `<part> is <rate> of|off|on what`, solving for the base.
279
+ */
280
+ parseWhatBase(parser, builder) {
281
+ parser.parseExpression(chunkNZFKROS7_cjs.BindingPower.Conditional, builder);
282
+ if (parser.match("TO")) {
283
+ expectWord(parser, "what", '"81 is 9 to what power"');
284
+ expectWord(parser, "power", '"81 is 9 to what power"');
285
+ builder.emitOpcode(51 /* CALL_BUILTIN */);
286
+ builder.emitIndex(LOG_BUILTIN);
287
+ builder.emitIndex(1);
288
+ builder.emitOpcode(2 /* SWAP */);
289
+ builder.emitOpcode(51 /* CALL_BUILTIN */);
290
+ builder.emitIndex(LOG_BUILTIN);
291
+ builder.emitIndex(1);
292
+ builder.emitOpcode(2 /* SWAP */);
293
+ builder.emitOpcode(23 /* DIV */);
294
+ return;
295
+ }
296
+ const preposition = parser.match("OF_WHAT") ? "of" : parser.match("OFF_WHAT") ? "off" : parser.match("ON_WHAT") ? "on" : null;
297
+ if (preposition === null) {
298
+ throw chunkM4N5GA5Z_cjs.ErrorFactory.parsing(
299
+ "IS_WHAT_EXPECTED_PREPOSITION",
300
+ 'Expected "of what", "off what" or "on what", as in "20 is 10% of what"'
301
+ );
302
+ }
303
+ if (preposition === "of") {
304
+ builder.emitOpcode(23 /* DIV */);
305
+ return;
306
+ }
307
+ builder.emitOpcode(10 /* PUSH_NUMBER */);
308
+ builder.emitNumber(1);
309
+ if (preposition === "off") {
310
+ builder.emitOpcode(2 /* SWAP */);
311
+ builder.emitOpcode(21 /* SUB */);
312
+ } else {
313
+ builder.emitOpcode(20 /* ADD */);
314
+ }
315
+ builder.emitOpcode(23 /* DIV */);
316
+ }
317
+ };
318
+ var LOG_BUILTIN = 5;
319
+ function nextWordIs(parser, word) {
320
+ return (parser.peek()?.text ?? "").toLowerCase() === word;
321
+ }
322
+ function expectWord(parser, word, example) {
323
+ if (!nextWordIs(parser, word)) {
324
+ throw chunkM4N5GA5Z_cjs.ErrorFactory.parsing(
325
+ "IS_WHAT_EXPECTED_WORD",
326
+ `Expected "${word}" here, as in ${example}`
327
+ );
328
+ }
329
+ parser.consume();
330
+ }
331
+ function readPreposition(parser) {
332
+ if (parser.match("OF")) return "of";
333
+ const word = (parser.peek()?.text ?? "").toLowerCase();
334
+ if (word === "off" || word === "on") {
335
+ parser.consume();
336
+ return word;
337
+ }
338
+ return null;
339
+ }
340
+
341
+ // src/packages/percentage/parselets/OnOffBaseParselet.ts
342
+ var OnOffBaseParselet = class {
343
+ /** @param sign - `1` for a markup (`on`), `-1` for a discount (`off`). */
344
+ constructor(sign) {
345
+ this.sign = sign;
346
+ this.category = "Percentage";
347
+ this.bindingPower = chunkNZFKROS7_cjs.BindingPower.Conditional;
348
+ }
349
+ parse(parser, _left, _token, builder) {
350
+ parser.parseExpression(chunkNZFKROS7_cjs.BindingPower.Conditional, builder);
351
+ builder.emitOpcode(2 /* SWAP */);
352
+ builder.emitOpcode(10 /* PUSH_NUMBER */);
353
+ builder.emitNumber(1);
354
+ if (this.sign === 1) {
355
+ builder.emitOpcode(20 /* ADD */);
356
+ } else {
357
+ builder.emitOpcode(2 /* SWAP */);
358
+ builder.emitOpcode(21 /* SUB */);
359
+ }
360
+ builder.emitOpcode(22 /* MUL */);
210
361
  }
211
362
  };
212
363
 
364
+ // src/packages/percentage/normalizer/PercentOnOffNormalizerRule.ts
365
+ function percentOnOffNormalizerRule(priority = 68) {
366
+ return {
367
+ name: "percentage:on-off",
368
+ priority,
369
+ match(tokens, pos) {
370
+ if (tokens[pos]?.type !== "PERCENT") return null;
371
+ const word = tokens[pos + 1];
372
+ if (word?.type !== "IDENT") return null;
373
+ const text = (word.text ?? word.value ?? "").toLowerCase();
374
+ if (text !== "on" && text !== "off") return null;
375
+ const after = (tokens[pos + 2]?.text ?? "").toLowerCase();
376
+ if (after === "what") return null;
377
+ return {
378
+ consumed: 2,
379
+ replacement: [
380
+ tokens[pos],
381
+ chunkAHCLWAM5_cjs.createFusedToken(text === "on" ? "PCT_ON" : "PCT_OFF", text, [word])
382
+ ],
383
+ ruleName: "percentage:on-off"
384
+ };
385
+ }
386
+ };
387
+ }
388
+
213
389
  // src/packages/percentage/parselets/OfParselet.ts
214
390
  var OfParselet = class {
215
391
  constructor() {
216
392
  this.category = "Percentage";
217
- this.bindingPower = chunkC4XZV6E7_cjs.BindingPower.Product;
393
+ this.bindingPower = chunkNZFKROS7_cjs.BindingPower.Product;
218
394
  }
219
395
  parse(parser, left, token, builder) {
220
396
  parser.parseExpression(this.bindingPower, builder);
@@ -226,7 +402,7 @@ var OfParselet = class {
226
402
  var OfWhatIsParselet = class {
227
403
  constructor() {
228
404
  this.category = "Percentage";
229
- this.bindingPower = chunkC4XZV6E7_cjs.BindingPower.Product;
405
+ this.bindingPower = chunkNZFKROS7_cjs.BindingPower.Product;
230
406
  }
231
407
  parse(parser, left, token, builder) {
232
408
  parser.parseExpression(this.bindingPower, builder);
@@ -240,7 +416,7 @@ var OnOffWhatIsParselet = class {
240
416
  constructor(sign) {
241
417
  this.sign = sign;
242
418
  this.category = "Percentage";
243
- this.bindingPower = chunkC4XZV6E7_cjs.BindingPower.Product;
419
+ this.bindingPower = chunkNZFKROS7_cjs.BindingPower.Product;
244
420
  }
245
421
  parse(parser, left, token, builder) {
246
422
  builder.emitOpcode(10 /* PUSH_NUMBER */);
@@ -283,7 +459,7 @@ var IncreaseByParselet = class {
283
459
  constructor(multiplier) {
284
460
  this.multiplier = multiplier;
285
461
  this.category = "Percentage";
286
- this.bindingPower = chunkC4XZV6E7_cjs.BindingPower.Conditional;
462
+ this.bindingPower = chunkNZFKROS7_cjs.BindingPower.Conditional;
287
463
  }
288
464
  parse(parser, left, token, builder) {
289
465
  parser.parseExpression(this.bindingPower, builder);
@@ -299,14 +475,14 @@ var IncreaseByParselet = class {
299
475
  var PercentageChangeParselet = class {
300
476
  constructor() {
301
477
  this.category = "Percentage";
302
- this.bindingPower = chunkC4XZV6E7_cjs.BindingPower.Conditional;
478
+ this.bindingPower = chunkNZFKROS7_cjs.BindingPower.Conditional;
303
479
  }
304
480
  parse(parser, left, token, builder) {
305
481
  if (left.type === "UNIT") {
306
482
  return;
307
483
  }
308
484
  const nextToken = parser.peek();
309
- if (nextToken && ((nextToken.type === "UNIT" || nextToken.type === "IDENT") && chunkEHAHVROS_cjs.isKnownUnit(nextToken.value) || nextToken.type === "IN")) {
485
+ if (nextToken && ((nextToken.type === "UNIT" || nextToken.type === "IDENT") && chunkCLVQBF5C_cjs.isKnownUnit(nextToken.value) || nextToken.type === "IN")) {
310
486
  parser.consume();
311
487
  builder.emitOpcode(13 /* PUSH_STRING */);
312
488
  builder.emitString(nextToken.value);
@@ -328,11 +504,22 @@ var PERCENTAGE_PACKAGE = {
328
504
  name: "solve-percentage",
329
505
  phrases: {
330
506
  "of what is": "OF_WHAT_IS",
507
+ // Fused so that parsing the rate cannot swallow the "of": it is an infix
508
+ // operator in its own right ("10% of 200"), so a sub-expression would
509
+ // consume it and the trailing "what" before this parselet ever looked.
510
+ "of what": "OF_WHAT",
511
+ "off what": "OFF_WHAT",
512
+ "on what": "ON_WHAT",
331
513
  "on what is": "ON_WHAT_IS",
332
514
  "off what is": "OFF_WHAT_IS"
333
515
  },
334
516
  infixParselets: [
335
517
  { tokenType: "PERCENT", parselet: new PercentParselet() },
518
+ { tokenType: "IS", parselet: new IsWhatParselet() },
519
+ // "10% on 200" is 220 and "10% off 200" is 180: the percentage comes
520
+ // first, the base second. The reverse of "200 + 10%".
521
+ { tokenType: "PCT_ON", parselet: new OnOffBaseParselet(1) },
522
+ { tokenType: "PCT_OFF", parselet: new OnOffBaseParselet(-1) },
336
523
  { tokenType: "OF", parselet: new OfParselet() },
337
524
  { tokenType: "OF_WHAT_IS", parselet: new OfWhatIsParselet() },
338
525
  { tokenType: "ON_WHAT_IS", parselet: new OnOffWhatIsParselet(1) },
@@ -344,6 +531,9 @@ var PERCENTAGE_PACKAGE = {
344
531
  prefixParselets: [
345
532
  { tokenType: "INCREASE", parselet: new IncreaseDecreaseParselet(1) },
346
533
  { tokenType: "DECREASE", parselet: new IncreaseDecreaseParselet(-1) }
534
+ ],
535
+ normalizerRules: [
536
+ percentOnOffNormalizerRule()
347
537
  ]
348
538
  };
349
539
 
@@ -370,6 +560,13 @@ var builtinNameToIndex = {
370
560
  arcsin: 11,
371
561
  arccos: 12,
372
562
  arctan: 13,
563
+ // Degree-taking variants (VMBuiltins.ts 87-92).
564
+ sind: 87,
565
+ cosd: 88,
566
+ tand: 89,
567
+ asind: 90,
568
+ acosd: 91,
569
+ atand: 92,
373
570
  sinh: 15,
374
571
  cosh: 16,
375
572
  tanh: 17,
@@ -456,10 +653,10 @@ var FunctionCallParselet = class {
456
653
  parser.consume("LPAREN");
457
654
  let argCount = 0;
458
655
  if (parser.peek()?.type !== "RPAREN") {
459
- parser.parseExpression(chunkC4XZV6E7_cjs.BindingPower.Lowest, builder);
656
+ parser.parseExpression(chunkNZFKROS7_cjs.BindingPower.Lowest, builder);
460
657
  argCount++;
461
658
  while (parser.match("COMMA")) {
462
- parser.parseExpression(chunkC4XZV6E7_cjs.BindingPower.Lowest, builder);
659
+ parser.parseExpression(chunkNZFKROS7_cjs.BindingPower.Lowest, builder);
463
660
  argCount++;
464
661
  }
465
662
  }
@@ -562,18 +759,18 @@ function parseIso8601(s) {
562
759
  }
563
760
  function formatIso8601Local(epochMs) {
564
761
  const d = new Date(epochMs);
565
- const pad = (n) => String(n).padStart(2, "0");
762
+ const pad3 = (n) => String(n).padStart(2, "0");
566
763
  const year = d.getFullYear();
567
- const month = pad(d.getMonth() + 1);
568
- const day = pad(d.getDate());
569
- const hours = pad(d.getHours());
570
- const minutes = pad(d.getMinutes());
571
- const seconds = pad(d.getSeconds());
764
+ const month = pad3(d.getMonth() + 1);
765
+ const day = pad3(d.getDate());
766
+ const hours = pad3(d.getHours());
767
+ const minutes = pad3(d.getMinutes());
768
+ const seconds = pad3(d.getSeconds());
572
769
  const offsetMinutesTotal = -d.getTimezoneOffset();
573
770
  const sign = offsetMinutesTotal >= 0 ? "+" : "-";
574
771
  const absOffset = Math.abs(offsetMinutesTotal);
575
- const offsetHours = pad(Math.floor(absOffset / 60));
576
- const offsetMinutes = pad(absOffset % 60);
772
+ const offsetHours = pad3(Math.floor(absOffset / 60));
773
+ const offsetMinutes = pad3(absOffset % 60);
577
774
  return `${year}-${month}-${day}T${hours}:${minutes}:${seconds}${sign}${offsetHours}:${offsetMinutes}`;
578
775
  }
579
776
  var MS_TIMESTAMP_THRESHOLD = 1e12;
@@ -582,27 +779,27 @@ function unixTimestampToEpochMs(n) {
582
779
  }
583
780
 
584
781
  // src/packages/datetime/parselets/DatetimeTimestampPluginFunctions.ts
585
- var WORKDAYS_IN_FN_IDX = chunkJ73SJHR3_cjs.allocatePluginFunctionIndex();
586
- var WEEKDAY_ON_FN_IDX = chunkJ73SJHR3_cjs.allocatePluginFunctionIndex();
587
- var TO_DATE_FN_IDX = chunkJ73SJHR3_cjs.allocatePluginFunctionIndex();
588
- var TO_TIMESTAMP_FN_IDX = chunkJ73SJHR3_cjs.allocatePluginFunctionIndex();
589
- var MONTH_ON_FN_IDX = chunkJ73SJHR3_cjs.allocatePluginFunctionIndex();
590
- var WEEK_ON_FN_IDX = chunkJ73SJHR3_cjs.allocatePluginFunctionIndex();
591
- var IS_WEEKEND_FN_IDX = chunkJ73SJHR3_cjs.allocatePluginFunctionIndex();
592
- var IS_WORKDAY_FN_IDX = chunkJ73SJHR3_cjs.allocatePluginFunctionIndex();
593
- var SPAN_BETWEEN_FN_IDX = chunkJ73SJHR3_cjs.allocatePluginFunctionIndex();
782
+ var WORKDAYS_IN_FN_IDX = chunk2NPS5DQ3_cjs.allocatePluginFunctionIndex();
783
+ var WEEKDAY_ON_FN_IDX = chunk2NPS5DQ3_cjs.allocatePluginFunctionIndex();
784
+ var TO_DATE_FN_IDX = chunk2NPS5DQ3_cjs.allocatePluginFunctionIndex();
785
+ var TO_TIMESTAMP_FN_IDX = chunk2NPS5DQ3_cjs.allocatePluginFunctionIndex();
786
+ var MONTH_ON_FN_IDX = chunk2NPS5DQ3_cjs.allocatePluginFunctionIndex();
787
+ var WEEK_ON_FN_IDX = chunk2NPS5DQ3_cjs.allocatePluginFunctionIndex();
788
+ var IS_WEEKEND_FN_IDX = chunk2NPS5DQ3_cjs.allocatePluginFunctionIndex();
789
+ var IS_WORKDAY_FN_IDX = chunk2NPS5DQ3_cjs.allocatePluginFunctionIndex();
790
+ var SPAN_BETWEEN_FN_IDX = chunk2NPS5DQ3_cjs.allocatePluginFunctionIndex();
594
791
  function workdaysInDurationHandler(args) {
595
792
  const v = args[0];
596
793
  let totalDays;
597
794
  if (v.type === 6 /* Uom */ && v.unit) {
598
- const measure = chunkQNJ4ACRT_cjs.getMeasure(v.unit);
795
+ const measure = chunkNJTXJ5AG_cjs.getMeasure(v.unit);
599
796
  if (measure !== "time") {
600
797
  return chunkOC6LTXYS_cjs.errorValue(
601
798
  "WORKDAYS_IN_EXPECTED_DURATION",
602
799
  `"workdays in" expects a time duration, got unit "${v.unit}"`
603
800
  );
604
801
  }
605
- totalDays = chunkQNJ4ACRT_cjs.convertUnit(v.toNumber(), v.unit, "day");
802
+ totalDays = chunkNJTXJ5AG_cjs.convertUnit(v.toNumber(), v.unit, "day");
606
803
  } else {
607
804
  totalDays = v.toNumber();
608
805
  }
@@ -763,9 +960,9 @@ var DurationBetweenParselet = class {
763
960
  }
764
961
  parse(parser, token, builder) {
765
962
  const unit = token.value;
766
- parser.parseExpression(chunkC4XZV6E7_cjs.BindingPower.Product, builder);
767
- parser.consume("PLUS");
768
- parser.parseExpression(chunkC4XZV6E7_cjs.BindingPower.Lowest, builder);
963
+ parser.parseExpression(chunkNZFKROS7_cjs.BindingPower.Product, builder);
964
+ parser.consume("AND_CONJ");
965
+ parser.parseExpression(chunkNZFKROS7_cjs.BindingPower.Lowest, builder);
769
966
  builder.emitOpcode(50 /* CALL_PLUGIN */);
770
967
  builder.emitIndex(SPAN_BETWEEN_FN_IDX);
771
968
  builder.emitIndex(2);
@@ -780,7 +977,7 @@ var DayTypePredicateParselet = class {
780
977
  constructor(functionIndex) {
781
978
  this.functionIndex = functionIndex;
782
979
  this.category = "Date/Time";
783
- this.bindingPower = chunkC4XZV6E7_cjs.BindingPower.Conditional;
980
+ this.bindingPower = chunkNZFKROS7_cjs.BindingPower.Conditional;
784
981
  }
785
982
  parse(parser, left, token, builder) {
786
983
  builder.emitOpcode(50 /* CALL_PLUGIN */);
@@ -806,7 +1003,7 @@ var CurrentTimestampParselet = class {
806
1003
  var ToDateParselet = class {
807
1004
  constructor() {
808
1005
  this.category = "Date/Time";
809
- this.bindingPower = chunkC4XZV6E7_cjs.BindingPower.Conditional;
1006
+ this.bindingPower = chunkNZFKROS7_cjs.BindingPower.Conditional;
810
1007
  }
811
1008
  parse(parser, left, token, builder) {
812
1009
  builder.emitOpcode(50 /* CALL_PLUGIN */);
@@ -819,7 +1016,7 @@ var ToDateParselet = class {
819
1016
  var ToTimestampParselet = class {
820
1017
  constructor() {
821
1018
  this.category = "Date/Time";
822
- this.bindingPower = chunkC4XZV6E7_cjs.BindingPower.Conditional;
1019
+ this.bindingPower = chunkNZFKROS7_cjs.BindingPower.Conditional;
823
1020
  }
824
1021
  parse(parser, left, token, builder) {
825
1022
  builder.emitOpcode(50 /* CALL_PLUGIN */);
@@ -853,7 +1050,7 @@ function untilSinceNormalizerRule(priority = 60) {
853
1050
  const fusedType = keywordToken.type === "UNTIL" ? "UNTIL_UNIT" : "SINCE_UNIT";
854
1051
  return {
855
1052
  consumed: 2,
856
- replacement: [chunkEBSPLUW4_cjs.createFusedToken(fusedType, unitToken.value, [unitToken, keywordToken])],
1053
+ replacement: [chunkAHCLWAM5_cjs.createFusedToken(fusedType, unitToken.value, [unitToken, keywordToken])],
857
1054
  ruleName: `datetime:${keywordToken.type.toLowerCase()}`
858
1055
  };
859
1056
  }
@@ -890,7 +1087,7 @@ function betweenUnitNormalizerRule(priority = 60) {
890
1087
  }
891
1088
  return {
892
1089
  consumed: howManyLength + 2,
893
- replacement: [chunkEBSPLUW4_cjs.createFusedToken("BETWEEN_UNIT", unitToken.value, sources)],
1090
+ replacement: [chunkAHCLWAM5_cjs.createFusedToken("BETWEEN_UNIT", unitToken.value, sources)],
894
1091
  ruleName: "datetime:between"
895
1092
  };
896
1093
  }
@@ -911,7 +1108,7 @@ function workdayRateDenominatorNormalizerRule(priority = 70) {
911
1108
  const syntheticOne = {
912
1109
  ...unit,
913
1110
  type: "NUMBER",
914
- typeId: chunkGW32KPCU_cjs.tokenTypeId("NUMBER"),
1111
+ typeId: chunkR24DI24X_cjs.tokenTypeId("NUMBER"),
915
1112
  value: "1",
916
1113
  text: "1"
917
1114
  };
@@ -926,7 +1123,7 @@ function workdayRateDenominatorNormalizerRule(priority = 70) {
926
1123
 
927
1124
  // src/packages/datetime/normalizer/DateLiteralNormalizerRule.ts
928
1125
  var DATETIME_LITERAL_TYPE = "DATETIME_LITERAL";
929
- var DATETIME_LITERAL_TYPE_ID = chunkGW32KPCU_cjs.tokenTypeId(DATETIME_LITERAL_TYPE);
1126
+ var DATETIME_LITERAL_TYPE_ID = chunkR24DI24X_cjs.tokenTypeId(DATETIME_LITERAL_TYPE);
930
1127
  var PLAIN_INTEGER = /^\d+$/;
931
1128
  var DOT_DAY_MONTH = /^\d{1,2}\.\d{1,2}$/;
932
1129
  var DOT_LEADING_YEAR = /^\.(\d{2}|\d{4})$/;
@@ -946,7 +1143,7 @@ function buildDateToken(day, month, year, sourceTokens, ruleName) {
946
1143
  }
947
1144
  const first = sourceTokens[0];
948
1145
  const text = sourceTokens.map((t) => t.value).join("");
949
- const fusedToken = new chunkNMD5VRN4_cjs.LexerToken(
1146
+ const fusedToken = new chunkM4F66R4O_cjs.LexerToken(
950
1147
  DATETIME_LITERAL_TYPE,
951
1148
  DATETIME_LITERAL_TYPE_ID,
952
1149
  String(date.getTime()),
@@ -1008,6 +1205,201 @@ function dateLiteralNormalizerRule() {
1008
1205
  };
1009
1206
  }
1010
1207
 
1208
+ // src/packages/datetime/normalizer/MonthNameDateNormalizerRule.ts
1209
+ var MONTHS = {
1210
+ january: 1,
1211
+ jan: 1,
1212
+ february: 2,
1213
+ feb: 2,
1214
+ march: 3,
1215
+ mar: 3,
1216
+ april: 4,
1217
+ apr: 4,
1218
+ may: 5,
1219
+ june: 6,
1220
+ jun: 6,
1221
+ july: 7,
1222
+ jul: 7,
1223
+ august: 8,
1224
+ aug: 8,
1225
+ september: 9,
1226
+ sep: 9,
1227
+ sept: 9,
1228
+ october: 10,
1229
+ oct: 10,
1230
+ november: 11,
1231
+ nov: 11,
1232
+ december: 12,
1233
+ dec: 12
1234
+ };
1235
+ function monthOf(token) {
1236
+ if (token === void 0) return 0;
1237
+ if (token.type !== "IDENT" && token.type !== "UNIT") return 0;
1238
+ return MONTHS[(token.text ?? token.value ?? "").toLowerCase()] ?? 0;
1239
+ }
1240
+ var PLAIN_INTEGER2 = /^\d+$/;
1241
+ function looksLikeYear(digits) {
1242
+ return digits.length === 4;
1243
+ }
1244
+ function monthNameDateNormalizerRule(priority = 64) {
1245
+ return {
1246
+ name: "datetime:month-name-date",
1247
+ priority,
1248
+ match(tokens, pos) {
1249
+ const RULE = "datetime:month-name-date";
1250
+ const first = tokens[pos];
1251
+ if (first?.type === "NUMBER" && PLAIN_INTEGER2.test(first.text ?? "")) {
1252
+ const month2 = monthOf(tokens[pos + 1]);
1253
+ if (month2 === 0) return null;
1254
+ const day2 = Number(first.text);
1255
+ if (day2 < 1 || day2 > 31) return null;
1256
+ const yearToken2 = tokens[pos + 2];
1257
+ if (yearToken2?.type === "NUMBER" && PLAIN_INTEGER2.test(yearToken2.text ?? "") && looksLikeYear(yearToken2.text ?? "")) {
1258
+ return buildDateToken(day2, month2, Number(yearToken2.text), tokens.slice(pos, pos + 3), RULE);
1259
+ }
1260
+ return buildDateToken(day2, month2, (/* @__PURE__ */ new Date()).getFullYear(), tokens.slice(pos, pos + 2), RULE);
1261
+ }
1262
+ const month = monthOf(first);
1263
+ if (month === 0) return null;
1264
+ const second = tokens[pos + 1];
1265
+ if (second?.type !== "NUMBER" || !PLAIN_INTEGER2.test(second.text ?? "")) return null;
1266
+ if (looksLikeYear(second.text ?? "")) {
1267
+ return buildDateToken(1, month, Number(second.text), tokens.slice(pos, pos + 2), RULE);
1268
+ }
1269
+ const day = Number(second.text);
1270
+ const comma = tokens[pos + 2];
1271
+ const yearToken = tokens[pos + 3];
1272
+ if (comma?.type === "COMMA" && yearToken?.type === "NUMBER" && PLAIN_INTEGER2.test(yearToken.text ?? "") && looksLikeYear(yearToken.text ?? "")) {
1273
+ return buildDateToken(day, month, Number(yearToken.text), tokens.slice(pos, pos + 4), RULE);
1274
+ }
1275
+ if (comma?.type === "NUMBER" && PLAIN_INTEGER2.test(comma.text ?? "") && looksLikeYear(comma.text ?? "")) {
1276
+ return buildDateToken(day, month, Number(comma.text), tokens.slice(pos, pos + 3), RULE);
1277
+ }
1278
+ return buildDateToken(day, month, (/* @__PURE__ */ new Date()).getFullYear(), tokens.slice(pos, pos + 2), RULE);
1279
+ }
1280
+ };
1281
+ }
1282
+
1283
+ // src/packages/datetime/parselets/DaysInPeriodParselet.ts
1284
+ var DAYS_COUNT_BUILTIN = 93;
1285
+ var QUARTERS = {
1286
+ q1: [1, 3],
1287
+ q2: [4, 6],
1288
+ q3: [7, 9],
1289
+ q4: [10, 12]
1290
+ };
1291
+ function daysInMonth(year, month) {
1292
+ return new Date(year, month, 0).getDate();
1293
+ }
1294
+ function daysInYear(year) {
1295
+ return daysInMonth(year, 2) === 29 ? 366 : 365;
1296
+ }
1297
+ var DaysInPeriodParselet = class {
1298
+ constructor() {
1299
+ this.category = "Datetime";
1300
+ }
1301
+ parse(parser, _token, builder) {
1302
+ const total = this.readPeriodDays(parser);
1303
+ builder.emitOpcode(10 /* PUSH_NUMBER */);
1304
+ builder.emitNumber(total);
1305
+ builder.emitOpcode(51 /* CALL_BUILTIN */);
1306
+ builder.emitIndex(DAYS_COUNT_BUILTIN);
1307
+ builder.emitIndex(1);
1308
+ }
1309
+ /** The number of days in the period written after `days in`. */
1310
+ readPeriodDays(parser) {
1311
+ const next = parser.peek();
1312
+ const word = (next?.text ?? next?.value ?? "").toLowerCase();
1313
+ const quarter = QUARTERS[word];
1314
+ if (quarter !== void 0) {
1315
+ parser.consume();
1316
+ const year2 = this.readOptionalYear(parser) ?? (/* @__PURE__ */ new Date()).getFullYear();
1317
+ let total = 0;
1318
+ for (let month = quarter[0]; month <= quarter[1]; month++) {
1319
+ total += daysInMonth(year2, month);
1320
+ }
1321
+ return total;
1322
+ }
1323
+ if (next?.type === "DATETIME_LITERAL") {
1324
+ parser.consume();
1325
+ const date = new Date(Number(next.value));
1326
+ return daysInMonth(date.getFullYear(), date.getMonth() + 1);
1327
+ }
1328
+ const year = this.readOptionalYear(parser);
1329
+ if (year !== null) return daysInYear(year);
1330
+ throw chunkM4N5GA5Z_cjs.ErrorFactory.parsing(
1331
+ "DAYS_IN_EXPECTED_PERIOD",
1332
+ 'Expected a month, quarter or year, as in "days in February 2020" or "days in Q3"'
1333
+ );
1334
+ }
1335
+ /** A four-digit year sitting at the cursor, consumed if present. */
1336
+ readOptionalYear(parser) {
1337
+ const token = parser.peek();
1338
+ if (token?.type !== "NUMBER") return null;
1339
+ const text = token.text ?? token.value ?? "";
1340
+ if (!/^\d{4}$/.test(text)) return null;
1341
+ parser.consume();
1342
+ return Number(text);
1343
+ }
1344
+ };
1345
+
1346
+ // src/packages/datetime/normalizer/DaysInPeriodNormalizerRule.ts
1347
+ var QUARTERS2 = /* @__PURE__ */ new Set(["q1", "q2", "q3", "q4"]);
1348
+ var MONTHS2 = /* @__PURE__ */ new Set([
1349
+ "january",
1350
+ "jan",
1351
+ "february",
1352
+ "feb",
1353
+ "march",
1354
+ "mar",
1355
+ "april",
1356
+ "apr",
1357
+ "may",
1358
+ "june",
1359
+ "jun",
1360
+ "july",
1361
+ "jul",
1362
+ "august",
1363
+ "aug",
1364
+ "september",
1365
+ "sep",
1366
+ "sept",
1367
+ "october",
1368
+ "oct",
1369
+ "november",
1370
+ "nov",
1371
+ "december",
1372
+ "dec"
1373
+ ]);
1374
+ function startsPeriod(token) {
1375
+ if (token === void 0) return false;
1376
+ if (token.type === "DATETIME_LITERAL") return true;
1377
+ const word = (token.text ?? token.value ?? "").toLowerCase();
1378
+ if (token.type === "IDENT" || token.type === "UNIT") {
1379
+ return QUARTERS2.has(word) || MONTHS2.has(word);
1380
+ }
1381
+ return token.type === "NUMBER" && /^\d{4}$/.test(token.text ?? "");
1382
+ }
1383
+ function daysInPeriodNormalizerRule(priority = 60) {
1384
+ return {
1385
+ name: "datetime:days-in-period",
1386
+ priority,
1387
+ match(tokens, pos) {
1388
+ const days = tokens[pos];
1389
+ if (days === void 0) return null;
1390
+ if ((days.text ?? days.value ?? "").toLowerCase() !== "days") return null;
1391
+ if (days.type !== "UNIT" && days.type !== "IDENT") return null;
1392
+ if (tokens[pos + 1]?.type !== "IN") return null;
1393
+ if (!startsPeriod(tokens[pos + 2])) return null;
1394
+ return {
1395
+ consumed: 2,
1396
+ replacement: [chunkAHCLWAM5_cjs.createFusedToken("DAYS_IN_PERIOD", "days in", [days, tokens[pos + 1]])],
1397
+ ruleName: "datetime:days-in-period"
1398
+ };
1399
+ }
1400
+ };
1401
+ }
1402
+
1011
1403
  // src/packages/datetime/normalizer/DailyNoteLinkNormalizerRule.ts
1012
1404
  function dailyNoteLinkNormalizerRule() {
1013
1405
  return {
@@ -1056,6 +1448,10 @@ var DATETIME_PACKAGE = {
1056
1448
  "what week is it": "WEEK_ON",
1057
1449
  "what week is it on": "WEEK_ON",
1058
1450
  "week of": "WEEK_ON",
1451
+ // The spelling Soulver documents. Same question, same handler.
1452
+ "week number on": "WEEK_ON",
1453
+ "week number of": "WEEK_ON",
1454
+ "week number": "WEEK_ON",
1059
1455
  "what week is it in": "WEEK_IN",
1060
1456
  "what week will it be in": "WEEK_IN",
1061
1457
  // Postfix day-type predicates.
@@ -1066,6 +1462,7 @@ var DATETIME_PACKAGE = {
1066
1462
  "is a business day": "IS_WORKDAY"
1067
1463
  },
1068
1464
  prefixParselets: [
1465
+ { tokenType: "DAYS_IN_PERIOD", parselet: new DaysInPeriodParselet() },
1069
1466
  { tokenType: "NOW", parselet: new NowParselet(0) },
1070
1467
  { tokenType: "TODAY", parselet: new NowParselet(0) },
1071
1468
  { tokenType: "TOMORROW", parselet: new NowParselet(1) },
@@ -1096,6 +1493,11 @@ var DATETIME_PACKAGE = {
1096
1493
  betweenUnitNormalizerRule(),
1097
1494
  workdayRateDenominatorNormalizerRule(),
1098
1495
  dateLiteralNormalizerRule(),
1496
+ // Dates with the month spelled out ("March 9, 2024"). Priority 64, just
1497
+ // under the numeric rule, so an all-numeric literal is still claimed by
1498
+ // the rule that has always claimed it.
1499
+ monthNameDateNormalizerRule(),
1500
+ daysInPeriodNormalizerRule(),
1099
1501
  dailyNoteLinkNormalizerRule()
1100
1502
  ],
1101
1503
  pluginFunctions: [
@@ -1434,10 +1836,10 @@ function tryConsumeZoneReference(parser) {
1434
1836
  }
1435
1837
 
1436
1838
  // src/packages/time/parselets/TimezonePluginFunctions.ts
1437
- var ZONE_CONVERT_FN_IDX = chunkJ73SJHR3_cjs.allocatePluginFunctionIndex();
1438
- var TIME_IN_ZONE_FN_IDX = chunkJ73SJHR3_cjs.allocatePluginFunctionIndex();
1439
- var DATE_IN_ZONE_FN_IDX = chunkJ73SJHR3_cjs.allocatePluginFunctionIndex();
1440
- var TIME_DIFFERENCE_FN_IDX = chunkJ73SJHR3_cjs.allocatePluginFunctionIndex();
1839
+ var ZONE_CONVERT_FN_IDX = chunk2NPS5DQ3_cjs.allocatePluginFunctionIndex();
1840
+ var TIME_IN_ZONE_FN_IDX = chunk2NPS5DQ3_cjs.allocatePluginFunctionIndex();
1841
+ var DATE_IN_ZONE_FN_IDX = chunk2NPS5DQ3_cjs.allocatePluginFunctionIndex();
1842
+ var TIME_DIFFERENCE_FN_IDX = chunk2NPS5DQ3_cjs.allocatePluginFunctionIndex();
1441
1843
  function zoneConvertHandler(args) {
1442
1844
  const totalMinutes = args[0].toNumber();
1443
1845
  const sourceZoneRef = args[1].value;
@@ -1608,7 +2010,7 @@ var TimeDifferenceParselet = class {
1608
2010
  `Expected a city or zone name after "time difference between" (e.g. "time difference between Seattle and Moscow")`
1609
2011
  );
1610
2012
  }
1611
- parser.consume("PLUS");
2013
+ parser.consume("AND_CONJ");
1612
2014
  const zone2 = tryConsumeZoneReference(parser);
1613
2015
  if (zone2 === null) {
1614
2016
  throw chunkM4N5GA5Z_cjs.ErrorFactory.parsing(
@@ -1686,14 +2088,14 @@ var VideoTimecodeParselet = class {
1686
2088
  // src/packages/time/timecode/TimecodeMath.ts
1687
2089
  function framesToTimecodeString(totalFrames, fps) {
1688
2090
  const fpsWhole = Math.max(1, Math.round(fps));
1689
- const pad = (n) => String(n).padStart(2, "0");
2091
+ const pad3 = (n) => String(n).padStart(2, "0");
1690
2092
  const frameCount = Math.trunc(totalFrames);
1691
2093
  const frames = (frameCount % fpsWhole + fpsWhole) % fpsWhole;
1692
2094
  const totalSeconds = Math.floor(frameCount / fpsWhole);
1693
2095
  const hours = Math.floor(totalSeconds / 3600);
1694
2096
  const minutes = Math.floor(totalSeconds % 3600 / 60);
1695
2097
  const seconds = totalSeconds % 60;
1696
- return `${pad(hours)}:${pad(minutes)}:${pad(seconds)}:${pad(frames)}`;
2098
+ return `${pad3(hours)}:${pad3(minutes)}:${pad3(seconds)}:${pad3(frames)}`;
1697
2099
  }
1698
2100
 
1699
2101
  // src/packages/time/parselets/FrameCountParselet.ts
@@ -1747,7 +2149,7 @@ function clockTimeNormalizerRule(priority = 65) {
1747
2149
  name: "time:clock-time",
1748
2150
  priority,
1749
2151
  match(tokens, pos) {
1750
- if (chunkEBSPLUW4_cjs.isInsideRangeContext(tokens, pos)) return null;
2152
+ if (chunkAHCLWAM5_cjs.isInsideRangeContext(tokens, pos)) return null;
1751
2153
  const hourToken = tokens[pos];
1752
2154
  if (hourToken.type !== "NUMBER") return null;
1753
2155
  const hour = parseInt(hourToken.value, 10);
@@ -1763,7 +2165,7 @@ function clockTimeNormalizerRule(priority = 65) {
1763
2165
  const consumed = hasAmPm ? 4 : 3;
1764
2166
  return {
1765
2167
  consumed,
1766
- replacement: [chunkEBSPLUW4_cjs.createFusedToken("CLOCK_TIME", String(totalMinutes), tokens.slice(pos, pos + consumed))],
2168
+ replacement: [chunkAHCLWAM5_cjs.createFusedToken("CLOCK_TIME", String(totalMinutes), tokens.slice(pos, pos + consumed))],
1767
2169
  ruleName: "time:clock-time"
1768
2170
  };
1769
2171
  }
@@ -1773,7 +2175,7 @@ function clockTimeNormalizerRule(priority = 65) {
1773
2175
  if (totalMinutes === null) return null;
1774
2176
  return {
1775
2177
  consumed: 2,
1776
- replacement: [chunkEBSPLUW4_cjs.createFusedToken("CLOCK_TIME", String(totalMinutes), tokens.slice(pos, pos + 2))],
2178
+ replacement: [chunkAHCLWAM5_cjs.createFusedToken("CLOCK_TIME", String(totalMinutes), tokens.slice(pos, pos + 2))],
1777
2179
  ruleName: "time:clock-time"
1778
2180
  };
1779
2181
  }
@@ -1796,7 +2198,7 @@ function clockTimeIntervalNormalizerRule(priority = 66) {
1796
2198
  if (end?.type !== "CLOCK_TIME") return null;
1797
2199
  return {
1798
2200
  consumed: 3,
1799
- replacement: [chunkEBSPLUW4_cjs.createFusedToken("CLOCK_TIME_INTERVAL", `${start.value}:${end.value}`, tokens.slice(pos, pos + 3))],
2201
+ replacement: [chunkAHCLWAM5_cjs.createFusedToken("CLOCK_TIME_INTERVAL", `${start.value}:${end.value}`, tokens.slice(pos, pos + 3))],
1800
2202
  ruleName: "time:clock-time-interval"
1801
2203
  };
1802
2204
  }
@@ -1815,7 +2217,7 @@ function fpsRateNormalizerRule(priority = 65) {
1815
2217
  if (fpsToken?.type !== "IDENT" || fpsToken.value.toLowerCase() !== "fps") return null;
1816
2218
  return {
1817
2219
  consumed: 2,
1818
- replacement: [chunkEBSPLUW4_cjs.createFusedToken("FPS_RATE", numberToken.value, tokens.slice(pos, pos + 2))],
2220
+ replacement: [chunkAHCLWAM5_cjs.createFusedToken("FPS_RATE", numberToken.value, tokens.slice(pos, pos + 2))],
1819
2221
  ruleName: "time:fps-rate"
1820
2222
  };
1821
2223
  }
@@ -1828,7 +2230,7 @@ function laptimeNormalizerRule(priority = 70) {
1828
2230
  name: "time:laptime",
1829
2231
  priority,
1830
2232
  match(tokens, pos) {
1831
- if (chunkEBSPLUW4_cjs.isInsideRangeContext(tokens, pos)) return null;
2233
+ if (chunkAHCLWAM5_cjs.isInsideRangeContext(tokens, pos)) return null;
1832
2234
  const h = tokens[pos];
1833
2235
  const c1 = tokens[pos + 1];
1834
2236
  const m = tokens[pos + 2];
@@ -1847,7 +2249,7 @@ function laptimeNormalizerRule(priority = 70) {
1847
2249
  const totalSeconds = hours * 3600 + minutes * 60 + seconds;
1848
2250
  return {
1849
2251
  consumed: 5,
1850
- replacement: [chunkEBSPLUW4_cjs.createFusedToken("LAPTIME", String(totalSeconds), tokens.slice(pos, pos + 5))],
2252
+ replacement: [chunkAHCLWAM5_cjs.createFusedToken("LAPTIME", String(totalSeconds), tokens.slice(pos, pos + 5))],
1851
2253
  ruleName: "time:laptime"
1852
2254
  };
1853
2255
  }
@@ -1860,7 +2262,7 @@ function videoTimecodeNormalizerRule(priority = 75) {
1860
2262
  name: "time:video-timecode",
1861
2263
  priority,
1862
2264
  match(tokens, pos) {
1863
- if (chunkEBSPLUW4_cjs.isInsideRangeContext(tokens, pos)) return null;
2265
+ if (chunkAHCLWAM5_cjs.isInsideRangeContext(tokens, pos)) return null;
1864
2266
  const h = tokens[pos];
1865
2267
  const c1 = tokens[pos + 1];
1866
2268
  const m = tokens[pos + 2];
@@ -1886,7 +2288,7 @@ function videoTimecodeNormalizerRule(priority = 75) {
1886
2288
  return {
1887
2289
  consumed: 7,
1888
2290
  replacement: [
1889
- chunkEBSPLUW4_cjs.createFusedToken("VIDEO_TIMECODE", `${hours},${minutes},${seconds},${frames}`, tokens.slice(pos, pos + 7))
2291
+ chunkAHCLWAM5_cjs.createFusedToken("VIDEO_TIMECODE", `${hours},${minutes},${seconds},${frames}`, tokens.slice(pos, pos + 7))
1890
2292
  ],
1891
2293
  ruleName: "time:video-timecode"
1892
2294
  };
@@ -1906,16 +2308,79 @@ function frameCountNormalizerRule(priority = 65) {
1906
2308
  if (framesToken?.type !== "IDENT" || framesToken.value.toLowerCase() !== "frames") return null;
1907
2309
  return {
1908
2310
  consumed: 2,
1909
- replacement: [chunkEBSPLUW4_cjs.createFusedToken("FRAME_COUNT", numberToken.value, tokens.slice(pos, pos + 2))],
2311
+ replacement: [chunkAHCLWAM5_cjs.createFusedToken("FRAME_COUNT", numberToken.value, tokens.slice(pos, pos + 2))],
1910
2312
  ruleName: "time:frame-count"
1911
2313
  };
1912
2314
  }
1913
2315
  };
1914
2316
  }
1915
2317
 
2318
+ // src/packages/time/TimespanConverters.ts
2319
+ var TIME_KIND = 14;
2320
+ var TIMESPAN_PARTS = [
2321
+ ["week", "weeks", 604800],
2322
+ ["day", "days", 86400],
2323
+ ["hour", "hours", 3600],
2324
+ ["minute", "minutes", 60],
2325
+ ["second", "seconds", 1]
2326
+ ];
2327
+ function durationSeconds(value) {
2328
+ if (value.type === 0 /* Number */) return value.toNumber();
2329
+ if (value.type !== 6 /* Uom */ || value.unit === void 0) return null;
2330
+ const entry = chunkMBNQVDVC_cjs.UNIT_TABLE[value.unit.toLowerCase()];
2331
+ if (entry === void 0 || entry[0] !== TIME_KIND) return null;
2332
+ return value.toNumber() * entry[1];
2333
+ }
2334
+ function toTimespanString(value) {
2335
+ const total = durationSeconds(value);
2336
+ if (total === null) {
2337
+ return chunkOC6LTXYS_cjs.stringValue(`"as timespan" needs a duration, got ${value.unit ?? "a non-duration"}`);
2338
+ }
2339
+ const negative = total < 0;
2340
+ let remaining = Math.abs(total);
2341
+ const parts = [];
2342
+ for (const [singular, plural, seconds] of TIMESPAN_PARTS) {
2343
+ if (seconds === 1) break;
2344
+ const count = Math.floor(remaining / seconds);
2345
+ if (count > 0) {
2346
+ parts.push(`${count} ${count === 1 ? singular : plural}`);
2347
+ remaining -= count * seconds;
2348
+ }
2349
+ }
2350
+ const secondsLeft = Math.round(remaining * 1e3) / 1e3;
2351
+ if (secondsLeft > 0 || parts.length === 0) {
2352
+ parts.push(`${secondsLeft} ${secondsLeft === 1 ? "second" : "seconds"}`);
2353
+ }
2354
+ return chunkOC6LTXYS_cjs.stringValue((negative ? "-" : "") + parts.join(" "));
2355
+ }
2356
+ function toLaptimeString(value) {
2357
+ const total = durationSeconds(value);
2358
+ if (total === null) {
2359
+ return chunkOC6LTXYS_cjs.stringValue(`"as laptime" needs a duration, got ${value.unit ?? "a non-duration"}`);
2360
+ }
2361
+ const negative = total < 0;
2362
+ const rounded = Math.round(Math.abs(total) * 1e3) / 1e3;
2363
+ const hours = Math.floor(rounded / 3600);
2364
+ const minutes = Math.floor(rounded % 3600 / 60);
2365
+ const seconds = rounded % 60;
2366
+ const whole = Math.floor(seconds);
2367
+ const fraction = Math.round((seconds - whole) * 1e3);
2368
+ const base = `${pad(hours)}:${pad(minutes)}:${pad(whole)}`;
2369
+ return chunkOC6LTXYS_cjs.stringValue((negative ? "-" : "") + (fraction > 0 ? `${base}.${String(fraction).padStart(3, "0")}` : base));
2370
+ }
2371
+ function pad(n) {
2372
+ return n < 10 ? `0${n}` : String(n);
2373
+ }
2374
+
1916
2375
  // src/packages/time/TimePackage.ts
1917
2376
  var TIME_PACKAGE = {
1918
2377
  name: "solve-time",
2378
+ asConverters: {
2379
+ // The two ways of writing a duration out. See TimespanConverters.ts.
2380
+ timespan: toTimespanString,
2381
+ laptime: toLaptimeString,
2382
+ lap: toLaptimeString
2383
+ },
1919
2384
  phrases: {
1920
2385
  "time in": "TIME_IN",
1921
2386
  "date in": "DATE_IN",
@@ -2012,14 +2477,14 @@ var keywordLedRollPattern = definePhrasePattern({
2012
2477
  {
2013
2478
  slots: [
2014
2479
  { kind: "keyword", tokenTypes: ["BETWEEN"] },
2015
- { kind: "expr", bindingPower: chunkC4XZV6E7_cjs.BindingPower.Product },
2480
+ { kind: "expr", bindingPower: chunkNZFKROS7_cjs.BindingPower.Product },
2016
2481
  // The word "and" lexes as PLUS, not a dedicated AND token, en.ts's
2017
2482
  // keywordMap maps `and: "PLUS"` (a synonym for arithmetic "+").
2018
2483
  // The original hand-written parselet never actually checked the
2019
2484
  // separator's token type (a bare `parser.consume()`), so it never
2020
2485
  // surfaced this; matching that real lexer behavior explicitly here
2021
2486
  // rather than the aspirational "AND" the old code's comment implied.
2022
- { kind: "keyword", tokenTypes: ["PLUS"] },
2487
+ { kind: "keyword", tokenTypes: ["AND_CONJ"] },
2023
2488
  { kind: "expr" }
2024
2489
  ],
2025
2490
  emit: emitDiceRollCall
@@ -2027,7 +2492,7 @@ var keywordLedRollPattern = definePhrasePattern({
2027
2492
  {
2028
2493
  slots: [
2029
2494
  { kind: "keyword", tokenTypes: ["FROM"] },
2030
- { kind: "expr", bindingPower: chunkC4XZV6E7_cjs.BindingPower.Product },
2495
+ { kind: "expr", bindingPower: chunkNZFKROS7_cjs.BindingPower.Product },
2031
2496
  { kind: "keyword", tokenTypes: ["TO"] },
2032
2497
  { kind: "expr" }
2033
2498
  ],
@@ -2171,11 +2636,11 @@ var GlobalVariableAsyncResolver = class {
2171
2636
  const op = opcodes[i];
2172
2637
  if (op === 62 /* LOAD_GLOBAL_VAR */) {
2173
2638
  const varName = strings[opcodes[i + 1]];
2174
- if (!chunkJ73SJHR3_cjs.sharedGlobalVariableStore.has(varName)) {
2639
+ if (!chunk2NPS5DQ3_cjs.sharedGlobalVariableStore.has(varName)) {
2175
2640
  return this.pendingResultFor(varName, packageId, signal);
2176
2641
  }
2177
2642
  }
2178
- i = chunkWQTTOGXC_cjs.nextInstruction(opcodes, i);
2643
+ i = chunk4AVD7NZW_cjs.nextInstruction(opcodes, i);
2179
2644
  }
2180
2645
  return null;
2181
2646
  }
@@ -2183,7 +2648,7 @@ var GlobalVariableAsyncResolver = class {
2183
2648
  let resolver = this.pending.get(varName);
2184
2649
  if (!resolver) {
2185
2650
  resolver = new Promise((resolve) => {
2186
- const unsubscribe = chunkJ73SJHR3_cjs.sharedGlobalVariableStore.subscribe((name, value) => {
2651
+ const unsubscribe = chunk2NPS5DQ3_cjs.sharedGlobalVariableStore.subscribe((name, value) => {
2187
2652
  if (name !== varName) return;
2188
2653
  unsubscribe();
2189
2654
  this.pending.delete(varName);
@@ -2230,12 +2695,12 @@ var VARIABLES_PACKAGE = {
2230
2695
 
2231
2696
  // src/packages/uom/parselets/UomLiteralParselet.ts
2232
2697
  function resolveUnitAlias(rawUnit) {
2233
- return chunkOT6OJY7C_cjs.resolveCurrencyAlias(rawUnit) ?? rawUnit;
2698
+ return chunk4CVLFLOB_cjs.resolveCurrencyAlias(rawUnit) ?? rawUnit;
2234
2699
  }
2235
2700
  var UomLiteralParselet = class {
2236
2701
  constructor() {
2237
2702
  this.category = "UoM";
2238
- this.bindingPower = chunkC4XZV6E7_cjs.BindingPower.Postfix;
2703
+ this.bindingPower = chunkNZFKROS7_cjs.BindingPower.Postfix;
2239
2704
  }
2240
2705
  parse(parser, left, token, builder) {
2241
2706
  const unit = resolveUnitAlias(token.value);
@@ -2268,14 +2733,14 @@ var UomLiteralParselet = class {
2268
2733
 
2269
2734
  // src/packages/uom/parselets/ConvertParselet.ts
2270
2735
  function resolveUnitAlias2(rawUnit) {
2271
- return chunkOT6OJY7C_cjs.resolveCurrencyAlias(rawUnit) ?? rawUnit;
2736
+ return chunk4CVLFLOB_cjs.resolveCurrencyAlias(rawUnit) ?? rawUnit;
2272
2737
  }
2273
2738
  var ConvertParselet = class {
2274
2739
  constructor() {
2275
2740
  this.category = "UoM";
2276
2741
  }
2277
2742
  parse(parser, token, builder) {
2278
- parser.parseExpression(chunkC4XZV6E7_cjs.BindingPower.Postfix, builder);
2743
+ parser.parseExpression(chunkNZFKROS7_cjs.BindingPower.Postfix, builder);
2279
2744
  const nextToken = parser.peek();
2280
2745
  if (nextToken && nextToken.type === "UNIT") {
2281
2746
  parser.consume();
@@ -2340,13 +2805,317 @@ function uomPossibilitiesNormalizerRule(priority = 60) {
2340
2805
  if (questionToken.type !== "QUESTION") return null;
2341
2806
  return {
2342
2807
  consumed: 3,
2343
- replacement: [chunkEBSPLUW4_cjs.createFusedToken("UOM_POSSIBILITIES_QUERY", unitToken.value, [unitToken, toToken, questionToken])],
2808
+ replacement: [chunkAHCLWAM5_cjs.createFusedToken("UOM_POSSIBILITIES_QUERY", unitToken.value, [unitToken, toToken, questionToken])],
2344
2809
  ruleName: "uom:possibilities"
2345
2810
  };
2346
2811
  }
2347
2812
  };
2348
2813
  }
2349
2814
 
2815
+ // src/packages/uom/normalizer/CompoundQuantityNormalizerRule.ts
2816
+ var TIME_KIND2 = 14;
2817
+ function unitEntry(token, previousKind) {
2818
+ if (token === void 0 || token.type !== "UNIT") return void 0;
2819
+ const spelling = (token.value ?? "").toLowerCase();
2820
+ if (spelling === "m" && previousKind === TIME_KIND2) {
2821
+ return { kind: TIME_KIND2, ratio: 60, spelling: "minutes" };
2822
+ }
2823
+ const entry = chunkMBNQVDVC_cjs.UNIT_TABLE[spelling];
2824
+ return entry === void 0 ? void 0 : { kind: entry[0], ratio: entry[1], spelling };
2825
+ }
2826
+ var PLAIN_NUMBER = /^\d+(\.\d+)?$/;
2827
+ function compoundQuantityNormalizerRule(priority = 63) {
2828
+ return {
2829
+ name: "uom:compound-quantity",
2830
+ priority,
2831
+ match(tokens, pos) {
2832
+ const firstNumber = tokens[pos];
2833
+ if (firstNumber?.type !== "NUMBER" || !PLAIN_NUMBER.test(firstNumber.text ?? "")) return null;
2834
+ const firstUnit = unitEntry(tokens[pos + 1]);
2835
+ if (firstUnit === void 0) return null;
2836
+ const kind = firstUnit.kind;
2837
+ let lastRatio = firstUnit.ratio;
2838
+ let total = Number(firstNumber.text) * lastRatio;
2839
+ let smallestUnit = firstUnit.spelling;
2840
+ let smallestUnitToken = tokens[pos + 1];
2841
+ let consumed = 2;
2842
+ for (; ; ) {
2843
+ const number = tokens[pos + consumed];
2844
+ if (number?.type !== "NUMBER" || !PLAIN_NUMBER.test(number.text ?? "")) break;
2845
+ const entry = unitEntry(tokens[pos + consumed + 1], kind);
2846
+ if (entry === void 0) break;
2847
+ if (entry.kind !== kind || entry.ratio >= lastRatio) break;
2848
+ total += Number(number.text) * entry.ratio;
2849
+ lastRatio = entry.ratio;
2850
+ smallestUnit = entry.spelling;
2851
+ smallestUnitToken = tokens[pos + consumed + 1];
2852
+ consumed += 2;
2853
+ }
2854
+ if (consumed === 2) return null;
2855
+ const source = tokens.slice(pos, pos + consumed);
2856
+ const scaled = total / lastRatio;
2857
+ return {
2858
+ consumed,
2859
+ replacement: [
2860
+ chunkAHCLWAM5_cjs.createFusedToken("NUMBER", String(scaled), source),
2861
+ chunkAHCLWAM5_cjs.createFusedToken("UNIT", smallestUnit, [smallestUnitToken])
2862
+ ],
2863
+ ruleName: "uom:compound-quantity"
2864
+ };
2865
+ }
2866
+ };
2867
+ }
2868
+
2869
+ // src/packages/uom/normalizer/TwoUnitConversionNormalizerRule.ts
2870
+ function isUnit(token) {
2871
+ if (token === void 0 || token.type !== "UNIT") return false;
2872
+ return chunkMBNQVDVC_cjs.UNIT_TABLE[(token.value ?? "").toLowerCase()] !== void 0;
2873
+ }
2874
+ function twoUnitConversionNormalizerRule(priority = 59) {
2875
+ return {
2876
+ name: "uom:two-unit-conversion",
2877
+ priority,
2878
+ match(tokens, pos) {
2879
+ const preposition = tokens[pos];
2880
+ if (preposition?.type !== "IN" && preposition?.type !== "TO") return null;
2881
+ if (!isUnit(tokens[pos + 1])) return null;
2882
+ if (tokens[pos + 2]?.type !== "AND_CONJ") return null;
2883
+ if (!isUnit(tokens[pos + 3])) return null;
2884
+ const major = tokens[pos + 1].value;
2885
+ const minor = tokens[pos + 3].value;
2886
+ return {
2887
+ consumed: 4,
2888
+ replacement: [
2889
+ chunkAHCLWAM5_cjs.createFusedToken("IN_TWO_UNITS", `${major} ${minor}`, tokens.slice(pos, pos + 4))
2890
+ ],
2891
+ ruleName: "uom:two-unit-conversion"
2892
+ };
2893
+ }
2894
+ };
2895
+ }
2896
+
2897
+ // src/packages/uom/normalizer/DegreeSymbolNormalizerRule.ts
2898
+ function degreeSymbolNormalizerRule(priority = 74) {
2899
+ return {
2900
+ name: "uom:degree-symbol",
2901
+ priority,
2902
+ match(tokens, pos) {
2903
+ const number = tokens[pos];
2904
+ if (number?.type !== "NUMBER") return null;
2905
+ const symbol = tokens[pos + 1];
2906
+ if (symbol === void 0 || symbol.type !== "IDENT") return null;
2907
+ if ((symbol.text ?? symbol.value ?? "") !== "\xB0") return null;
2908
+ return {
2909
+ consumed: 2,
2910
+ replacement: [number, chunkAHCLWAM5_cjs.createFusedToken("UNIT", "degrees", [symbol])],
2911
+ ruleName: "uom:degree-symbol"
2912
+ };
2913
+ }
2914
+ };
2915
+ }
2916
+
2917
+ // src/packages/uom/parselets/TwoUnitConversionParselet.ts
2918
+ var TwoUnitConversionParselet = class {
2919
+ constructor(builtinIndex) {
2920
+ this.builtinIndex = builtinIndex;
2921
+ this.category = "Uom";
2922
+ this.bindingPower = chunkNZFKROS7_cjs.BindingPower.Conditional;
2923
+ }
2924
+ parse(_parser, _left, token, builder) {
2925
+ const [major, minor] = String(token.value).split(" ");
2926
+ builder.emitOpcode(13 /* PUSH_STRING */);
2927
+ builder.emitString(major);
2928
+ builder.emitOpcode(13 /* PUSH_STRING */);
2929
+ builder.emitString(minor);
2930
+ builder.emitOpcode(51 /* CALL_BUILTIN */);
2931
+ builder.emitIndex(this.builtinIndex);
2932
+ builder.emitIndex(3);
2933
+ }
2934
+ };
2935
+
2936
+ // src/packages/uom/normalizer/BareRateDenominatorNormalizerRule.ts
2937
+ function isUnit2(token) {
2938
+ if (token === void 0 || token.type !== "UNIT") return false;
2939
+ return chunkMBNQVDVC_cjs.UNIT_TABLE[(token.value ?? "").toLowerCase()] !== void 0;
2940
+ }
2941
+ var PER_WORDS = /* @__PURE__ */ new Set(["per", "a", "an", "each", "every"]);
2942
+ function isCountLabel(token) {
2943
+ if (token === void 0 || token.type !== "IDENT") return false;
2944
+ const word = (token.text ?? token.value ?? "").toLowerCase();
2945
+ if (PER_WORDS.has(word)) return false;
2946
+ return /^[a-z][a-z_]{2,}$/.test(word) && chunkMBNQVDVC_cjs.UNIT_TABLE[word] === void 0;
2947
+ }
2948
+ function bareRateDenominatorNormalizerRule(priority = 75) {
2949
+ return {
2950
+ name: "uom:bare-rate-denominator",
2951
+ priority,
2952
+ match(tokens, pos) {
2953
+ const head = tokens[pos];
2954
+ if (head === void 0) return null;
2955
+ if (head.type === "NUMBER" && isCountLabel(tokens[pos + 1])) {
2956
+ const label = tokens[pos + 1];
2957
+ const after = tokens[pos + 2];
2958
+ const introduces = after?.type === "SLASH" || after?.type === "IDENT" && PER_WORDS.has((after.text ?? after.value ?? "").toLowerCase());
2959
+ if (introduces && isUnit2(tokens[pos + 3])) {
2960
+ return {
2961
+ consumed: 2,
2962
+ replacement: [head, chunkAHCLWAM5_cjs.createFusedToken("UNIT", label.value, [label])],
2963
+ ruleName: "uom:bare-rate-denominator"
2964
+ };
2965
+ }
2966
+ }
2967
+ if (!isUnit2(tokens[pos + 1])) return null;
2968
+ const isSlash = head.type === "SLASH";
2969
+ const word = (head.text ?? head.value ?? "").toLowerCase();
2970
+ const isPerWord = head.type === "IDENT" && PER_WORDS.has(word);
2971
+ if (!isSlash && !isPerWord) return null;
2972
+ const unitToken = tokens[pos + 1];
2973
+ return {
2974
+ consumed: 2,
2975
+ replacement: [
2976
+ chunkAHCLWAM5_cjs.createFusedToken("PER_UNIT", unitToken.value, [head, unitToken])
2977
+ ],
2978
+ ruleName: "uom:bare-rate-denominator"
2979
+ };
2980
+ }
2981
+ };
2982
+ }
2983
+
2984
+ // src/packages/uom/parselets/PerUnitParselet.ts
2985
+ var PerUnitParselet = class {
2986
+ constructor(builtinIndex) {
2987
+ this.builtinIndex = builtinIndex;
2988
+ this.category = "Uom";
2989
+ this.bindingPower = chunkNZFKROS7_cjs.BindingPower.Product;
2990
+ }
2991
+ parse(_parser, _left, token, builder) {
2992
+ builder.emitOpcode(13 /* PUSH_STRING */);
2993
+ builder.emitString(String(token.value));
2994
+ builder.emitOpcode(51 /* CALL_BUILTIN */);
2995
+ builder.emitIndex(this.builtinIndex);
2996
+ builder.emitIndex(2);
2997
+ }
2998
+ };
2999
+
3000
+ // src/packages/uom/parselets/AtRateParselet.ts
3001
+ var AtRateParselet = class {
3002
+ constructor(builtinIndex) {
3003
+ this.builtinIndex = builtinIndex;
3004
+ this.category = "Uom";
3005
+ this.bindingPower = chunkNZFKROS7_cjs.BindingPower.Product;
3006
+ }
3007
+ parse(parser, _left, _token, builder) {
3008
+ parser.parseExpression(chunkNZFKROS7_cjs.BindingPower.Conditional, builder);
3009
+ builder.emitOpcode(51 /* CALL_BUILTIN */);
3010
+ builder.emitIndex(this.builtinIndex);
3011
+ builder.emitIndex(2);
3012
+ }
3013
+ };
3014
+
3015
+ // src/packages/uom/normalizer/AtRateNormalizerRule.ts
3016
+ function isUnit3(token) {
3017
+ if (token === void 0 || token.type !== "UNIT") return false;
3018
+ return chunkMBNQVDVC_cjs.UNIT_TABLE[(token.value ?? "").toLowerCase()] !== void 0;
3019
+ }
3020
+ var PER_WORDS2 = /* @__PURE__ */ new Set(["per", "a", "an", "each", "every"]);
3021
+ function hasRateAhead(tokens, from) {
3022
+ for (let i = from; i < tokens.length && i < from + 5; i++) {
3023
+ const token = tokens[i];
3024
+ if (token.type === "PER_UNIT") return true;
3025
+ if (token.type === "EOF" || token.type === "COMMA" || token.type === "RATE_AT") return false;
3026
+ const word = (token.text ?? token.value ?? "").toLowerCase();
3027
+ const introducesDenominator = token.type === "SLASH" || token.type === "IDENT" && PER_WORDS2.has(word);
3028
+ if (introducesDenominator && isUnit3(tokens[i + 1])) return true;
3029
+ }
3030
+ return false;
3031
+ }
3032
+ function atRateNormalizerRule(priority = 73) {
3033
+ return {
3034
+ name: "uom:at-rate",
3035
+ priority,
3036
+ match(tokens, pos) {
3037
+ if (tokens[pos]?.type !== "RATE_AT") return null;
3038
+ if (!hasRateAhead(tokens, pos + 1)) return null;
3039
+ return {
3040
+ consumed: 1,
3041
+ replacement: [chunkAHCLWAM5_cjs.createFusedToken("AT_RATE", "at", [tokens[pos]])],
3042
+ ruleName: "uom:at-rate"
3043
+ };
3044
+ }
3045
+ };
3046
+ }
3047
+
3048
+ // src/packages/uom/normalizer/ForDurationNormalizerRule.ts
3049
+ function isUnit4(token) {
3050
+ if (token === void 0 || token.type !== "UNIT") return false;
3051
+ return chunkMBNQVDVC_cjs.UNIT_TABLE[(token.value ?? "").toLowerCase()] !== void 0;
3052
+ }
3053
+ var ARTICLES = /* @__PURE__ */ new Set(["a", "an"]);
3054
+ function forDurationNormalizerRule(priority = 76) {
3055
+ return {
3056
+ name: "uom:for-duration",
3057
+ priority,
3058
+ match(tokens, pos) {
3059
+ const head = tokens[pos];
3060
+ if (head?.type !== "FOR_DURATION") return null;
3061
+ const article = tokens[pos + 1];
3062
+ if (article?.type !== "IDENT") return null;
3063
+ if (!ARTICLES.has((article.text ?? article.value ?? "").toLowerCase())) return null;
3064
+ if (!isUnit4(tokens[pos + 2])) return null;
3065
+ return {
3066
+ consumed: 3,
3067
+ replacement: [
3068
+ chunkAHCLWAM5_cjs.createFusedToken("STAR", "*", [head]),
3069
+ chunkAHCLWAM5_cjs.createFusedToken("NUMBER", "1", [article]),
3070
+ tokens[pos + 2]
3071
+ ],
3072
+ ruleName: "uom:for-duration"
3073
+ };
3074
+ }
3075
+ };
3076
+ }
3077
+
3078
+ // src/packages/uom/normalizer/ReversedConversionNormalizerRule.ts
3079
+ function isUnit5(token) {
3080
+ if (token === void 0 || token.type !== "UNIT") return false;
3081
+ return chunkMBNQVDVC_cjs.UNIT_TABLE[(token.value ?? "").toLowerCase()] !== void 0;
3082
+ }
3083
+ var ARTICLES2 = /* @__PURE__ */ new Set(["a", "an"]);
3084
+ function reversedConversionNormalizerRule(priority = 61) {
3085
+ return {
3086
+ name: "uom:reversed-conversion",
3087
+ priority,
3088
+ match(tokens, pos) {
3089
+ if (pos !== 0 || !isUnit5(tokens[pos])) return null;
3090
+ if (tokens[pos + 1]?.type !== "IN") return null;
3091
+ const target = tokens[pos];
3092
+ const third = tokens[pos + 2];
3093
+ if (third === void 0) return null;
3094
+ if (third.type === "NUMBER" && isUnit5(tokens[pos + 3])) {
3095
+ return {
3096
+ consumed: 4,
3097
+ replacement: [third, tokens[pos + 3], tokens[pos + 1], target],
3098
+ ruleName: "uom:reversed-conversion"
3099
+ };
3100
+ }
3101
+ const word = (third.text ?? third.value ?? "").toLowerCase();
3102
+ if (third.type === "IDENT" && ARTICLES2.has(word) && isUnit5(tokens[pos + 3])) {
3103
+ return {
3104
+ consumed: 4,
3105
+ replacement: [
3106
+ chunkAHCLWAM5_cjs.createFusedToken("NUMBER", "1", [third]),
3107
+ tokens[pos + 3],
3108
+ tokens[pos + 1],
3109
+ target
3110
+ ],
3111
+ ruleName: "uom:reversed-conversion"
3112
+ };
3113
+ }
3114
+ return null;
3115
+ }
3116
+ };
3117
+ }
3118
+
2350
3119
  // src/packages/uom/data/IngredientDensities.ts
2351
3120
  var INGREDIENT_DENSITIES = {
2352
3121
  water: 1,
@@ -2443,7 +3212,7 @@ function getIngredientDensity(name) {
2443
3212
  }
2444
3213
 
2445
3214
  // src/packages/uom/parselets/CookingPluginFunctions.ts
2446
- var COOKING_CONVERT_IDX = chunkJ73SJHR3_cjs.allocatePluginFunctionIndex();
3215
+ var COOKING_CONVERT_IDX = chunk2NPS5DQ3_cjs.allocatePluginFunctionIndex();
2447
3216
  function cookingConvertHandler(args) {
2448
3217
  const amountValue = args[0];
2449
3218
  const ingredientName = args[1].value;
@@ -2456,8 +3225,8 @@ function cookingConvertHandler(args) {
2456
3225
  }
2457
3226
  const sourceUnit = amountValue.unit;
2458
3227
  const amount = amountValue.toNumber();
2459
- const sourceMeasure = chunkQNJ4ACRT_cjs.getMeasure(sourceUnit);
2460
- const targetMeasure = chunkQNJ4ACRT_cjs.getMeasure(targetUnitText);
3228
+ const sourceMeasure = chunkNJTXJ5AG_cjs.getMeasure(sourceUnit);
3229
+ const targetMeasure = chunkNJTXJ5AG_cjs.getMeasure(targetUnitText);
2461
3230
  const isMassOrVolume = (m) => m === "mass" || m === "volume";
2462
3231
  if (!isMassOrVolume(sourceMeasure)) {
2463
3232
  return chunkOC6LTXYS_cjs.errorValue(
@@ -2472,7 +3241,7 @@ function cookingConvertHandler(args) {
2472
3241
  );
2473
3242
  }
2474
3243
  if (sourceMeasure === targetMeasure) {
2475
- return chunkOC6LTXYS_cjs.uomValue(chunkQNJ4ACRT_cjs.convertUnit(amount, sourceUnit, targetUnitText), targetUnitText);
3244
+ return chunkOC6LTXYS_cjs.uomValue(chunkNJTXJ5AG_cjs.convertUnit(amount, sourceUnit, targetUnitText), targetUnitText);
2476
3245
  }
2477
3246
  const density = getIngredientDensity(ingredientName);
2478
3247
  if (density === void 0) {
@@ -2482,14 +3251,14 @@ function cookingConvertHandler(args) {
2482
3251
  );
2483
3252
  }
2484
3253
  if (sourceMeasure === "mass") {
2485
- const grams2 = chunkQNJ4ACRT_cjs.convertUnit(amount, sourceUnit, "g");
3254
+ const grams2 = chunkNJTXJ5AG_cjs.convertUnit(amount, sourceUnit, "g");
2486
3255
  const mL2 = grams2 / density;
2487
- const result2 = chunkQNJ4ACRT_cjs.convertUnit(mL2, "ml", targetUnitText);
3256
+ const result2 = chunkNJTXJ5AG_cjs.convertUnit(mL2, "ml", targetUnitText);
2488
3257
  return chunkOC6LTXYS_cjs.uomValue(result2, targetUnitText);
2489
3258
  }
2490
- const mL = chunkQNJ4ACRT_cjs.convertUnit(amount, sourceUnit, "ml");
3259
+ const mL = chunkNJTXJ5AG_cjs.convertUnit(amount, sourceUnit, "ml");
2491
3260
  const grams = mL * density;
2492
- const result = chunkQNJ4ACRT_cjs.convertUnit(grams, "g", targetUnitText);
3261
+ const result = chunkNJTXJ5AG_cjs.convertUnit(grams, "g", targetUnitText);
2493
3262
  return chunkOC6LTXYS_cjs.uomValue(result, targetUnitText);
2494
3263
  }
2495
3264
 
@@ -2533,7 +3302,7 @@ function ingredientNameNormalizerRule(priority = 68) {
2533
3302
  const candidate = candidateTokens.map((t) => t.value.toLowerCase()).join(" ");
2534
3303
  return {
2535
3304
  consumed: wordCount,
2536
- replacement: [chunkEBSPLUW4_cjs.createFusedToken("INGREDIENT_NAME", candidate, candidateTokens)],
3305
+ replacement: [chunkAHCLWAM5_cjs.createFusedToken("INGREDIENT_NAME", candidate, candidateTokens)],
2537
3306
  ruleName: "uom-cooking:ingredient-name"
2538
3307
  };
2539
3308
  }
@@ -2550,11 +3319,21 @@ var UOM_PACKAGE = {
2550
3319
  { tokenType: "UOM_POSSIBILITIES_QUERY", parselet: new PossibilitiesParselet() }
2551
3320
  ],
2552
3321
  infixParselets: [
3322
+ { tokenType: "IN_TWO_UNITS", parselet: new TwoUnitConversionParselet(94) },
3323
+ { tokenType: "PER_UNIT", parselet: new PerUnitParselet(95) },
3324
+ { tokenType: "AT_RATE", parselet: new AtRateParselet(96) },
2553
3325
  { tokenType: "UNIT", parselet: new UomLiteralParselet() },
2554
3326
  { tokenType: "INGREDIENT_NAME", parselet: new CookingConversionParselet() }
2555
3327
  ],
2556
3328
  normalizerRules: [
2557
3329
  uomPossibilitiesNormalizerRule(),
3330
+ compoundQuantityNormalizerRule(),
3331
+ twoUnitConversionNormalizerRule(),
3332
+ degreeSymbolNormalizerRule(),
3333
+ atRateNormalizerRule(),
3334
+ forDurationNormalizerRule(),
3335
+ bareRateDenominatorNormalizerRule(),
3336
+ reversedConversionNormalizerRule(),
2558
3337
  ingredientNameNormalizerRule()
2559
3338
  ],
2560
3339
  pluginFunctions: [
@@ -2568,8 +3347,8 @@ var CurrencySymbolParselet = class {
2568
3347
  this.category = "UoM";
2569
3348
  }
2570
3349
  parse(parser, token, builder) {
2571
- const currency = chunkOT6OJY7C_cjs.resolveCurrencyAlias(token.value) ?? token.value.toUpperCase();
2572
- parser.parseExpression(chunkC4XZV6E7_cjs.BindingPower.Prefix, builder);
3350
+ const currency = chunk4CVLFLOB_cjs.resolveCurrencyAlias(token.value) ?? token.value.toUpperCase();
3351
+ parser.parseExpression(chunkNZFKROS7_cjs.BindingPower.Prefix, builder);
2573
3352
  if (parser.peek()?.type === "UNIT") {
2574
3353
  parser.consume();
2575
3354
  }
@@ -2590,7 +3369,7 @@ var InParselet = class {
2590
3369
  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")) {
2591
3370
  parser.consume();
2592
3371
  builder.emitOpcode(13 /* PUSH_STRING */);
2593
- builder.emitString(chunkOT6OJY7C_cjs.resolveCurrencyAlias(targetToken.value) ?? targetToken.value);
3372
+ builder.emitString(chunk4CVLFLOB_cjs.resolveCurrencyAlias(targetToken.value) ?? targetToken.value);
2594
3373
  builder.emitOpcode(84 /* UOM_CONVERT_IN */);
2595
3374
  }
2596
3375
  }
@@ -2599,7 +3378,7 @@ var InParselet = class {
2599
3378
  // src/packages/currency/CurrencyPackage.ts
2600
3379
  var CURRENCY_PACKAGE = {
2601
3380
  name: "solve-currency",
2602
- asyncResolvers: [new chunkWQTTOGXC_cjs.CurrencyAsyncResolver()],
3381
+ asyncResolvers: [new chunk4AVD7NZW_cjs.CurrencyAsyncResolver()],
2603
3382
  prefixParselets: [
2604
3383
  { tokenType: "DOLLAR", parselet: new CurrencySymbolParselet() },
2605
3384
  { tokenType: "POUND", parselet: new CurrencySymbolParselet() },
@@ -2712,7 +3491,7 @@ var MatrixLiteralParselet = class {
2712
3491
  var MatrixIndexParselet = class {
2713
3492
  constructor() {
2714
3493
  this.category = "Matrix";
2715
- this.bindingPower = chunkC4XZV6E7_cjs.BindingPower.Postfix;
3494
+ this.bindingPower = chunkNZFKROS7_cjs.BindingPower.Postfix;
2716
3495
  }
2717
3496
  parse(parser, _left, _token, builder) {
2718
3497
  const isSlice = this.hasTopLevelColon(parser);
@@ -2815,8 +3594,8 @@ function parseTransform(parser, builder) {
2815
3594
  }
2816
3595
  return { kind: 2, name };
2817
3596
  }
2818
- const transformBuilder = new chunkC4XZV6E7_cjs.BytecodeBuilder();
2819
- parser.parseExpression(chunkC4XZV6E7_cjs.BindingPower.Lowest, transformBuilder);
3597
+ const transformBuilder = new chunkNZFKROS7_cjs.BytecodeBuilder();
3598
+ parser.parseExpression(chunkNZFKROS7_cjs.BindingPower.Lowest, transformBuilder);
2820
3599
  parser.setBuilder(builder);
2821
3600
  const program = transformBuilder.build();
2822
3601
  if (program.hasAsync) {
@@ -2902,7 +3681,7 @@ var ReduceParselet = class {
2902
3681
  parseCollectionExpr(parser, builder);
2903
3682
  let hasInitial = 0;
2904
3683
  if (parser.match("COMMA")) {
2905
- parser.parseExpression(chunkC4XZV6E7_cjs.BindingPower.Lowest, builder);
3684
+ parser.parseExpression(chunkNZFKROS7_cjs.BindingPower.Lowest, builder);
2906
3685
  hasInitial = 1;
2907
3686
  }
2908
3687
  parser.consume("RPAREN");
@@ -2917,10 +3696,10 @@ var SumParselet = class {
2917
3696
  }
2918
3697
  parse(parser, _token, builder) {
2919
3698
  parser.consume("LPAREN");
2920
- const bodyBuilder = new chunkC4XZV6E7_cjs.BytecodeBuilder();
3699
+ const bodyBuilder = new chunkNZFKROS7_cjs.BytecodeBuilder();
2921
3700
  bodyBuilder.emitOpcode(60 /* LOAD_VAR */);
2922
3701
  bodyBuilder.emitString("acc");
2923
- parser.parseExpression(chunkC4XZV6E7_cjs.BindingPower.Lowest, bodyBuilder);
3702
+ parser.parseExpression(chunkNZFKROS7_cjs.BindingPower.Lowest, bodyBuilder);
2924
3703
  parser.setBuilder(builder);
2925
3704
  bodyBuilder.emitOpcode(20 /* ADD */);
2926
3705
  const bodyProgram = bodyBuilder.build();
@@ -2944,10 +3723,10 @@ var ProdParselet = class {
2944
3723
  }
2945
3724
  parse(parser, _token, builder) {
2946
3725
  parser.consume("LPAREN");
2947
- const bodyBuilder = new chunkC4XZV6E7_cjs.BytecodeBuilder();
3726
+ const bodyBuilder = new chunkNZFKROS7_cjs.BytecodeBuilder();
2948
3727
  bodyBuilder.emitOpcode(60 /* LOAD_VAR */);
2949
3728
  bodyBuilder.emitString("acc");
2950
- parser.parseExpression(chunkC4XZV6E7_cjs.BindingPower.Lowest, bodyBuilder);
3729
+ parser.parseExpression(chunkNZFKROS7_cjs.BindingPower.Lowest, bodyBuilder);
2951
3730
  parser.setBuilder(builder);
2952
3731
  bodyBuilder.emitOpcode(22 /* MUL */);
2953
3732
  const bodyProgram = bodyBuilder.build();
@@ -2995,7 +3774,7 @@ function mapReduceCallNormalizerRule(priority = 80) {
2995
3774
  return {
2996
3775
  consumed: 1,
2997
3776
  replacement: [
2998
- new chunkNMD5VRN4_cjs.LexerToken(tokenType, chunkGW32KPCU_cjs.tokenTypeId(tokenType), token.value, token.value, token.offset, 0, token.line, token.col)
3777
+ new chunkM4F66R4O_cjs.LexerToken(tokenType, chunkR24DI24X_cjs.tokenTypeId(tokenType), token.value, token.value, token.offset, 0, token.line, token.col)
2999
3778
  ],
3000
3779
  ruleName: "mapreduce:call"
3001
3780
  };
@@ -3037,7 +3816,7 @@ var ExpandParselet = class {
3037
3816
  }
3038
3817
  parse(parser, _token, builder) {
3039
3818
  parser.consume("LPAREN");
3040
- parser.parseExpression(chunkC4XZV6E7_cjs.BindingPower.Lowest, builder);
3819
+ parser.parseExpression(chunkNZFKROS7_cjs.BindingPower.Lowest, builder);
3041
3820
  parser.consume("RPAREN");
3042
3821
  builder.emitOpcode(51 /* CALL_BUILTIN */);
3043
3822
  builder.emitIndex(SYMBOLIC_BUILTIN_EXPAND);
@@ -3052,7 +3831,7 @@ var FactorParselet = class {
3052
3831
  }
3053
3832
  parse(parser, _token, builder) {
3054
3833
  parser.consume("LPAREN");
3055
- parser.parseExpression(chunkC4XZV6E7_cjs.BindingPower.Lowest, builder);
3834
+ parser.parseExpression(chunkNZFKROS7_cjs.BindingPower.Lowest, builder);
3056
3835
  parser.consume("RPAREN");
3057
3836
  builder.emitOpcode(51 /* CALL_BUILTIN */);
3058
3837
  builder.emitIndex(SYMBOLIC_BUILTIN_FACTOR);
@@ -3067,9 +3846,9 @@ var SolveParselet = class {
3067
3846
  }
3068
3847
  parse(parser, _token, builder) {
3069
3848
  parser.consume("LPAREN");
3070
- parser.parseExpression(chunkC4XZV6E7_cjs.BindingPower.Lowest, builder);
3849
+ parser.parseExpression(chunkNZFKROS7_cjs.BindingPower.Lowest, builder);
3071
3850
  if (parser.match("EQUALS")) {
3072
- parser.parseExpression(chunkC4XZV6E7_cjs.BindingPower.Lowest, builder);
3851
+ parser.parseExpression(chunkNZFKROS7_cjs.BindingPower.Lowest, builder);
3073
3852
  } else {
3074
3853
  builder.emitOpcode(10 /* PUSH_NUMBER */);
3075
3854
  builder.emitNumber(0);
@@ -3115,11 +3894,11 @@ var DerParselet = class {
3115
3894
  }
3116
3895
  parse(parser, _token, builder) {
3117
3896
  parser.consume("LPAREN");
3118
- parser.parseExpression(chunkC4XZV6E7_cjs.BindingPower.Lowest, builder);
3897
+ parser.parseExpression(chunkNZFKROS7_cjs.BindingPower.Lowest, builder);
3119
3898
  parser.consume("COMMA");
3120
3899
  consumeVariableName(parser, builder, "der");
3121
3900
  if (parser.match("COMMA")) {
3122
- parser.parseExpression(chunkC4XZV6E7_cjs.BindingPower.Lowest, builder);
3901
+ parser.parseExpression(chunkNZFKROS7_cjs.BindingPower.Lowest, builder);
3123
3902
  } else {
3124
3903
  builder.emitOpcode(10 /* PUSH_NUMBER */);
3125
3904
  builder.emitNumber(1);
@@ -3138,7 +3917,7 @@ var IntegralParselet = class {
3138
3917
  }
3139
3918
  parse(parser, _token, builder) {
3140
3919
  parser.consume("LPAREN");
3141
- parser.parseExpression(chunkC4XZV6E7_cjs.BindingPower.Lowest, builder);
3920
+ parser.parseExpression(chunkNZFKROS7_cjs.BindingPower.Lowest, builder);
3142
3921
  parser.consume("COMMA");
3143
3922
  consumeVariableName(parser, builder, "integral");
3144
3923
  parser.consume("RPAREN");
@@ -3155,13 +3934,13 @@ var TaylorParselet = class {
3155
3934
  }
3156
3935
  parse(parser, _token, builder) {
3157
3936
  parser.consume("LPAREN");
3158
- parser.parseExpression(chunkC4XZV6E7_cjs.BindingPower.Lowest, builder);
3937
+ parser.parseExpression(chunkNZFKROS7_cjs.BindingPower.Lowest, builder);
3159
3938
  parser.consume("COMMA");
3160
3939
  consumeVariableName(parser, builder, "taylor");
3161
3940
  parser.consume("EQUALS");
3162
- parser.parseExpression(chunkC4XZV6E7_cjs.BindingPower.Lowest, builder);
3941
+ parser.parseExpression(chunkNZFKROS7_cjs.BindingPower.Lowest, builder);
3163
3942
  parser.consume("COMMA");
3164
- parser.parseExpression(chunkC4XZV6E7_cjs.BindingPower.Lowest, builder);
3943
+ parser.parseExpression(chunkNZFKROS7_cjs.BindingPower.Lowest, builder);
3165
3944
  parser.consume("RPAREN");
3166
3945
  builder.emitOpcode(51 /* CALL_BUILTIN */);
3167
3946
  builder.emitIndex(SYMBOLIC_BUILTIN_TAYLOR);
@@ -3178,7 +3957,7 @@ var JacobianParselet = class {
3178
3957
  parser.consume("LPAREN");
3179
3958
  let count = 0;
3180
3959
  do {
3181
- parser.parseExpression(chunkC4XZV6E7_cjs.BindingPower.Lowest, builder);
3960
+ parser.parseExpression(chunkNZFKROS7_cjs.BindingPower.Lowest, builder);
3182
3961
  count++;
3183
3962
  } while (parser.match("COMMA"));
3184
3963
  parser.consume("RPAREN");
@@ -3209,7 +3988,7 @@ var ConjParselet = class {
3209
3988
  }
3210
3989
  parse(parser, _token, builder) {
3211
3990
  parser.consume("LPAREN");
3212
- parser.parseExpression(chunkC4XZV6E7_cjs.BindingPower.Lowest, builder);
3991
+ parser.parseExpression(chunkNZFKROS7_cjs.BindingPower.Lowest, builder);
3213
3992
  parser.consume("RPAREN");
3214
3993
  builder.emitOpcode(51 /* CALL_BUILTIN */);
3215
3994
  builder.emitIndex(SYMBOLIC_BUILTIN_CONJ);
@@ -3224,7 +4003,7 @@ var ReParselet = class {
3224
4003
  }
3225
4004
  parse(parser, _token, builder) {
3226
4005
  parser.consume("LPAREN");
3227
- parser.parseExpression(chunkC4XZV6E7_cjs.BindingPower.Lowest, builder);
4006
+ parser.parseExpression(chunkNZFKROS7_cjs.BindingPower.Lowest, builder);
3228
4007
  parser.consume("RPAREN");
3229
4008
  builder.emitOpcode(51 /* CALL_BUILTIN */);
3230
4009
  builder.emitIndex(SYMBOLIC_BUILTIN_RE);
@@ -3239,7 +4018,7 @@ var ImParselet = class {
3239
4018
  }
3240
4019
  parse(parser, _token, builder) {
3241
4020
  parser.consume("LPAREN");
3242
- parser.parseExpression(chunkC4XZV6E7_cjs.BindingPower.Lowest, builder);
4021
+ parser.parseExpression(chunkNZFKROS7_cjs.BindingPower.Lowest, builder);
3243
4022
  parser.consume("RPAREN");
3244
4023
  builder.emitOpcode(51 /* CALL_BUILTIN */);
3245
4024
  builder.emitIndex(SYMBOLIC_BUILTIN_IM);
@@ -3254,7 +4033,7 @@ var CancelParselet = class {
3254
4033
  }
3255
4034
  parse(parser, _token, builder) {
3256
4035
  parser.consume("LPAREN");
3257
- parser.parseExpression(chunkC4XZV6E7_cjs.BindingPower.Lowest, builder);
4036
+ parser.parseExpression(chunkNZFKROS7_cjs.BindingPower.Lowest, builder);
3258
4037
  parser.consume("RPAREN");
3259
4038
  builder.emitOpcode(51 /* CALL_BUILTIN */);
3260
4039
  builder.emitIndex(SYMBOLIC_BUILTIN_CANCEL);
@@ -3269,7 +4048,7 @@ var ApartParselet = class {
3269
4048
  }
3270
4049
  parse(parser, _token, builder) {
3271
4050
  parser.consume("LPAREN");
3272
- parser.parseExpression(chunkC4XZV6E7_cjs.BindingPower.Lowest, builder);
4051
+ parser.parseExpression(chunkNZFKROS7_cjs.BindingPower.Lowest, builder);
3273
4052
  parser.consume("RPAREN");
3274
4053
  builder.emitOpcode(51 /* CALL_BUILTIN */);
3275
4054
  builder.emitIndex(SYMBOLIC_BUILTIN_APART);
@@ -3292,9 +4071,9 @@ function imaginaryLiteralNormalizerRule(priority = 75) {
3292
4071
  return {
3293
4072
  consumed: 2,
3294
4073
  replacement: [
3295
- new chunkNMD5VRN4_cjs.LexerToken(
4074
+ new chunkM4F66R4O_cjs.LexerToken(
3296
4075
  "IMAGINARY",
3297
- chunkGW32KPCU_cjs.tokenTypeId("IMAGINARY"),
4076
+ chunkR24DI24X_cjs.tokenTypeId("IMAGINARY"),
3298
4077
  number.value,
3299
4078
  number.value,
3300
4079
  number.offset,
@@ -3338,7 +4117,7 @@ function symbolicCallNormalizerRule(priority = 80) {
3338
4117
  return {
3339
4118
  consumed: 1,
3340
4119
  replacement: [
3341
- new chunkNMD5VRN4_cjs.LexerToken(tokenType, chunkGW32KPCU_cjs.tokenTypeId(tokenType), token.value, token.value, token.offset, 0, token.line, token.col)
4120
+ new chunkM4F66R4O_cjs.LexerToken(tokenType, chunkR24DI24X_cjs.tokenTypeId(tokenType), token.value, token.value, token.offset, 0, token.line, token.col)
3342
4121
  ],
3343
4122
  ruleName: "symbolic:call"
3344
4123
  };
@@ -3510,7 +4289,7 @@ var ComparisonParselet = class {
3510
4289
  constructor(opcode) {
3511
4290
  this.opcode = opcode;
3512
4291
  this.category = "Conditionals";
3513
- this.bindingPower = chunkC4XZV6E7_cjs.BindingPower.Conditional;
4292
+ this.bindingPower = chunkNZFKROS7_cjs.BindingPower.Conditional;
3514
4293
  }
3515
4294
  parse(parser, left, token, builder) {
3516
4295
  parser.parseExpression(this.bindingPower, builder);
@@ -3573,9 +4352,9 @@ var CONDITIONALS_PACKAGE = {
3573
4352
  { tokenType: "GT", parselet: new ComparisonParselet(44 /* GT */) },
3574
4353
  { tokenType: "LTE", parselet: new ComparisonParselet(43 /* LTE */) },
3575
4354
  { tokenType: "GTE", parselet: new ComparisonParselet(45 /* GTE */) },
3576
- { tokenType: "OR", parselet: new LogicalParselet(131 /* LOGICAL_OR */, chunkC4XZV6E7_cjs.BindingPower.LogicalOr) },
3577
- { tokenType: "LOGICAL_AND", parselet: new LogicalParselet(130 /* LOGICAL_AND */, chunkC4XZV6E7_cjs.BindingPower.LogicalAnd) },
3578
- { tokenType: "LOGICAL_OR", parselet: new LogicalParselet(131 /* LOGICAL_OR */, chunkC4XZV6E7_cjs.BindingPower.LogicalOr) }
4355
+ { tokenType: "OR", parselet: new LogicalParselet(131 /* LOGICAL_OR */, chunkNZFKROS7_cjs.BindingPower.LogicalOr) },
4356
+ { tokenType: "LOGICAL_AND", parselet: new LogicalParselet(130 /* LOGICAL_AND */, chunkNZFKROS7_cjs.BindingPower.LogicalAnd) },
4357
+ { tokenType: "LOGICAL_OR", parselet: new LogicalParselet(131 /* LOGICAL_OR */, chunkNZFKROS7_cjs.BindingPower.LogicalOr) }
3579
4358
  ]
3580
4359
  };
3581
4360
 
@@ -3589,6 +4368,9 @@ var BUILTIN_CONVERTERS = {
3589
4368
  hex: 71 /* TO_HEX */,
3590
4369
  fraction: 140 /* TO_FRACTION */,
3591
4370
  multiplier: 141 /* TO_MULTIPLIER */,
4371
+ // "20 to 40 as x". Only reachable in converter position, immediately after
4372
+ // "as", so a variable named x is unaffected.
4373
+ x: 141 /* TO_MULTIPLIER */,
3592
4374
  sci: 142 /* TO_SCI */,
3593
4375
  scientific: 142 /* TO_SCI */,
3594
4376
  binary: 143 /* TO_BINARY */,
@@ -3596,10 +4378,15 @@ var BUILTIN_CONVERTERS = {
3596
4378
  octal: 144 /* TO_OCTAL */,
3597
4379
  oct: 144 /* TO_OCTAL */
3598
4380
  };
4381
+ var BASE_CONVERTERS = {
4382
+ 2: 143 /* TO_BINARY */,
4383
+ 8: 144 /* TO_OCTAL */,
4384
+ 16: 71 /* TO_HEX */
4385
+ };
3599
4386
  var AsConverterParselet = class {
3600
4387
  constructor() {
3601
4388
  this.category = "Converters";
3602
- this.bindingPower = chunkC4XZV6E7_cjs.BindingPower.Conditional;
4389
+ this.bindingPower = chunkNZFKROS7_cjs.BindingPower.Conditional;
3603
4390
  }
3604
4391
  parse(parser, left, token, builder) {
3605
4392
  const nextToken = parser.peek();
@@ -3616,8 +4403,26 @@ var AsConverterParselet = class {
3616
4403
  }
3617
4404
  parser.consume();
3618
4405
  const name = nextToken.value.toLowerCase();
4406
+ if (name === "base") {
4407
+ const radixToken = parser.peek();
4408
+ const radix = radixToken?.type === "NUMBER" ? Number(radixToken.value) : NaN;
4409
+ const opcode = BASE_CONVERTERS[radix];
4410
+ if (opcode === void 0) {
4411
+ throw chunkM4N5GA5Z_cjs.ErrorFactory.parsing(
4412
+ "AS_CONVERTER_UNSUPPORTED_BASE",
4413
+ `"as base ${radixToken?.value ?? "?"}": only bases ${Object.keys(BASE_CONVERTERS).join(", ")} can be written out`
4414
+ );
4415
+ }
4416
+ parser.consume();
4417
+ builder.emitOpcode(opcode);
4418
+ return;
4419
+ }
3619
4420
  const builtinOp = BUILTIN_CONVERTERS[name];
3620
4421
  if (builtinOp !== void 0) {
4422
+ if (builtinOp === 141 /* TO_MULTIPLIER */ && parser.match("OF")) {
4423
+ parser.parseExpression(chunkNZFKROS7_cjs.BindingPower.Conditional, builder);
4424
+ builder.emitOpcode(23 /* DIV */);
4425
+ }
3621
4426
  builder.emitOpcode(builtinOp);
3622
4427
  return;
3623
4428
  }
@@ -3627,11 +4432,181 @@ var AsConverterParselet = class {
3627
4432
  }
3628
4433
  };
3629
4434
 
4435
+ // src/packages/converters/parselets/RoundingParselets.ts
4436
+ var INCREMENT_WORDS = {
4437
+ ten: 10,
4438
+ hundred: 100,
4439
+ thousand: 1e3,
4440
+ million: 1e6,
4441
+ billion: 1e9
4442
+ };
4443
+ function emitRoundToIncrement(builder, increment, direction) {
4444
+ builder.emitOpcode(10 /* PUSH_NUMBER */);
4445
+ builder.emitNumber(increment);
4446
+ builder.emitOpcode(23 /* DIV */);
4447
+ emitRoundDirection(builder, direction);
4448
+ builder.emitOpcode(10 /* PUSH_NUMBER */);
4449
+ builder.emitNumber(increment);
4450
+ builder.emitOpcode(22 /* MUL */);
4451
+ }
4452
+ function emitRoundDirection(builder, direction) {
4453
+ const builtin = direction === "up" ? CEIL_BUILTIN : direction === "down" ? FLOOR_BUILTIN : ROUND_BUILTIN;
4454
+ builder.emitOpcode(51 /* CALL_BUILTIN */);
4455
+ builder.emitIndex(builtin);
4456
+ builder.emitIndex(1);
4457
+ }
4458
+ var ROUND_BUILTIN = 8;
4459
+ var CEIL_BUILTIN = 6;
4460
+ var FLOOR_BUILTIN = 7;
4461
+ function readDirection(parser) {
4462
+ const next = parser.peek();
4463
+ const word = (next?.text ?? "").toLowerCase();
4464
+ if (word === "up") {
4465
+ parser.consume();
4466
+ return "up";
4467
+ }
4468
+ if (word === "down") {
4469
+ parser.consume();
4470
+ return "down";
4471
+ }
4472
+ return "nearest";
4473
+ }
4474
+ var RoundedParselet = class {
4475
+ constructor() {
4476
+ this.category = "Converters";
4477
+ this.bindingPower = chunkNZFKROS7_cjs.BindingPower.Conditional;
4478
+ }
4479
+ parse(parser, _left, _token, builder) {
4480
+ const direction = readDirection(parser);
4481
+ if (parser.match("TO_NEAREST")) {
4482
+ emitRoundToIncrement(builder, readIncrement(parser), direction);
4483
+ return;
4484
+ }
4485
+ emitRoundDirection(builder, direction);
4486
+ }
4487
+ };
4488
+ var ToNearestParselet = class {
4489
+ constructor() {
4490
+ this.category = "Converters";
4491
+ this.bindingPower = chunkNZFKROS7_cjs.BindingPower.Conditional;
4492
+ }
4493
+ parse(parser, _left, _token, builder) {
4494
+ emitRoundToIncrement(builder, readIncrement(parser), "nearest");
4495
+ }
4496
+ };
4497
+ function readIncrement(parser) {
4498
+ const next = parser.peek();
4499
+ const word = (next?.text ?? "").toLowerCase();
4500
+ const magnitude = INCREMENT_WORDS[word];
4501
+ if (magnitude !== void 0) {
4502
+ parser.consume();
4503
+ return magnitude;
4504
+ }
4505
+ if (next?.type === "NUMBER") {
4506
+ parser.consume();
4507
+ const value = Number(next.value);
4508
+ if (Number.isFinite(value) && value > 0) return value;
4509
+ }
4510
+ throw chunkM4N5GA5Z_cjs.ErrorFactory.parsing(
4511
+ "INVALID_ROUNDING_INCREMENT",
4512
+ `"to nearest ${next?.value ?? "?"}": expected a positive number or one of ${Object.keys(INCREMENT_WORDS).join(", ")}`
4513
+ );
4514
+ }
4515
+ var DecimalPlacesParselet = class {
4516
+ constructor() {
4517
+ this.category = "Converters";
4518
+ this.bindingPower = chunkNZFKROS7_cjs.BindingPower.Conditional;
4519
+ }
4520
+ parse(_parser, _left, token, builder) {
4521
+ const places = Number(token.value);
4522
+ if (!Number.isFinite(places) || places < 0 || places > 100) {
4523
+ throw chunkM4N5GA5Z_cjs.ErrorFactory.parsing(
4524
+ "INVALID_DECIMAL_PLACES",
4525
+ `"to ${token.value} dp": expected a place count between 0 and 100`
4526
+ );
4527
+ }
4528
+ const scale = Math.pow(10, places);
4529
+ builder.emitOpcode(10 /* PUSH_NUMBER */);
4530
+ builder.emitNumber(scale);
4531
+ builder.emitOpcode(22 /* MUL */);
4532
+ emitRoundDirection(builder, "nearest");
4533
+ builder.emitOpcode(10 /* PUSH_NUMBER */);
4534
+ builder.emitNumber(scale);
4535
+ builder.emitOpcode(23 /* DIV */);
4536
+ }
4537
+ };
4538
+
4539
+ // src/packages/converters/normalizer/DecimalPlacesNormalizerRule.ts
4540
+ var PLACE_WORDS = /* @__PURE__ */ new Set(["dp", "dps", "decimals", "digits", "digit"]);
4541
+ var PLACE_PHRASES = /* @__PURE__ */ new Set(["decimal"]);
4542
+ function decimalPlacesNormalizerRule(priority = 66) {
4543
+ return {
4544
+ name: "converters:decimal-places",
4545
+ priority,
4546
+ match(tokens, pos) {
4547
+ if (tokens[pos]?.type !== "TO") return null;
4548
+ const count = tokens[pos + 1];
4549
+ if (count?.type !== "NUMBER") return null;
4550
+ const unitToken = tokens[pos + 2];
4551
+ if (!unitToken) return null;
4552
+ const word = (unitToken.text ?? unitToken.value ?? "").toLowerCase();
4553
+ let consumed = 3;
4554
+ if (PLACE_PHRASES.has(word)) {
4555
+ const tail = tokens[pos + 3];
4556
+ const tailWord = (tail?.text ?? "").toLowerCase();
4557
+ if (tailWord !== "place" && tailWord !== "places") return null;
4558
+ consumed = 4;
4559
+ } else if (!PLACE_WORDS.has(word)) {
4560
+ return null;
4561
+ }
4562
+ return {
4563
+ consumed,
4564
+ replacement: [
4565
+ chunkAHCLWAM5_cjs.createFusedToken("DECIMAL_PLACES", count.value, tokens.slice(pos, pos + consumed))
4566
+ ],
4567
+ ruleName: "converters:decimal-places"
4568
+ };
4569
+ }
4570
+ };
4571
+ }
4572
+
4573
+ // src/packages/converters/normalizer/ConverterPrepositionNormalizerRule.ts
4574
+ function converterPrepositionNormalizerRule(priority = 67) {
4575
+ return {
4576
+ name: "converters:preposition",
4577
+ priority,
4578
+ match(tokens, pos) {
4579
+ const preposition = tokens[pos];
4580
+ if (preposition?.type !== "IN" && preposition?.type !== "TO") return null;
4581
+ if (tokens[pos + 1]?.type !== "CONVERTER_NAME") return null;
4582
+ return {
4583
+ consumed: 1,
4584
+ replacement: [chunkAHCLWAM5_cjs.createFusedToken("AS", "as", [preposition])],
4585
+ ruleName: "converters:preposition"
4586
+ };
4587
+ }
4588
+ };
4589
+ }
4590
+
3630
4591
  // src/packages/converters/ConvertersPackage.ts
3631
4592
  var CONVERTERS_PACKAGE = {
3632
4593
  name: "solve-converters",
4594
+ phrases: {
4595
+ // A phrase, not a bare "nearest": "to" is already an infix operator
4596
+ // (percentage change), so the two words have to arrive as one token for
4597
+ // the parser to tell the grammars apart. See RoundingParselets.ts.
4598
+ "to nearest": "TO_NEAREST",
4599
+ "to the nearest": "TO_NEAREST"
4600
+ },
3633
4601
  infixParselets: [
3634
- { tokenType: "AS", parselet: new AsConverterParselet() }
4602
+ { tokenType: "AS", parselet: new AsConverterParselet() },
4603
+ { tokenType: "ROUNDED", parselet: new RoundedParselet() },
4604
+ { tokenType: "TO_NEAREST", parselet: new ToNearestParselet() },
4605
+ { tokenType: "DECIMAL_PLACES", parselet: new DecimalPlacesParselet() }
4606
+ ],
4607
+ normalizerRules: [
4608
+ decimalPlacesNormalizerRule(),
4609
+ converterPrepositionNormalizerRule()
3635
4610
  ]
3636
4611
  };
3637
4612
 
@@ -3643,10 +4618,10 @@ var VariadicAggregateParselet = class {
3643
4618
  }
3644
4619
  parse(parser, token, builder) {
3645
4620
  let argCount = 0;
3646
- parser.parseExpression(chunkC4XZV6E7_cjs.BindingPower.Lowest, builder);
4621
+ parser.parseExpression(chunkNZFKROS7_cjs.BindingPower.Conjunction, builder);
3647
4622
  argCount++;
3648
- while (parser.match("COMMA")) {
3649
- parser.parseExpression(chunkC4XZV6E7_cjs.BindingPower.Lowest, builder);
4623
+ while (parser.match("COMMA") || parser.match("AND_CONJ")) {
4624
+ parser.parseExpression(chunkNZFKROS7_cjs.BindingPower.Conjunction, builder);
3650
4625
  argCount++;
3651
4626
  }
3652
4627
  builder.emitOpcode(51 /* CALL_BUILTIN */);
@@ -3655,14 +4630,126 @@ var VariadicAggregateParselet = class {
3655
4630
  }
3656
4631
  };
3657
4632
 
4633
+ // src/packages/mathphrases/parselets/UnaryPhraseParselet.ts
4634
+ function unaryPhraseParselet(builtinIndex) {
4635
+ return {
4636
+ category: "MathPhrases",
4637
+ parse(parser, _token, builder) {
4638
+ parser.parseExpression(chunkNZFKROS7_cjs.BindingPower.Lowest, builder);
4639
+ builder.emitOpcode(51 /* CALL_BUILTIN */);
4640
+ builder.emitIndex(builtinIndex);
4641
+ builder.emitIndex(1);
4642
+ }
4643
+ };
4644
+ }
4645
+
4646
+ // src/packages/mathphrases/parselets/RemainderRootParselets.ts
4647
+ var remainderOfParselet = {
4648
+ category: "MathPhrases",
4649
+ parse(parser, _token, builder) {
4650
+ parser.parseExpression(chunkNZFKROS7_cjs.BindingPower.Product, builder);
4651
+ if (!parser.match("DIVIDE_BY") && !parser.match("SLASH")) {
4652
+ throw chunkM4N5GA5Z_cjs.ErrorFactory.parsing(
4653
+ "REMAINDER_EXPECTED_DIVIDED_BY",
4654
+ 'Expected "divided by" or "/", as in "remainder of 21 divided by 5"'
4655
+ );
4656
+ }
4657
+ parser.parseExpression(chunkNZFKROS7_cjs.BindingPower.Product, builder);
4658
+ builder.emitOpcode(24 /* MOD */);
4659
+ }
4660
+ };
4661
+ var nthRootParselet = {
4662
+ category: "MathPhrases",
4663
+ parse(parser, _token, builder) {
4664
+ parser.parseExpression(chunkNZFKROS7_cjs.BindingPower.Product, builder);
4665
+ if (!parser.match("OF")) {
4666
+ throw chunkM4N5GA5Z_cjs.ErrorFactory.parsing(
4667
+ "ROOT_EXPECTED_OF",
4668
+ 'Expected "of", as in "root 5 of 100"'
4669
+ );
4670
+ }
4671
+ parser.parseExpression(chunkNZFKROS7_cjs.BindingPower.Conditional, builder);
4672
+ builder.emitOpcode(2 /* SWAP */);
4673
+ builder.emitOpcode(10 /* PUSH_NUMBER */);
4674
+ builder.emitNumber(1);
4675
+ builder.emitOpcode(2 /* SWAP */);
4676
+ builder.emitOpcode(23 /* DIV */);
4677
+ builder.emitOpcode(25 /* EXP */);
4678
+ }
4679
+ };
4680
+ var logBaseParselet = {
4681
+ category: "MathPhrases",
4682
+ parse(parser, _token, builder) {
4683
+ parser.parseExpression(chunkNZFKROS7_cjs.BindingPower.Conditional, builder);
4684
+ emitNaturalLog(builder);
4685
+ if (!parser.match("LOG_BASE")) {
4686
+ throw chunkM4N5GA5Z_cjs.ErrorFactory.parsing(
4687
+ "LOG_EXPECTED_BASE",
4688
+ 'Expected "base", as in "log 20 base 4"'
4689
+ );
4690
+ }
4691
+ parser.parseExpression(chunkNZFKROS7_cjs.BindingPower.Conditional, builder);
4692
+ emitNaturalLog(builder);
4693
+ builder.emitOpcode(23 /* DIV */);
4694
+ }
4695
+ };
4696
+ var LOG_BUILTIN2 = 5;
4697
+ function emitNaturalLog(builder) {
4698
+ builder.emitOpcode(51 /* CALL_BUILTIN */);
4699
+ builder.emitIndex(LOG_BUILTIN2);
4700
+ builder.emitIndex(1);
4701
+ }
4702
+
4703
+ // src/packages/mathphrases/normalizer/FunctionPhraseNormalizerRule.ts
4704
+ function functionPhraseNormalizerRule(priority = 69) {
4705
+ return {
4706
+ name: "mathphrases:function-phrase",
4707
+ priority,
4708
+ match(tokens, pos) {
4709
+ const head = tokens[pos];
4710
+ if (head?.type !== "FUNC") return null;
4711
+ const word = (head.value ?? "").toLowerCase();
4712
+ if (word !== "root" && word !== "log") return null;
4713
+ if (tokens[pos + 1]?.type === "LPAREN") return null;
4714
+ if (word === "root") {
4715
+ return {
4716
+ consumed: 1,
4717
+ replacement: [chunkAHCLWAM5_cjs.createFusedToken("NTH_ROOT", "root", [head])],
4718
+ ruleName: "mathphrases:function-phrase"
4719
+ };
4720
+ }
4721
+ const baseAt = findBase(tokens, pos + 1);
4722
+ if (baseAt === -1) return null;
4723
+ const replacement = tokens.slice(pos, baseAt + 1).map((token, index) => {
4724
+ if (index === 0) return chunkAHCLWAM5_cjs.createFusedToken("LOG_PHRASE", "log", [token]);
4725
+ if (index === baseAt - pos) return chunkAHCLWAM5_cjs.createFusedToken("LOG_BASE", "base", [token]);
4726
+ return token;
4727
+ });
4728
+ return {
4729
+ consumed: baseAt - pos + 1,
4730
+ replacement,
4731
+ ruleName: "mathphrases:function-phrase"
4732
+ };
4733
+ }
4734
+ };
4735
+ }
4736
+ function findBase(tokens, from) {
4737
+ for (let i = from; i < tokens.length && i < from + 8; i++) {
4738
+ const token = tokens[i];
4739
+ if (token.type === "EOF" || token.type === "COMMA" || token.type === "RPAREN") return -1;
4740
+ if ((token.text ?? token.value ?? "").toLowerCase() === "base") return i;
4741
+ }
4742
+ return -1;
4743
+ }
4744
+
3658
4745
  // src/packages/mathphrases/parselets/LargerSmallerParselet.ts
3659
4746
  function largerSmallerParselet(builtinIndex) {
3660
4747
  return {
3661
4748
  category: "MathPhrases",
3662
4749
  parse(parser, token, builder) {
3663
- parser.parseExpression(chunkC4XZV6E7_cjs.BindingPower.Product, builder);
3664
- parser.consume("PLUS");
3665
- parser.parseExpression(chunkC4XZV6E7_cjs.BindingPower.Lowest, builder);
4750
+ parser.parseExpression(chunkNZFKROS7_cjs.BindingPower.Conjunction, builder);
4751
+ parser.consume("AND_CONJ");
4752
+ parser.parseExpression(chunkNZFKROS7_cjs.BindingPower.Lowest, builder);
3666
4753
  builder.emitOpcode(51 /* CALL_BUILTIN */);
3667
4754
  builder.emitIndex(builtinIndex);
3668
4755
  builder.emitIndex(2);
@@ -3674,7 +4761,7 @@ function largerSmallerParselet(builtinIndex) {
3674
4761
  var halfParselet = {
3675
4762
  category: "MathPhrases",
3676
4763
  parse(parser, token, builder) {
3677
- parser.parseExpression(chunkC4XZV6E7_cjs.BindingPower.Lowest, builder);
4764
+ parser.parseExpression(chunkNZFKROS7_cjs.BindingPower.Lowest, builder);
3678
4765
  builder.emitOpcode(10 /* PUSH_NUMBER */);
3679
4766
  builder.emitNumber(2);
3680
4767
  builder.emitOpcode(23 /* DIV */);
@@ -3685,9 +4772,9 @@ var halfParselet = {
3685
4772
  var midpointParselet = {
3686
4773
  category: "MathPhrases",
3687
4774
  parse(parser, token, builder) {
3688
- parser.parseExpression(chunkC4XZV6E7_cjs.BindingPower.Product, builder);
3689
- parser.consume("PLUS");
3690
- parser.parseExpression(chunkC4XZV6E7_cjs.BindingPower.Lowest, builder);
4775
+ parser.parseExpression(chunkNZFKROS7_cjs.BindingPower.Conjunction, builder);
4776
+ parser.consume("AND_CONJ");
4777
+ parser.parseExpression(chunkNZFKROS7_cjs.BindingPower.Lowest, builder);
3691
4778
  builder.emitOpcode(20 /* ADD */);
3692
4779
  builder.emitOpcode(10 /* PUSH_NUMBER */);
3693
4780
  builder.emitNumber(2);
@@ -3702,8 +4789,8 @@ var randomNumberParselet = definePhrasePattern({
3702
4789
  {
3703
4790
  slots: [
3704
4791
  { kind: "keyword", tokenTypes: ["BETWEEN"] },
3705
- { kind: "expr", bindingPower: chunkC4XZV6E7_cjs.BindingPower.Product },
3706
- { kind: "keyword", tokenTypes: ["PLUS"] },
4792
+ { kind: "expr", bindingPower: chunkNZFKROS7_cjs.BindingPower.Product },
4793
+ { kind: "keyword", tokenTypes: ["AND_CONJ"] },
3707
4794
  { kind: "expr" }
3708
4795
  ],
3709
4796
  emit: (builder) => {
@@ -3722,18 +4809,18 @@ var ClampParselet = class {
3722
4809
  this.category = "MathPhrases";
3723
4810
  }
3724
4811
  parse(parser, token, builder) {
3725
- parser.parseExpression(chunkC4XZV6E7_cjs.BindingPower.Lowest, builder);
4812
+ parser.parseExpression(chunkNZFKROS7_cjs.BindingPower.Lowest, builder);
3726
4813
  const mid = parser.peek();
3727
4814
  if (mid?.type === "BETWEEN") {
3728
4815
  parser.consume();
3729
- parser.parseExpression(chunkC4XZV6E7_cjs.BindingPower.Product, builder);
3730
- parser.consume("PLUS");
3731
- parser.parseExpression(chunkC4XZV6E7_cjs.BindingPower.Lowest, builder);
4816
+ parser.parseExpression(chunkNZFKROS7_cjs.BindingPower.Product, builder);
4817
+ parser.consume("AND_CONJ");
4818
+ parser.parseExpression(chunkNZFKROS7_cjs.BindingPower.Lowest, builder);
3732
4819
  } else if (mid?.type === "FROM") {
3733
4820
  parser.consume();
3734
- parser.parseExpression(chunkC4XZV6E7_cjs.BindingPower.Lowest, builder);
4821
+ parser.parseExpression(chunkNZFKROS7_cjs.BindingPower.Lowest, builder);
3735
4822
  parser.consume("TO");
3736
- parser.parseExpression(chunkC4XZV6E7_cjs.BindingPower.Lowest, builder);
4823
+ parser.parseExpression(chunkNZFKROS7_cjs.BindingPower.Lowest, builder);
3737
4824
  } else {
3738
4825
  throw chunkM4N5GA5Z_cjs.ErrorFactory.parsing(
3739
4826
  "CLAMP_EXPECTED_BETWEEN_OR_FROM",
@@ -3751,7 +4838,7 @@ var PROPORTION_BUILTIN_INDEX = 46;
3751
4838
  var ProportionParselet = class {
3752
4839
  constructor() {
3753
4840
  this.category = "MathPhrases";
3754
- this.bindingPower = chunkC4XZV6E7_cjs.BindingPower.Conditional;
4841
+ this.bindingPower = chunkNZFKROS7_cjs.BindingPower.Conditional;
3755
4842
  }
3756
4843
  parse(parser, left, token, builder) {
3757
4844
  parser.parseExpression(this.bindingPower, builder);
@@ -3778,6 +4865,10 @@ var TOTAL = 44;
3778
4865
  var COUNT = 45;
3779
4866
  var MIN = 9;
3780
4867
  var MAX = 10;
4868
+ var GCD = 38;
4869
+ var LCM = 39;
4870
+ var SQRT = 0;
4871
+ var CBRT = 21;
3781
4872
  var MATHPHRASES_PACKAGE = {
3782
4873
  name: "solve-mathphrases",
3783
4874
  phrases: {
@@ -3786,11 +4877,20 @@ var MATHPHRASES_PACKAGE = {
3786
4877
  "total of": "TOTAL_OF",
3787
4878
  "count of": "COUNT_OF",
3788
4879
  "larger of": "LARGER_OF",
4880
+ "greater of": "LARGER_OF",
3789
4881
  "smaller of": "SMALLER_OF",
4882
+ "lesser of": "SMALLER_OF",
3790
4883
  "half of": "HALF_OF",
3791
4884
  "midpoint between": "MIDPOINT_BETWEEN",
3792
4885
  "random number": "RANDOM_NUMBER",
3793
- "is to": "IS_TO"
4886
+ "is to": "IS_TO",
4887
+ // Two-operand phrases reusing the gcd/lcm builtins.
4888
+ "remainder of": "REMAINDER_OF",
4889
+ "gcd of": "GCD_OF",
4890
+ "lcm of": "LCM_OF",
4891
+ // sqrt/cbrt spelled the way they are said.
4892
+ "square root of": "SQUARE_ROOT_OF",
4893
+ "cube root of": "CUBE_ROOT_OF"
3794
4894
  },
3795
4895
  prefixParselets: [
3796
4896
  { tokenType: "AVERAGE_OF", parselet: new VariadicAggregateParselet(AVERAGE) },
@@ -3802,31 +4902,170 @@ var MATHPHRASES_PACKAGE = {
3802
4902
  { tokenType: "HALF_OF", parselet: halfParselet },
3803
4903
  { tokenType: "MIDPOINT_BETWEEN", parselet: midpointParselet },
3804
4904
  { tokenType: "RANDOM_NUMBER", parselet: randomNumberParselet },
4905
+ { tokenType: "REMAINDER_OF", parselet: remainderOfParselet },
4906
+ { tokenType: "NTH_ROOT", parselet: nthRootParselet },
4907
+ { tokenType: "LOG_PHRASE", parselet: logBaseParselet },
4908
+ { tokenType: "GCD_OF", parselet: largerSmallerParselet(GCD) },
4909
+ { tokenType: "LCM_OF", parselet: largerSmallerParselet(LCM) },
4910
+ { tokenType: "SQUARE_ROOT_OF", parselet: unaryPhraseParselet(SQRT) },
4911
+ { tokenType: "CUBE_ROOT_OF", parselet: unaryPhraseParselet(CBRT) },
3805
4912
  { tokenType: "CLAMP", parselet: new ClampParselet() }
3806
4913
  ],
3807
4914
  infixParselets: [
3808
4915
  { tokenType: "IS_TO", parselet: new ProportionParselet() }
4916
+ ],
4917
+ normalizerRules: [
4918
+ functionPhraseNormalizerRule()
3809
4919
  ]
3810
4920
  };
3811
4921
 
3812
- // src/packages/finance/parselets/CompoundInterestParselet.ts
3813
- var CompoundInterestParselet = class {
4922
+ // src/packages/finance/parselets/InvestmentParselets.ts
4923
+ var PERIODS_PER_YEAR = {
4924
+ annually: 1,
4925
+ yearly: 1,
4926
+ // Semi-annual is not on Soulver's page, but it is the one interval a
4927
+ // reader of the other four would expect to exist, and leaving it out makes
4928
+ // the set look arbitrary rather than deliberate.
4929
+ "semi-annually": 2,
4930
+ quarterly: 4,
4931
+ monthly: 12,
4932
+ fortnightly: 26,
4933
+ weekly: 52,
4934
+ daily: 365
4935
+ };
4936
+ function readCompoundingInterval(parser) {
4937
+ if (!parser.match("COMPOUNDING")) return 1;
4938
+ const token = parser.peek();
4939
+ const name = (token?.text ?? token?.value ?? "").toLowerCase();
4940
+ const periods = PERIODS_PER_YEAR[name];
4941
+ if (periods === void 0) {
4942
+ throw chunkM4N5GA5Z_cjs.ErrorFactory.parsing(
4943
+ "UNKNOWN_COMPOUNDING_INTERVAL",
4944
+ `compounding ${name || "?"}: expected one of ${Object.keys(PERIODS_PER_YEAR).join(", ")}`
4945
+ );
4946
+ }
4947
+ parser.consume();
4948
+ return periods;
4949
+ }
4950
+ var InvestmentGrowthParselet = class {
4951
+ /**
4952
+ * @param annualIndex - Builtin for the annual case, so the common form
4953
+ * keeps using the same code path (and the same rounding) it always did.
4954
+ * @param intervalIndex - Builtin taking an explicit periods-per-year.
4955
+ */
4956
+ constructor(annualIndex, intervalIndex) {
4957
+ this.annualIndex = annualIndex;
4958
+ this.intervalIndex = intervalIndex;
4959
+ this.category = "Finance";
4960
+ this.bindingPower = chunkNZFKROS7_cjs.BindingPower.Conditional;
4961
+ }
4962
+ parse(parser, _left, _token, builder) {
4963
+ parser.parseExpression(chunkNZFKROS7_cjs.BindingPower.Conditional, builder);
4964
+ if (!parser.match("RATE_AT")) parser.consume("AT");
4965
+ parser.parseExpression(chunkNZFKROS7_cjs.BindingPower.Conditional, builder);
4966
+ const periods = readCompoundingInterval(parser);
4967
+ builder.emitOpcode(2 /* SWAP */);
4968
+ if (periods === 1) {
4969
+ builder.emitOpcode(51 /* CALL_BUILTIN */);
4970
+ builder.emitIndex(this.annualIndex);
4971
+ builder.emitIndex(3);
4972
+ return;
4973
+ }
4974
+ builder.emitOpcode(10 /* PUSH_NUMBER */);
4975
+ builder.emitNumber(periods);
4976
+ builder.emitOpcode(51 /* CALL_BUILTIN */);
4977
+ builder.emitIndex(this.intervalIndex);
4978
+ builder.emitIndex(4);
4979
+ }
4980
+ };
4981
+ var PresentValueParselet = class {
3814
4982
  constructor(builtinIndex) {
3815
4983
  this.builtinIndex = builtinIndex;
3816
4984
  this.category = "Finance";
3817
4985
  }
3818
4986
  parse(parser, _token, builder) {
3819
- parser.parseExpression(chunkC4XZV6E7_cjs.BindingPower.Lowest, builder);
3820
- parser.consume("OVER");
3821
- parser.parseExpression(chunkC4XZV6E7_cjs.BindingPower.Lowest, builder);
3822
- parser.consume("RATE_AT");
3823
- parser.parseExpression(chunkC4XZV6E7_cjs.BindingPower.Lowest, builder);
4987
+ parser.parseExpression(chunkNZFKROS7_cjs.BindingPower.Conditional, builder);
4988
+ if (!parser.match("AFTER") && !parser.match("OVER") && !parser.match("IN")) {
4989
+ parser.consume("AFTER");
4990
+ }
4991
+ parser.parseExpression(chunkNZFKROS7_cjs.BindingPower.Conditional, builder);
4992
+ if (!parser.match("RATE_AT")) parser.consume("AT");
4993
+ parser.parseExpression(chunkNZFKROS7_cjs.BindingPower.Conditional, builder);
3824
4994
  builder.emitOpcode(2 /* SWAP */);
3825
4995
  builder.emitOpcode(51 /* CALL_BUILTIN */);
3826
4996
  builder.emitIndex(this.builtinIndex);
3827
4997
  builder.emitIndex(3);
3828
4998
  }
3829
4999
  };
5000
+ var ReturnOnInvestmentParselet = class {
5001
+ constructor(builtinIndex) {
5002
+ this.builtinIndex = builtinIndex;
5003
+ this.category = "Finance";
5004
+ this.bindingPower = chunkNZFKROS7_cjs.BindingPower.Conditional;
5005
+ }
5006
+ parse(parser, _left, _token, builder) {
5007
+ parser.parseExpression(chunkNZFKROS7_cjs.BindingPower.Conditional, builder);
5008
+ parser.consume("RETURNED");
5009
+ builder.emitOpcode(51 /* CALL_BUILTIN */);
5010
+ builder.emitIndex(this.builtinIndex);
5011
+ builder.emitIndex(2);
5012
+ }
5013
+ };
5014
+ var AnnualReturnParselet = class {
5015
+ constructor(builtinIndex) {
5016
+ this.builtinIndex = builtinIndex;
5017
+ this.category = "Finance";
5018
+ }
5019
+ parse(parser, _token, builder) {
5020
+ parser.parseExpression(chunkNZFKROS7_cjs.BindingPower.Conditional, builder);
5021
+ parser.consume("INVESTED");
5022
+ parser.parseExpression(chunkNZFKROS7_cjs.BindingPower.Conditional, builder);
5023
+ parser.consume("RETURNED");
5024
+ if (!parser.match("AFTER") && !parser.match("OVER") && !parser.match("IN")) {
5025
+ parser.consume("AFTER");
5026
+ }
5027
+ parser.parseExpression(chunkNZFKROS7_cjs.BindingPower.Conditional, builder);
5028
+ builder.emitOpcode(51 /* CALL_BUILTIN */);
5029
+ builder.emitIndex(this.builtinIndex);
5030
+ builder.emitIndex(3);
5031
+ }
5032
+ };
5033
+
5034
+ // src/packages/finance/parselets/CompoundInterestParselet.ts
5035
+ var CompoundInterestParselet = class {
5036
+ /**
5037
+ * @param builtinIndex - Annual-compounding builtin.
5038
+ * @param intervalIndex - Builtin taking an explicit periods-per-year, used
5039
+ * when the expression ends in "compounding monthly" or similar.
5040
+ */
5041
+ constructor(builtinIndex, intervalIndex) {
5042
+ this.builtinIndex = builtinIndex;
5043
+ this.intervalIndex = intervalIndex;
5044
+ this.category = "Finance";
5045
+ }
5046
+ parse(parser, _token, builder) {
5047
+ parser.parseExpression(chunkNZFKROS7_cjs.BindingPower.Conditional, builder);
5048
+ if (!parser.match("AFTER") && !parser.match("FOR_DURATION")) {
5049
+ parser.consume("OVER");
5050
+ }
5051
+ parser.parseExpression(chunkNZFKROS7_cjs.BindingPower.Lowest, builder);
5052
+ if (!parser.match("RATE_AT")) parser.consume("AT");
5053
+ parser.parseExpression(chunkNZFKROS7_cjs.BindingPower.Lowest, builder);
5054
+ const periods = readCompoundingInterval(parser);
5055
+ builder.emitOpcode(2 /* SWAP */);
5056
+ if (periods === 1) {
5057
+ builder.emitOpcode(51 /* CALL_BUILTIN */);
5058
+ builder.emitIndex(this.builtinIndex);
5059
+ builder.emitIndex(3);
5060
+ return;
5061
+ }
5062
+ builder.emitOpcode(10 /* PUSH_NUMBER */);
5063
+ builder.emitNumber(periods);
5064
+ builder.emitOpcode(51 /* CALL_BUILTIN */);
5065
+ builder.emitIndex(this.intervalIndex);
5066
+ builder.emitIndex(4);
5067
+ }
5068
+ };
3830
5069
 
3831
5070
  // src/packages/finance/parselets/LoanRepaymentParselet.ts
3832
5071
  var LoanRepaymentParselet = class {
@@ -3836,11 +5075,11 @@ var LoanRepaymentParselet = class {
3836
5075
  this.category = "Finance";
3837
5076
  }
3838
5077
  parse(parser, _token, builder) {
3839
- parser.parseExpression(chunkC4XZV6E7_cjs.BindingPower.Lowest, builder);
5078
+ parser.parseExpression(chunkNZFKROS7_cjs.BindingPower.Lowest, builder);
3840
5079
  parser.consume("OVER");
3841
- parser.parseExpression(chunkC4XZV6E7_cjs.BindingPower.Lowest, builder);
3842
- parser.consume("RATE_AT");
3843
- parser.parseExpression(chunkC4XZV6E7_cjs.BindingPower.Lowest, builder);
5080
+ parser.parseExpression(chunkNZFKROS7_cjs.BindingPower.Lowest, builder);
5081
+ if (!parser.match("RATE_AT")) parser.consume("AT");
5082
+ parser.parseExpression(chunkNZFKROS7_cjs.BindingPower.Lowest, builder);
3844
5083
  builder.emitOpcode(2 /* SWAP */);
3845
5084
  builder.emitOpcode(10 /* PUSH_NUMBER */);
3846
5085
  builder.emitNumber(this.periodsPerYear);
@@ -3857,9 +5096,9 @@ var SalesTaxParselet = class {
3857
5096
  this.category = "Finance";
3858
5097
  }
3859
5098
  parse(parser, _token, builder) {
3860
- parser.parseExpression(chunkC4XZV6E7_cjs.BindingPower.Lowest, builder);
5099
+ parser.parseExpression(chunkNZFKROS7_cjs.BindingPower.Lowest, builder);
3861
5100
  parser.consume("RATE_AT");
3862
- parser.parseExpression(chunkC4XZV6E7_cjs.BindingPower.Lowest, builder);
5101
+ parser.parseExpression(chunkNZFKROS7_cjs.BindingPower.Lowest, builder);
3863
5102
  builder.emitOpcode(51 /* CALL_BUILTIN */);
3864
5103
  builder.emitIndex(this.builtinIndex);
3865
5104
  builder.emitIndex(2);
@@ -3867,20 +5106,20 @@ var SalesTaxParselet = class {
3867
5106
  };
3868
5107
 
3869
5108
  // src/packages/finance/parselets/InflationPluginFunctions.ts
3870
- var INFLATION_FROM_YEAR_TO_PRESENT_IDX = chunkJ73SJHR3_cjs.allocatePluginFunctionIndex();
3871
- var INFLATION_TO_YEAR_FROM_PRESENT_IDX = chunkJ73SJHR3_cjs.allocatePluginFunctionIndex();
3872
- var INFLATION_FUTURE_VALUE_IDX = chunkJ73SJHR3_cjs.allocatePluginFunctionIndex();
5109
+ var INFLATION_FROM_YEAR_TO_PRESENT_IDX = chunk2NPS5DQ3_cjs.allocatePluginFunctionIndex();
5110
+ var INFLATION_TO_YEAR_FROM_PRESENT_IDX = chunk2NPS5DQ3_cjs.allocatePluginFunctionIndex();
5111
+ var INFLATION_FUTURE_VALUE_IDX = chunk2NPS5DQ3_cjs.allocatePluginFunctionIndex();
3873
5112
  function yearRangeError(code, badYear) {
3874
5113
  return chunkOC6LTXYS_cjs.errorValue(
3875
5114
  code,
3876
- `Year ${badYear} is outside the bundled CPI table's range (${chunkJ73SJHR3_cjs.CPI_MIN_YEAR}-${chunkJ73SJHR3_cjs.CPI_MAX_YEAR})`
5115
+ `Year ${badYear} is outside the bundled CPI table's range (${chunk2NPS5DQ3_cjs.CPI_MIN_YEAR}-${chunk2NPS5DQ3_cjs.CPI_MAX_YEAR})`
3877
5116
  );
3878
5117
  }
3879
5118
  function inflationFromYearToPresentHandler(args) {
3880
5119
  const amountValue = args[0];
3881
5120
  const fromYear = args[1].toNumber();
3882
5121
  const toYear = (/* @__PURE__ */ new Date()).getFullYear();
3883
- const result = chunkJ73SJHR3_cjs.adjustForInflation(amountValue.toNumber(), fromYear, toYear);
5122
+ const result = chunk2NPS5DQ3_cjs.adjustForInflation(amountValue.toNumber(), fromYear, toYear);
3884
5123
  if (result === void 0) return yearRangeError("INFLATION_YEAR_OUT_OF_RANGE", fromYear);
3885
5124
  return amountValue.type === 6 /* Uom */ ? chunkOC6LTXYS_cjs.uomValue(result, amountValue.unit) : chunkOC6LTXYS_cjs.numberValue(result);
3886
5125
  }
@@ -3888,7 +5127,7 @@ function inflationToYearFromPresentHandler(args) {
3888
5127
  const amountValue = args[0];
3889
5128
  const fromYear = (/* @__PURE__ */ new Date()).getFullYear();
3890
5129
  const toYear = args[1].toNumber();
3891
- const result = chunkJ73SJHR3_cjs.adjustForInflation(amountValue.toNumber(), fromYear, toYear);
5130
+ const result = chunk2NPS5DQ3_cjs.adjustForInflation(amountValue.toNumber(), fromYear, toYear);
3892
5131
  if (result === void 0) return yearRangeError("INFLATION_YEAR_OUT_OF_RANGE", toYear);
3893
5132
  return amountValue.type === 6 /* Uom */ ? chunkOC6LTXYS_cjs.uomValue(result, amountValue.unit) : chunkOC6LTXYS_cjs.numberValue(result);
3894
5133
  }
@@ -3901,8 +5140,8 @@ function inflationFutureValueHandler(args) {
3901
5140
  return chunkOC6LTXYS_cjs.errorValue("INVALID_RATE", `inflationFutureValue: rate ${rate} makes (1 + rate) non-positive`);
3902
5141
  }
3903
5142
  const amount = amountValue.toNumber();
3904
- const fv = amount * Math.pow(1 + rate, years);
3905
- return amountValue.type === 6 /* Uom */ ? chunkOC6LTXYS_cjs.uomValue(fv, amountValue.unit) : chunkOC6LTXYS_cjs.numberValue(fv);
5143
+ const worth = amount / Math.pow(1 + rate, years);
5144
+ return amountValue.type === 6 /* Uom */ ? chunkOC6LTXYS_cjs.uomValue(worth, amountValue.unit) : chunkOC6LTXYS_cjs.numberValue(worth);
3906
5145
  }
3907
5146
 
3908
5147
  // src/packages/finance/parselets/InflationQueryParselet.ts
@@ -3913,10 +5152,10 @@ var InflationQueryParselet = class {
3913
5152
  this.category = "Finance";
3914
5153
  }
3915
5154
  parse(parser, _token, builder) {
3916
- parser.parseExpression(chunkC4XZV6E7_cjs.BindingPower.Product, builder);
5155
+ parser.parseExpression(chunkNZFKROS7_cjs.BindingPower.Product, builder);
3917
5156
  if (this.variant === "what-was") {
3918
5157
  parser.consume("WORTH_IN");
3919
- parser.parseExpression(chunkC4XZV6E7_cjs.BindingPower.Lowest, builder);
5158
+ parser.parseExpression(chunkNZFKROS7_cjs.BindingPower.Lowest, builder);
3920
5159
  builder.emitOpcode(50 /* CALL_PLUGIN */);
3921
5160
  builder.emitIndex(INFLATION_TO_YEAR_FROM_PRESENT_IDX);
3922
5161
  builder.emitIndex(2);
@@ -3925,7 +5164,7 @@ var InflationQueryParselet = class {
3925
5164
  const next = parser.peek();
3926
5165
  if (next?.type === "FROM") {
3927
5166
  parser.consume();
3928
- parser.parseExpression(chunkC4XZV6E7_cjs.BindingPower.Lowest, builder);
5167
+ parser.parseExpression(chunkNZFKROS7_cjs.BindingPower.Lowest, builder);
3929
5168
  builder.emitOpcode(50 /* CALL_PLUGIN */);
3930
5169
  builder.emitIndex(INFLATION_FROM_YEAR_TO_PRESENT_IDX);
3931
5170
  builder.emitIndex(2);
@@ -3933,9 +5172,9 @@ var InflationQueryParselet = class {
3933
5172
  }
3934
5173
  if (next?.type === "IN") {
3935
5174
  parser.consume();
3936
- parser.parseExpression(chunkC4XZV6E7_cjs.BindingPower.Lowest, builder);
5175
+ parser.parseExpression(chunkNZFKROS7_cjs.BindingPower.Lowest, builder);
3937
5176
  parser.consume("WORTH_IN");
3938
- parser.parseExpression(chunkC4XZV6E7_cjs.BindingPower.Lowest, builder);
5177
+ parser.parseExpression(chunkNZFKROS7_cjs.BindingPower.Lowest, builder);
3939
5178
  builder.emitOpcode(51 /* CALL_BUILTIN */);
3940
5179
  builder.emitIndex(INFLATION_ADJUST_BUILTIN_IDX);
3941
5180
  builder.emitIndex(3);
@@ -3955,11 +5194,11 @@ var InflationFutureValueParselet = class {
3955
5194
  this.category = "Finance";
3956
5195
  }
3957
5196
  parse(parser, _token, builder) {
3958
- parser.parseExpression(chunkC4XZV6E7_cjs.BindingPower.Product, builder);
5197
+ parser.parseExpression(chunkNZFKROS7_cjs.BindingPower.Product, builder);
3959
5198
  parser.consume("IN");
3960
- parser.parseExpression(chunkC4XZV6E7_cjs.BindingPower.Lowest, builder);
5199
+ parser.parseExpression(chunkNZFKROS7_cjs.BindingPower.Lowest, builder);
3961
5200
  parser.consume("ASSUMING");
3962
- parser.parseExpression(chunkC4XZV6E7_cjs.BindingPower.Lowest, builder);
5201
+ parser.parseExpression(chunkNZFKROS7_cjs.BindingPower.Lowest, builder);
3963
5202
  const inflationToken = parser.peek();
3964
5203
  const inflationWord = inflationToken && inflationToken.type === "IDENT" ? inflationToken.value.toLowerCase() : void 0;
3965
5204
  if (inflationWord !== "inflation") {
@@ -4008,7 +5247,7 @@ function inYearDollarsNormalizerRule(priority = 70) {
4008
5247
  if (word !== "dollars" && word !== "dollar") return null;
4009
5248
  return {
4010
5249
  consumed: 3,
4011
- replacement: [chunkEBSPLUW4_cjs.createFusedToken("IN_YEAR_DOLLARS", yearToken.value, tokens.slice(pos, pos + 3))],
5250
+ replacement: [chunkAHCLWAM5_cjs.createFusedToken("IN_YEAR_DOLLARS", yearToken.value, tokens.slice(pos, pos + 3))],
4012
5251
  ruleName: "finance:in-year-dollars"
4013
5252
  };
4014
5253
  }
@@ -4020,8 +5259,14 @@ var COMPOUND_FV = 51;
4020
5259
  var COMPOUND_INTEREST = 52;
4021
5260
  var LOAN_REPAYMENT = 55;
4022
5261
  var LOAN_INTEREST = 56;
4023
- var TAX_ADD = 58;
5262
+ var COMPOUND_FV_EVERY = 80;
5263
+ var COMPOUND_INTEREST_EVERY = 81;
5264
+ var PRESENT_VALUE = 82;
5265
+ var ROI = 83;
5266
+ var ANNUAL_RETURN = 84;
4024
5267
  var TAX_REMOVE = 59;
5268
+ var TAX_IN = 85;
5269
+ var TAX_ON = 86;
4025
5270
  var FINANCE_PACKAGE = {
4026
5271
  name: "solve-finance",
4027
5272
  phrases: {
@@ -4037,17 +5282,29 @@ var FINANCE_PACKAGE = {
4037
5282
  "total interest on": "TOTAL_LOAN_INTEREST_ON",
4038
5283
  "tax on": "TAX_ON",
4039
5284
  "tax off": "TAX_OFF",
5285
+ // The tax already inside a gross amount. Soulver documents "VAT in",
5286
+ // "VAT of" and "VAT from" as three spellings of one thing.
5287
+ "tax in": "TAX_IN_PHRASE",
5288
+ "tax of": "TAX_IN_PHRASE",
5289
+ "tax from": "TAX_IN_PHRASE",
5290
+ "vat in": "TAX_IN_PHRASE",
5291
+ "vat of": "TAX_IN_PHRASE",
5292
+ "vat from": "TAX_IN_PHRASE",
4040
5293
  "vat on": "TAX_ON",
4041
5294
  "vat off": "TAX_OFF",
4042
5295
  "what is": "WHAT_IS",
4043
5296
  "what was": "WHAT_WAS",
5297
+ "present value of": "PRESENT_VALUE_OF",
5298
+ "annual return on": "ANNUAL_RETURN_ON",
4044
5299
  "value of": "VALUE_OF",
4045
5300
  "worth in": "WORTH_IN",
4046
5301
  "assuming": "ASSUMING"
4047
5302
  },
4048
5303
  prefixParselets: [
4049
- { tokenType: "COMPOUND_INTEREST_ON", parselet: new CompoundInterestParselet(COMPOUND_FV) },
4050
- { tokenType: "INTEREST_ON", parselet: new CompoundInterestParselet(COMPOUND_INTEREST) },
5304
+ { tokenType: "PRESENT_VALUE_OF", parselet: new PresentValueParselet(PRESENT_VALUE) },
5305
+ { tokenType: "ANNUAL_RETURN_ON", parselet: new AnnualReturnParselet(ANNUAL_RETURN) },
5306
+ { tokenType: "COMPOUND_INTEREST_ON", parselet: new CompoundInterestParselet(COMPOUND_FV, COMPOUND_FV_EVERY) },
5307
+ { tokenType: "INTEREST_ON", parselet: new CompoundInterestParselet(COMPOUND_INTEREST, COMPOUND_INTEREST_EVERY) },
4051
5308
  { tokenType: "DAILY_REPAYMENT_ON", parselet: new LoanRepaymentParselet(LOAN_REPAYMENT, 365) },
4052
5309
  { tokenType: "MONTHLY_REPAYMENT_ON", parselet: new LoanRepaymentParselet(LOAN_REPAYMENT, 12) },
4053
5310
  { tokenType: "ANNUAL_REPAYMENT_ON", parselet: new LoanRepaymentParselet(LOAN_REPAYMENT, 1) },
@@ -4056,13 +5313,20 @@ var FINANCE_PACKAGE = {
4056
5313
  { tokenType: "MONTHLY_LOAN_INTEREST_ON", parselet: new LoanRepaymentParselet(LOAN_INTEREST, 12) },
4057
5314
  { tokenType: "ANNUAL_LOAN_INTEREST_ON", parselet: new LoanRepaymentParselet(LOAN_INTEREST, 1) },
4058
5315
  { tokenType: "TOTAL_LOAN_INTEREST_ON", parselet: new LoanRepaymentParselet(LOAN_INTEREST, 0) },
4059
- { tokenType: "TAX_ON", parselet: new SalesTaxParselet(TAX_ADD) },
5316
+ { tokenType: "TAX_ON", parselet: new SalesTaxParselet(TAX_ON) },
4060
5317
  { tokenType: "TAX_OFF", parselet: new SalesTaxParselet(TAX_REMOVE) },
5318
+ { tokenType: "TAX_IN_PHRASE", parselet: new SalesTaxParselet(TAX_IN) },
4061
5319
  { tokenType: "WHAT_IS", parselet: new InflationQueryParselet("what-is") },
4062
5320
  { tokenType: "WHAT_WAS", parselet: new InflationQueryParselet("what-was") },
4063
5321
  { tokenType: "VALUE_OF", parselet: new InflationFutureValueParselet() }
4064
5322
  ],
4065
5323
  infixParselets: [
5324
+ // The documented bare forms: "$1,000 after 3 years at 7%" and the "for
5325
+ // ... compounding monthly" variant. Both pivot words behave identically;
5326
+ // Soulver uses "after" for the annual form and "for" with an interval.
5327
+ { tokenType: "AFTER", parselet: new InvestmentGrowthParselet(COMPOUND_FV, COMPOUND_FV_EVERY) },
5328
+ { tokenType: "FOR_DURATION", parselet: new InvestmentGrowthParselet(COMPOUND_FV, COMPOUND_FV_EVERY) },
5329
+ { tokenType: "INVESTED", parselet: new ReturnOnInvestmentParselet(ROI) },
4066
5330
  { tokenType: "IN_YEAR_DOLLARS", parselet: new InYearDollarsParselet() }
4067
5331
  ],
4068
5332
  normalizerRules: [
@@ -4132,7 +5396,7 @@ function createQueryResolver(opts) {
4132
5396
  i += 3;
4133
5397
  continue;
4134
5398
  }
4135
- i = chunkWQTTOGXC_cjs.nextInstruction(opcodes, i);
5399
+ i = chunk4AVD7NZW_cjs.nextInstruction(opcodes, i);
4136
5400
  }
4137
5401
  return null;
4138
5402
  },
@@ -4320,7 +5584,7 @@ function fetchCityWeather(city, signal) {
4320
5584
  }
4321
5585
 
4322
5586
  // src/packages/weather/WeatherPackage.ts
4323
- var WEATHER_FN_IDX = chunkJ73SJHR3_cjs.allocatePluginFunctionIndex();
5587
+ var WEATHER_FN_IDX = chunk2NPS5DQ3_cjs.allocatePluginFunctionIndex();
4324
5588
  var { resolver: weatherResolver, pluginFunction: weatherPluginFunction } = createQueryResolver({
4325
5589
  namespace: "weather",
4326
5590
  pluginFunctionIndex: WEATHER_FN_IDX,
@@ -4425,6 +5689,15 @@ function requireYear(text, raw) {
4425
5689
  function tryParseDatePhrase(parser) {
4426
5690
  const first = parser.peek();
4427
5691
  if (!first) return null;
5692
+ if (first.type === "DATETIME_LITERAL") {
5693
+ parser.consume();
5694
+ const date = new Date(Number(first.value));
5695
+ if (Number.isNaN(date.getTime())) return null;
5696
+ return {
5697
+ isoDate: `${date.getFullYear()}-${pad2(date.getMonth() + 1)}-${pad2(date.getDate())}`,
5698
+ raw: first.text ?? first.value
5699
+ };
5700
+ }
4428
5701
  if (first.type === "NUMBER") {
4429
5702
  const g1 = parser.consume();
4430
5703
  const sepTok = parser.peek();
@@ -4550,7 +5823,7 @@ var MAJOR_TICKERS = /* @__PURE__ */ new Set([
4550
5823
 
4551
5824
  // src/packages/stocks/normalizer/StockTickerNormalizerRule.ts
4552
5825
  var STOCK_TICKER_TYPE = "STOCK_TICKER";
4553
- var STOCK_TICKER_TYPE_ID = chunkGW32KPCU_cjs.tokenTypeId(STOCK_TICKER_TYPE);
5826
+ var STOCK_TICKER_TYPE_ID = chunkR24DI24X_cjs.tokenTypeId(STOCK_TICKER_TYPE);
4554
5827
  function stockTickerNormalizerRule() {
4555
5828
  return {
4556
5829
  name: "stocks:bare-ticker",
@@ -4560,7 +5833,7 @@ function stockTickerNormalizerRule() {
4560
5833
  if (!token || token.type !== "IDENT") return null;
4561
5834
  if (!MAJOR_TICKERS.has(token.value)) return null;
4562
5835
  if (pos > 0 && tokens[pos - 1].type === "COLON") return null;
4563
- const fusedToken = new chunkNMD5VRN4_cjs.LexerToken(
5836
+ const fusedToken = new chunkM4F66R4O_cjs.LexerToken(
4564
5837
  STOCK_TICKER_TYPE,
4565
5838
  STOCK_TICKER_TYPE_ID,
4566
5839
  token.value,
@@ -4629,7 +5902,7 @@ function finishStockExpression(parser, builder, ticker, currentFnIdx, historical
4629
5902
  const suffix = tryParseDateSuffix(parser);
4630
5903
  if (sawStar && !suffix) {
4631
5904
  emitStockQuery(builder, ticker, null, currentFnIdx, historicalFnIdx);
4632
- parser.parseExpression(chunkC4XZV6E7_cjs.BindingPower.Product, builder);
5905
+ parser.parseExpression(chunkNZFKROS7_cjs.BindingPower.Product, builder);
4633
5906
  builder.emitOpcode(22 /* MUL */);
4634
5907
  return;
4635
5908
  }
@@ -4669,8 +5942,8 @@ function bareStockTickerParselet(currentFnIdx, historicalFnIdx) {
4669
5942
 
4670
5943
  // src/packages/stocks/StocksPackage.ts
4671
5944
  function createStocksPackage(config = {}) {
4672
- const currentFnIdx = chunkJ73SJHR3_cjs.allocatePluginFunctionIndex();
4673
- const historicalFnIdx = chunkJ73SJHR3_cjs.allocatePluginFunctionIndex();
5945
+ const currentFnIdx = chunk2NPS5DQ3_cjs.allocatePluginFunctionIndex();
5946
+ const historicalFnIdx = chunk2NPS5DQ3_cjs.allocatePluginFunctionIndex();
4674
5947
  function notConfigured(what) {
4675
5948
  return chunkOC6LTXYS_cjs.errorValue(
4676
5949
  "STOCKS_NOT_CONFIGURED",
@@ -4745,7 +6018,7 @@ function knowledgeQueryParselet(pluginFnIdx) {
4745
6018
 
4746
6019
  // src/packages/knowledge/KnowledgePackage.ts
4747
6020
  function createKnowledgePackage(config = {}) {
4748
- const fnIdx = chunkJ73SJHR3_cjs.allocatePluginFunctionIndex();
6021
+ const fnIdx = chunk2NPS5DQ3_cjs.allocatePluginFunctionIndex();
4749
6022
  const { resolver, pluginFunction } = createQueryResolver({
4750
6023
  namespace: "knowledge",
4751
6024
  pluginFunctionIndex: fnIdx,
@@ -4801,7 +6074,7 @@ function requireContext(context) {
4801
6074
  }
4802
6075
  return null;
4803
6076
  }
4804
- var PREV_FN_IDX = chunkJ73SJHR3_cjs.allocatePluginFunctionIndex();
6077
+ var PREV_FN_IDX = chunk2NPS5DQ3_cjs.allocatePluginFunctionIndex();
4805
6078
  function prevHandler(_args, context) {
4806
6079
  const ctxError = requireContext(context);
4807
6080
  if (ctxError) return ctxError;
@@ -4810,7 +6083,7 @@ function prevHandler(_args, context) {
4810
6083
  const err = checkLineValue(v, targetLine);
4811
6084
  return err ?? v;
4812
6085
  }
4813
- var LINE_REF_FN_IDX = chunkJ73SJHR3_cjs.allocatePluginFunctionIndex();
6086
+ var LINE_REF_FN_IDX = chunk2NPS5DQ3_cjs.allocatePluginFunctionIndex();
4814
6087
  function lineRefHandler(args, context) {
4815
6088
  const ctxError = requireContext(context);
4816
6089
  if (ctxError) return ctxError;
@@ -4845,13 +6118,13 @@ function aggregateRange(from, to, context, isAverage) {
4845
6118
  const result = isAverage ? total / count : total;
4846
6119
  return sawUom ? chunkOC6LTXYS_cjs.uomValue(result, commonUnit) : chunkOC6LTXYS_cjs.numberValue(result);
4847
6120
  }
4848
- var SUM_RANGE_FN_IDX = chunkJ73SJHR3_cjs.allocatePluginFunctionIndex();
6121
+ var SUM_RANGE_FN_IDX = chunk2NPS5DQ3_cjs.allocatePluginFunctionIndex();
4849
6122
  function sumRangeHandler(args, context) {
4850
6123
  const ctxError = requireContext(context);
4851
6124
  if (ctxError) return ctxError;
4852
6125
  return aggregateRange(args[0].toNumber(), args[1].toNumber(), context, false);
4853
6126
  }
4854
- var AVERAGE_RANGE_FN_IDX = chunkJ73SJHR3_cjs.allocatePluginFunctionIndex();
6127
+ var AVERAGE_RANGE_FN_IDX = chunk2NPS5DQ3_cjs.allocatePluginFunctionIndex();
4855
6128
  function averageRangeHandler(args, context) {
4856
6129
  const ctxError = requireContext(context);
4857
6130
  if (ctxError) return ctxError;
@@ -4888,13 +6161,13 @@ function aggregateAbove(context, isAverage) {
4888
6161
  const result = isAverage ? total / count : total;
4889
6162
  return sawUom ? chunkOC6LTXYS_cjs.uomValue(result, commonUnit) : chunkOC6LTXYS_cjs.numberValue(result);
4890
6163
  }
4891
- var TOTAL_ABOVE_FN_IDX = chunkJ73SJHR3_cjs.allocatePluginFunctionIndex();
6164
+ var TOTAL_ABOVE_FN_IDX = chunk2NPS5DQ3_cjs.allocatePluginFunctionIndex();
4892
6165
  function totalAboveHandler(_args, context) {
4893
6166
  const ctxError = requireContext(context);
4894
6167
  if (ctxError) return ctxError;
4895
6168
  return aggregateAbove(context, false);
4896
6169
  }
4897
- var AVERAGE_ABOVE_FN_IDX = chunkJ73SJHR3_cjs.allocatePluginFunctionIndex();
6170
+ var AVERAGE_ABOVE_FN_IDX = chunk2NPS5DQ3_cjs.allocatePluginFunctionIndex();
4898
6171
  function averageAboveHandler(_args, context) {
4899
6172
  const ctxError = requireContext(context);
4900
6173
  if (ctxError) return ctxError;
@@ -4966,9 +6239,9 @@ var AboveAggregateParselet = class {
4966
6239
  };
4967
6240
 
4968
6241
  // src/packages/lines/normalizer/LineRefNormalizerRule.ts
4969
- var LINE_REF_TYPE_ID = chunkGW32KPCU_cjs.tokenTypeId("LINE_REF");
4970
- var SUM_RANGE_CALL_TYPE_ID = chunkGW32KPCU_cjs.tokenTypeId("SUM_RANGE_CALL");
4971
- var AVERAGE_RANGE_CALL_TYPE_ID = chunkGW32KPCU_cjs.tokenTypeId("AVERAGE_RANGE_CALL");
6242
+ var LINE_REF_TYPE_ID = chunkR24DI24X_cjs.tokenTypeId("LINE_REF");
6243
+ var SUM_RANGE_CALL_TYPE_ID = chunkR24DI24X_cjs.tokenTypeId("SUM_RANGE_CALL");
6244
+ var AVERAGE_RANGE_CALL_TYPE_ID = chunkR24DI24X_cjs.tokenTypeId("AVERAGE_RANGE_CALL");
4972
6245
  function lineRefNormalizerRule() {
4973
6246
  return {
4974
6247
  name: "lines:line-ref",
@@ -5005,7 +6278,7 @@ function lineRefNormalizerRule() {
5005
6278
  };
5006
6279
  }
5007
6280
  function makeLineRefToken(lineNumberText, sourceToken) {
5008
- return new chunkNMD5VRN4_cjs.LexerToken(
6281
+ return new chunkM4F66R4O_cjs.LexerToken(
5009
6282
  "LINE_REF",
5010
6283
  LINE_REF_TYPE_ID,
5011
6284
  lineNumberText,
@@ -5031,7 +6304,7 @@ function rangeCallNormalizerRule() {
5031
6304
  return {
5032
6305
  consumed: 1,
5033
6306
  replacement: [
5034
- new chunkNMD5VRN4_cjs.LexerToken(tokenType, typeId, token.value, token.value, token.offset, 0, token.line, token.col)
6307
+ new chunkM4F66R4O_cjs.LexerToken(tokenType, typeId, token.value, token.value, token.offset, 0, token.line, token.col)
5035
6308
  ],
5036
6309
  ruleName: "lines:range-call"
5037
6310
  };
@@ -5118,5 +6391,5 @@ exports.WEATHER_PACKAGE = WEATHER_PACKAGE;
5118
6391
  exports.containsSymbolicCall = containsSymbolicCall;
5119
6392
  exports.createKnowledgePackage = createKnowledgePackage;
5120
6393
  exports.createStocksPackage = createStocksPackage;
5121
- //# sourceMappingURL=chunk-RZCWSXTA.cjs.map
5122
- //# sourceMappingURL=chunk-RZCWSXTA.cjs.map
6394
+ //# sourceMappingURL=chunk-43G3JT2E.cjs.map
6395
+ //# sourceMappingURL=chunk-43G3JT2E.cjs.map