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.
- package/dist/{Lexer-D9l4Yrk2.d.ts → Lexer-Cfm79Dak.d.cts} +18 -2
- package/dist/{Lexer-BJdhlnej.d.cts → Lexer-W9MBOP0V.d.ts} +18 -2
- package/dist/{PackageRegistry-DaH4aIKP.d.ts → PackageRegistry-CjDt-Jy_.d.ts} +60 -4
- package/dist/{PackageRegistry-D-Tv_7ca.d.cts → PackageRegistry-pHtKythi.d.cts} +60 -4
- package/dist/{Parselet-CK8cQu2a.d.cts → Parselet-Cu0bLSis.d.cts} +1 -1
- package/dist/{Parselet-ConOIdRO.d.ts → Parselet-DEdF9I7n.d.ts} +1 -1
- package/dist/{Token-0jpvUdPY.d.cts → Token-BzG5G4ja.d.cts} +44 -0
- package/dist/{Token-0jpvUdPY.d.ts → Token-BzG5G4ja.d.ts} +44 -0
- package/dist/TokenNormalizer-DGVa24Q-.d.cts +377 -0
- package/dist/TokenNormalizer-t_GotBxr.d.ts +377 -0
- package/dist/{chunk-HWSZJQCI.js → chunk-2CS6OMZK.js} +37 -15
- package/dist/chunk-2CS6OMZK.js.map +1 -0
- package/dist/{chunk-XJCMXB2E.cjs → chunk-2MV4HBKC.cjs} +187 -61
- package/dist/chunk-2MV4HBKC.cjs.map +1 -0
- package/dist/{chunk-J73SJHR3.cjs → chunk-2NPS5DQ3.cjs} +558 -341
- package/dist/chunk-2NPS5DQ3.cjs.map +1 -0
- package/dist/{chunk-QWY3VEZN.js → chunk-3MNKQZ77.js} +65 -12
- package/dist/chunk-3MNKQZ77.js.map +1 -0
- package/dist/{chunk-RZCWSXTA.cjs → chunk-43G3JT2E.cjs} +1491 -218
- package/dist/chunk-43G3JT2E.cjs.map +1 -0
- package/dist/{chunk-WQTTOGXC.cjs → chunk-4AVD7NZW.cjs} +4 -4
- package/dist/{chunk-WQTTOGXC.cjs.map → chunk-4AVD7NZW.cjs.map} +1 -1
- package/dist/{chunk-HTXVVJRA.cjs → chunk-4CVLFLOB.cjs} +112 -2
- package/dist/chunk-4CVLFLOB.cjs.map +1 -0
- package/dist/{chunk-5YEMOYSE.js → chunk-6GCKCWLB.js} +9 -3
- package/dist/chunk-6GCKCWLB.js.map +1 -0
- package/dist/{chunk-EBSPLUW4.cjs → chunk-AHCLWAM5.cjs} +28 -10
- package/dist/chunk-AHCLWAM5.cjs.map +1 -0
- package/dist/{chunk-KVILKGMS.js → chunk-AJA6LUI7.js} +36 -2
- package/dist/chunk-AJA6LUI7.js.map +1 -0
- package/dist/{chunk-EHAHVROS.cjs → chunk-CLVQBF5C.cjs} +5 -5
- package/dist/{chunk-EHAHVROS.cjs.map → chunk-CLVQBF5C.cjs.map} +1 -1
- package/dist/{chunk-34RRD7PC.js → chunk-CYFK5SY2.js} +111 -3
- package/dist/chunk-CYFK5SY2.js.map +1 -0
- package/dist/{chunk-M5LX5AOO.js → chunk-DDQVZGLO.js} +25 -12
- package/dist/chunk-DDQVZGLO.js.map +1 -0
- package/dist/{chunk-LR7YASZF.cjs → chunk-EAAHVJ4P.cjs} +3 -3
- package/dist/chunk-EAAHVJ4P.cjs.map +1 -0
- package/dist/{chunk-3PPFLFH4.js → chunk-FIFQPKBA.js} +1417 -144
- package/dist/chunk-FIFQPKBA.js.map +1 -0
- package/dist/{chunk-RIN643A3.js → chunk-GPPLSM2Z.js} +61 -2
- package/dist/chunk-GPPLSM2Z.js.map +1 -0
- package/dist/{chunk-4QADQTWS.js → chunk-KHJUSHFU.js} +231 -14
- package/dist/chunk-KHJUSHFU.js.map +1 -0
- package/dist/{chunk-NMD5VRN4.cjs → chunk-M4F66R4O.cjs} +74 -73
- package/dist/chunk-M4F66R4O.cjs.map +1 -0
- package/dist/{chunk-6NTVRDQV.cjs → chunk-M4Q47KHF.cjs} +126 -73
- package/dist/chunk-M4Q47KHF.cjs.map +1 -0
- package/dist/{chunk-LIPPNDBE.js → chunk-M5E34VG5.js} +3 -3
- package/dist/{chunk-LIPPNDBE.js.map → chunk-M5E34VG5.js.map} +1 -1
- package/dist/{chunk-53B6KDDJ.cjs → chunk-MBNQVDVC.cjs} +38 -3
- package/dist/{chunk-53B6KDDJ.cjs.map → chunk-MBNQVDVC.cjs.map} +1 -1
- package/dist/{chunk-SDGRK7EP.js → chunk-MEOHSQEH.js} +6 -5
- package/dist/chunk-MEOHSQEH.js.map +1 -0
- package/dist/{chunk-NMCRQP3Z.cjs → chunk-MTX2KVU7.cjs} +71 -70
- package/dist/chunk-MTX2KVU7.cjs.map +1 -0
- package/dist/{chunk-QNJ4ACRT.cjs → chunk-NJTXJ5AG.cjs} +37 -15
- package/dist/chunk-NJTXJ5AG.cjs.map +1 -0
- package/dist/{chunk-C4XZV6E7.cjs → chunk-NZFKROS7.cjs} +26 -20
- package/dist/chunk-NZFKROS7.cjs.map +1 -0
- package/dist/{chunk-64W6GLLZ.js → chunk-OFXOTECC.js} +24 -6
- package/dist/chunk-OFXOTECC.js.map +1 -0
- package/dist/{chunk-3YNVWKR2.cjs → chunk-OUHT5Z36.cjs} +28 -5
- package/dist/chunk-OUHT5Z36.cjs.map +1 -0
- package/dist/{chunk-EIGTWK5N.js → chunk-OWRHDUJD.js} +3 -3
- package/dist/chunk-OWRHDUJD.js.map +1 -0
- package/dist/{chunk-4MG4XKO2.js → chunk-PCPX42KL.js} +38 -3
- package/dist/{chunk-4MG4XKO2.js.map → chunk-PCPX42KL.js.map} +1 -1
- package/dist/{chunk-GW32KPCU.cjs → chunk-R24DI24X.cjs} +61 -2
- package/dist/chunk-R24DI24X.cjs.map +1 -0
- package/dist/{chunk-XVWCOTR6.js → chunk-SFQWJMKT.js} +7 -6
- package/dist/chunk-SFQWJMKT.js.map +1 -0
- package/dist/{chunk-6BKTCEUP.cjs → chunk-TY3TLZAW.cjs} +36 -2
- package/dist/chunk-TY3TLZAW.cjs.map +1 -0
- package/dist/{chunk-DM3LMRBC.js → chunk-UKPGSAZW.js} +187 -61
- package/dist/chunk-UKPGSAZW.js.map +1 -0
- package/dist/{chunk-NH2O2AUR.js → chunk-WC5FFSHB.js} +26 -4
- package/dist/chunk-WC5FFSHB.js.map +1 -0
- package/dist/{chunk-CLL7RUQV.cjs → chunk-WWOLFXTX.cjs} +40 -18
- package/dist/chunk-WWOLFXTX.cjs.map +1 -0
- package/dist/{chunk-JBSYC7BB.cjs → chunk-X2BWAZAU.cjs} +66 -53
- package/dist/chunk-X2BWAZAU.cjs.map +1 -0
- package/dist/{chunk-GOLDJNMZ.js → chunk-XBTEO4OB.js} +28 -5
- package/dist/chunk-XBTEO4OB.js.map +1 -0
- package/dist/{chunk-EPOXXJBK.js → chunk-YU2CUNFO.js} +3 -3
- package/dist/{chunk-EPOXXJBK.js.map → chunk-YU2CUNFO.js.map} +1 -1
- package/dist/constants.cjs +4 -4
- package/dist/constants.js +1 -1
- package/dist/engine.cjs +29 -30
- package/dist/engine.d.cts +6 -6
- package/dist/engine.d.ts +6 -6
- package/dist/engine.js +19 -20
- package/dist/format.cjs +7 -8
- package/dist/format.cjs.map +1 -1
- package/dist/format.js +2 -3
- package/dist/format.js.map +1 -1
- package/dist/index.cjs +28 -29
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +6 -6
- package/dist/index.d.ts +6 -6
- package/dist/index.js +20 -21
- package/dist/index.js.map +1 -1
- package/dist/language.cjs +74 -12
- package/dist/language.cjs.map +1 -1
- package/dist/language.d.cts +6 -6
- package/dist/language.d.ts +6 -6
- package/dist/language.js +68 -6
- package/dist/language.js.map +1 -1
- package/dist/lexer.cjs +19 -19
- package/dist/lexer.d.cts +3 -3
- package/dist/lexer.d.ts +3 -3
- package/dist/lexer.js +7 -7
- package/dist/normalizer.cjs +12 -12
- package/dist/normalizer.d.cts +4 -210
- package/dist/normalizer.d.ts +4 -210
- package/dist/normalizer.js +6 -6
- package/dist/packages.cjs +36 -37
- package/dist/packages.d.cts +5 -47
- package/dist/packages.d.ts +5 -47
- package/dist/packages.js +13 -14
- package/dist/parser.cjs +12 -12
- package/dist/parser.d.cts +3 -2
- package/dist/parser.d.ts +3 -2
- package/dist/parser.js +4 -4
- package/dist/resolvers.d.cts +1 -1
- package/dist/resolvers.d.ts +1 -1
- package/dist/uom.cjs +14 -14
- package/dist/uom.d.cts +12 -4
- package/dist/uom.d.ts +12 -4
- package/dist/uom.js +4 -4
- package/dist/vm.cjs +17 -17
- package/dist/vm.js +6 -6
- package/package.json +2 -1
- package/dist/NormalizerRule-BrVoVjmP.d.cts +0 -163
- package/dist/NormalizerRule-CEjf1FyD.d.ts +0 -163
- package/dist/chunk-34RRD7PC.js.map +0 -1
- package/dist/chunk-3PPFLFH4.js.map +0 -1
- package/dist/chunk-3YNVWKR2.cjs.map +0 -1
- package/dist/chunk-4QADQTWS.js.map +0 -1
- package/dist/chunk-5YEMOYSE.js.map +0 -1
- package/dist/chunk-64W6GLLZ.js.map +0 -1
- package/dist/chunk-6BKTCEUP.cjs.map +0 -1
- package/dist/chunk-6NTVRDQV.cjs.map +0 -1
- package/dist/chunk-C4XZV6E7.cjs.map +0 -1
- package/dist/chunk-CLL7RUQV.cjs.map +0 -1
- package/dist/chunk-DM3LMRBC.js.map +0 -1
- package/dist/chunk-EBSPLUW4.cjs.map +0 -1
- package/dist/chunk-EIGTWK5N.js.map +0 -1
- package/dist/chunk-GOLDJNMZ.js.map +0 -1
- package/dist/chunk-GW32KPCU.cjs.map +0 -1
- package/dist/chunk-HTXVVJRA.cjs.map +0 -1
- package/dist/chunk-HWSZJQCI.js.map +0 -1
- package/dist/chunk-J73SJHR3.cjs.map +0 -1
- package/dist/chunk-JBSYC7BB.cjs.map +0 -1
- package/dist/chunk-KVILKGMS.js.map +0 -1
- package/dist/chunk-LR7YASZF.cjs.map +0 -1
- package/dist/chunk-M5LX5AOO.js.map +0 -1
- package/dist/chunk-NH2O2AUR.js.map +0 -1
- package/dist/chunk-NMCRQP3Z.cjs.map +0 -1
- package/dist/chunk-NMD5VRN4.cjs.map +0 -1
- package/dist/chunk-OT6OJY7C.cjs +0 -114
- package/dist/chunk-OT6OJY7C.cjs.map +0 -1
- package/dist/chunk-QNJ4ACRT.cjs.map +0 -1
- package/dist/chunk-QWY3VEZN.js.map +0 -1
- package/dist/chunk-RFYD5TJE.js +0 -111
- package/dist/chunk-RFYD5TJE.js.map +0 -1
- package/dist/chunk-RIN643A3.js.map +0 -1
- package/dist/chunk-RZCWSXTA.cjs.map +0 -1
- package/dist/chunk-SDGRK7EP.js.map +0 -1
- package/dist/chunk-XJCMXB2E.cjs.map +0 -1
- package/dist/chunk-XVWCOTR6.js.map +0 -1
|
@@ -1,17 +1,18 @@
|
|
|
1
|
-
import { BindingPower, BytecodeBuilder } from './chunk-
|
|
2
|
-
import { createFusedToken, isInsideRangeContext } from './chunk-
|
|
1
|
+
import { BindingPower, BytecodeBuilder } from './chunk-6GCKCWLB.js';
|
|
2
|
+
import { createFusedToken, isInsideRangeContext } from './chunk-OFXOTECC.js';
|
|
3
3
|
import { stripQuotes } from './chunk-PFSDGGYL.js';
|
|
4
|
-
import { CurrencyAsyncResolver, nextInstruction } from './chunk-
|
|
4
|
+
import { CurrencyAsyncResolver, nextInstruction } from './chunk-YU2CUNFO.js';
|
|
5
5
|
import { getActiveQueryClient } from './chunk-NLNDMWZC.js';
|
|
6
|
-
import { allocatePluginFunctionIndex, sharedGlobalVariableStore, adjustForInflation, CPI_MIN_YEAR, CPI_MAX_YEAR } from './chunk-
|
|
6
|
+
import { allocatePluginFunctionIndex, sharedGlobalVariableStore, adjustForInflation, CPI_MIN_YEAR, CPI_MAX_YEAR } from './chunk-KHJUSHFU.js';
|
|
7
7
|
import { createTimeoutSignal } from './chunk-Z4X2RWOF.js';
|
|
8
|
-
import { resolveCurrencyAlias } from './chunk-
|
|
8
|
+
import { resolveCurrencyAlias } from './chunk-CYFK5SY2.js';
|
|
9
9
|
import { errorValue, numberValue, stringValue, boolValue, uomValue, datetimeValue, timecodeUnit, ValueType } from './chunk-BVAR5J2F.js';
|
|
10
|
-
import { getMeasure, convertUnit } from './chunk-
|
|
11
|
-
import { LexerToken } from './chunk-
|
|
12
|
-
import { tokenTypeId } from './chunk-
|
|
13
|
-
import { getLocale } from './chunk-
|
|
14
|
-
import { isKnownUnit } from './chunk-
|
|
10
|
+
import { getMeasure, convertUnit } from './chunk-WC5FFSHB.js';
|
|
11
|
+
import { LexerToken } from './chunk-SFQWJMKT.js';
|
|
12
|
+
import { tokenTypeId } from './chunk-GPPLSM2Z.js';
|
|
13
|
+
import { getLocale } from './chunk-XBTEO4OB.js';
|
|
14
|
+
import { isKnownUnit } from './chunk-M5E34VG5.js';
|
|
15
|
+
import { UNIT_TABLE } from './chunk-PCPX42KL.js';
|
|
15
16
|
import { ErrorFactory } from './chunk-6RDSQURE.js';
|
|
16
17
|
|
|
17
18
|
// src/packages/arithmetic/parselets/NumberParselet.ts
|
|
@@ -124,6 +125,19 @@ var SUFFIX_MAGNITUDE = {
|
|
|
124
125
|
B: 9,
|
|
125
126
|
T: 12
|
|
126
127
|
};
|
|
128
|
+
var WORD_MAGNITUDE = {
|
|
129
|
+
thousand: 3,
|
|
130
|
+
thousands: 3,
|
|
131
|
+
million: 6,
|
|
132
|
+
millions: 6,
|
|
133
|
+
mn: 6,
|
|
134
|
+
billion: 9,
|
|
135
|
+
billions: 9,
|
|
136
|
+
bn: 9,
|
|
137
|
+
trillion: 12,
|
|
138
|
+
trillions: 12,
|
|
139
|
+
tn: 12
|
|
140
|
+
};
|
|
127
141
|
var PLAIN_DECIMAL = /^\d+(\.\d+)?$/;
|
|
128
142
|
function scaleDecimalString(raw, magnitude) {
|
|
129
143
|
const dotIdx = raw.indexOf(".");
|
|
@@ -147,8 +161,8 @@ function largeNumberSuffixNormalizerRule(priority = 65) {
|
|
|
147
161
|
const suffixToken = tokens[pos + 1];
|
|
148
162
|
if (!suffixToken) return null;
|
|
149
163
|
if (suffixToken.type !== "IDENT" && suffixToken.type !== "UNIT") return null;
|
|
150
|
-
|
|
151
|
-
const magnitude = SUFFIX_MAGNITUDE[suffixToken.value];
|
|
164
|
+
const adjacent = numberToken.offset + numberToken.text.length === suffixToken.offset;
|
|
165
|
+
const magnitude = adjacent ? SUFFIX_MAGNITUDE[suffixToken.value] ?? WORD_MAGNITUDE[suffixToken.value.toLowerCase()] : WORD_MAGNITUDE[suffixToken.value.toLowerCase()];
|
|
152
166
|
if (magnitude === void 0) return null;
|
|
153
167
|
const scaled = scaleDecimalString(numberToken.text, magnitude);
|
|
154
168
|
return {
|
|
@@ -177,6 +191,10 @@ var ARITHMETIC_PACKAGE = {
|
|
|
177
191
|
],
|
|
178
192
|
infixParselets: [
|
|
179
193
|
{ tokenType: "PLUS", parselet: new BinaryOpParselet(BindingPower.Sum, 20 /* ADD */) },
|
|
194
|
+
// The word "and", which adds exactly like "+" but at a looser binding
|
|
195
|
+
// power so a phrase parselet can use it as a list separator. See
|
|
196
|
+
// Token.ts's AND_CONJ comment for why it is not simply mapped to PLUS.
|
|
197
|
+
{ tokenType: "AND_CONJ", parselet: new BinaryOpParselet(BindingPower.Conjunction, 20 /* ADD */) },
|
|
180
198
|
{ tokenType: "MINUS", parselet: new BinaryOpParselet(BindingPower.Sum, 21 /* SUB */) },
|
|
181
199
|
{ tokenType: "STAR", parselet: new BinaryOpParselet(BindingPower.Product, 22 /* MUL */) },
|
|
182
200
|
{ tokenType: "SLASH", parselet: new BinaryOpParselet(BindingPower.Product, 23 /* DIV */) },
|
|
@@ -205,9 +223,167 @@ var PercentParselet = class {
|
|
|
205
223
|
builder.emitOpcode(10 /* PUSH_NUMBER */);
|
|
206
224
|
builder.emitNumber(100);
|
|
207
225
|
builder.emitOpcode(23 /* DIV */);
|
|
226
|
+
builder.emitOpcode(74 /* TO_PERCENTAGE */);
|
|
227
|
+
}
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
// src/packages/percentage/parselets/IsWhatParselet.ts
|
|
231
|
+
var IsWhatParselet = class {
|
|
232
|
+
constructor() {
|
|
233
|
+
this.category = "Percentage";
|
|
234
|
+
// Below arithmetic, so the whole expression to the left is the value being
|
|
235
|
+
// asked about: `10 + 10 is what % of 200` asks about 20.
|
|
236
|
+
this.bindingPower = BindingPower.Conditional;
|
|
237
|
+
}
|
|
238
|
+
parse(parser, _left, _token, builder) {
|
|
239
|
+
if (nextWordIs(parser, "what")) {
|
|
240
|
+
parser.consume();
|
|
241
|
+
this.parseWhatRate(parser, builder);
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
this.parseWhatBase(parser, builder);
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* `<part> is what % of|off|on <base>`, solving for the rate.
|
|
248
|
+
*/
|
|
249
|
+
parseWhatRate(parser, builder) {
|
|
250
|
+
if (!parser.match("PERCENT")) {
|
|
251
|
+
throw ErrorFactory.parsing(
|
|
252
|
+
"IS_WHAT_EXPECTED_PERCENT",
|
|
253
|
+
'Expected "%" after "is what", as in "20 is what % of 200"'
|
|
254
|
+
);
|
|
255
|
+
}
|
|
256
|
+
const preposition = readPreposition(parser);
|
|
257
|
+
if (preposition === null) {
|
|
258
|
+
builder.emitOpcode(74 /* TO_PERCENTAGE */);
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
parser.parseExpression(BindingPower.Conditional, builder);
|
|
262
|
+
builder.emitOpcode(23 /* DIV */);
|
|
263
|
+
if (preposition === "off") {
|
|
264
|
+
builder.emitOpcode(10 /* PUSH_NUMBER */);
|
|
265
|
+
builder.emitNumber(1);
|
|
266
|
+
builder.emitOpcode(2 /* SWAP */);
|
|
267
|
+
builder.emitOpcode(21 /* SUB */);
|
|
268
|
+
} else if (preposition === "on") {
|
|
269
|
+
builder.emitOpcode(10 /* PUSH_NUMBER */);
|
|
270
|
+
builder.emitNumber(1);
|
|
271
|
+
builder.emitOpcode(21 /* SUB */);
|
|
272
|
+
}
|
|
273
|
+
builder.emitOpcode(74 /* TO_PERCENTAGE */);
|
|
274
|
+
}
|
|
275
|
+
/**
|
|
276
|
+
* `<part> is <rate> of|off|on what`, solving for the base.
|
|
277
|
+
*/
|
|
278
|
+
parseWhatBase(parser, builder) {
|
|
279
|
+
parser.parseExpression(BindingPower.Conditional, builder);
|
|
280
|
+
if (parser.match("TO")) {
|
|
281
|
+
expectWord(parser, "what", '"81 is 9 to what power"');
|
|
282
|
+
expectWord(parser, "power", '"81 is 9 to what power"');
|
|
283
|
+
builder.emitOpcode(51 /* CALL_BUILTIN */);
|
|
284
|
+
builder.emitIndex(LOG_BUILTIN);
|
|
285
|
+
builder.emitIndex(1);
|
|
286
|
+
builder.emitOpcode(2 /* SWAP */);
|
|
287
|
+
builder.emitOpcode(51 /* CALL_BUILTIN */);
|
|
288
|
+
builder.emitIndex(LOG_BUILTIN);
|
|
289
|
+
builder.emitIndex(1);
|
|
290
|
+
builder.emitOpcode(2 /* SWAP */);
|
|
291
|
+
builder.emitOpcode(23 /* DIV */);
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
294
|
+
const preposition = parser.match("OF_WHAT") ? "of" : parser.match("OFF_WHAT") ? "off" : parser.match("ON_WHAT") ? "on" : null;
|
|
295
|
+
if (preposition === null) {
|
|
296
|
+
throw ErrorFactory.parsing(
|
|
297
|
+
"IS_WHAT_EXPECTED_PREPOSITION",
|
|
298
|
+
'Expected "of what", "off what" or "on what", as in "20 is 10% of what"'
|
|
299
|
+
);
|
|
300
|
+
}
|
|
301
|
+
if (preposition === "of") {
|
|
302
|
+
builder.emitOpcode(23 /* DIV */);
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
builder.emitOpcode(10 /* PUSH_NUMBER */);
|
|
306
|
+
builder.emitNumber(1);
|
|
307
|
+
if (preposition === "off") {
|
|
308
|
+
builder.emitOpcode(2 /* SWAP */);
|
|
309
|
+
builder.emitOpcode(21 /* SUB */);
|
|
310
|
+
} else {
|
|
311
|
+
builder.emitOpcode(20 /* ADD */);
|
|
312
|
+
}
|
|
313
|
+
builder.emitOpcode(23 /* DIV */);
|
|
314
|
+
}
|
|
315
|
+
};
|
|
316
|
+
var LOG_BUILTIN = 5;
|
|
317
|
+
function nextWordIs(parser, word) {
|
|
318
|
+
return (parser.peek()?.text ?? "").toLowerCase() === word;
|
|
319
|
+
}
|
|
320
|
+
function expectWord(parser, word, example) {
|
|
321
|
+
if (!nextWordIs(parser, word)) {
|
|
322
|
+
throw ErrorFactory.parsing(
|
|
323
|
+
"IS_WHAT_EXPECTED_WORD",
|
|
324
|
+
`Expected "${word}" here, as in ${example}`
|
|
325
|
+
);
|
|
326
|
+
}
|
|
327
|
+
parser.consume();
|
|
328
|
+
}
|
|
329
|
+
function readPreposition(parser) {
|
|
330
|
+
if (parser.match("OF")) return "of";
|
|
331
|
+
const word = (parser.peek()?.text ?? "").toLowerCase();
|
|
332
|
+
if (word === "off" || word === "on") {
|
|
333
|
+
parser.consume();
|
|
334
|
+
return word;
|
|
335
|
+
}
|
|
336
|
+
return null;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// src/packages/percentage/parselets/OnOffBaseParselet.ts
|
|
340
|
+
var OnOffBaseParselet = class {
|
|
341
|
+
/** @param sign - `1` for a markup (`on`), `-1` for a discount (`off`). */
|
|
342
|
+
constructor(sign) {
|
|
343
|
+
this.sign = sign;
|
|
344
|
+
this.category = "Percentage";
|
|
345
|
+
this.bindingPower = BindingPower.Conditional;
|
|
346
|
+
}
|
|
347
|
+
parse(parser, _left, _token, builder) {
|
|
348
|
+
parser.parseExpression(BindingPower.Conditional, builder);
|
|
349
|
+
builder.emitOpcode(2 /* SWAP */);
|
|
350
|
+
builder.emitOpcode(10 /* PUSH_NUMBER */);
|
|
351
|
+
builder.emitNumber(1);
|
|
352
|
+
if (this.sign === 1) {
|
|
353
|
+
builder.emitOpcode(20 /* ADD */);
|
|
354
|
+
} else {
|
|
355
|
+
builder.emitOpcode(2 /* SWAP */);
|
|
356
|
+
builder.emitOpcode(21 /* SUB */);
|
|
357
|
+
}
|
|
358
|
+
builder.emitOpcode(22 /* MUL */);
|
|
208
359
|
}
|
|
209
360
|
};
|
|
210
361
|
|
|
362
|
+
// src/packages/percentage/normalizer/PercentOnOffNormalizerRule.ts
|
|
363
|
+
function percentOnOffNormalizerRule(priority = 68) {
|
|
364
|
+
return {
|
|
365
|
+
name: "percentage:on-off",
|
|
366
|
+
priority,
|
|
367
|
+
match(tokens, pos) {
|
|
368
|
+
if (tokens[pos]?.type !== "PERCENT") return null;
|
|
369
|
+
const word = tokens[pos + 1];
|
|
370
|
+
if (word?.type !== "IDENT") return null;
|
|
371
|
+
const text = (word.text ?? word.value ?? "").toLowerCase();
|
|
372
|
+
if (text !== "on" && text !== "off") return null;
|
|
373
|
+
const after = (tokens[pos + 2]?.text ?? "").toLowerCase();
|
|
374
|
+
if (after === "what") return null;
|
|
375
|
+
return {
|
|
376
|
+
consumed: 2,
|
|
377
|
+
replacement: [
|
|
378
|
+
tokens[pos],
|
|
379
|
+
createFusedToken(text === "on" ? "PCT_ON" : "PCT_OFF", text, [word])
|
|
380
|
+
],
|
|
381
|
+
ruleName: "percentage:on-off"
|
|
382
|
+
};
|
|
383
|
+
}
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
|
|
211
387
|
// src/packages/percentage/parselets/OfParselet.ts
|
|
212
388
|
var OfParselet = class {
|
|
213
389
|
constructor() {
|
|
@@ -326,11 +502,22 @@ var PERCENTAGE_PACKAGE = {
|
|
|
326
502
|
name: "solve-percentage",
|
|
327
503
|
phrases: {
|
|
328
504
|
"of what is": "OF_WHAT_IS",
|
|
505
|
+
// Fused so that parsing the rate cannot swallow the "of": it is an infix
|
|
506
|
+
// operator in its own right ("10% of 200"), so a sub-expression would
|
|
507
|
+
// consume it and the trailing "what" before this parselet ever looked.
|
|
508
|
+
"of what": "OF_WHAT",
|
|
509
|
+
"off what": "OFF_WHAT",
|
|
510
|
+
"on what": "ON_WHAT",
|
|
329
511
|
"on what is": "ON_WHAT_IS",
|
|
330
512
|
"off what is": "OFF_WHAT_IS"
|
|
331
513
|
},
|
|
332
514
|
infixParselets: [
|
|
333
515
|
{ tokenType: "PERCENT", parselet: new PercentParselet() },
|
|
516
|
+
{ tokenType: "IS", parselet: new IsWhatParselet() },
|
|
517
|
+
// "10% on 200" is 220 and "10% off 200" is 180: the percentage comes
|
|
518
|
+
// first, the base second. The reverse of "200 + 10%".
|
|
519
|
+
{ tokenType: "PCT_ON", parselet: new OnOffBaseParselet(1) },
|
|
520
|
+
{ tokenType: "PCT_OFF", parselet: new OnOffBaseParselet(-1) },
|
|
334
521
|
{ tokenType: "OF", parselet: new OfParselet() },
|
|
335
522
|
{ tokenType: "OF_WHAT_IS", parselet: new OfWhatIsParselet() },
|
|
336
523
|
{ tokenType: "ON_WHAT_IS", parselet: new OnOffWhatIsParselet(1) },
|
|
@@ -342,6 +529,9 @@ var PERCENTAGE_PACKAGE = {
|
|
|
342
529
|
prefixParselets: [
|
|
343
530
|
{ tokenType: "INCREASE", parselet: new IncreaseDecreaseParselet(1) },
|
|
344
531
|
{ tokenType: "DECREASE", parselet: new IncreaseDecreaseParselet(-1) }
|
|
532
|
+
],
|
|
533
|
+
normalizerRules: [
|
|
534
|
+
percentOnOffNormalizerRule()
|
|
345
535
|
]
|
|
346
536
|
};
|
|
347
537
|
|
|
@@ -368,6 +558,13 @@ var builtinNameToIndex = {
|
|
|
368
558
|
arcsin: 11,
|
|
369
559
|
arccos: 12,
|
|
370
560
|
arctan: 13,
|
|
561
|
+
// Degree-taking variants (VMBuiltins.ts 87-92).
|
|
562
|
+
sind: 87,
|
|
563
|
+
cosd: 88,
|
|
564
|
+
tand: 89,
|
|
565
|
+
asind: 90,
|
|
566
|
+
acosd: 91,
|
|
567
|
+
atand: 92,
|
|
371
568
|
sinh: 15,
|
|
372
569
|
cosh: 16,
|
|
373
570
|
tanh: 17,
|
|
@@ -560,18 +757,18 @@ function parseIso8601(s) {
|
|
|
560
757
|
}
|
|
561
758
|
function formatIso8601Local(epochMs) {
|
|
562
759
|
const d = new Date(epochMs);
|
|
563
|
-
const
|
|
760
|
+
const pad3 = (n) => String(n).padStart(2, "0");
|
|
564
761
|
const year = d.getFullYear();
|
|
565
|
-
const month =
|
|
566
|
-
const day =
|
|
567
|
-
const hours =
|
|
568
|
-
const minutes =
|
|
569
|
-
const seconds =
|
|
762
|
+
const month = pad3(d.getMonth() + 1);
|
|
763
|
+
const day = pad3(d.getDate());
|
|
764
|
+
const hours = pad3(d.getHours());
|
|
765
|
+
const minutes = pad3(d.getMinutes());
|
|
766
|
+
const seconds = pad3(d.getSeconds());
|
|
570
767
|
const offsetMinutesTotal = -d.getTimezoneOffset();
|
|
571
768
|
const sign = offsetMinutesTotal >= 0 ? "+" : "-";
|
|
572
769
|
const absOffset = Math.abs(offsetMinutesTotal);
|
|
573
|
-
const offsetHours =
|
|
574
|
-
const offsetMinutes =
|
|
770
|
+
const offsetHours = pad3(Math.floor(absOffset / 60));
|
|
771
|
+
const offsetMinutes = pad3(absOffset % 60);
|
|
575
772
|
return `${year}-${month}-${day}T${hours}:${minutes}:${seconds}${sign}${offsetHours}:${offsetMinutes}`;
|
|
576
773
|
}
|
|
577
774
|
var MS_TIMESTAMP_THRESHOLD = 1e12;
|
|
@@ -762,7 +959,7 @@ var DurationBetweenParselet = class {
|
|
|
762
959
|
parse(parser, token, builder) {
|
|
763
960
|
const unit = token.value;
|
|
764
961
|
parser.parseExpression(BindingPower.Product, builder);
|
|
765
|
-
parser.consume("
|
|
962
|
+
parser.consume("AND_CONJ");
|
|
766
963
|
parser.parseExpression(BindingPower.Lowest, builder);
|
|
767
964
|
builder.emitOpcode(50 /* CALL_PLUGIN */);
|
|
768
965
|
builder.emitIndex(SPAN_BETWEEN_FN_IDX);
|
|
@@ -1006,6 +1203,201 @@ function dateLiteralNormalizerRule() {
|
|
|
1006
1203
|
};
|
|
1007
1204
|
}
|
|
1008
1205
|
|
|
1206
|
+
// src/packages/datetime/normalizer/MonthNameDateNormalizerRule.ts
|
|
1207
|
+
var MONTHS = {
|
|
1208
|
+
january: 1,
|
|
1209
|
+
jan: 1,
|
|
1210
|
+
february: 2,
|
|
1211
|
+
feb: 2,
|
|
1212
|
+
march: 3,
|
|
1213
|
+
mar: 3,
|
|
1214
|
+
april: 4,
|
|
1215
|
+
apr: 4,
|
|
1216
|
+
may: 5,
|
|
1217
|
+
june: 6,
|
|
1218
|
+
jun: 6,
|
|
1219
|
+
july: 7,
|
|
1220
|
+
jul: 7,
|
|
1221
|
+
august: 8,
|
|
1222
|
+
aug: 8,
|
|
1223
|
+
september: 9,
|
|
1224
|
+
sep: 9,
|
|
1225
|
+
sept: 9,
|
|
1226
|
+
october: 10,
|
|
1227
|
+
oct: 10,
|
|
1228
|
+
november: 11,
|
|
1229
|
+
nov: 11,
|
|
1230
|
+
december: 12,
|
|
1231
|
+
dec: 12
|
|
1232
|
+
};
|
|
1233
|
+
function monthOf(token) {
|
|
1234
|
+
if (token === void 0) return 0;
|
|
1235
|
+
if (token.type !== "IDENT" && token.type !== "UNIT") return 0;
|
|
1236
|
+
return MONTHS[(token.text ?? token.value ?? "").toLowerCase()] ?? 0;
|
|
1237
|
+
}
|
|
1238
|
+
var PLAIN_INTEGER2 = /^\d+$/;
|
|
1239
|
+
function looksLikeYear(digits) {
|
|
1240
|
+
return digits.length === 4;
|
|
1241
|
+
}
|
|
1242
|
+
function monthNameDateNormalizerRule(priority = 64) {
|
|
1243
|
+
return {
|
|
1244
|
+
name: "datetime:month-name-date",
|
|
1245
|
+
priority,
|
|
1246
|
+
match(tokens, pos) {
|
|
1247
|
+
const RULE = "datetime:month-name-date";
|
|
1248
|
+
const first = tokens[pos];
|
|
1249
|
+
if (first?.type === "NUMBER" && PLAIN_INTEGER2.test(first.text ?? "")) {
|
|
1250
|
+
const month2 = monthOf(tokens[pos + 1]);
|
|
1251
|
+
if (month2 === 0) return null;
|
|
1252
|
+
const day2 = Number(first.text);
|
|
1253
|
+
if (day2 < 1 || day2 > 31) return null;
|
|
1254
|
+
const yearToken2 = tokens[pos + 2];
|
|
1255
|
+
if (yearToken2?.type === "NUMBER" && PLAIN_INTEGER2.test(yearToken2.text ?? "") && looksLikeYear(yearToken2.text ?? "")) {
|
|
1256
|
+
return buildDateToken(day2, month2, Number(yearToken2.text), tokens.slice(pos, pos + 3), RULE);
|
|
1257
|
+
}
|
|
1258
|
+
return buildDateToken(day2, month2, (/* @__PURE__ */ new Date()).getFullYear(), tokens.slice(pos, pos + 2), RULE);
|
|
1259
|
+
}
|
|
1260
|
+
const month = monthOf(first);
|
|
1261
|
+
if (month === 0) return null;
|
|
1262
|
+
const second = tokens[pos + 1];
|
|
1263
|
+
if (second?.type !== "NUMBER" || !PLAIN_INTEGER2.test(second.text ?? "")) return null;
|
|
1264
|
+
if (looksLikeYear(second.text ?? "")) {
|
|
1265
|
+
return buildDateToken(1, month, Number(second.text), tokens.slice(pos, pos + 2), RULE);
|
|
1266
|
+
}
|
|
1267
|
+
const day = Number(second.text);
|
|
1268
|
+
const comma = tokens[pos + 2];
|
|
1269
|
+
const yearToken = tokens[pos + 3];
|
|
1270
|
+
if (comma?.type === "COMMA" && yearToken?.type === "NUMBER" && PLAIN_INTEGER2.test(yearToken.text ?? "") && looksLikeYear(yearToken.text ?? "")) {
|
|
1271
|
+
return buildDateToken(day, month, Number(yearToken.text), tokens.slice(pos, pos + 4), RULE);
|
|
1272
|
+
}
|
|
1273
|
+
if (comma?.type === "NUMBER" && PLAIN_INTEGER2.test(comma.text ?? "") && looksLikeYear(comma.text ?? "")) {
|
|
1274
|
+
return buildDateToken(day, month, Number(comma.text), tokens.slice(pos, pos + 3), RULE);
|
|
1275
|
+
}
|
|
1276
|
+
return buildDateToken(day, month, (/* @__PURE__ */ new Date()).getFullYear(), tokens.slice(pos, pos + 2), RULE);
|
|
1277
|
+
}
|
|
1278
|
+
};
|
|
1279
|
+
}
|
|
1280
|
+
|
|
1281
|
+
// src/packages/datetime/parselets/DaysInPeriodParselet.ts
|
|
1282
|
+
var DAYS_COUNT_BUILTIN = 93;
|
|
1283
|
+
var QUARTERS = {
|
|
1284
|
+
q1: [1, 3],
|
|
1285
|
+
q2: [4, 6],
|
|
1286
|
+
q3: [7, 9],
|
|
1287
|
+
q4: [10, 12]
|
|
1288
|
+
};
|
|
1289
|
+
function daysInMonth(year, month) {
|
|
1290
|
+
return new Date(year, month, 0).getDate();
|
|
1291
|
+
}
|
|
1292
|
+
function daysInYear(year) {
|
|
1293
|
+
return daysInMonth(year, 2) === 29 ? 366 : 365;
|
|
1294
|
+
}
|
|
1295
|
+
var DaysInPeriodParselet = class {
|
|
1296
|
+
constructor() {
|
|
1297
|
+
this.category = "Datetime";
|
|
1298
|
+
}
|
|
1299
|
+
parse(parser, _token, builder) {
|
|
1300
|
+
const total = this.readPeriodDays(parser);
|
|
1301
|
+
builder.emitOpcode(10 /* PUSH_NUMBER */);
|
|
1302
|
+
builder.emitNumber(total);
|
|
1303
|
+
builder.emitOpcode(51 /* CALL_BUILTIN */);
|
|
1304
|
+
builder.emitIndex(DAYS_COUNT_BUILTIN);
|
|
1305
|
+
builder.emitIndex(1);
|
|
1306
|
+
}
|
|
1307
|
+
/** The number of days in the period written after `days in`. */
|
|
1308
|
+
readPeriodDays(parser) {
|
|
1309
|
+
const next = parser.peek();
|
|
1310
|
+
const word = (next?.text ?? next?.value ?? "").toLowerCase();
|
|
1311
|
+
const quarter = QUARTERS[word];
|
|
1312
|
+
if (quarter !== void 0) {
|
|
1313
|
+
parser.consume();
|
|
1314
|
+
const year2 = this.readOptionalYear(parser) ?? (/* @__PURE__ */ new Date()).getFullYear();
|
|
1315
|
+
let total = 0;
|
|
1316
|
+
for (let month = quarter[0]; month <= quarter[1]; month++) {
|
|
1317
|
+
total += daysInMonth(year2, month);
|
|
1318
|
+
}
|
|
1319
|
+
return total;
|
|
1320
|
+
}
|
|
1321
|
+
if (next?.type === "DATETIME_LITERAL") {
|
|
1322
|
+
parser.consume();
|
|
1323
|
+
const date = new Date(Number(next.value));
|
|
1324
|
+
return daysInMonth(date.getFullYear(), date.getMonth() + 1);
|
|
1325
|
+
}
|
|
1326
|
+
const year = this.readOptionalYear(parser);
|
|
1327
|
+
if (year !== null) return daysInYear(year);
|
|
1328
|
+
throw ErrorFactory.parsing(
|
|
1329
|
+
"DAYS_IN_EXPECTED_PERIOD",
|
|
1330
|
+
'Expected a month, quarter or year, as in "days in February 2020" or "days in Q3"'
|
|
1331
|
+
);
|
|
1332
|
+
}
|
|
1333
|
+
/** A four-digit year sitting at the cursor, consumed if present. */
|
|
1334
|
+
readOptionalYear(parser) {
|
|
1335
|
+
const token = parser.peek();
|
|
1336
|
+
if (token?.type !== "NUMBER") return null;
|
|
1337
|
+
const text = token.text ?? token.value ?? "";
|
|
1338
|
+
if (!/^\d{4}$/.test(text)) return null;
|
|
1339
|
+
parser.consume();
|
|
1340
|
+
return Number(text);
|
|
1341
|
+
}
|
|
1342
|
+
};
|
|
1343
|
+
|
|
1344
|
+
// src/packages/datetime/normalizer/DaysInPeriodNormalizerRule.ts
|
|
1345
|
+
var QUARTERS2 = /* @__PURE__ */ new Set(["q1", "q2", "q3", "q4"]);
|
|
1346
|
+
var MONTHS2 = /* @__PURE__ */ new Set([
|
|
1347
|
+
"january",
|
|
1348
|
+
"jan",
|
|
1349
|
+
"february",
|
|
1350
|
+
"feb",
|
|
1351
|
+
"march",
|
|
1352
|
+
"mar",
|
|
1353
|
+
"april",
|
|
1354
|
+
"apr",
|
|
1355
|
+
"may",
|
|
1356
|
+
"june",
|
|
1357
|
+
"jun",
|
|
1358
|
+
"july",
|
|
1359
|
+
"jul",
|
|
1360
|
+
"august",
|
|
1361
|
+
"aug",
|
|
1362
|
+
"september",
|
|
1363
|
+
"sep",
|
|
1364
|
+
"sept",
|
|
1365
|
+
"october",
|
|
1366
|
+
"oct",
|
|
1367
|
+
"november",
|
|
1368
|
+
"nov",
|
|
1369
|
+
"december",
|
|
1370
|
+
"dec"
|
|
1371
|
+
]);
|
|
1372
|
+
function startsPeriod(token) {
|
|
1373
|
+
if (token === void 0) return false;
|
|
1374
|
+
if (token.type === "DATETIME_LITERAL") return true;
|
|
1375
|
+
const word = (token.text ?? token.value ?? "").toLowerCase();
|
|
1376
|
+
if (token.type === "IDENT" || token.type === "UNIT") {
|
|
1377
|
+
return QUARTERS2.has(word) || MONTHS2.has(word);
|
|
1378
|
+
}
|
|
1379
|
+
return token.type === "NUMBER" && /^\d{4}$/.test(token.text ?? "");
|
|
1380
|
+
}
|
|
1381
|
+
function daysInPeriodNormalizerRule(priority = 60) {
|
|
1382
|
+
return {
|
|
1383
|
+
name: "datetime:days-in-period",
|
|
1384
|
+
priority,
|
|
1385
|
+
match(tokens, pos) {
|
|
1386
|
+
const days = tokens[pos];
|
|
1387
|
+
if (days === void 0) return null;
|
|
1388
|
+
if ((days.text ?? days.value ?? "").toLowerCase() !== "days") return null;
|
|
1389
|
+
if (days.type !== "UNIT" && days.type !== "IDENT") return null;
|
|
1390
|
+
if (tokens[pos + 1]?.type !== "IN") return null;
|
|
1391
|
+
if (!startsPeriod(tokens[pos + 2])) return null;
|
|
1392
|
+
return {
|
|
1393
|
+
consumed: 2,
|
|
1394
|
+
replacement: [createFusedToken("DAYS_IN_PERIOD", "days in", [days, tokens[pos + 1]])],
|
|
1395
|
+
ruleName: "datetime:days-in-period"
|
|
1396
|
+
};
|
|
1397
|
+
}
|
|
1398
|
+
};
|
|
1399
|
+
}
|
|
1400
|
+
|
|
1009
1401
|
// src/packages/datetime/normalizer/DailyNoteLinkNormalizerRule.ts
|
|
1010
1402
|
function dailyNoteLinkNormalizerRule() {
|
|
1011
1403
|
return {
|
|
@@ -1054,6 +1446,10 @@ var DATETIME_PACKAGE = {
|
|
|
1054
1446
|
"what week is it": "WEEK_ON",
|
|
1055
1447
|
"what week is it on": "WEEK_ON",
|
|
1056
1448
|
"week of": "WEEK_ON",
|
|
1449
|
+
// The spelling Soulver documents. Same question, same handler.
|
|
1450
|
+
"week number on": "WEEK_ON",
|
|
1451
|
+
"week number of": "WEEK_ON",
|
|
1452
|
+
"week number": "WEEK_ON",
|
|
1057
1453
|
"what week is it in": "WEEK_IN",
|
|
1058
1454
|
"what week will it be in": "WEEK_IN",
|
|
1059
1455
|
// Postfix day-type predicates.
|
|
@@ -1064,6 +1460,7 @@ var DATETIME_PACKAGE = {
|
|
|
1064
1460
|
"is a business day": "IS_WORKDAY"
|
|
1065
1461
|
},
|
|
1066
1462
|
prefixParselets: [
|
|
1463
|
+
{ tokenType: "DAYS_IN_PERIOD", parselet: new DaysInPeriodParselet() },
|
|
1067
1464
|
{ tokenType: "NOW", parselet: new NowParselet(0) },
|
|
1068
1465
|
{ tokenType: "TODAY", parselet: new NowParselet(0) },
|
|
1069
1466
|
{ tokenType: "TOMORROW", parselet: new NowParselet(1) },
|
|
@@ -1094,6 +1491,11 @@ var DATETIME_PACKAGE = {
|
|
|
1094
1491
|
betweenUnitNormalizerRule(),
|
|
1095
1492
|
workdayRateDenominatorNormalizerRule(),
|
|
1096
1493
|
dateLiteralNormalizerRule(),
|
|
1494
|
+
// Dates with the month spelled out ("March 9, 2024"). Priority 64, just
|
|
1495
|
+
// under the numeric rule, so an all-numeric literal is still claimed by
|
|
1496
|
+
// the rule that has always claimed it.
|
|
1497
|
+
monthNameDateNormalizerRule(),
|
|
1498
|
+
daysInPeriodNormalizerRule(),
|
|
1097
1499
|
dailyNoteLinkNormalizerRule()
|
|
1098
1500
|
],
|
|
1099
1501
|
pluginFunctions: [
|
|
@@ -1606,7 +2008,7 @@ var TimeDifferenceParselet = class {
|
|
|
1606
2008
|
`Expected a city or zone name after "time difference between" (e.g. "time difference between Seattle and Moscow")`
|
|
1607
2009
|
);
|
|
1608
2010
|
}
|
|
1609
|
-
parser.consume("
|
|
2011
|
+
parser.consume("AND_CONJ");
|
|
1610
2012
|
const zone2 = tryConsumeZoneReference(parser);
|
|
1611
2013
|
if (zone2 === null) {
|
|
1612
2014
|
throw ErrorFactory.parsing(
|
|
@@ -1684,14 +2086,14 @@ var VideoTimecodeParselet = class {
|
|
|
1684
2086
|
// src/packages/time/timecode/TimecodeMath.ts
|
|
1685
2087
|
function framesToTimecodeString(totalFrames, fps) {
|
|
1686
2088
|
const fpsWhole = Math.max(1, Math.round(fps));
|
|
1687
|
-
const
|
|
2089
|
+
const pad3 = (n) => String(n).padStart(2, "0");
|
|
1688
2090
|
const frameCount = Math.trunc(totalFrames);
|
|
1689
2091
|
const frames = (frameCount % fpsWhole + fpsWhole) % fpsWhole;
|
|
1690
2092
|
const totalSeconds = Math.floor(frameCount / fpsWhole);
|
|
1691
2093
|
const hours = Math.floor(totalSeconds / 3600);
|
|
1692
2094
|
const minutes = Math.floor(totalSeconds % 3600 / 60);
|
|
1693
2095
|
const seconds = totalSeconds % 60;
|
|
1694
|
-
return `${
|
|
2096
|
+
return `${pad3(hours)}:${pad3(minutes)}:${pad3(seconds)}:${pad3(frames)}`;
|
|
1695
2097
|
}
|
|
1696
2098
|
|
|
1697
2099
|
// src/packages/time/parselets/FrameCountParselet.ts
|
|
@@ -1911,9 +2313,72 @@ function frameCountNormalizerRule(priority = 65) {
|
|
|
1911
2313
|
};
|
|
1912
2314
|
}
|
|
1913
2315
|
|
|
2316
|
+
// src/packages/time/TimespanConverters.ts
|
|
2317
|
+
var TIME_KIND = 14;
|
|
2318
|
+
var TIMESPAN_PARTS = [
|
|
2319
|
+
["week", "weeks", 604800],
|
|
2320
|
+
["day", "days", 86400],
|
|
2321
|
+
["hour", "hours", 3600],
|
|
2322
|
+
["minute", "minutes", 60],
|
|
2323
|
+
["second", "seconds", 1]
|
|
2324
|
+
];
|
|
2325
|
+
function durationSeconds(value) {
|
|
2326
|
+
if (value.type === 0 /* Number */) return value.toNumber();
|
|
2327
|
+
if (value.type !== 6 /* Uom */ || value.unit === void 0) return null;
|
|
2328
|
+
const entry = UNIT_TABLE[value.unit.toLowerCase()];
|
|
2329
|
+
if (entry === void 0 || entry[0] !== TIME_KIND) return null;
|
|
2330
|
+
return value.toNumber() * entry[1];
|
|
2331
|
+
}
|
|
2332
|
+
function toTimespanString(value) {
|
|
2333
|
+
const total = durationSeconds(value);
|
|
2334
|
+
if (total === null) {
|
|
2335
|
+
return stringValue(`"as timespan" needs a duration, got ${value.unit ?? "a non-duration"}`);
|
|
2336
|
+
}
|
|
2337
|
+
const negative = total < 0;
|
|
2338
|
+
let remaining = Math.abs(total);
|
|
2339
|
+
const parts = [];
|
|
2340
|
+
for (const [singular, plural, seconds] of TIMESPAN_PARTS) {
|
|
2341
|
+
if (seconds === 1) break;
|
|
2342
|
+
const count = Math.floor(remaining / seconds);
|
|
2343
|
+
if (count > 0) {
|
|
2344
|
+
parts.push(`${count} ${count === 1 ? singular : plural}`);
|
|
2345
|
+
remaining -= count * seconds;
|
|
2346
|
+
}
|
|
2347
|
+
}
|
|
2348
|
+
const secondsLeft = Math.round(remaining * 1e3) / 1e3;
|
|
2349
|
+
if (secondsLeft > 0 || parts.length === 0) {
|
|
2350
|
+
parts.push(`${secondsLeft} ${secondsLeft === 1 ? "second" : "seconds"}`);
|
|
2351
|
+
}
|
|
2352
|
+
return stringValue((negative ? "-" : "") + parts.join(" "));
|
|
2353
|
+
}
|
|
2354
|
+
function toLaptimeString(value) {
|
|
2355
|
+
const total = durationSeconds(value);
|
|
2356
|
+
if (total === null) {
|
|
2357
|
+
return stringValue(`"as laptime" needs a duration, got ${value.unit ?? "a non-duration"}`);
|
|
2358
|
+
}
|
|
2359
|
+
const negative = total < 0;
|
|
2360
|
+
const rounded = Math.round(Math.abs(total) * 1e3) / 1e3;
|
|
2361
|
+
const hours = Math.floor(rounded / 3600);
|
|
2362
|
+
const minutes = Math.floor(rounded % 3600 / 60);
|
|
2363
|
+
const seconds = rounded % 60;
|
|
2364
|
+
const whole = Math.floor(seconds);
|
|
2365
|
+
const fraction = Math.round((seconds - whole) * 1e3);
|
|
2366
|
+
const base = `${pad(hours)}:${pad(minutes)}:${pad(whole)}`;
|
|
2367
|
+
return stringValue((negative ? "-" : "") + (fraction > 0 ? `${base}.${String(fraction).padStart(3, "0")}` : base));
|
|
2368
|
+
}
|
|
2369
|
+
function pad(n) {
|
|
2370
|
+
return n < 10 ? `0${n}` : String(n);
|
|
2371
|
+
}
|
|
2372
|
+
|
|
1914
2373
|
// src/packages/time/TimePackage.ts
|
|
1915
2374
|
var TIME_PACKAGE = {
|
|
1916
2375
|
name: "solve-time",
|
|
2376
|
+
asConverters: {
|
|
2377
|
+
// The two ways of writing a duration out. See TimespanConverters.ts.
|
|
2378
|
+
timespan: toTimespanString,
|
|
2379
|
+
laptime: toLaptimeString,
|
|
2380
|
+
lap: toLaptimeString
|
|
2381
|
+
},
|
|
1917
2382
|
phrases: {
|
|
1918
2383
|
"time in": "TIME_IN",
|
|
1919
2384
|
"date in": "DATE_IN",
|
|
@@ -2017,7 +2482,7 @@ var keywordLedRollPattern = definePhrasePattern({
|
|
|
2017
2482
|
// separator's token type (a bare `parser.consume()`), so it never
|
|
2018
2483
|
// surfaced this; matching that real lexer behavior explicitly here
|
|
2019
2484
|
// rather than the aspirational "AND" the old code's comment implied.
|
|
2020
|
-
{ kind: "keyword", tokenTypes: ["
|
|
2485
|
+
{ kind: "keyword", tokenTypes: ["AND_CONJ"] },
|
|
2021
2486
|
{ kind: "expr" }
|
|
2022
2487
|
],
|
|
2023
2488
|
emit: emitDiceRollCall
|
|
@@ -2345,99 +2810,403 @@ function uomPossibilitiesNormalizerRule(priority = 60) {
|
|
|
2345
2810
|
};
|
|
2346
2811
|
}
|
|
2347
2812
|
|
|
2348
|
-
// src/packages/uom/
|
|
2349
|
-
var
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
|
|
2398
|
-
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
|
|
2408
|
-
|
|
2409
|
-
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2813
|
+
// src/packages/uom/normalizer/CompoundQuantityNormalizerRule.ts
|
|
2814
|
+
var TIME_KIND2 = 14;
|
|
2815
|
+
function unitEntry(token, previousKind) {
|
|
2816
|
+
if (token === void 0 || token.type !== "UNIT") return void 0;
|
|
2817
|
+
const spelling = (token.value ?? "").toLowerCase();
|
|
2818
|
+
if (spelling === "m" && previousKind === TIME_KIND2) {
|
|
2819
|
+
return { kind: TIME_KIND2, ratio: 60, spelling: "minutes" };
|
|
2820
|
+
}
|
|
2821
|
+
const entry = UNIT_TABLE[spelling];
|
|
2822
|
+
return entry === void 0 ? void 0 : { kind: entry[0], ratio: entry[1], spelling };
|
|
2823
|
+
}
|
|
2824
|
+
var PLAIN_NUMBER = /^\d+(\.\d+)?$/;
|
|
2825
|
+
function compoundQuantityNormalizerRule(priority = 63) {
|
|
2826
|
+
return {
|
|
2827
|
+
name: "uom:compound-quantity",
|
|
2828
|
+
priority,
|
|
2829
|
+
match(tokens, pos) {
|
|
2830
|
+
const firstNumber = tokens[pos];
|
|
2831
|
+
if (firstNumber?.type !== "NUMBER" || !PLAIN_NUMBER.test(firstNumber.text ?? "")) return null;
|
|
2832
|
+
const firstUnit = unitEntry(tokens[pos + 1]);
|
|
2833
|
+
if (firstUnit === void 0) return null;
|
|
2834
|
+
const kind = firstUnit.kind;
|
|
2835
|
+
let lastRatio = firstUnit.ratio;
|
|
2836
|
+
let total = Number(firstNumber.text) * lastRatio;
|
|
2837
|
+
let smallestUnit = firstUnit.spelling;
|
|
2838
|
+
let smallestUnitToken = tokens[pos + 1];
|
|
2839
|
+
let consumed = 2;
|
|
2840
|
+
for (; ; ) {
|
|
2841
|
+
const number = tokens[pos + consumed];
|
|
2842
|
+
if (number?.type !== "NUMBER" || !PLAIN_NUMBER.test(number.text ?? "")) break;
|
|
2843
|
+
const entry = unitEntry(tokens[pos + consumed + 1], kind);
|
|
2844
|
+
if (entry === void 0) break;
|
|
2845
|
+
if (entry.kind !== kind || entry.ratio >= lastRatio) break;
|
|
2846
|
+
total += Number(number.text) * entry.ratio;
|
|
2847
|
+
lastRatio = entry.ratio;
|
|
2848
|
+
smallestUnit = entry.spelling;
|
|
2849
|
+
smallestUnitToken = tokens[pos + consumed + 1];
|
|
2850
|
+
consumed += 2;
|
|
2851
|
+
}
|
|
2852
|
+
if (consumed === 2) return null;
|
|
2853
|
+
const source = tokens.slice(pos, pos + consumed);
|
|
2854
|
+
const scaled = total / lastRatio;
|
|
2855
|
+
return {
|
|
2856
|
+
consumed,
|
|
2857
|
+
replacement: [
|
|
2858
|
+
createFusedToken("NUMBER", String(scaled), source),
|
|
2859
|
+
createFusedToken("UNIT", smallestUnit, [smallestUnitToken])
|
|
2860
|
+
],
|
|
2861
|
+
ruleName: "uom:compound-quantity"
|
|
2862
|
+
};
|
|
2863
|
+
}
|
|
2864
|
+
};
|
|
2865
|
+
}
|
|
2866
|
+
|
|
2867
|
+
// src/packages/uom/normalizer/TwoUnitConversionNormalizerRule.ts
|
|
2868
|
+
function isUnit(token) {
|
|
2869
|
+
if (token === void 0 || token.type !== "UNIT") return false;
|
|
2870
|
+
return UNIT_TABLE[(token.value ?? "").toLowerCase()] !== void 0;
|
|
2871
|
+
}
|
|
2872
|
+
function twoUnitConversionNormalizerRule(priority = 59) {
|
|
2873
|
+
return {
|
|
2874
|
+
name: "uom:two-unit-conversion",
|
|
2875
|
+
priority,
|
|
2876
|
+
match(tokens, pos) {
|
|
2877
|
+
const preposition = tokens[pos];
|
|
2878
|
+
if (preposition?.type !== "IN" && preposition?.type !== "TO") return null;
|
|
2879
|
+
if (!isUnit(tokens[pos + 1])) return null;
|
|
2880
|
+
if (tokens[pos + 2]?.type !== "AND_CONJ") return null;
|
|
2881
|
+
if (!isUnit(tokens[pos + 3])) return null;
|
|
2882
|
+
const major = tokens[pos + 1].value;
|
|
2883
|
+
const minor = tokens[pos + 3].value;
|
|
2884
|
+
return {
|
|
2885
|
+
consumed: 4,
|
|
2886
|
+
replacement: [
|
|
2887
|
+
createFusedToken("IN_TWO_UNITS", `${major} ${minor}`, tokens.slice(pos, pos + 4))
|
|
2888
|
+
],
|
|
2889
|
+
ruleName: "uom:two-unit-conversion"
|
|
2890
|
+
};
|
|
2891
|
+
}
|
|
2892
|
+
};
|
|
2893
|
+
}
|
|
2894
|
+
|
|
2895
|
+
// src/packages/uom/normalizer/DegreeSymbolNormalizerRule.ts
|
|
2896
|
+
function degreeSymbolNormalizerRule(priority = 74) {
|
|
2897
|
+
return {
|
|
2898
|
+
name: "uom:degree-symbol",
|
|
2899
|
+
priority,
|
|
2900
|
+
match(tokens, pos) {
|
|
2901
|
+
const number = tokens[pos];
|
|
2902
|
+
if (number?.type !== "NUMBER") return null;
|
|
2903
|
+
const symbol = tokens[pos + 1];
|
|
2904
|
+
if (symbol === void 0 || symbol.type !== "IDENT") return null;
|
|
2905
|
+
if ((symbol.text ?? symbol.value ?? "") !== "\xB0") return null;
|
|
2906
|
+
return {
|
|
2907
|
+
consumed: 2,
|
|
2908
|
+
replacement: [number, createFusedToken("UNIT", "degrees", [symbol])],
|
|
2909
|
+
ruleName: "uom:degree-symbol"
|
|
2910
|
+
};
|
|
2911
|
+
}
|
|
2912
|
+
};
|
|
2913
|
+
}
|
|
2914
|
+
|
|
2915
|
+
// src/packages/uom/parselets/TwoUnitConversionParselet.ts
|
|
2916
|
+
var TwoUnitConversionParselet = class {
|
|
2917
|
+
constructor(builtinIndex) {
|
|
2918
|
+
this.builtinIndex = builtinIndex;
|
|
2919
|
+
this.category = "Uom";
|
|
2920
|
+
this.bindingPower = BindingPower.Conditional;
|
|
2921
|
+
}
|
|
2922
|
+
parse(_parser, _left, token, builder) {
|
|
2923
|
+
const [major, minor] = String(token.value).split(" ");
|
|
2924
|
+
builder.emitOpcode(13 /* PUSH_STRING */);
|
|
2925
|
+
builder.emitString(major);
|
|
2926
|
+
builder.emitOpcode(13 /* PUSH_STRING */);
|
|
2927
|
+
builder.emitString(minor);
|
|
2928
|
+
builder.emitOpcode(51 /* CALL_BUILTIN */);
|
|
2929
|
+
builder.emitIndex(this.builtinIndex);
|
|
2930
|
+
builder.emitIndex(3);
|
|
2931
|
+
}
|
|
2932
|
+
};
|
|
2933
|
+
|
|
2934
|
+
// src/packages/uom/normalizer/BareRateDenominatorNormalizerRule.ts
|
|
2935
|
+
function isUnit2(token) {
|
|
2936
|
+
if (token === void 0 || token.type !== "UNIT") return false;
|
|
2937
|
+
return UNIT_TABLE[(token.value ?? "").toLowerCase()] !== void 0;
|
|
2938
|
+
}
|
|
2939
|
+
var PER_WORDS = /* @__PURE__ */ new Set(["per", "a", "an", "each", "every"]);
|
|
2940
|
+
function isCountLabel(token) {
|
|
2941
|
+
if (token === void 0 || token.type !== "IDENT") return false;
|
|
2942
|
+
const word = (token.text ?? token.value ?? "").toLowerCase();
|
|
2943
|
+
if (PER_WORDS.has(word)) return false;
|
|
2944
|
+
return /^[a-z][a-z_]{2,}$/.test(word) && UNIT_TABLE[word] === void 0;
|
|
2945
|
+
}
|
|
2946
|
+
function bareRateDenominatorNormalizerRule(priority = 75) {
|
|
2947
|
+
return {
|
|
2948
|
+
name: "uom:bare-rate-denominator",
|
|
2949
|
+
priority,
|
|
2950
|
+
match(tokens, pos) {
|
|
2951
|
+
const head = tokens[pos];
|
|
2952
|
+
if (head === void 0) return null;
|
|
2953
|
+
if (head.type === "NUMBER" && isCountLabel(tokens[pos + 1])) {
|
|
2954
|
+
const label = tokens[pos + 1];
|
|
2955
|
+
const after = tokens[pos + 2];
|
|
2956
|
+
const introduces = after?.type === "SLASH" || after?.type === "IDENT" && PER_WORDS.has((after.text ?? after.value ?? "").toLowerCase());
|
|
2957
|
+
if (introduces && isUnit2(tokens[pos + 3])) {
|
|
2958
|
+
return {
|
|
2959
|
+
consumed: 2,
|
|
2960
|
+
replacement: [head, createFusedToken("UNIT", label.value, [label])],
|
|
2961
|
+
ruleName: "uom:bare-rate-denominator"
|
|
2962
|
+
};
|
|
2963
|
+
}
|
|
2964
|
+
}
|
|
2965
|
+
if (!isUnit2(tokens[pos + 1])) return null;
|
|
2966
|
+
const isSlash = head.type === "SLASH";
|
|
2967
|
+
const word = (head.text ?? head.value ?? "").toLowerCase();
|
|
2968
|
+
const isPerWord = head.type === "IDENT" && PER_WORDS.has(word);
|
|
2969
|
+
if (!isSlash && !isPerWord) return null;
|
|
2970
|
+
const unitToken = tokens[pos + 1];
|
|
2971
|
+
return {
|
|
2972
|
+
consumed: 2,
|
|
2973
|
+
replacement: [
|
|
2974
|
+
createFusedToken("PER_UNIT", unitToken.value, [head, unitToken])
|
|
2975
|
+
],
|
|
2976
|
+
ruleName: "uom:bare-rate-denominator"
|
|
2977
|
+
};
|
|
2978
|
+
}
|
|
2979
|
+
};
|
|
2980
|
+
}
|
|
2981
|
+
|
|
2982
|
+
// src/packages/uom/parselets/PerUnitParselet.ts
|
|
2983
|
+
var PerUnitParselet = class {
|
|
2984
|
+
constructor(builtinIndex) {
|
|
2985
|
+
this.builtinIndex = builtinIndex;
|
|
2986
|
+
this.category = "Uom";
|
|
2987
|
+
this.bindingPower = BindingPower.Product;
|
|
2988
|
+
}
|
|
2989
|
+
parse(_parser, _left, token, builder) {
|
|
2990
|
+
builder.emitOpcode(13 /* PUSH_STRING */);
|
|
2991
|
+
builder.emitString(String(token.value));
|
|
2992
|
+
builder.emitOpcode(51 /* CALL_BUILTIN */);
|
|
2993
|
+
builder.emitIndex(this.builtinIndex);
|
|
2994
|
+
builder.emitIndex(2);
|
|
2995
|
+
}
|
|
2996
|
+
};
|
|
2997
|
+
|
|
2998
|
+
// src/packages/uom/parselets/AtRateParselet.ts
|
|
2999
|
+
var AtRateParselet = class {
|
|
3000
|
+
constructor(builtinIndex) {
|
|
3001
|
+
this.builtinIndex = builtinIndex;
|
|
3002
|
+
this.category = "Uom";
|
|
3003
|
+
this.bindingPower = BindingPower.Product;
|
|
3004
|
+
}
|
|
3005
|
+
parse(parser, _left, _token, builder) {
|
|
3006
|
+
parser.parseExpression(BindingPower.Conditional, builder);
|
|
3007
|
+
builder.emitOpcode(51 /* CALL_BUILTIN */);
|
|
3008
|
+
builder.emitIndex(this.builtinIndex);
|
|
3009
|
+
builder.emitIndex(2);
|
|
3010
|
+
}
|
|
3011
|
+
};
|
|
3012
|
+
|
|
3013
|
+
// src/packages/uom/normalizer/AtRateNormalizerRule.ts
|
|
3014
|
+
function isUnit3(token) {
|
|
3015
|
+
if (token === void 0 || token.type !== "UNIT") return false;
|
|
3016
|
+
return UNIT_TABLE[(token.value ?? "").toLowerCase()] !== void 0;
|
|
3017
|
+
}
|
|
3018
|
+
var PER_WORDS2 = /* @__PURE__ */ new Set(["per", "a", "an", "each", "every"]);
|
|
3019
|
+
function hasRateAhead(tokens, from) {
|
|
3020
|
+
for (let i = from; i < tokens.length && i < from + 5; i++) {
|
|
3021
|
+
const token = tokens[i];
|
|
3022
|
+
if (token.type === "PER_UNIT") return true;
|
|
3023
|
+
if (token.type === "EOF" || token.type === "COMMA" || token.type === "RATE_AT") return false;
|
|
3024
|
+
const word = (token.text ?? token.value ?? "").toLowerCase();
|
|
3025
|
+
const introducesDenominator = token.type === "SLASH" || token.type === "IDENT" && PER_WORDS2.has(word);
|
|
3026
|
+
if (introducesDenominator && isUnit3(tokens[i + 1])) return true;
|
|
3027
|
+
}
|
|
3028
|
+
return false;
|
|
3029
|
+
}
|
|
3030
|
+
function atRateNormalizerRule(priority = 73) {
|
|
3031
|
+
return {
|
|
3032
|
+
name: "uom:at-rate",
|
|
3033
|
+
priority,
|
|
3034
|
+
match(tokens, pos) {
|
|
3035
|
+
if (tokens[pos]?.type !== "RATE_AT") return null;
|
|
3036
|
+
if (!hasRateAhead(tokens, pos + 1)) return null;
|
|
3037
|
+
return {
|
|
3038
|
+
consumed: 1,
|
|
3039
|
+
replacement: [createFusedToken("AT_RATE", "at", [tokens[pos]])],
|
|
3040
|
+
ruleName: "uom:at-rate"
|
|
3041
|
+
};
|
|
3042
|
+
}
|
|
3043
|
+
};
|
|
3044
|
+
}
|
|
3045
|
+
|
|
3046
|
+
// src/packages/uom/normalizer/ForDurationNormalizerRule.ts
|
|
3047
|
+
function isUnit4(token) {
|
|
3048
|
+
if (token === void 0 || token.type !== "UNIT") return false;
|
|
3049
|
+
return UNIT_TABLE[(token.value ?? "").toLowerCase()] !== void 0;
|
|
3050
|
+
}
|
|
3051
|
+
var ARTICLES = /* @__PURE__ */ new Set(["a", "an"]);
|
|
3052
|
+
function forDurationNormalizerRule(priority = 76) {
|
|
3053
|
+
return {
|
|
3054
|
+
name: "uom:for-duration",
|
|
3055
|
+
priority,
|
|
3056
|
+
match(tokens, pos) {
|
|
3057
|
+
const head = tokens[pos];
|
|
3058
|
+
if (head?.type !== "FOR_DURATION") return null;
|
|
3059
|
+
const article = tokens[pos + 1];
|
|
3060
|
+
if (article?.type !== "IDENT") return null;
|
|
3061
|
+
if (!ARTICLES.has((article.text ?? article.value ?? "").toLowerCase())) return null;
|
|
3062
|
+
if (!isUnit4(tokens[pos + 2])) return null;
|
|
3063
|
+
return {
|
|
3064
|
+
consumed: 3,
|
|
3065
|
+
replacement: [
|
|
3066
|
+
createFusedToken("STAR", "*", [head]),
|
|
3067
|
+
createFusedToken("NUMBER", "1", [article]),
|
|
3068
|
+
tokens[pos + 2]
|
|
3069
|
+
],
|
|
3070
|
+
ruleName: "uom:for-duration"
|
|
3071
|
+
};
|
|
3072
|
+
}
|
|
3073
|
+
};
|
|
3074
|
+
}
|
|
3075
|
+
|
|
3076
|
+
// src/packages/uom/normalizer/ReversedConversionNormalizerRule.ts
|
|
3077
|
+
function isUnit5(token) {
|
|
3078
|
+
if (token === void 0 || token.type !== "UNIT") return false;
|
|
3079
|
+
return UNIT_TABLE[(token.value ?? "").toLowerCase()] !== void 0;
|
|
3080
|
+
}
|
|
3081
|
+
var ARTICLES2 = /* @__PURE__ */ new Set(["a", "an"]);
|
|
3082
|
+
function reversedConversionNormalizerRule(priority = 61) {
|
|
3083
|
+
return {
|
|
3084
|
+
name: "uom:reversed-conversion",
|
|
3085
|
+
priority,
|
|
3086
|
+
match(tokens, pos) {
|
|
3087
|
+
if (pos !== 0 || !isUnit5(tokens[pos])) return null;
|
|
3088
|
+
if (tokens[pos + 1]?.type !== "IN") return null;
|
|
3089
|
+
const target = tokens[pos];
|
|
3090
|
+
const third = tokens[pos + 2];
|
|
3091
|
+
if (third === void 0) return null;
|
|
3092
|
+
if (third.type === "NUMBER" && isUnit5(tokens[pos + 3])) {
|
|
3093
|
+
return {
|
|
3094
|
+
consumed: 4,
|
|
3095
|
+
replacement: [third, tokens[pos + 3], tokens[pos + 1], target],
|
|
3096
|
+
ruleName: "uom:reversed-conversion"
|
|
3097
|
+
};
|
|
3098
|
+
}
|
|
3099
|
+
const word = (third.text ?? third.value ?? "").toLowerCase();
|
|
3100
|
+
if (third.type === "IDENT" && ARTICLES2.has(word) && isUnit5(tokens[pos + 3])) {
|
|
3101
|
+
return {
|
|
3102
|
+
consumed: 4,
|
|
3103
|
+
replacement: [
|
|
3104
|
+
createFusedToken("NUMBER", "1", [third]),
|
|
3105
|
+
tokens[pos + 3],
|
|
3106
|
+
tokens[pos + 1],
|
|
3107
|
+
target
|
|
3108
|
+
],
|
|
3109
|
+
ruleName: "uom:reversed-conversion"
|
|
3110
|
+
};
|
|
3111
|
+
}
|
|
3112
|
+
return null;
|
|
3113
|
+
}
|
|
3114
|
+
};
|
|
3115
|
+
}
|
|
3116
|
+
|
|
3117
|
+
// src/packages/uom/data/IngredientDensities.ts
|
|
3118
|
+
var INGREDIENT_DENSITIES = {
|
|
3119
|
+
water: 1,
|
|
3120
|
+
milk: 1.03,
|
|
3121
|
+
"whole milk": 1.03,
|
|
3122
|
+
"skim milk": 1.035,
|
|
3123
|
+
buttermilk: 1.03,
|
|
3124
|
+
"heavy cream": 1,
|
|
3125
|
+
"whipping cream": 1,
|
|
3126
|
+
"sour cream": 0.96,
|
|
3127
|
+
yogurt: 1.03,
|
|
3128
|
+
"greek yogurt": 1.05,
|
|
3129
|
+
"cream cheese": 1.01,
|
|
3130
|
+
"condensed milk": 1.3,
|
|
3131
|
+
"evaporated milk": 1.06,
|
|
3132
|
+
"coconut milk": 0.97,
|
|
3133
|
+
"almond milk": 1.03,
|
|
3134
|
+
"soy milk": 1.03,
|
|
3135
|
+
butter: 0.9595,
|
|
3136
|
+
margarine: 0.96,
|
|
3137
|
+
"vegetable oil": 0.92,
|
|
3138
|
+
"olive oil": 0.913,
|
|
3139
|
+
"canola oil": 0.92,
|
|
3140
|
+
"coconut oil": 0.92,
|
|
3141
|
+
"sunflower oil": 0.92,
|
|
3142
|
+
"sesame oil": 0.92,
|
|
3143
|
+
mayonnaise: 0.91,
|
|
3144
|
+
sugar: 0.845,
|
|
3145
|
+
"granulated sugar": 0.845,
|
|
3146
|
+
"brown sugar": 0.93,
|
|
3147
|
+
"powdered sugar": 0.51,
|
|
3148
|
+
"icing sugar": 0.51,
|
|
3149
|
+
honey: 1.42,
|
|
3150
|
+
"maple syrup": 1.32,
|
|
3151
|
+
"corn syrup": 1.38,
|
|
3152
|
+
molasses: 1.4,
|
|
3153
|
+
nutella: 1.14,
|
|
3154
|
+
"peanut butter": 1.09,
|
|
3155
|
+
flour: 0.53,
|
|
3156
|
+
"all-purpose flour": 0.53,
|
|
3157
|
+
"all purpose flour": 0.53,
|
|
3158
|
+
"bread flour": 0.54,
|
|
3159
|
+
"cake flour": 0.47,
|
|
3160
|
+
"whole wheat flour": 0.55,
|
|
3161
|
+
cornstarch: 0.53,
|
|
3162
|
+
cornmeal: 0.65,
|
|
3163
|
+
semolina: 0.6,
|
|
3164
|
+
rice: 0.79,
|
|
3165
|
+
"white rice": 0.79,
|
|
3166
|
+
"brown rice": 0.8,
|
|
3167
|
+
oats: 0.34,
|
|
3168
|
+
"rolled oats": 0.38,
|
|
3169
|
+
breadcrumbs: 0.43,
|
|
3170
|
+
yeast: 0.68,
|
|
3171
|
+
"dry yeast": 0.68,
|
|
3172
|
+
salt: 1.2,
|
|
3173
|
+
"table salt": 1.2,
|
|
3174
|
+
"kosher salt": 0.96,
|
|
3175
|
+
"baking powder": 0.9,
|
|
3176
|
+
"baking soda": 0.99,
|
|
3177
|
+
"cream of tartar": 0.68,
|
|
3178
|
+
cinnamon: 0.56,
|
|
3179
|
+
ginger: 0.5,
|
|
3180
|
+
"vanilla extract": 0.88,
|
|
3181
|
+
"lemon juice": 1.03,
|
|
3182
|
+
"lime juice": 1.02,
|
|
3183
|
+
"orange juice": 1.04,
|
|
3184
|
+
vinegar: 1.01,
|
|
3185
|
+
cocoa: 0.41,
|
|
3186
|
+
"cocoa powder": 0.41,
|
|
3187
|
+
"chocolate chips": 0.72,
|
|
3188
|
+
"chocolate chunks": 0.72,
|
|
3189
|
+
coconut: 0.35,
|
|
3190
|
+
"shredded coconut": 0.35,
|
|
3191
|
+
raisins: 0.68,
|
|
3192
|
+
almonds: 0.55,
|
|
3193
|
+
walnuts: 0.5,
|
|
3194
|
+
pecans: 0.5,
|
|
3195
|
+
pistachios: 0.55,
|
|
3196
|
+
peanuts: 0.6,
|
|
3197
|
+
"shredded cheese": 0.4,
|
|
3198
|
+
parmesan: 0.44,
|
|
3199
|
+
"tomato paste": 1.06,
|
|
3200
|
+
"tomato sauce": 1.02,
|
|
3201
|
+
ketchup: 1.14,
|
|
3202
|
+
broth: 1,
|
|
3203
|
+
stock: 1,
|
|
3204
|
+
wine: 0.99,
|
|
3205
|
+
beer: 1.01
|
|
3206
|
+
};
|
|
3207
|
+
var MAX_INGREDIENT_NAME_WORDS = 2;
|
|
3208
|
+
function getIngredientDensity(name) {
|
|
3209
|
+
return INGREDIENT_DENSITIES[name.toLowerCase().trim()];
|
|
2441
3210
|
}
|
|
2442
3211
|
|
|
2443
3212
|
// src/packages/uom/parselets/CookingPluginFunctions.ts
|
|
@@ -2548,11 +3317,21 @@ var UOM_PACKAGE = {
|
|
|
2548
3317
|
{ tokenType: "UOM_POSSIBILITIES_QUERY", parselet: new PossibilitiesParselet() }
|
|
2549
3318
|
],
|
|
2550
3319
|
infixParselets: [
|
|
3320
|
+
{ tokenType: "IN_TWO_UNITS", parselet: new TwoUnitConversionParselet(94) },
|
|
3321
|
+
{ tokenType: "PER_UNIT", parselet: new PerUnitParselet(95) },
|
|
3322
|
+
{ tokenType: "AT_RATE", parselet: new AtRateParselet(96) },
|
|
2551
3323
|
{ tokenType: "UNIT", parselet: new UomLiteralParselet() },
|
|
2552
3324
|
{ tokenType: "INGREDIENT_NAME", parselet: new CookingConversionParselet() }
|
|
2553
3325
|
],
|
|
2554
3326
|
normalizerRules: [
|
|
2555
3327
|
uomPossibilitiesNormalizerRule(),
|
|
3328
|
+
compoundQuantityNormalizerRule(),
|
|
3329
|
+
twoUnitConversionNormalizerRule(),
|
|
3330
|
+
degreeSymbolNormalizerRule(),
|
|
3331
|
+
atRateNormalizerRule(),
|
|
3332
|
+
forDurationNormalizerRule(),
|
|
3333
|
+
bareRateDenominatorNormalizerRule(),
|
|
3334
|
+
reversedConversionNormalizerRule(),
|
|
2556
3335
|
ingredientNameNormalizerRule()
|
|
2557
3336
|
],
|
|
2558
3337
|
pluginFunctions: [
|
|
@@ -3587,6 +4366,9 @@ var BUILTIN_CONVERTERS = {
|
|
|
3587
4366
|
hex: 71 /* TO_HEX */,
|
|
3588
4367
|
fraction: 140 /* TO_FRACTION */,
|
|
3589
4368
|
multiplier: 141 /* TO_MULTIPLIER */,
|
|
4369
|
+
// "20 to 40 as x". Only reachable in converter position, immediately after
|
|
4370
|
+
// "as", so a variable named x is unaffected.
|
|
4371
|
+
x: 141 /* TO_MULTIPLIER */,
|
|
3590
4372
|
sci: 142 /* TO_SCI */,
|
|
3591
4373
|
scientific: 142 /* TO_SCI */,
|
|
3592
4374
|
binary: 143 /* TO_BINARY */,
|
|
@@ -3594,6 +4376,11 @@ var BUILTIN_CONVERTERS = {
|
|
|
3594
4376
|
octal: 144 /* TO_OCTAL */,
|
|
3595
4377
|
oct: 144 /* TO_OCTAL */
|
|
3596
4378
|
};
|
|
4379
|
+
var BASE_CONVERTERS = {
|
|
4380
|
+
2: 143 /* TO_BINARY */,
|
|
4381
|
+
8: 144 /* TO_OCTAL */,
|
|
4382
|
+
16: 71 /* TO_HEX */
|
|
4383
|
+
};
|
|
3597
4384
|
var AsConverterParselet = class {
|
|
3598
4385
|
constructor() {
|
|
3599
4386
|
this.category = "Converters";
|
|
@@ -3614,8 +4401,26 @@ var AsConverterParselet = class {
|
|
|
3614
4401
|
}
|
|
3615
4402
|
parser.consume();
|
|
3616
4403
|
const name = nextToken.value.toLowerCase();
|
|
4404
|
+
if (name === "base") {
|
|
4405
|
+
const radixToken = parser.peek();
|
|
4406
|
+
const radix = radixToken?.type === "NUMBER" ? Number(radixToken.value) : NaN;
|
|
4407
|
+
const opcode = BASE_CONVERTERS[radix];
|
|
4408
|
+
if (opcode === void 0) {
|
|
4409
|
+
throw ErrorFactory.parsing(
|
|
4410
|
+
"AS_CONVERTER_UNSUPPORTED_BASE",
|
|
4411
|
+
`"as base ${radixToken?.value ?? "?"}": only bases ${Object.keys(BASE_CONVERTERS).join(", ")} can be written out`
|
|
4412
|
+
);
|
|
4413
|
+
}
|
|
4414
|
+
parser.consume();
|
|
4415
|
+
builder.emitOpcode(opcode);
|
|
4416
|
+
return;
|
|
4417
|
+
}
|
|
3617
4418
|
const builtinOp = BUILTIN_CONVERTERS[name];
|
|
3618
4419
|
if (builtinOp !== void 0) {
|
|
4420
|
+
if (builtinOp === 141 /* TO_MULTIPLIER */ && parser.match("OF")) {
|
|
4421
|
+
parser.parseExpression(BindingPower.Conditional, builder);
|
|
4422
|
+
builder.emitOpcode(23 /* DIV */);
|
|
4423
|
+
}
|
|
3619
4424
|
builder.emitOpcode(builtinOp);
|
|
3620
4425
|
return;
|
|
3621
4426
|
}
|
|
@@ -3625,11 +4430,181 @@ var AsConverterParselet = class {
|
|
|
3625
4430
|
}
|
|
3626
4431
|
};
|
|
3627
4432
|
|
|
4433
|
+
// src/packages/converters/parselets/RoundingParselets.ts
|
|
4434
|
+
var INCREMENT_WORDS = {
|
|
4435
|
+
ten: 10,
|
|
4436
|
+
hundred: 100,
|
|
4437
|
+
thousand: 1e3,
|
|
4438
|
+
million: 1e6,
|
|
4439
|
+
billion: 1e9
|
|
4440
|
+
};
|
|
4441
|
+
function emitRoundToIncrement(builder, increment, direction) {
|
|
4442
|
+
builder.emitOpcode(10 /* PUSH_NUMBER */);
|
|
4443
|
+
builder.emitNumber(increment);
|
|
4444
|
+
builder.emitOpcode(23 /* DIV */);
|
|
4445
|
+
emitRoundDirection(builder, direction);
|
|
4446
|
+
builder.emitOpcode(10 /* PUSH_NUMBER */);
|
|
4447
|
+
builder.emitNumber(increment);
|
|
4448
|
+
builder.emitOpcode(22 /* MUL */);
|
|
4449
|
+
}
|
|
4450
|
+
function emitRoundDirection(builder, direction) {
|
|
4451
|
+
const builtin = direction === "up" ? CEIL_BUILTIN : direction === "down" ? FLOOR_BUILTIN : ROUND_BUILTIN;
|
|
4452
|
+
builder.emitOpcode(51 /* CALL_BUILTIN */);
|
|
4453
|
+
builder.emitIndex(builtin);
|
|
4454
|
+
builder.emitIndex(1);
|
|
4455
|
+
}
|
|
4456
|
+
var ROUND_BUILTIN = 8;
|
|
4457
|
+
var CEIL_BUILTIN = 6;
|
|
4458
|
+
var FLOOR_BUILTIN = 7;
|
|
4459
|
+
function readDirection(parser) {
|
|
4460
|
+
const next = parser.peek();
|
|
4461
|
+
const word = (next?.text ?? "").toLowerCase();
|
|
4462
|
+
if (word === "up") {
|
|
4463
|
+
parser.consume();
|
|
4464
|
+
return "up";
|
|
4465
|
+
}
|
|
4466
|
+
if (word === "down") {
|
|
4467
|
+
parser.consume();
|
|
4468
|
+
return "down";
|
|
4469
|
+
}
|
|
4470
|
+
return "nearest";
|
|
4471
|
+
}
|
|
4472
|
+
var RoundedParselet = class {
|
|
4473
|
+
constructor() {
|
|
4474
|
+
this.category = "Converters";
|
|
4475
|
+
this.bindingPower = BindingPower.Conditional;
|
|
4476
|
+
}
|
|
4477
|
+
parse(parser, _left, _token, builder) {
|
|
4478
|
+
const direction = readDirection(parser);
|
|
4479
|
+
if (parser.match("TO_NEAREST")) {
|
|
4480
|
+
emitRoundToIncrement(builder, readIncrement(parser), direction);
|
|
4481
|
+
return;
|
|
4482
|
+
}
|
|
4483
|
+
emitRoundDirection(builder, direction);
|
|
4484
|
+
}
|
|
4485
|
+
};
|
|
4486
|
+
var ToNearestParselet = class {
|
|
4487
|
+
constructor() {
|
|
4488
|
+
this.category = "Converters";
|
|
4489
|
+
this.bindingPower = BindingPower.Conditional;
|
|
4490
|
+
}
|
|
4491
|
+
parse(parser, _left, _token, builder) {
|
|
4492
|
+
emitRoundToIncrement(builder, readIncrement(parser), "nearest");
|
|
4493
|
+
}
|
|
4494
|
+
};
|
|
4495
|
+
function readIncrement(parser) {
|
|
4496
|
+
const next = parser.peek();
|
|
4497
|
+
const word = (next?.text ?? "").toLowerCase();
|
|
4498
|
+
const magnitude = INCREMENT_WORDS[word];
|
|
4499
|
+
if (magnitude !== void 0) {
|
|
4500
|
+
parser.consume();
|
|
4501
|
+
return magnitude;
|
|
4502
|
+
}
|
|
4503
|
+
if (next?.type === "NUMBER") {
|
|
4504
|
+
parser.consume();
|
|
4505
|
+
const value = Number(next.value);
|
|
4506
|
+
if (Number.isFinite(value) && value > 0) return value;
|
|
4507
|
+
}
|
|
4508
|
+
throw ErrorFactory.parsing(
|
|
4509
|
+
"INVALID_ROUNDING_INCREMENT",
|
|
4510
|
+
`"to nearest ${next?.value ?? "?"}": expected a positive number or one of ${Object.keys(INCREMENT_WORDS).join(", ")}`
|
|
4511
|
+
);
|
|
4512
|
+
}
|
|
4513
|
+
var DecimalPlacesParselet = class {
|
|
4514
|
+
constructor() {
|
|
4515
|
+
this.category = "Converters";
|
|
4516
|
+
this.bindingPower = BindingPower.Conditional;
|
|
4517
|
+
}
|
|
4518
|
+
parse(_parser, _left, token, builder) {
|
|
4519
|
+
const places = Number(token.value);
|
|
4520
|
+
if (!Number.isFinite(places) || places < 0 || places > 100) {
|
|
4521
|
+
throw ErrorFactory.parsing(
|
|
4522
|
+
"INVALID_DECIMAL_PLACES",
|
|
4523
|
+
`"to ${token.value} dp": expected a place count between 0 and 100`
|
|
4524
|
+
);
|
|
4525
|
+
}
|
|
4526
|
+
const scale = Math.pow(10, places);
|
|
4527
|
+
builder.emitOpcode(10 /* PUSH_NUMBER */);
|
|
4528
|
+
builder.emitNumber(scale);
|
|
4529
|
+
builder.emitOpcode(22 /* MUL */);
|
|
4530
|
+
emitRoundDirection(builder, "nearest");
|
|
4531
|
+
builder.emitOpcode(10 /* PUSH_NUMBER */);
|
|
4532
|
+
builder.emitNumber(scale);
|
|
4533
|
+
builder.emitOpcode(23 /* DIV */);
|
|
4534
|
+
}
|
|
4535
|
+
};
|
|
4536
|
+
|
|
4537
|
+
// src/packages/converters/normalizer/DecimalPlacesNormalizerRule.ts
|
|
4538
|
+
var PLACE_WORDS = /* @__PURE__ */ new Set(["dp", "dps", "decimals", "digits", "digit"]);
|
|
4539
|
+
var PLACE_PHRASES = /* @__PURE__ */ new Set(["decimal"]);
|
|
4540
|
+
function decimalPlacesNormalizerRule(priority = 66) {
|
|
4541
|
+
return {
|
|
4542
|
+
name: "converters:decimal-places",
|
|
4543
|
+
priority,
|
|
4544
|
+
match(tokens, pos) {
|
|
4545
|
+
if (tokens[pos]?.type !== "TO") return null;
|
|
4546
|
+
const count = tokens[pos + 1];
|
|
4547
|
+
if (count?.type !== "NUMBER") return null;
|
|
4548
|
+
const unitToken = tokens[pos + 2];
|
|
4549
|
+
if (!unitToken) return null;
|
|
4550
|
+
const word = (unitToken.text ?? unitToken.value ?? "").toLowerCase();
|
|
4551
|
+
let consumed = 3;
|
|
4552
|
+
if (PLACE_PHRASES.has(word)) {
|
|
4553
|
+
const tail = tokens[pos + 3];
|
|
4554
|
+
const tailWord = (tail?.text ?? "").toLowerCase();
|
|
4555
|
+
if (tailWord !== "place" && tailWord !== "places") return null;
|
|
4556
|
+
consumed = 4;
|
|
4557
|
+
} else if (!PLACE_WORDS.has(word)) {
|
|
4558
|
+
return null;
|
|
4559
|
+
}
|
|
4560
|
+
return {
|
|
4561
|
+
consumed,
|
|
4562
|
+
replacement: [
|
|
4563
|
+
createFusedToken("DECIMAL_PLACES", count.value, tokens.slice(pos, pos + consumed))
|
|
4564
|
+
],
|
|
4565
|
+
ruleName: "converters:decimal-places"
|
|
4566
|
+
};
|
|
4567
|
+
}
|
|
4568
|
+
};
|
|
4569
|
+
}
|
|
4570
|
+
|
|
4571
|
+
// src/packages/converters/normalizer/ConverterPrepositionNormalizerRule.ts
|
|
4572
|
+
function converterPrepositionNormalizerRule(priority = 67) {
|
|
4573
|
+
return {
|
|
4574
|
+
name: "converters:preposition",
|
|
4575
|
+
priority,
|
|
4576
|
+
match(tokens, pos) {
|
|
4577
|
+
const preposition = tokens[pos];
|
|
4578
|
+
if (preposition?.type !== "IN" && preposition?.type !== "TO") return null;
|
|
4579
|
+
if (tokens[pos + 1]?.type !== "CONVERTER_NAME") return null;
|
|
4580
|
+
return {
|
|
4581
|
+
consumed: 1,
|
|
4582
|
+
replacement: [createFusedToken("AS", "as", [preposition])],
|
|
4583
|
+
ruleName: "converters:preposition"
|
|
4584
|
+
};
|
|
4585
|
+
}
|
|
4586
|
+
};
|
|
4587
|
+
}
|
|
4588
|
+
|
|
3628
4589
|
// src/packages/converters/ConvertersPackage.ts
|
|
3629
4590
|
var CONVERTERS_PACKAGE = {
|
|
3630
4591
|
name: "solve-converters",
|
|
4592
|
+
phrases: {
|
|
4593
|
+
// A phrase, not a bare "nearest": "to" is already an infix operator
|
|
4594
|
+
// (percentage change), so the two words have to arrive as one token for
|
|
4595
|
+
// the parser to tell the grammars apart. See RoundingParselets.ts.
|
|
4596
|
+
"to nearest": "TO_NEAREST",
|
|
4597
|
+
"to the nearest": "TO_NEAREST"
|
|
4598
|
+
},
|
|
3631
4599
|
infixParselets: [
|
|
3632
|
-
{ tokenType: "AS", parselet: new AsConverterParselet() }
|
|
4600
|
+
{ tokenType: "AS", parselet: new AsConverterParselet() },
|
|
4601
|
+
{ tokenType: "ROUNDED", parselet: new RoundedParselet() },
|
|
4602
|
+
{ tokenType: "TO_NEAREST", parselet: new ToNearestParselet() },
|
|
4603
|
+
{ tokenType: "DECIMAL_PLACES", parselet: new DecimalPlacesParselet() }
|
|
4604
|
+
],
|
|
4605
|
+
normalizerRules: [
|
|
4606
|
+
decimalPlacesNormalizerRule(),
|
|
4607
|
+
converterPrepositionNormalizerRule()
|
|
3633
4608
|
]
|
|
3634
4609
|
};
|
|
3635
4610
|
|
|
@@ -3641,10 +4616,10 @@ var VariadicAggregateParselet = class {
|
|
|
3641
4616
|
}
|
|
3642
4617
|
parse(parser, token, builder) {
|
|
3643
4618
|
let argCount = 0;
|
|
3644
|
-
parser.parseExpression(BindingPower.
|
|
4619
|
+
parser.parseExpression(BindingPower.Conjunction, builder);
|
|
3645
4620
|
argCount++;
|
|
3646
|
-
while (parser.match("COMMA")) {
|
|
3647
|
-
parser.parseExpression(BindingPower.
|
|
4621
|
+
while (parser.match("COMMA") || parser.match("AND_CONJ")) {
|
|
4622
|
+
parser.parseExpression(BindingPower.Conjunction, builder);
|
|
3648
4623
|
argCount++;
|
|
3649
4624
|
}
|
|
3650
4625
|
builder.emitOpcode(51 /* CALL_BUILTIN */);
|
|
@@ -3653,13 +4628,125 @@ var VariadicAggregateParselet = class {
|
|
|
3653
4628
|
}
|
|
3654
4629
|
};
|
|
3655
4630
|
|
|
4631
|
+
// src/packages/mathphrases/parselets/UnaryPhraseParselet.ts
|
|
4632
|
+
function unaryPhraseParselet(builtinIndex) {
|
|
4633
|
+
return {
|
|
4634
|
+
category: "MathPhrases",
|
|
4635
|
+
parse(parser, _token, builder) {
|
|
4636
|
+
parser.parseExpression(BindingPower.Lowest, builder);
|
|
4637
|
+
builder.emitOpcode(51 /* CALL_BUILTIN */);
|
|
4638
|
+
builder.emitIndex(builtinIndex);
|
|
4639
|
+
builder.emitIndex(1);
|
|
4640
|
+
}
|
|
4641
|
+
};
|
|
4642
|
+
}
|
|
4643
|
+
|
|
4644
|
+
// src/packages/mathphrases/parselets/RemainderRootParselets.ts
|
|
4645
|
+
var remainderOfParselet = {
|
|
4646
|
+
category: "MathPhrases",
|
|
4647
|
+
parse(parser, _token, builder) {
|
|
4648
|
+
parser.parseExpression(BindingPower.Product, builder);
|
|
4649
|
+
if (!parser.match("DIVIDE_BY") && !parser.match("SLASH")) {
|
|
4650
|
+
throw ErrorFactory.parsing(
|
|
4651
|
+
"REMAINDER_EXPECTED_DIVIDED_BY",
|
|
4652
|
+
'Expected "divided by" or "/", as in "remainder of 21 divided by 5"'
|
|
4653
|
+
);
|
|
4654
|
+
}
|
|
4655
|
+
parser.parseExpression(BindingPower.Product, builder);
|
|
4656
|
+
builder.emitOpcode(24 /* MOD */);
|
|
4657
|
+
}
|
|
4658
|
+
};
|
|
4659
|
+
var nthRootParselet = {
|
|
4660
|
+
category: "MathPhrases",
|
|
4661
|
+
parse(parser, _token, builder) {
|
|
4662
|
+
parser.parseExpression(BindingPower.Product, builder);
|
|
4663
|
+
if (!parser.match("OF")) {
|
|
4664
|
+
throw ErrorFactory.parsing(
|
|
4665
|
+
"ROOT_EXPECTED_OF",
|
|
4666
|
+
'Expected "of", as in "root 5 of 100"'
|
|
4667
|
+
);
|
|
4668
|
+
}
|
|
4669
|
+
parser.parseExpression(BindingPower.Conditional, builder);
|
|
4670
|
+
builder.emitOpcode(2 /* SWAP */);
|
|
4671
|
+
builder.emitOpcode(10 /* PUSH_NUMBER */);
|
|
4672
|
+
builder.emitNumber(1);
|
|
4673
|
+
builder.emitOpcode(2 /* SWAP */);
|
|
4674
|
+
builder.emitOpcode(23 /* DIV */);
|
|
4675
|
+
builder.emitOpcode(25 /* EXP */);
|
|
4676
|
+
}
|
|
4677
|
+
};
|
|
4678
|
+
var logBaseParselet = {
|
|
4679
|
+
category: "MathPhrases",
|
|
4680
|
+
parse(parser, _token, builder) {
|
|
4681
|
+
parser.parseExpression(BindingPower.Conditional, builder);
|
|
4682
|
+
emitNaturalLog(builder);
|
|
4683
|
+
if (!parser.match("LOG_BASE")) {
|
|
4684
|
+
throw ErrorFactory.parsing(
|
|
4685
|
+
"LOG_EXPECTED_BASE",
|
|
4686
|
+
'Expected "base", as in "log 20 base 4"'
|
|
4687
|
+
);
|
|
4688
|
+
}
|
|
4689
|
+
parser.parseExpression(BindingPower.Conditional, builder);
|
|
4690
|
+
emitNaturalLog(builder);
|
|
4691
|
+
builder.emitOpcode(23 /* DIV */);
|
|
4692
|
+
}
|
|
4693
|
+
};
|
|
4694
|
+
var LOG_BUILTIN2 = 5;
|
|
4695
|
+
function emitNaturalLog(builder) {
|
|
4696
|
+
builder.emitOpcode(51 /* CALL_BUILTIN */);
|
|
4697
|
+
builder.emitIndex(LOG_BUILTIN2);
|
|
4698
|
+
builder.emitIndex(1);
|
|
4699
|
+
}
|
|
4700
|
+
|
|
4701
|
+
// src/packages/mathphrases/normalizer/FunctionPhraseNormalizerRule.ts
|
|
4702
|
+
function functionPhraseNormalizerRule(priority = 69) {
|
|
4703
|
+
return {
|
|
4704
|
+
name: "mathphrases:function-phrase",
|
|
4705
|
+
priority,
|
|
4706
|
+
match(tokens, pos) {
|
|
4707
|
+
const head = tokens[pos];
|
|
4708
|
+
if (head?.type !== "FUNC") return null;
|
|
4709
|
+
const word = (head.value ?? "").toLowerCase();
|
|
4710
|
+
if (word !== "root" && word !== "log") return null;
|
|
4711
|
+
if (tokens[pos + 1]?.type === "LPAREN") return null;
|
|
4712
|
+
if (word === "root") {
|
|
4713
|
+
return {
|
|
4714
|
+
consumed: 1,
|
|
4715
|
+
replacement: [createFusedToken("NTH_ROOT", "root", [head])],
|
|
4716
|
+
ruleName: "mathphrases:function-phrase"
|
|
4717
|
+
};
|
|
4718
|
+
}
|
|
4719
|
+
const baseAt = findBase(tokens, pos + 1);
|
|
4720
|
+
if (baseAt === -1) return null;
|
|
4721
|
+
const replacement = tokens.slice(pos, baseAt + 1).map((token, index) => {
|
|
4722
|
+
if (index === 0) return createFusedToken("LOG_PHRASE", "log", [token]);
|
|
4723
|
+
if (index === baseAt - pos) return createFusedToken("LOG_BASE", "base", [token]);
|
|
4724
|
+
return token;
|
|
4725
|
+
});
|
|
4726
|
+
return {
|
|
4727
|
+
consumed: baseAt - pos + 1,
|
|
4728
|
+
replacement,
|
|
4729
|
+
ruleName: "mathphrases:function-phrase"
|
|
4730
|
+
};
|
|
4731
|
+
}
|
|
4732
|
+
};
|
|
4733
|
+
}
|
|
4734
|
+
function findBase(tokens, from) {
|
|
4735
|
+
for (let i = from; i < tokens.length && i < from + 8; i++) {
|
|
4736
|
+
const token = tokens[i];
|
|
4737
|
+
if (token.type === "EOF" || token.type === "COMMA" || token.type === "RPAREN") return -1;
|
|
4738
|
+
if ((token.text ?? token.value ?? "").toLowerCase() === "base") return i;
|
|
4739
|
+
}
|
|
4740
|
+
return -1;
|
|
4741
|
+
}
|
|
4742
|
+
|
|
3656
4743
|
// src/packages/mathphrases/parselets/LargerSmallerParselet.ts
|
|
3657
4744
|
function largerSmallerParselet(builtinIndex) {
|
|
3658
4745
|
return {
|
|
3659
4746
|
category: "MathPhrases",
|
|
3660
4747
|
parse(parser, token, builder) {
|
|
3661
|
-
parser.parseExpression(BindingPower.
|
|
3662
|
-
parser.consume("
|
|
4748
|
+
parser.parseExpression(BindingPower.Conjunction, builder);
|
|
4749
|
+
parser.consume("AND_CONJ");
|
|
3663
4750
|
parser.parseExpression(BindingPower.Lowest, builder);
|
|
3664
4751
|
builder.emitOpcode(51 /* CALL_BUILTIN */);
|
|
3665
4752
|
builder.emitIndex(builtinIndex);
|
|
@@ -3683,8 +4770,8 @@ var halfParselet = {
|
|
|
3683
4770
|
var midpointParselet = {
|
|
3684
4771
|
category: "MathPhrases",
|
|
3685
4772
|
parse(parser, token, builder) {
|
|
3686
|
-
parser.parseExpression(BindingPower.
|
|
3687
|
-
parser.consume("
|
|
4773
|
+
parser.parseExpression(BindingPower.Conjunction, builder);
|
|
4774
|
+
parser.consume("AND_CONJ");
|
|
3688
4775
|
parser.parseExpression(BindingPower.Lowest, builder);
|
|
3689
4776
|
builder.emitOpcode(20 /* ADD */);
|
|
3690
4777
|
builder.emitOpcode(10 /* PUSH_NUMBER */);
|
|
@@ -3701,7 +4788,7 @@ var randomNumberParselet = definePhrasePattern({
|
|
|
3701
4788
|
slots: [
|
|
3702
4789
|
{ kind: "keyword", tokenTypes: ["BETWEEN"] },
|
|
3703
4790
|
{ kind: "expr", bindingPower: BindingPower.Product },
|
|
3704
|
-
{ kind: "keyword", tokenTypes: ["
|
|
4791
|
+
{ kind: "keyword", tokenTypes: ["AND_CONJ"] },
|
|
3705
4792
|
{ kind: "expr" }
|
|
3706
4793
|
],
|
|
3707
4794
|
emit: (builder) => {
|
|
@@ -3725,7 +4812,7 @@ var ClampParselet = class {
|
|
|
3725
4812
|
if (mid?.type === "BETWEEN") {
|
|
3726
4813
|
parser.consume();
|
|
3727
4814
|
parser.parseExpression(BindingPower.Product, builder);
|
|
3728
|
-
parser.consume("
|
|
4815
|
+
parser.consume("AND_CONJ");
|
|
3729
4816
|
parser.parseExpression(BindingPower.Lowest, builder);
|
|
3730
4817
|
} else if (mid?.type === "FROM") {
|
|
3731
4818
|
parser.consume();
|
|
@@ -3776,6 +4863,10 @@ var TOTAL = 44;
|
|
|
3776
4863
|
var COUNT = 45;
|
|
3777
4864
|
var MIN = 9;
|
|
3778
4865
|
var MAX = 10;
|
|
4866
|
+
var GCD = 38;
|
|
4867
|
+
var LCM = 39;
|
|
4868
|
+
var SQRT = 0;
|
|
4869
|
+
var CBRT = 21;
|
|
3779
4870
|
var MATHPHRASES_PACKAGE = {
|
|
3780
4871
|
name: "solve-mathphrases",
|
|
3781
4872
|
phrases: {
|
|
@@ -3784,11 +4875,20 @@ var MATHPHRASES_PACKAGE = {
|
|
|
3784
4875
|
"total of": "TOTAL_OF",
|
|
3785
4876
|
"count of": "COUNT_OF",
|
|
3786
4877
|
"larger of": "LARGER_OF",
|
|
4878
|
+
"greater of": "LARGER_OF",
|
|
3787
4879
|
"smaller of": "SMALLER_OF",
|
|
4880
|
+
"lesser of": "SMALLER_OF",
|
|
3788
4881
|
"half of": "HALF_OF",
|
|
3789
4882
|
"midpoint between": "MIDPOINT_BETWEEN",
|
|
3790
4883
|
"random number": "RANDOM_NUMBER",
|
|
3791
|
-
"is to": "IS_TO"
|
|
4884
|
+
"is to": "IS_TO",
|
|
4885
|
+
// Two-operand phrases reusing the gcd/lcm builtins.
|
|
4886
|
+
"remainder of": "REMAINDER_OF",
|
|
4887
|
+
"gcd of": "GCD_OF",
|
|
4888
|
+
"lcm of": "LCM_OF",
|
|
4889
|
+
// sqrt/cbrt spelled the way they are said.
|
|
4890
|
+
"square root of": "SQUARE_ROOT_OF",
|
|
4891
|
+
"cube root of": "CUBE_ROOT_OF"
|
|
3792
4892
|
},
|
|
3793
4893
|
prefixParselets: [
|
|
3794
4894
|
{ tokenType: "AVERAGE_OF", parselet: new VariadicAggregateParselet(AVERAGE) },
|
|
@@ -3800,29 +4900,168 @@ var MATHPHRASES_PACKAGE = {
|
|
|
3800
4900
|
{ tokenType: "HALF_OF", parselet: halfParselet },
|
|
3801
4901
|
{ tokenType: "MIDPOINT_BETWEEN", parselet: midpointParselet },
|
|
3802
4902
|
{ tokenType: "RANDOM_NUMBER", parselet: randomNumberParselet },
|
|
4903
|
+
{ tokenType: "REMAINDER_OF", parselet: remainderOfParselet },
|
|
4904
|
+
{ tokenType: "NTH_ROOT", parselet: nthRootParselet },
|
|
4905
|
+
{ tokenType: "LOG_PHRASE", parselet: logBaseParselet },
|
|
4906
|
+
{ tokenType: "GCD_OF", parselet: largerSmallerParselet(GCD) },
|
|
4907
|
+
{ tokenType: "LCM_OF", parselet: largerSmallerParselet(LCM) },
|
|
4908
|
+
{ tokenType: "SQUARE_ROOT_OF", parselet: unaryPhraseParselet(SQRT) },
|
|
4909
|
+
{ tokenType: "CUBE_ROOT_OF", parselet: unaryPhraseParselet(CBRT) },
|
|
3803
4910
|
{ tokenType: "CLAMP", parselet: new ClampParselet() }
|
|
3804
4911
|
],
|
|
3805
4912
|
infixParselets: [
|
|
3806
4913
|
{ tokenType: "IS_TO", parselet: new ProportionParselet() }
|
|
4914
|
+
],
|
|
4915
|
+
normalizerRules: [
|
|
4916
|
+
functionPhraseNormalizerRule()
|
|
3807
4917
|
]
|
|
3808
4918
|
};
|
|
3809
4919
|
|
|
4920
|
+
// src/packages/finance/parselets/InvestmentParselets.ts
|
|
4921
|
+
var PERIODS_PER_YEAR = {
|
|
4922
|
+
annually: 1,
|
|
4923
|
+
yearly: 1,
|
|
4924
|
+
// Semi-annual is not on Soulver's page, but it is the one interval a
|
|
4925
|
+
// reader of the other four would expect to exist, and leaving it out makes
|
|
4926
|
+
// the set look arbitrary rather than deliberate.
|
|
4927
|
+
"semi-annually": 2,
|
|
4928
|
+
quarterly: 4,
|
|
4929
|
+
monthly: 12,
|
|
4930
|
+
fortnightly: 26,
|
|
4931
|
+
weekly: 52,
|
|
4932
|
+
daily: 365
|
|
4933
|
+
};
|
|
4934
|
+
function readCompoundingInterval(parser) {
|
|
4935
|
+
if (!parser.match("COMPOUNDING")) return 1;
|
|
4936
|
+
const token = parser.peek();
|
|
4937
|
+
const name = (token?.text ?? token?.value ?? "").toLowerCase();
|
|
4938
|
+
const periods = PERIODS_PER_YEAR[name];
|
|
4939
|
+
if (periods === void 0) {
|
|
4940
|
+
throw ErrorFactory.parsing(
|
|
4941
|
+
"UNKNOWN_COMPOUNDING_INTERVAL",
|
|
4942
|
+
`compounding ${name || "?"}: expected one of ${Object.keys(PERIODS_PER_YEAR).join(", ")}`
|
|
4943
|
+
);
|
|
4944
|
+
}
|
|
4945
|
+
parser.consume();
|
|
4946
|
+
return periods;
|
|
4947
|
+
}
|
|
4948
|
+
var InvestmentGrowthParselet = class {
|
|
4949
|
+
/**
|
|
4950
|
+
* @param annualIndex - Builtin for the annual case, so the common form
|
|
4951
|
+
* keeps using the same code path (and the same rounding) it always did.
|
|
4952
|
+
* @param intervalIndex - Builtin taking an explicit periods-per-year.
|
|
4953
|
+
*/
|
|
4954
|
+
constructor(annualIndex, intervalIndex) {
|
|
4955
|
+
this.annualIndex = annualIndex;
|
|
4956
|
+
this.intervalIndex = intervalIndex;
|
|
4957
|
+
this.category = "Finance";
|
|
4958
|
+
this.bindingPower = BindingPower.Conditional;
|
|
4959
|
+
}
|
|
4960
|
+
parse(parser, _left, _token, builder) {
|
|
4961
|
+
parser.parseExpression(BindingPower.Conditional, builder);
|
|
4962
|
+
if (!parser.match("RATE_AT")) parser.consume("AT");
|
|
4963
|
+
parser.parseExpression(BindingPower.Conditional, builder);
|
|
4964
|
+
const periods = readCompoundingInterval(parser);
|
|
4965
|
+
builder.emitOpcode(2 /* SWAP */);
|
|
4966
|
+
if (periods === 1) {
|
|
4967
|
+
builder.emitOpcode(51 /* CALL_BUILTIN */);
|
|
4968
|
+
builder.emitIndex(this.annualIndex);
|
|
4969
|
+
builder.emitIndex(3);
|
|
4970
|
+
return;
|
|
4971
|
+
}
|
|
4972
|
+
builder.emitOpcode(10 /* PUSH_NUMBER */);
|
|
4973
|
+
builder.emitNumber(periods);
|
|
4974
|
+
builder.emitOpcode(51 /* CALL_BUILTIN */);
|
|
4975
|
+
builder.emitIndex(this.intervalIndex);
|
|
4976
|
+
builder.emitIndex(4);
|
|
4977
|
+
}
|
|
4978
|
+
};
|
|
4979
|
+
var PresentValueParselet = class {
|
|
4980
|
+
constructor(builtinIndex) {
|
|
4981
|
+
this.builtinIndex = builtinIndex;
|
|
4982
|
+
this.category = "Finance";
|
|
4983
|
+
}
|
|
4984
|
+
parse(parser, _token, builder) {
|
|
4985
|
+
parser.parseExpression(BindingPower.Conditional, builder);
|
|
4986
|
+
if (!parser.match("AFTER") && !parser.match("OVER") && !parser.match("IN")) {
|
|
4987
|
+
parser.consume("AFTER");
|
|
4988
|
+
}
|
|
4989
|
+
parser.parseExpression(BindingPower.Conditional, builder);
|
|
4990
|
+
if (!parser.match("RATE_AT")) parser.consume("AT");
|
|
4991
|
+
parser.parseExpression(BindingPower.Conditional, builder);
|
|
4992
|
+
builder.emitOpcode(2 /* SWAP */);
|
|
4993
|
+
builder.emitOpcode(51 /* CALL_BUILTIN */);
|
|
4994
|
+
builder.emitIndex(this.builtinIndex);
|
|
4995
|
+
builder.emitIndex(3);
|
|
4996
|
+
}
|
|
4997
|
+
};
|
|
4998
|
+
var ReturnOnInvestmentParselet = class {
|
|
4999
|
+
constructor(builtinIndex) {
|
|
5000
|
+
this.builtinIndex = builtinIndex;
|
|
5001
|
+
this.category = "Finance";
|
|
5002
|
+
this.bindingPower = BindingPower.Conditional;
|
|
5003
|
+
}
|
|
5004
|
+
parse(parser, _left, _token, builder) {
|
|
5005
|
+
parser.parseExpression(BindingPower.Conditional, builder);
|
|
5006
|
+
parser.consume("RETURNED");
|
|
5007
|
+
builder.emitOpcode(51 /* CALL_BUILTIN */);
|
|
5008
|
+
builder.emitIndex(this.builtinIndex);
|
|
5009
|
+
builder.emitIndex(2);
|
|
5010
|
+
}
|
|
5011
|
+
};
|
|
5012
|
+
var AnnualReturnParselet = class {
|
|
5013
|
+
constructor(builtinIndex) {
|
|
5014
|
+
this.builtinIndex = builtinIndex;
|
|
5015
|
+
this.category = "Finance";
|
|
5016
|
+
}
|
|
5017
|
+
parse(parser, _token, builder) {
|
|
5018
|
+
parser.parseExpression(BindingPower.Conditional, builder);
|
|
5019
|
+
parser.consume("INVESTED");
|
|
5020
|
+
parser.parseExpression(BindingPower.Conditional, builder);
|
|
5021
|
+
parser.consume("RETURNED");
|
|
5022
|
+
if (!parser.match("AFTER") && !parser.match("OVER") && !parser.match("IN")) {
|
|
5023
|
+
parser.consume("AFTER");
|
|
5024
|
+
}
|
|
5025
|
+
parser.parseExpression(BindingPower.Conditional, builder);
|
|
5026
|
+
builder.emitOpcode(51 /* CALL_BUILTIN */);
|
|
5027
|
+
builder.emitIndex(this.builtinIndex);
|
|
5028
|
+
builder.emitIndex(3);
|
|
5029
|
+
}
|
|
5030
|
+
};
|
|
5031
|
+
|
|
3810
5032
|
// src/packages/finance/parselets/CompoundInterestParselet.ts
|
|
3811
5033
|
var CompoundInterestParselet = class {
|
|
3812
|
-
|
|
5034
|
+
/**
|
|
5035
|
+
* @param builtinIndex - Annual-compounding builtin.
|
|
5036
|
+
* @param intervalIndex - Builtin taking an explicit periods-per-year, used
|
|
5037
|
+
* when the expression ends in "compounding monthly" or similar.
|
|
5038
|
+
*/
|
|
5039
|
+
constructor(builtinIndex, intervalIndex) {
|
|
3813
5040
|
this.builtinIndex = builtinIndex;
|
|
5041
|
+
this.intervalIndex = intervalIndex;
|
|
3814
5042
|
this.category = "Finance";
|
|
3815
5043
|
}
|
|
3816
5044
|
parse(parser, _token, builder) {
|
|
5045
|
+
parser.parseExpression(BindingPower.Conditional, builder);
|
|
5046
|
+
if (!parser.match("AFTER") && !parser.match("FOR_DURATION")) {
|
|
5047
|
+
parser.consume("OVER");
|
|
5048
|
+
}
|
|
3817
5049
|
parser.parseExpression(BindingPower.Lowest, builder);
|
|
3818
|
-
parser.consume("
|
|
3819
|
-
parser.parseExpression(BindingPower.Lowest, builder);
|
|
3820
|
-
parser.consume("RATE_AT");
|
|
5050
|
+
if (!parser.match("RATE_AT")) parser.consume("AT");
|
|
3821
5051
|
parser.parseExpression(BindingPower.Lowest, builder);
|
|
5052
|
+
const periods = readCompoundingInterval(parser);
|
|
3822
5053
|
builder.emitOpcode(2 /* SWAP */);
|
|
5054
|
+
if (periods === 1) {
|
|
5055
|
+
builder.emitOpcode(51 /* CALL_BUILTIN */);
|
|
5056
|
+
builder.emitIndex(this.builtinIndex);
|
|
5057
|
+
builder.emitIndex(3);
|
|
5058
|
+
return;
|
|
5059
|
+
}
|
|
5060
|
+
builder.emitOpcode(10 /* PUSH_NUMBER */);
|
|
5061
|
+
builder.emitNumber(periods);
|
|
3823
5062
|
builder.emitOpcode(51 /* CALL_BUILTIN */);
|
|
3824
|
-
builder.emitIndex(this.
|
|
3825
|
-
builder.emitIndex(
|
|
5063
|
+
builder.emitIndex(this.intervalIndex);
|
|
5064
|
+
builder.emitIndex(4);
|
|
3826
5065
|
}
|
|
3827
5066
|
};
|
|
3828
5067
|
|
|
@@ -3837,7 +5076,7 @@ var LoanRepaymentParselet = class {
|
|
|
3837
5076
|
parser.parseExpression(BindingPower.Lowest, builder);
|
|
3838
5077
|
parser.consume("OVER");
|
|
3839
5078
|
parser.parseExpression(BindingPower.Lowest, builder);
|
|
3840
|
-
parser.
|
|
5079
|
+
if (!parser.match("RATE_AT")) parser.consume("AT");
|
|
3841
5080
|
parser.parseExpression(BindingPower.Lowest, builder);
|
|
3842
5081
|
builder.emitOpcode(2 /* SWAP */);
|
|
3843
5082
|
builder.emitOpcode(10 /* PUSH_NUMBER */);
|
|
@@ -3899,8 +5138,8 @@ function inflationFutureValueHandler(args) {
|
|
|
3899
5138
|
return errorValue("INVALID_RATE", `inflationFutureValue: rate ${rate} makes (1 + rate) non-positive`);
|
|
3900
5139
|
}
|
|
3901
5140
|
const amount = amountValue.toNumber();
|
|
3902
|
-
const
|
|
3903
|
-
return amountValue.type === 6 /* Uom */ ? uomValue(
|
|
5141
|
+
const worth = amount / Math.pow(1 + rate, years);
|
|
5142
|
+
return amountValue.type === 6 /* Uom */ ? uomValue(worth, amountValue.unit) : numberValue(worth);
|
|
3904
5143
|
}
|
|
3905
5144
|
|
|
3906
5145
|
// src/packages/finance/parselets/InflationQueryParselet.ts
|
|
@@ -4018,8 +5257,14 @@ var COMPOUND_FV = 51;
|
|
|
4018
5257
|
var COMPOUND_INTEREST = 52;
|
|
4019
5258
|
var LOAN_REPAYMENT = 55;
|
|
4020
5259
|
var LOAN_INTEREST = 56;
|
|
4021
|
-
var
|
|
5260
|
+
var COMPOUND_FV_EVERY = 80;
|
|
5261
|
+
var COMPOUND_INTEREST_EVERY = 81;
|
|
5262
|
+
var PRESENT_VALUE = 82;
|
|
5263
|
+
var ROI = 83;
|
|
5264
|
+
var ANNUAL_RETURN = 84;
|
|
4022
5265
|
var TAX_REMOVE = 59;
|
|
5266
|
+
var TAX_IN = 85;
|
|
5267
|
+
var TAX_ON = 86;
|
|
4023
5268
|
var FINANCE_PACKAGE = {
|
|
4024
5269
|
name: "solve-finance",
|
|
4025
5270
|
phrases: {
|
|
@@ -4035,17 +5280,29 @@ var FINANCE_PACKAGE = {
|
|
|
4035
5280
|
"total interest on": "TOTAL_LOAN_INTEREST_ON",
|
|
4036
5281
|
"tax on": "TAX_ON",
|
|
4037
5282
|
"tax off": "TAX_OFF",
|
|
5283
|
+
// The tax already inside a gross amount. Soulver documents "VAT in",
|
|
5284
|
+
// "VAT of" and "VAT from" as three spellings of one thing.
|
|
5285
|
+
"tax in": "TAX_IN_PHRASE",
|
|
5286
|
+
"tax of": "TAX_IN_PHRASE",
|
|
5287
|
+
"tax from": "TAX_IN_PHRASE",
|
|
5288
|
+
"vat in": "TAX_IN_PHRASE",
|
|
5289
|
+
"vat of": "TAX_IN_PHRASE",
|
|
5290
|
+
"vat from": "TAX_IN_PHRASE",
|
|
4038
5291
|
"vat on": "TAX_ON",
|
|
4039
5292
|
"vat off": "TAX_OFF",
|
|
4040
5293
|
"what is": "WHAT_IS",
|
|
4041
5294
|
"what was": "WHAT_WAS",
|
|
5295
|
+
"present value of": "PRESENT_VALUE_OF",
|
|
5296
|
+
"annual return on": "ANNUAL_RETURN_ON",
|
|
4042
5297
|
"value of": "VALUE_OF",
|
|
4043
5298
|
"worth in": "WORTH_IN",
|
|
4044
5299
|
"assuming": "ASSUMING"
|
|
4045
5300
|
},
|
|
4046
5301
|
prefixParselets: [
|
|
4047
|
-
{ tokenType: "
|
|
4048
|
-
{ tokenType: "
|
|
5302
|
+
{ tokenType: "PRESENT_VALUE_OF", parselet: new PresentValueParselet(PRESENT_VALUE) },
|
|
5303
|
+
{ tokenType: "ANNUAL_RETURN_ON", parselet: new AnnualReturnParselet(ANNUAL_RETURN) },
|
|
5304
|
+
{ tokenType: "COMPOUND_INTEREST_ON", parselet: new CompoundInterestParselet(COMPOUND_FV, COMPOUND_FV_EVERY) },
|
|
5305
|
+
{ tokenType: "INTEREST_ON", parselet: new CompoundInterestParselet(COMPOUND_INTEREST, COMPOUND_INTEREST_EVERY) },
|
|
4049
5306
|
{ tokenType: "DAILY_REPAYMENT_ON", parselet: new LoanRepaymentParselet(LOAN_REPAYMENT, 365) },
|
|
4050
5307
|
{ tokenType: "MONTHLY_REPAYMENT_ON", parselet: new LoanRepaymentParselet(LOAN_REPAYMENT, 12) },
|
|
4051
5308
|
{ tokenType: "ANNUAL_REPAYMENT_ON", parselet: new LoanRepaymentParselet(LOAN_REPAYMENT, 1) },
|
|
@@ -4054,13 +5311,20 @@ var FINANCE_PACKAGE = {
|
|
|
4054
5311
|
{ tokenType: "MONTHLY_LOAN_INTEREST_ON", parselet: new LoanRepaymentParselet(LOAN_INTEREST, 12) },
|
|
4055
5312
|
{ tokenType: "ANNUAL_LOAN_INTEREST_ON", parselet: new LoanRepaymentParselet(LOAN_INTEREST, 1) },
|
|
4056
5313
|
{ tokenType: "TOTAL_LOAN_INTEREST_ON", parselet: new LoanRepaymentParselet(LOAN_INTEREST, 0) },
|
|
4057
|
-
{ tokenType: "TAX_ON", parselet: new SalesTaxParselet(
|
|
5314
|
+
{ tokenType: "TAX_ON", parselet: new SalesTaxParselet(TAX_ON) },
|
|
4058
5315
|
{ tokenType: "TAX_OFF", parselet: new SalesTaxParselet(TAX_REMOVE) },
|
|
5316
|
+
{ tokenType: "TAX_IN_PHRASE", parselet: new SalesTaxParselet(TAX_IN) },
|
|
4059
5317
|
{ tokenType: "WHAT_IS", parselet: new InflationQueryParselet("what-is") },
|
|
4060
5318
|
{ tokenType: "WHAT_WAS", parselet: new InflationQueryParselet("what-was") },
|
|
4061
5319
|
{ tokenType: "VALUE_OF", parselet: new InflationFutureValueParselet() }
|
|
4062
5320
|
],
|
|
4063
5321
|
infixParselets: [
|
|
5322
|
+
// The documented bare forms: "$1,000 after 3 years at 7%" and the "for
|
|
5323
|
+
// ... compounding monthly" variant. Both pivot words behave identically;
|
|
5324
|
+
// Soulver uses "after" for the annual form and "for" with an interval.
|
|
5325
|
+
{ tokenType: "AFTER", parselet: new InvestmentGrowthParselet(COMPOUND_FV, COMPOUND_FV_EVERY) },
|
|
5326
|
+
{ tokenType: "FOR_DURATION", parselet: new InvestmentGrowthParselet(COMPOUND_FV, COMPOUND_FV_EVERY) },
|
|
5327
|
+
{ tokenType: "INVESTED", parselet: new ReturnOnInvestmentParselet(ROI) },
|
|
4064
5328
|
{ tokenType: "IN_YEAR_DOLLARS", parselet: new InYearDollarsParselet() }
|
|
4065
5329
|
],
|
|
4066
5330
|
normalizerRules: [
|
|
@@ -4423,6 +5687,15 @@ function requireYear(text, raw) {
|
|
|
4423
5687
|
function tryParseDatePhrase(parser) {
|
|
4424
5688
|
const first = parser.peek();
|
|
4425
5689
|
if (!first) return null;
|
|
5690
|
+
if (first.type === "DATETIME_LITERAL") {
|
|
5691
|
+
parser.consume();
|
|
5692
|
+
const date = new Date(Number(first.value));
|
|
5693
|
+
if (Number.isNaN(date.getTime())) return null;
|
|
5694
|
+
return {
|
|
5695
|
+
isoDate: `${date.getFullYear()}-${pad2(date.getMonth() + 1)}-${pad2(date.getDate())}`,
|
|
5696
|
+
raw: first.text ?? first.value
|
|
5697
|
+
};
|
|
5698
|
+
}
|
|
4426
5699
|
if (first.type === "NUMBER") {
|
|
4427
5700
|
const g1 = parser.consume();
|
|
4428
5701
|
const sepTok = parser.peek();
|
|
@@ -5093,5 +6366,5 @@ var BUILTIN_PACKAGES = [
|
|
|
5093
6366
|
];
|
|
5094
6367
|
|
|
5095
6368
|
export { ARITHMETIC_PACKAGE, BIGINT_PACKAGE, BUILTIN_PACKAGES, CONDITIONALS_PACKAGE, CONVERTERS_PACKAGE, CURRENCY_PACKAGE, DATETIME_PACKAGE, DICE_PACKAGE, FINANCE_PACKAGE, FUNCTION_PACKAGE, LINES_PACKAGE, MAPREDUCE_PACKAGE, MATHPHRASES_PACKAGE, MATRIX_PACKAGE, PERCENTAGE_PACKAGE, SYMBOLIC_PACKAGE, TIME_PACKAGE, UOM_PACKAGE, VARIABLES_PACKAGE, VECTOR_PACKAGE, WEATHER_PACKAGE, containsSymbolicCall, createKnowledgePackage, createStocksPackage };
|
|
5096
|
-
//# sourceMappingURL=chunk-
|
|
5097
|
-
//# sourceMappingURL=chunk-
|
|
6369
|
+
//# sourceMappingURL=chunk-FIFQPKBA.js.map
|
|
6370
|
+
//# sourceMappingURL=chunk-FIFQPKBA.js.map
|