@briza/illogical 2.1.0 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/illogical.cjs CHANGED
@@ -121,6 +121,71 @@ function areAllResults(values) {
121
121
  function areAllNumbers(results) {
122
122
  return results.every(isNumber);
123
123
  }
124
+ function isReference(operand) {
125
+ return operand.constructor.name === 'Reference';
126
+ }
127
+ function isValue(operand) {
128
+ return operand.constructor.name === 'Value';
129
+ }
130
+
131
+ /**
132
+ * Convert a value to number if possible, otherwise return undefined
133
+ * @param value value to be converted to number
134
+ */
135
+ const toNumber = value => {
136
+ const isValueNumber = isNumber(value);
137
+ if (isValueNumber) {
138
+ return value;
139
+ } else if (isString(value)) {
140
+ if (value.match(/^\d+\.\d+$/)) {
141
+ return parseFloat(value);
142
+ } else if (value.match(/^0$|^[1-9]\d*$/)) {
143
+ return parseInt(value);
144
+ }
145
+ }
146
+ return undefined;
147
+ };
148
+
149
+ /**
150
+ * Convert a value to string if possible, otherwise return undefined
151
+ * @param value value to be converted to string
152
+ */
153
+ const toString = value => {
154
+ if (isNumber(value)) {
155
+ return `${value}`;
156
+ } else if (isString(value)) {
157
+ return value;
158
+ }
159
+ return undefined;
160
+ };
161
+ /**
162
+ * Convert a value to number if it's type is string, otherwise return NaN
163
+ * @param value value to be converted to number
164
+ */
165
+ const toDateNumber = value => {
166
+ if (isString(value)) {
167
+ return Date.parse(value);
168
+ }
169
+ return NaN;
170
+ };
171
+ const formatDateNumber = dateNumber => new Date(dateNumber).toISOString().split('T')[0];
172
+ const toDateDuration = value => {
173
+ if (!isString(value)) {
174
+ return;
175
+ }
176
+ const isDateDuration = value.match(/^([1-9]\d*)([dmy])$/);
177
+ if (!isDateDuration) {
178
+ return;
179
+ }
180
+ const unit = isDateDuration[2];
181
+ if (unit !== 'd' && unit !== 'm' && unit !== 'y') {
182
+ return;
183
+ }
184
+ return {
185
+ amount: parseInt(isDateDuration[1], 10),
186
+ unit
187
+ };
188
+ };
124
189
 
125
190
  /**
126
191
  * Abstract arithmetic expression
@@ -159,6 +224,21 @@ class Arithmetic {
159
224
  }
160
225
  return presentValues;
161
226
  }
227
+ getDateCalculationResults(results) {
228
+ const [date, ...durations] = results;
229
+ const dateNumber = toDateNumber(date);
230
+ if (isNaN(dateNumber)) {
231
+ return false;
232
+ }
233
+ const parsedDurations = durations.flatMap(d => {
234
+ const parsedDuration = toDateDuration(d);
235
+ return parsedDuration ? [parsedDuration] : [];
236
+ });
237
+ if (parsedDurations.length !== durations.length) {
238
+ return false;
239
+ }
240
+ return [dateNumber, ...parsedDurations];
241
+ }
162
242
 
163
243
  /**
164
244
  * Performs the arithmetic operation on the operands evaluated values.
@@ -280,6 +360,63 @@ class Multiply extends Arithmetic {
280
360
  }
281
361
  }
282
362
 
363
+ const dateArithmeticTypeCheck = (...operands) => {
364
+ const [first, ...rest] = operands;
365
+ const restAreAllReferences = rest.every(op => isReference(op));
366
+ const values = rest.filter(op => isValue(op));
367
+ const valuesAreAllDurations = values.every(op => !!toDateDuration(op.evaluate()));
368
+ const valuesAreAllNumbers = values.every(op => isNumber(op.evaluate()));
369
+ if (isReference(first)) {
370
+ if (!restAreAllReferences && !valuesAreAllDurations && !valuesAreAllNumbers) {
371
+ throw new Error('sum expression value literals should be all numbers or all date durations');
372
+ }
373
+ } else if (isValue(first)) {
374
+ if (isNumber(first.evaluate())) {
375
+ if (!restAreAllReferences && !valuesAreAllNumbers) {
376
+ throw new Error('sum expression value literals should be all numbers');
377
+ }
378
+ } else {
379
+ if (!isNaN(toDateNumber(first.evaluate()))) {
380
+ if (!restAreAllReferences && !valuesAreAllDurations) {
381
+ throw new Error('sum expression value literals should be all date durations');
382
+ }
383
+ } else {
384
+ throw new Error(
385
+ // eslint-disable-next-line max-len
386
+ 'sum expression value literals should be all numbers or starting with an iso date string followed by date durations');
387
+ }
388
+ }
389
+ }
390
+ };
391
+
392
+ const mutateDateWithDuration = (dateNumber, duration, operator) => {
393
+ const {
394
+ amount,
395
+ unit
396
+ } = duration;
397
+ const date = new Date(dateNumber);
398
+ let targetYear = date.getUTCFullYear();
399
+ let targetMonthIndex = date.getUTCMonth();
400
+ const day = date.getUTCDate();
401
+ let targetDay = day;
402
+ if (unit === 'd') {
403
+ const targetDate = new Date(targetYear, targetMonthIndex, targetDay);
404
+ targetDate.setDate(operator === 'sum' ? targetDate.getDate() + amount : targetDate.getDate() - amount);
405
+ targetYear = targetDate.getFullYear();
406
+ targetMonthIndex = targetDate.getMonth();
407
+ targetDay = targetDate.getDate();
408
+ } else if (unit === 'm' || unit === 'y') {
409
+ if (unit === 'm') {
410
+ targetMonthIndex = operator === 'sum' ? targetMonthIndex + amount : targetMonthIndex - amount;
411
+ } else if (unit === 'y') {
412
+ targetYear = operator === 'sum' ? targetYear + amount : targetYear - amount;
413
+ }
414
+ const lastDayOfTargetMonth = new Date(targetYear, targetMonthIndex + 1, 0).getDate();
415
+ targetDay = Math.min(day, lastDayOfTargetMonth);
416
+ }
417
+ return new Date(Date.UTC(targetYear, targetMonthIndex, targetDay)).getTime();
418
+ };
419
+
283
420
  // Operator key
284
421
  const OPERATOR$j = Symbol('SUBTRACT');
285
422
  const subtractWithExpectedDecimals = operateWithExpectedDecimals$1('subtract');
@@ -301,9 +438,15 @@ class Subtract extends Arithmetic {
301
438
  if (operands.length < 2) {
302
439
  throw new Error('subtract expression requires at least 2 operands');
303
440
  }
441
+ dateArithmeticTypeCheck(...operands);
304
442
  super('-', OPERATOR$j, operands);
305
443
  }
306
444
  operate(results) {
445
+ const dateCalculationResults = this.getDateCalculationResults(results);
446
+ if (dateCalculationResults) {
447
+ const [dateNumber, ...durations] = dateCalculationResults;
448
+ return formatDateNumber(durations.reduce((mutated, duration) => mutateDateWithDuration(mutated, duration, 'subtract'), dateNumber));
449
+ }
307
450
  const presentResults = this.getResultValues(results);
308
451
  if (presentResults === false) {
309
452
  return false;
@@ -333,9 +476,15 @@ class Sum extends Arithmetic {
333
476
  if (operands.length < 2) {
334
477
  throw new Error('sum expression requires at least 2 operands');
335
478
  }
479
+ dateArithmeticTypeCheck(...operands);
336
480
  super('+', OPERATOR$i, operands);
337
481
  }
338
482
  operate(results) {
483
+ const dateCalculationResults = this.getDateCalculationResults(results);
484
+ if (dateCalculationResults) {
485
+ const [dateNumber, ...durations] = dateCalculationResults;
486
+ return formatDateNumber(durations.reduce((mutated, duration) => mutateDateWithDuration(mutated, duration, 'sum'), dateNumber));
487
+ }
339
488
  const presentResults = this.getResultValues(results);
340
489
  if (presentResults === false) {
341
490
  return false;
@@ -374,6 +523,17 @@ class Operand {
374
523
  * @return {string}
375
524
  */
376
525
  function printValue(value) {
526
+ const dateDuration = toDateDuration(value);
527
+ if (dateDuration) {
528
+ switch (dateDuration.unit) {
529
+ case 'd':
530
+ return `"${dateDuration.amount} ${dateDuration.amount > 1 ? 'days' : 'day'}"`;
531
+ case 'm':
532
+ return `"${dateDuration.amount} ${dateDuration.amount > 1 ? 'months' : 'month'}"`;
533
+ case 'y':
534
+ return `"${dateDuration.amount} ${dateDuration.amount > 1 ? 'years' : 'year'}"`;
535
+ }
536
+ }
377
537
  if (isString(value)) {
378
538
  return `"${value}"`;
379
539
  }
@@ -428,47 +588,6 @@ class Value extends Operand {
428
588
  }
429
589
  }
430
590
 
431
- /**
432
- * Convert a value to number if possible, otherwise return undefined
433
- * @param value value to be converted to number
434
- */
435
- const toNumber = value => {
436
- const isValueNumber = isNumber(value);
437
- if (isValueNumber) {
438
- return value;
439
- } else if (isString(value)) {
440
- if (value.match(/^\d+\.\d+$/)) {
441
- return parseFloat(value);
442
- } else if (value.match(/^0$|^[1-9]\d*$/)) {
443
- return parseInt(value);
444
- }
445
- }
446
- return undefined;
447
- };
448
-
449
- /**
450
- * Convert a value to string if possible, otherwise return undefined
451
- * @param value value to be converted to string
452
- */
453
- const toString = value => {
454
- if (isNumber(value)) {
455
- return `${value}`;
456
- } else if (isString(value)) {
457
- return value;
458
- }
459
- return undefined;
460
- };
461
- /**
462
- * Convert a value to number if it's type is string, otherwise return NaN
463
- * @param value value to be converted to number
464
- */
465
- const toDateNumber = value => {
466
- if (isString(value)) {
467
- return Date.parse(value);
468
- }
469
- return NaN;
470
- };
471
-
472
591
  const keyWithArrayIndexRegex$1 = /^(?<currentKey>[^[\]]+?)(?<indexes>(?:\[\d+])+)?$/;
473
592
  const arrayIndexRegex$1 = /\[(\d+)]/g;
474
593
  function parseBacktickWrappedKey$1(key) {
@@ -2722,6 +2841,10 @@ function relationalCompare$1(left, right, op) {
2722
2841
  }
2723
2842
  return false;
2724
2843
  }
2844
+ function dateArithmeticReduce$1(values, op) {
2845
+ const [date, ...durations] = values;
2846
+ return formatDateNumber(durations.reduce((mutated, duration) => mutateDateWithDuration(mutated, toDateDuration(duration), op === OP_SUM ? 'sum' : 'subtract'), toDateNumber(date)));
2847
+ }
2725
2848
  function arithmeticReduce$1(values, op) {
2726
2849
  if (op === OP_SUM) {
2727
2850
  return values.reduce(addDecimals$1);
@@ -3144,22 +3267,37 @@ function interpret(compiled, ctx) {
3144
3267
  stack$1[++stackTop$1] = false;
3145
3268
  break;
3146
3269
  }
3270
+ const isDateArithmetic = !isNaN(toDateNumber(a));
3147
3271
  if (!isNumber(a) || !isNumber(b)) {
3148
- throw new Error(`arithmetic operand is not a number: ${!isNumber(a) ? a : b}`);
3272
+ if (op === OP_SUM || op === OP_SUBTRACT) {
3273
+ if (isDateArithmetic) {
3274
+ const duration = toDateDuration(b);
3275
+ if (!duration) {
3276
+ throw new Error(`arithmetic operand is not a date duration: ${b}`);
3277
+ }
3278
+ } else {
3279
+ throw new Error(`arithmetic operand is not a number: ${!isNumber(a) ? a : b}`);
3280
+ }
3281
+ } else {
3282
+ throw new Error(`arithmetic operand is not a number: ${!isNumber(a) ? a : b}`);
3283
+ }
3149
3284
  }
3150
3285
  if (op === OP_SUM) {
3151
- stack$1[++stackTop$1] = addDecimals$1(a, b);
3286
+ stack$1[++stackTop$1] = !isDateArithmetic && isNumber(a) && isNumber(b) ? addDecimals$1(a, b) : formatDateNumber(mutateDateWithDuration(toDateNumber(a), toDateDuration(b), 'sum'));
3152
3287
  } else if (op === OP_SUBTRACT) {
3153
- stack$1[++stackTop$1] = subtractDecimals$1(a, b);
3154
- } else if (op === OP_MULTIPLY) {
3155
- stack$1[++stackTop$1] = multiplyDecimals$1(a, b);
3156
- } else {
3157
- stack$1[++stackTop$1] = divideDecimals$1(a, b);
3288
+ stack$1[++stackTop$1] = !isDateArithmetic && isNumber(a) && isNumber(b) ? subtractDecimals$1(a, b) : formatDateNumber(mutateDateWithDuration(toDateNumber(a), toDateDuration(b), 'subtract'));
3289
+ } else if (isNumber(a) && isNumber(b)) {
3290
+ if (op === OP_MULTIPLY) {
3291
+ stack$1[++stackTop$1] = multiplyDecimals$1(a, b);
3292
+ } else {
3293
+ stack$1[++stackTop$1] = divideDecimals$1(a, b);
3294
+ }
3158
3295
  }
3159
3296
  break;
3160
3297
  }
3161
3298
  const values = new Array(n);
3162
3299
  let hasNull = false;
3300
+ const isDateArithmetic = !isNaN(toDateNumber(stack$1[stackTop$1 - n + 1])) && (op === OP_SUM || op === OP_SUBTRACT);
3163
3301
  for (let j = n - 1; j >= 0; j--) {
3164
3302
  const v = stack$1[stackTop$1--];
3165
3303
  if (v === null || v === undefined) {
@@ -3167,11 +3305,20 @@ function interpret(compiled, ctx) {
3167
3305
  break;
3168
3306
  }
3169
3307
  if (!isNumber(v)) {
3170
- throw new Error(`arithmetic operand is not a number: ${v}`);
3308
+ if (isDateArithmetic) {
3309
+ const duration = toDateDuration(v);
3310
+ if (!duration && j > 0) {
3311
+ throw new Error(`arithmetic operand is not a date duration: ${v}`);
3312
+ }
3313
+ } else {
3314
+ throw new Error(`arithmetic operand is not a number: ${v}`);
3315
+ }
3316
+ }
3317
+ if (isNumber(v) || isString(v)) {
3318
+ values[j] = v;
3171
3319
  }
3172
- values[j] = v;
3173
3320
  }
3174
- stack$1[++stackTop$1] = hasNull ? false : arithmeticReduce$1(values, op);
3321
+ stack$1[++stackTop$1] = hasNull ? false : isDateArithmetic && (op === OP_SUM || op === OP_SUBTRACT) && values.every(v => isString(v)) ? dateArithmeticReduce$1(values, op) : values.every(v => isNumber(v)) ? arithmeticReduce$1(values, op) : false;
3175
3322
  break;
3176
3323
  }
3177
3324
 
@@ -3547,6 +3694,10 @@ function arithmeticReduce(values, op) {
3547
3694
  }
3548
3695
  return values.reduce(divideDecimals);
3549
3696
  }
3697
+ function dateArithmeticReduce(values, op) {
3698
+ const [date, ...durations] = values;
3699
+ return formatDateNumber(durations.reduce((mutated, duration) => mutateDateWithDuration(mutated, toDateDuration(duration), op === OP_SUM ? 'sum' : 'subtract'), toDateNumber(date)));
3700
+ }
3550
3701
  function interpretSimplify(compiled, ctx, strictKeys, optionalKeys) {
3551
3702
  const {
3552
3703
  bytecode,
@@ -4197,31 +4348,45 @@ function interpretSimplify(compiled, ctx, strictKeys, optionalKeys) {
4197
4348
  stack[++stackTop] = false;
4198
4349
  break;
4199
4350
  }
4351
+ const isDateArithmetic = !isNaN(toDateNumber(aVal));
4200
4352
  if (!isNumber(aVal) || !isNumber(bVal)) {
4201
- throw new Error(`arithmetic operand is not a number: ${!isNumber(aVal) ? aVal : bVal}`);
4353
+ if (op === OP_SUM || op === OP_SUBTRACT) {
4354
+ if (isDateArithmetic) {
4355
+ const duration = toDateDuration(bVal);
4356
+ if (!duration) {
4357
+ throw new Error(`arithmetic operand is not a date duration: ${bVal}`);
4358
+ }
4359
+ } else {
4360
+ throw new Error(`arithmetic operand is not a number: ${!isNumber(aVal) ? aVal : bVal}`);
4361
+ }
4362
+ } else {
4363
+ throw new Error(`arithmetic operand is not a number: ${!isNumber(aVal) ? aVal : bVal}`);
4364
+ }
4202
4365
  }
4203
4366
  // After the throw above, aVal and bVal are narrowed to number
4204
4367
  if (op === OP_SUM) {
4205
- stack[++stackTop] = addDecimals(aVal, bVal);
4368
+ stack[++stackTop] = !isDateArithmetic && isNumber(aVal) && isNumber(bVal) ? addDecimals(aVal, bVal) : formatDateNumber(mutateDateWithDuration(toDateNumber(aVal), toDateDuration(bVal), 'sum'));
4206
4369
  } else if (op === OP_SUBTRACT) {
4207
- stack[++stackTop] = subtractDecimals(aVal, bVal);
4208
- } else if (op === OP_MULTIPLY) {
4209
- stack[++stackTop] = multiplyDecimals(aVal, bVal);
4210
- } else {
4211
- // Division — match OOP isInfinite guard: use a marker so
4212
- // comparisons can decide whether to preserve the expression.
4213
- const result = divideDecimals(aVal, bVal);
4214
- if (isUnusableResult(result)) {
4215
- // Push marker with original operands for reconstruction
4216
- const marker = {
4217
- _r: 4,
4218
- _val: result,
4219
- left: aVal,
4220
- right: bVal
4221
- };
4222
- stack[++stackTop] = marker;
4370
+ stack[++stackTop] = !isDateArithmetic && isNumber(aVal) && isNumber(bVal) ? subtractDecimals(aVal, bVal) : formatDateNumber(mutateDateWithDuration(toDateNumber(aVal), toDateDuration(bVal), 'subtract'));
4371
+ } else if (isNumber(aVal) && isNumber(bVal)) {
4372
+ if (op === OP_MULTIPLY) {
4373
+ stack[++stackTop] = multiplyDecimals(aVal, bVal);
4223
4374
  } else {
4224
- stack[++stackTop] = result;
4375
+ // Division — match OOP isInfinite guard: use a marker so
4376
+ // comparisons can decide whether to preserve the expression.
4377
+ const result = divideDecimals(aVal, bVal);
4378
+ if (isUnusableResult(result)) {
4379
+ // Push marker with original operands for reconstruction
4380
+ const marker = {
4381
+ _r: 4,
4382
+ _val: result,
4383
+ left: aVal,
4384
+ right: bVal
4385
+ };
4386
+ stack[++stackTop] = marker;
4387
+ } else {
4388
+ stack[++stackTop] = result;
4389
+ }
4225
4390
  }
4226
4391
  }
4227
4392
  break;
@@ -4229,6 +4394,8 @@ function interpretSimplify(compiled, ctx, strictKeys, optionalKeys) {
4229
4394
  // N-operand path
4230
4395
  const values = new Array(n);
4231
4396
  let hasNull = false;
4397
+ const firstOperand = slotVal(stack[stackTop - n + 1]);
4398
+ const isDateArithmetic = typeof firstOperand !== 'object' && !isNaN(toDateNumber(firstOperand)) && (op === OP_SUM || op === OP_SUBTRACT);
4232
4399
  for (let j = n - 1; j >= 0; j--) {
4233
4400
  const v = slotVal(stack[stackTop--]);
4234
4401
  if (v === null || v === undefined) {
@@ -4236,15 +4403,24 @@ function interpretSimplify(compiled, ctx, strictKeys, optionalKeys) {
4236
4403
  break;
4237
4404
  }
4238
4405
  if (!isNumber(v)) {
4239
- throw new Error(`arithmetic operand is not a number: ${v}`);
4406
+ if (isDateArithmetic) {
4407
+ const duration = toDateDuration(v);
4408
+ if (!duration && j > 0) {
4409
+ throw new Error(`arithmetic operand is not a date duration: ${v}`);
4410
+ }
4411
+ } else {
4412
+ throw new Error(`arithmetic operand is not a number: ${v}`);
4413
+ }
4240
4414
  }
4241
4415
  // After the throw above, v is narrowed to number
4242
- values[j] = v;
4416
+ if (isNumber(v) || isString(v)) {
4417
+ values[j] = v;
4418
+ }
4243
4419
  }
4244
- const reduced = hasNull ? false : arithmeticReduce(values, op);
4420
+ const reduced = hasNull ? false : isDateArithmetic && (op === OP_SUM || op === OP_SUBTRACT) && values.every(v => isString(v)) ? dateArithmeticReduce(values, op) : values.every(v => isNumber(v)) ? arithmeticReduce(values, op) : false;
4245
4421
  if (hasNull) {
4246
4422
  stack[++stackTop] = false;
4247
- } else if (op === OP_DIVIDE && reduced !== false && isUnusableResult(reduced)) {
4423
+ } else if (op === OP_DIVIDE && reduced !== false && isNumber(reduced) && isUnusableResult(reduced)) {
4248
4424
  // N-operand division producing Infinity/NaN — create marker
4249
4425
  const result = reduced;
4250
4426
  const marker = {
@@ -4254,7 +4430,7 @@ function interpretSimplify(compiled, ctx, strictKeys, optionalKeys) {
4254
4430
  right: values[1]
4255
4431
  };
4256
4432
  stack[++stackTop] = marker;
4257
- } else if (reduced !== false && isUnusableResult(reduced)) {
4433
+ } else if (reduced !== false && isNumber(reduced) && isUnusableResult(reduced)) {
4258
4434
  // Other arithmetic producing Infinity/NaN — preserve expression
4259
4435
  stack[++stackTop] = makeResidual([opNames[op], ...values]);
4260
4436
  } else {
@@ -117,6 +117,71 @@ function areAllResults(values) {
117
117
  function areAllNumbers(results) {
118
118
  return results.every(isNumber);
119
119
  }
120
+ function isReference(operand) {
121
+ return operand.constructor.name === 'Reference';
122
+ }
123
+ function isValue(operand) {
124
+ return operand.constructor.name === 'Value';
125
+ }
126
+
127
+ /**
128
+ * Convert a value to number if possible, otherwise return undefined
129
+ * @param value value to be converted to number
130
+ */
131
+ const toNumber = value => {
132
+ const isValueNumber = isNumber(value);
133
+ if (isValueNumber) {
134
+ return value;
135
+ } else if (isString(value)) {
136
+ if (value.match(/^\d+\.\d+$/)) {
137
+ return parseFloat(value);
138
+ } else if (value.match(/^0$|^[1-9]\d*$/)) {
139
+ return parseInt(value);
140
+ }
141
+ }
142
+ return undefined;
143
+ };
144
+
145
+ /**
146
+ * Convert a value to string if possible, otherwise return undefined
147
+ * @param value value to be converted to string
148
+ */
149
+ const toString = value => {
150
+ if (isNumber(value)) {
151
+ return `${value}`;
152
+ } else if (isString(value)) {
153
+ return value;
154
+ }
155
+ return undefined;
156
+ };
157
+ /**
158
+ * Convert a value to number if it's type is string, otherwise return NaN
159
+ * @param value value to be converted to number
160
+ */
161
+ const toDateNumber = value => {
162
+ if (isString(value)) {
163
+ return Date.parse(value);
164
+ }
165
+ return NaN;
166
+ };
167
+ const formatDateNumber = dateNumber => new Date(dateNumber).toISOString().split('T')[0];
168
+ const toDateDuration = value => {
169
+ if (!isString(value)) {
170
+ return;
171
+ }
172
+ const isDateDuration = value.match(/^([1-9]\d*)([dmy])$/);
173
+ if (!isDateDuration) {
174
+ return;
175
+ }
176
+ const unit = isDateDuration[2];
177
+ if (unit !== 'd' && unit !== 'm' && unit !== 'y') {
178
+ return;
179
+ }
180
+ return {
181
+ amount: parseInt(isDateDuration[1], 10),
182
+ unit
183
+ };
184
+ };
120
185
 
121
186
  /**
122
187
  * Abstract arithmetic expression
@@ -155,6 +220,21 @@ class Arithmetic {
155
220
  }
156
221
  return presentValues;
157
222
  }
223
+ getDateCalculationResults(results) {
224
+ const [date, ...durations] = results;
225
+ const dateNumber = toDateNumber(date);
226
+ if (isNaN(dateNumber)) {
227
+ return false;
228
+ }
229
+ const parsedDurations = durations.flatMap(d => {
230
+ const parsedDuration = toDateDuration(d);
231
+ return parsedDuration ? [parsedDuration] : [];
232
+ });
233
+ if (parsedDurations.length !== durations.length) {
234
+ return false;
235
+ }
236
+ return [dateNumber, ...parsedDurations];
237
+ }
158
238
 
159
239
  /**
160
240
  * Performs the arithmetic operation on the operands evaluated values.
@@ -276,6 +356,63 @@ class Multiply extends Arithmetic {
276
356
  }
277
357
  }
278
358
 
359
+ const dateArithmeticTypeCheck = (...operands) => {
360
+ const [first, ...rest] = operands;
361
+ const restAreAllReferences = rest.every(op => isReference(op));
362
+ const values = rest.filter(op => isValue(op));
363
+ const valuesAreAllDurations = values.every(op => !!toDateDuration(op.evaluate()));
364
+ const valuesAreAllNumbers = values.every(op => isNumber(op.evaluate()));
365
+ if (isReference(first)) {
366
+ if (!restAreAllReferences && !valuesAreAllDurations && !valuesAreAllNumbers) {
367
+ throw new Error('sum expression value literals should be all numbers or all date durations');
368
+ }
369
+ } else if (isValue(first)) {
370
+ if (isNumber(first.evaluate())) {
371
+ if (!restAreAllReferences && !valuesAreAllNumbers) {
372
+ throw new Error('sum expression value literals should be all numbers');
373
+ }
374
+ } else {
375
+ if (!isNaN(toDateNumber(first.evaluate()))) {
376
+ if (!restAreAllReferences && !valuesAreAllDurations) {
377
+ throw new Error('sum expression value literals should be all date durations');
378
+ }
379
+ } else {
380
+ throw new Error(
381
+ // eslint-disable-next-line max-len
382
+ 'sum expression value literals should be all numbers or starting with an iso date string followed by date durations');
383
+ }
384
+ }
385
+ }
386
+ };
387
+
388
+ const mutateDateWithDuration = (dateNumber, duration, operator) => {
389
+ const {
390
+ amount,
391
+ unit
392
+ } = duration;
393
+ const date = new Date(dateNumber);
394
+ let targetYear = date.getUTCFullYear();
395
+ let targetMonthIndex = date.getUTCMonth();
396
+ const day = date.getUTCDate();
397
+ let targetDay = day;
398
+ if (unit === 'd') {
399
+ const targetDate = new Date(targetYear, targetMonthIndex, targetDay);
400
+ targetDate.setDate(operator === 'sum' ? targetDate.getDate() + amount : targetDate.getDate() - amount);
401
+ targetYear = targetDate.getFullYear();
402
+ targetMonthIndex = targetDate.getMonth();
403
+ targetDay = targetDate.getDate();
404
+ } else if (unit === 'm' || unit === 'y') {
405
+ if (unit === 'm') {
406
+ targetMonthIndex = operator === 'sum' ? targetMonthIndex + amount : targetMonthIndex - amount;
407
+ } else if (unit === 'y') {
408
+ targetYear = operator === 'sum' ? targetYear + amount : targetYear - amount;
409
+ }
410
+ const lastDayOfTargetMonth = new Date(targetYear, targetMonthIndex + 1, 0).getDate();
411
+ targetDay = Math.min(day, lastDayOfTargetMonth);
412
+ }
413
+ return new Date(Date.UTC(targetYear, targetMonthIndex, targetDay)).getTime();
414
+ };
415
+
279
416
  // Operator key
280
417
  const OPERATOR$j = Symbol('SUBTRACT');
281
418
  const subtractWithExpectedDecimals = operateWithExpectedDecimals$1('subtract');
@@ -297,9 +434,15 @@ class Subtract extends Arithmetic {
297
434
  if (operands.length < 2) {
298
435
  throw new Error('subtract expression requires at least 2 operands');
299
436
  }
437
+ dateArithmeticTypeCheck(...operands);
300
438
  super('-', OPERATOR$j, operands);
301
439
  }
302
440
  operate(results) {
441
+ const dateCalculationResults = this.getDateCalculationResults(results);
442
+ if (dateCalculationResults) {
443
+ const [dateNumber, ...durations] = dateCalculationResults;
444
+ return formatDateNumber(durations.reduce((mutated, duration) => mutateDateWithDuration(mutated, duration, 'subtract'), dateNumber));
445
+ }
303
446
  const presentResults = this.getResultValues(results);
304
447
  if (presentResults === false) {
305
448
  return false;
@@ -329,9 +472,15 @@ class Sum extends Arithmetic {
329
472
  if (operands.length < 2) {
330
473
  throw new Error('sum expression requires at least 2 operands');
331
474
  }
475
+ dateArithmeticTypeCheck(...operands);
332
476
  super('+', OPERATOR$i, operands);
333
477
  }
334
478
  operate(results) {
479
+ const dateCalculationResults = this.getDateCalculationResults(results);
480
+ if (dateCalculationResults) {
481
+ const [dateNumber, ...durations] = dateCalculationResults;
482
+ return formatDateNumber(durations.reduce((mutated, duration) => mutateDateWithDuration(mutated, duration, 'sum'), dateNumber));
483
+ }
335
484
  const presentResults = this.getResultValues(results);
336
485
  if (presentResults === false) {
337
486
  return false;
@@ -370,6 +519,17 @@ class Operand {
370
519
  * @return {string}
371
520
  */
372
521
  function printValue(value) {
522
+ const dateDuration = toDateDuration(value);
523
+ if (dateDuration) {
524
+ switch (dateDuration.unit) {
525
+ case 'd':
526
+ return `"${dateDuration.amount} ${dateDuration.amount > 1 ? 'days' : 'day'}"`;
527
+ case 'm':
528
+ return `"${dateDuration.amount} ${dateDuration.amount > 1 ? 'months' : 'month'}"`;
529
+ case 'y':
530
+ return `"${dateDuration.amount} ${dateDuration.amount > 1 ? 'years' : 'year'}"`;
531
+ }
532
+ }
373
533
  if (isString(value)) {
374
534
  return `"${value}"`;
375
535
  }
@@ -424,47 +584,6 @@ class Value extends Operand {
424
584
  }
425
585
  }
426
586
 
427
- /**
428
- * Convert a value to number if possible, otherwise return undefined
429
- * @param value value to be converted to number
430
- */
431
- const toNumber = value => {
432
- const isValueNumber = isNumber(value);
433
- if (isValueNumber) {
434
- return value;
435
- } else if (isString(value)) {
436
- if (value.match(/^\d+\.\d+$/)) {
437
- return parseFloat(value);
438
- } else if (value.match(/^0$|^[1-9]\d*$/)) {
439
- return parseInt(value);
440
- }
441
- }
442
- return undefined;
443
- };
444
-
445
- /**
446
- * Convert a value to string if possible, otherwise return undefined
447
- * @param value value to be converted to string
448
- */
449
- const toString = value => {
450
- if (isNumber(value)) {
451
- return `${value}`;
452
- } else if (isString(value)) {
453
- return value;
454
- }
455
- return undefined;
456
- };
457
- /**
458
- * Convert a value to number if it's type is string, otherwise return NaN
459
- * @param value value to be converted to number
460
- */
461
- const toDateNumber = value => {
462
- if (isString(value)) {
463
- return Date.parse(value);
464
- }
465
- return NaN;
466
- };
467
-
468
587
  const keyWithArrayIndexRegex$1 = /^(?<currentKey>[^[\]]+?)(?<indexes>(?:\[\d+])+)?$/;
469
588
  const arrayIndexRegex$1 = /\[(\d+)]/g;
470
589
  function parseBacktickWrappedKey$1(key) {
@@ -2718,6 +2837,10 @@ function relationalCompare$1(left, right, op) {
2718
2837
  }
2719
2838
  return false;
2720
2839
  }
2840
+ function dateArithmeticReduce$1(values, op) {
2841
+ const [date, ...durations] = values;
2842
+ return formatDateNumber(durations.reduce((mutated, duration) => mutateDateWithDuration(mutated, toDateDuration(duration), op === OP_SUM ? 'sum' : 'subtract'), toDateNumber(date)));
2843
+ }
2721
2844
  function arithmeticReduce$1(values, op) {
2722
2845
  if (op === OP_SUM) {
2723
2846
  return values.reduce(addDecimals$1);
@@ -3140,22 +3263,37 @@ function interpret(compiled, ctx) {
3140
3263
  stack$1[++stackTop$1] = false;
3141
3264
  break;
3142
3265
  }
3266
+ const isDateArithmetic = !isNaN(toDateNumber(a));
3143
3267
  if (!isNumber(a) || !isNumber(b)) {
3144
- throw new Error(`arithmetic operand is not a number: ${!isNumber(a) ? a : b}`);
3268
+ if (op === OP_SUM || op === OP_SUBTRACT) {
3269
+ if (isDateArithmetic) {
3270
+ const duration = toDateDuration(b);
3271
+ if (!duration) {
3272
+ throw new Error(`arithmetic operand is not a date duration: ${b}`);
3273
+ }
3274
+ } else {
3275
+ throw new Error(`arithmetic operand is not a number: ${!isNumber(a) ? a : b}`);
3276
+ }
3277
+ } else {
3278
+ throw new Error(`arithmetic operand is not a number: ${!isNumber(a) ? a : b}`);
3279
+ }
3145
3280
  }
3146
3281
  if (op === OP_SUM) {
3147
- stack$1[++stackTop$1] = addDecimals$1(a, b);
3282
+ stack$1[++stackTop$1] = !isDateArithmetic && isNumber(a) && isNumber(b) ? addDecimals$1(a, b) : formatDateNumber(mutateDateWithDuration(toDateNumber(a), toDateDuration(b), 'sum'));
3148
3283
  } else if (op === OP_SUBTRACT) {
3149
- stack$1[++stackTop$1] = subtractDecimals$1(a, b);
3150
- } else if (op === OP_MULTIPLY) {
3151
- stack$1[++stackTop$1] = multiplyDecimals$1(a, b);
3152
- } else {
3153
- stack$1[++stackTop$1] = divideDecimals$1(a, b);
3284
+ stack$1[++stackTop$1] = !isDateArithmetic && isNumber(a) && isNumber(b) ? subtractDecimals$1(a, b) : formatDateNumber(mutateDateWithDuration(toDateNumber(a), toDateDuration(b), 'subtract'));
3285
+ } else if (isNumber(a) && isNumber(b)) {
3286
+ if (op === OP_MULTIPLY) {
3287
+ stack$1[++stackTop$1] = multiplyDecimals$1(a, b);
3288
+ } else {
3289
+ stack$1[++stackTop$1] = divideDecimals$1(a, b);
3290
+ }
3154
3291
  }
3155
3292
  break;
3156
3293
  }
3157
3294
  const values = new Array(n);
3158
3295
  let hasNull = false;
3296
+ const isDateArithmetic = !isNaN(toDateNumber(stack$1[stackTop$1 - n + 1])) && (op === OP_SUM || op === OP_SUBTRACT);
3159
3297
  for (let j = n - 1; j >= 0; j--) {
3160
3298
  const v = stack$1[stackTop$1--];
3161
3299
  if (v === null || v === undefined) {
@@ -3163,11 +3301,20 @@ function interpret(compiled, ctx) {
3163
3301
  break;
3164
3302
  }
3165
3303
  if (!isNumber(v)) {
3166
- throw new Error(`arithmetic operand is not a number: ${v}`);
3304
+ if (isDateArithmetic) {
3305
+ const duration = toDateDuration(v);
3306
+ if (!duration && j > 0) {
3307
+ throw new Error(`arithmetic operand is not a date duration: ${v}`);
3308
+ }
3309
+ } else {
3310
+ throw new Error(`arithmetic operand is not a number: ${v}`);
3311
+ }
3312
+ }
3313
+ if (isNumber(v) || isString(v)) {
3314
+ values[j] = v;
3167
3315
  }
3168
- values[j] = v;
3169
3316
  }
3170
- stack$1[++stackTop$1] = hasNull ? false : arithmeticReduce$1(values, op);
3317
+ stack$1[++stackTop$1] = hasNull ? false : isDateArithmetic && (op === OP_SUM || op === OP_SUBTRACT) && values.every(v => isString(v)) ? dateArithmeticReduce$1(values, op) : values.every(v => isNumber(v)) ? arithmeticReduce$1(values, op) : false;
3171
3318
  break;
3172
3319
  }
3173
3320
 
@@ -3543,6 +3690,10 @@ function arithmeticReduce(values, op) {
3543
3690
  }
3544
3691
  return values.reduce(divideDecimals);
3545
3692
  }
3693
+ function dateArithmeticReduce(values, op) {
3694
+ const [date, ...durations] = values;
3695
+ return formatDateNumber(durations.reduce((mutated, duration) => mutateDateWithDuration(mutated, toDateDuration(duration), op === OP_SUM ? 'sum' : 'subtract'), toDateNumber(date)));
3696
+ }
3546
3697
  function interpretSimplify(compiled, ctx, strictKeys, optionalKeys) {
3547
3698
  const {
3548
3699
  bytecode,
@@ -4193,31 +4344,45 @@ function interpretSimplify(compiled, ctx, strictKeys, optionalKeys) {
4193
4344
  stack[++stackTop] = false;
4194
4345
  break;
4195
4346
  }
4347
+ const isDateArithmetic = !isNaN(toDateNumber(aVal));
4196
4348
  if (!isNumber(aVal) || !isNumber(bVal)) {
4197
- throw new Error(`arithmetic operand is not a number: ${!isNumber(aVal) ? aVal : bVal}`);
4349
+ if (op === OP_SUM || op === OP_SUBTRACT) {
4350
+ if (isDateArithmetic) {
4351
+ const duration = toDateDuration(bVal);
4352
+ if (!duration) {
4353
+ throw new Error(`arithmetic operand is not a date duration: ${bVal}`);
4354
+ }
4355
+ } else {
4356
+ throw new Error(`arithmetic operand is not a number: ${!isNumber(aVal) ? aVal : bVal}`);
4357
+ }
4358
+ } else {
4359
+ throw new Error(`arithmetic operand is not a number: ${!isNumber(aVal) ? aVal : bVal}`);
4360
+ }
4198
4361
  }
4199
4362
  // After the throw above, aVal and bVal are narrowed to number
4200
4363
  if (op === OP_SUM) {
4201
- stack[++stackTop] = addDecimals(aVal, bVal);
4364
+ stack[++stackTop] = !isDateArithmetic && isNumber(aVal) && isNumber(bVal) ? addDecimals(aVal, bVal) : formatDateNumber(mutateDateWithDuration(toDateNumber(aVal), toDateDuration(bVal), 'sum'));
4202
4365
  } else if (op === OP_SUBTRACT) {
4203
- stack[++stackTop] = subtractDecimals(aVal, bVal);
4204
- } else if (op === OP_MULTIPLY) {
4205
- stack[++stackTop] = multiplyDecimals(aVal, bVal);
4206
- } else {
4207
- // Division — match OOP isInfinite guard: use a marker so
4208
- // comparisons can decide whether to preserve the expression.
4209
- const result = divideDecimals(aVal, bVal);
4210
- if (isUnusableResult(result)) {
4211
- // Push marker with original operands for reconstruction
4212
- const marker = {
4213
- _r: 4,
4214
- _val: result,
4215
- left: aVal,
4216
- right: bVal
4217
- };
4218
- stack[++stackTop] = marker;
4366
+ stack[++stackTop] = !isDateArithmetic && isNumber(aVal) && isNumber(bVal) ? subtractDecimals(aVal, bVal) : formatDateNumber(mutateDateWithDuration(toDateNumber(aVal), toDateDuration(bVal), 'subtract'));
4367
+ } else if (isNumber(aVal) && isNumber(bVal)) {
4368
+ if (op === OP_MULTIPLY) {
4369
+ stack[++stackTop] = multiplyDecimals(aVal, bVal);
4219
4370
  } else {
4220
- stack[++stackTop] = result;
4371
+ // Division — match OOP isInfinite guard: use a marker so
4372
+ // comparisons can decide whether to preserve the expression.
4373
+ const result = divideDecimals(aVal, bVal);
4374
+ if (isUnusableResult(result)) {
4375
+ // Push marker with original operands for reconstruction
4376
+ const marker = {
4377
+ _r: 4,
4378
+ _val: result,
4379
+ left: aVal,
4380
+ right: bVal
4381
+ };
4382
+ stack[++stackTop] = marker;
4383
+ } else {
4384
+ stack[++stackTop] = result;
4385
+ }
4221
4386
  }
4222
4387
  }
4223
4388
  break;
@@ -4225,6 +4390,8 @@ function interpretSimplify(compiled, ctx, strictKeys, optionalKeys) {
4225
4390
  // N-operand path
4226
4391
  const values = new Array(n);
4227
4392
  let hasNull = false;
4393
+ const firstOperand = slotVal(stack[stackTop - n + 1]);
4394
+ const isDateArithmetic = typeof firstOperand !== 'object' && !isNaN(toDateNumber(firstOperand)) && (op === OP_SUM || op === OP_SUBTRACT);
4228
4395
  for (let j = n - 1; j >= 0; j--) {
4229
4396
  const v = slotVal(stack[stackTop--]);
4230
4397
  if (v === null || v === undefined) {
@@ -4232,15 +4399,24 @@ function interpretSimplify(compiled, ctx, strictKeys, optionalKeys) {
4232
4399
  break;
4233
4400
  }
4234
4401
  if (!isNumber(v)) {
4235
- throw new Error(`arithmetic operand is not a number: ${v}`);
4402
+ if (isDateArithmetic) {
4403
+ const duration = toDateDuration(v);
4404
+ if (!duration && j > 0) {
4405
+ throw new Error(`arithmetic operand is not a date duration: ${v}`);
4406
+ }
4407
+ } else {
4408
+ throw new Error(`arithmetic operand is not a number: ${v}`);
4409
+ }
4236
4410
  }
4237
4411
  // After the throw above, v is narrowed to number
4238
- values[j] = v;
4412
+ if (isNumber(v) || isString(v)) {
4413
+ values[j] = v;
4414
+ }
4239
4415
  }
4240
- const reduced = hasNull ? false : arithmeticReduce(values, op);
4416
+ const reduced = hasNull ? false : isDateArithmetic && (op === OP_SUM || op === OP_SUBTRACT) && values.every(v => isString(v)) ? dateArithmeticReduce(values, op) : values.every(v => isNumber(v)) ? arithmeticReduce(values, op) : false;
4241
4417
  if (hasNull) {
4242
4418
  stack[++stackTop] = false;
4243
- } else if (op === OP_DIVIDE && reduced !== false && isUnusableResult(reduced)) {
4419
+ } else if (op === OP_DIVIDE && reduced !== false && isNumber(reduced) && isUnusableResult(reduced)) {
4244
4420
  // N-operand division producing Infinity/NaN — create marker
4245
4421
  const result = reduced;
4246
4422
  const marker = {
@@ -4250,7 +4426,7 @@ function interpretSimplify(compiled, ctx, strictKeys, optionalKeys) {
4250
4426
  right: values[1]
4251
4427
  };
4252
4428
  stack[++stackTop] = marker;
4253
- } else if (reduced !== false && isUnusableResult(reduced)) {
4429
+ } else if (reduced !== false && isNumber(reduced) && isUnusableResult(reduced)) {
4254
4430
  // Other arithmetic producing Infinity/NaN — preserve expression
4255
4431
  stack[++stackTop] = makeResidual([opNames[op], ...values]);
4256
4432
  } else {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@briza/illogical",
3
- "version": "2.1.0",
3
+ "version": "2.2.0",
4
4
  "description": "A micro conditional javascript engine used to parse the raw logical and comparison expressions, evaluate the expression in the given data context, and provide access to a text form of the given expressions.",
5
5
  "type": "module",
6
6
  "main": "./lib/illogical.cjs",
@@ -33,26 +33,26 @@
33
33
  "prepublishOnly": "npm run test && npm run build",
34
34
  "check-licenses": "license-checker --summary --excludePrivatePackages --onlyAllow \"MIT;MIT OR X11;Apache-2.0;ISC;BSD-3-Clause;BSD-2-Clause;CC-BY-4.0;Public Domain;BSD;CC-BY-3.0;CC0-1.0;Python-2.0;BlueOak-1.0.0;Unlicense\"",
35
35
  "bench": "npm run build && node --import tsx src/benchmark/evaluate.ts --cases conditions/sample-conditions --out benchmark/results-sample-evaluate-oop.json && node --import tsx src/benchmark/evaluate.ts --cases conditions/sample-conditions --options '{\"evaluator\":\"bytecode\"}' --out benchmark/results-sample-evaluate-bytecode.json && node --import tsx src/benchmark/evaluate.ts --cases conditions/synthetic-conditions --out benchmark/results-synthetic-evaluate-oop.json && node --import tsx src/benchmark/evaluate.ts --cases conditions/synthetic-conditions --options '{\"evaluator\":\"bytecode\"}' --out benchmark/results-synthetic-evaluate-bytecode.json && node --import tsx src/benchmark/simplify.ts --cases conditions/sample-conditions --out benchmark/results-sample-simplify-oop.json && node --import tsx src/benchmark/simplify.ts --cases conditions/sample-conditions --options '{\"evaluator\":\"bytecode\"}' --out benchmark/results-sample-simplify-bytecode.json && node --import tsx src/benchmark/simplify.ts --cases conditions/synthetic-conditions --out benchmark/results-synthetic-simplify-oop.json && node --import tsx src/benchmark/simplify.ts --cases conditions/synthetic-conditions --options '{\"evaluator\":\"bytecode\"}' --out benchmark/results-synthetic-simplify-bytecode.json && node --import tsx src/benchmark/report.ts benchmark/results-sample-evaluate-oop.json benchmark/results-sample-evaluate-bytecode.json --op evaluate --out benchmark/report-sample-evaluate.md && node --import tsx src/benchmark/report.ts benchmark/results-synthetic-evaluate-oop.json benchmark/results-synthetic-evaluate-bytecode.json --op evaluate --out benchmark/report-synthetic-evaluate.md && node --import tsx src/benchmark/report.ts benchmark/results-sample-simplify-oop.json benchmark/results-sample-simplify-bytecode.json --op simplify --out benchmark/report-sample-simplify.md && node --import tsx src/benchmark/report.ts benchmark/results-synthetic-simplify-oop.json benchmark/results-synthetic-simplify-bytecode.json --op simplify --out benchmark/report-synthetic-simplify.md",
36
- "bench:sample:oop:evaluate": "npm run build && node --import tsx src/benchmark/evaluate.ts --cases conditions/sample-conditions --out benchmark/results-sample-evaluate-oop.json",
37
- "bench:sample:oop:simplify": "npm run build && node --import tsx src/benchmark/simplify.ts --cases conditions/sample-conditions --out benchmark/results-sample-simplify-oop.json",
38
- "bench:synthetic:oop:evaluate": "npm run build && node --import tsx src/benchmark/evaluate.ts --cases conditions/synthetic-conditions --out benchmark/results-synthetic-evaluate-oop.json",
39
- "bench:synthetic:oop:simplify": "npm run build && node --import tsx src/benchmark/simplify.ts --cases conditions/synthetic-conditions --out benchmark/results-synthetic-simplify-oop.json",
40
- "bench:sample:bytecode:evaluate": "npm run build && node --import tsx src/benchmark/evaluate.ts --cases conditions/sample-conditions --options '{\"evaluator\":\"bytecode\"}' --out benchmark/results-sample-evaluate-bytecode.json",
41
- "bench:synthetic:bytecode:evaluate": "npm run build && node --import tsx src/benchmark/evaluate.ts --cases conditions/synthetic-conditions --options '{\"evaluator\":\"bytecode\"}' --out benchmark/results-synthetic-evaluate-bytecode.json",
42
- "bench:sample:bytecode:simplify": "npm run build && node --import tsx src/benchmark/simplify.ts --cases conditions/sample-conditions --options '{\"evaluator\":\"bytecode\"}' --out benchmark/results-sample-simplify-bytecode.json",
43
- "bench:synthetic:bytecode:simplify": "npm run build && node --import tsx src/benchmark/simplify.ts --cases conditions/synthetic-conditions --options '{\"evaluator\":\"bytecode\"}' --out benchmark/results-synthetic-simplify-bytecode.json",
44
- "bench:sample:compare:evaluate": "node --import tsx src/benchmark/compare.ts benchmark/results-sample-evaluate-oop.json benchmark/results-sample-evaluate-bytecode.json",
45
- "bench:synthetic:compare:evaluate": "node --import tsx src/benchmark/compare.ts benchmark/results-synthetic-evaluate-oop.json benchmark/results-synthetic-evaluate-bytecode.json",
46
- "bench:sample:compare:simplify": "node --import tsx src/benchmark/compare.ts benchmark/results-sample-simplify-oop.json benchmark/results-sample-simplify-bytecode.json",
47
- "bench:synthetic:compare:simplify": "node --import tsx src/benchmark/compare.ts benchmark/results-synthetic-simplify-oop.json benchmark/results-synthetic-simplify-bytecode.json",
48
- "bench:sample:report:evaluate": "node --import tsx src/benchmark/report.ts benchmark/results-sample-evaluate-oop.json benchmark/results-sample-evaluate-bytecode.json --op evaluate --out benchmark/report-sample-evaluate.md",
49
- "bench:synthetic:report:evaluate": "node --import tsx src/benchmark/report.ts benchmark/results-synthetic-evaluate-oop.json benchmark/results-synthetic-evaluate-bytecode.json --op evaluate --out benchmark/report-synthetic-evaluate.md",
50
- "bench:sample:report:full:evaluate": "node --import tsx src/benchmark/report.ts benchmark/results-sample-evaluate-oop.json benchmark/results-sample-evaluate-bytecode.json --op evaluate --full --out benchmark/report-sample-evaluate-full.md",
51
- "bench:synthetic:report:full:evaluate": "node --import tsx src/benchmark/report.ts benchmark/results-synthetic-evaluate-oop.json benchmark/results-synthetic-evaluate-bytecode.json --op evaluate --full --out benchmark/report-synthetic-evaluate-full.md",
52
- "bench:sample:report:simplify": "node --import tsx src/benchmark/report.ts benchmark/results-sample-simplify-oop.json benchmark/results-sample-simplify-bytecode.json --op simplify --out benchmark/report-sample-simplify.md",
53
- "bench:synthetic:report:simplify": "node --import tsx src/benchmark/report.ts benchmark/results-synthetic-simplify-oop.json benchmark/results-synthetic-simplify-bytecode.json --op simplify --out benchmark/report-synthetic-simplify.md",
54
- "bench:sample:report:full:simplify": "node --import tsx src/benchmark/report.ts benchmark/results-sample-simplify-oop.json benchmark/results-sample-simplify-bytecode.json --op simplify --full --out benchmark/report-sample-simplify-full.md",
55
- "bench:synthetic:report:full:simplify": "node --import tsx src/benchmark/report.ts benchmark/results-synthetic-simplify-oop.json benchmark/results-synthetic-simplify-bytecode.json --op simplify --full --out benchmark/report-synthetic-simplify-full.md",
36
+ "bench:sample:oop:evaluate": "npm run build && node --import tsx src/benchmark/evaluate.ts --cases conditions/sample-conditions --out benchmark/results-sample-evaluate-oop.json",
37
+ "bench:sample:oop:simplify": "npm run build && node --import tsx src/benchmark/simplify.ts --cases conditions/sample-conditions --out benchmark/results-sample-simplify-oop.json",
38
+ "bench:synthetic:oop:evaluate": "npm run build && node --import tsx src/benchmark/evaluate.ts --cases conditions/synthetic-conditions --out benchmark/results-synthetic-evaluate-oop.json",
39
+ "bench:synthetic:oop:simplify": "npm run build && node --import tsx src/benchmark/simplify.ts --cases conditions/synthetic-conditions --out benchmark/results-synthetic-simplify-oop.json",
40
+ "bench:sample:bytecode:evaluate": "npm run build && node --import tsx src/benchmark/evaluate.ts --cases conditions/sample-conditions --options '{\"evaluator\":\"bytecode\"}' --out benchmark/results-sample-evaluate-bytecode.json",
41
+ "bench:sample:bytecode:simplify": "npm run build && node --import tsx src/benchmark/simplify.ts --cases conditions/sample-conditions --options '{\"evaluator\":\"bytecode\"}' --out benchmark/results-sample-simplify-bytecode.json",
42
+ "bench:synthetic:bytecode:evaluate": "npm run build && node --import tsx src/benchmark/evaluate.ts --cases conditions/synthetic-conditions --options '{\"evaluator\":\"bytecode\"}' --out benchmark/results-synthetic-evaluate-bytecode.json",
43
+ "bench:synthetic:bytecode:simplify": "npm run build && node --import tsx src/benchmark/simplify.ts --cases conditions/synthetic-conditions --options '{\"evaluator\":\"bytecode\"}' --out benchmark/results-synthetic-simplify-bytecode.json",
44
+ "bench:sample:compare:evaluate": "node --import tsx src/benchmark/compare.ts benchmark/results-sample-evaluate-oop.json benchmark/results-sample-evaluate-bytecode.json",
45
+ "bench:synthetic:compare:evaluate": "node --import tsx src/benchmark/compare.ts benchmark/results-synthetic-evaluate-oop.json benchmark/results-synthetic-evaluate-bytecode.json",
46
+ "bench:sample:compare:simplify": "node --import tsx src/benchmark/compare.ts benchmark/results-sample-simplify-oop.json benchmark/results-sample-simplify-bytecode.json",
47
+ "bench:synthetic:compare:simplify": "node --import tsx src/benchmark/compare.ts benchmark/results-synthetic-simplify-oop.json benchmark/results-synthetic-simplify-bytecode.json",
48
+ "bench:sample:report:evaluate": "node --import tsx src/benchmark/report.ts benchmark/results-sample-evaluate-oop.json benchmark/results-sample-evaluate-bytecode.json --op evaluate --out benchmark/report-sample-evaluate.md",
49
+ "bench:synthetic:report:evaluate": "node --import tsx src/benchmark/report.ts benchmark/results-synthetic-evaluate-oop.json benchmark/results-synthetic-evaluate-bytecode.json --op evaluate --out benchmark/report-synthetic-evaluate.md",
50
+ "bench:sample:report:full:evaluate": "node --import tsx src/benchmark/report.ts benchmark/results-sample-evaluate-oop.json benchmark/results-sample-evaluate-bytecode.json --op evaluate --full --out benchmark/report-sample-evaluate-full.md",
51
+ "bench:synthetic:report:full:evaluate": "node --import tsx src/benchmark/report.ts benchmark/results-synthetic-evaluate-oop.json benchmark/results-synthetic-evaluate-bytecode.json --op evaluate --full --out benchmark/report-synthetic-evaluate-full.md",
52
+ "bench:sample:report:simplify": "node --import tsx src/benchmark/report.ts benchmark/results-sample-simplify-oop.json benchmark/results-sample-simplify-bytecode.json --op simplify --out benchmark/report-sample-simplify.md",
53
+ "bench:synthetic:report:simplify": "node --import tsx src/benchmark/report.ts benchmark/results-synthetic-simplify-oop.json benchmark/results-synthetic-simplify-bytecode.json --op simplify --out benchmark/report-synthetic-simplify.md",
54
+ "bench:sample:report:full:simplify": "node --import tsx src/benchmark/report.ts benchmark/results-sample-simplify-oop.json benchmark/results-sample-simplify-bytecode.json --op simplify --full --out benchmark/report-sample-simplify-full.md",
55
+ "bench:synthetic:report:full:simplify": "node --import tsx src/benchmark/report.ts benchmark/results-synthetic-simplify-oop.json benchmark/results-synthetic-simplify-bytecode.json --op simplify --full --out benchmark/report-synthetic-simplify-full.md",
56
56
  "get-bytecode": "node --import tsx src/bytecode/get-bytecode.ts",
57
57
  "debug-bytecode": "node --import tsx src/tools/debugger.ts"
58
58
  },
@@ -1,3 +1,6 @@
1
+ import { Operand } from '../operand/index.js';
2
+ import { Reference } from '../operand/reference.js';
3
+ import { Value } from '../operand/value.js';
1
4
  import { Evaluable, Result } from './evaluable.js';
2
5
  /**
3
6
  * Is number predicate.
@@ -45,3 +48,5 @@ export declare function areAllResults(values: (Result | Evaluable)[]): values is
45
48
  export declare function areAllNumbers(results: Result[]): results is number[];
46
49
  export declare function isUndefined(value: unknown): value is undefined;
47
50
  export declare function isNull(value: unknown): value is null;
51
+ export declare function isReference(operand: Operand): operand is Reference;
52
+ export declare function isValue(operand: Operand): operand is Value;
@@ -14,3 +14,9 @@ export declare const toString: (value: Result) => string | undefined;
14
14
  * @param value value to be converted to number
15
15
  */
16
16
  export declare const toDateNumber: (value: Result) => number;
17
+ export declare const formatDateNumber: (dateNumber: number) => string;
18
+ export type DateDuration = {
19
+ amount: number;
20
+ unit: 'd' | 'm' | 'y';
21
+ };
22
+ export declare const toDateDuration: (value: Result) => DateDuration | undefined;
@@ -0,0 +1,2 @@
1
+ import { Operand } from '../../operand/index.js';
2
+ export declare const dateArithmeticTypeCheck: (...operands: Operand[]) => void;
@@ -1,4 +1,5 @@
1
1
  import { Context, Evaluable, EvaluableType, Result, SimplifyArgs } from '../../common/evaluable.js';
2
+ import { DateDuration } from '../../common/util.js';
2
3
  import { Operand } from '../../operand/index.js';
3
4
  import { ExpressionInput } from '../../parser/index.js';
4
5
  import { Options } from '../../parser/options.js';
@@ -27,6 +28,7 @@ export declare abstract class Arithmetic implements Evaluable {
27
28
  * array of numbers
28
29
  */
29
30
  protected getResultValues(results: Result[]): number[] | false;
31
+ protected getDateCalculationResults(results: Result[]): [number, ...DateDuration[]] | false;
30
32
  /**
31
33
  * Performs the arithmetic operation on the operands evaluated values.
32
34
  * @param {Result[]} results Operand result values.
@@ -0,0 +1,2 @@
1
+ import { DateDuration } from '../../common/util.js';
2
+ export declare const mutateDateWithDuration: (dateNumber: number, duration: DateDuration, operator: "sum" | "subtract") => number;