fable 3.1.63 → 3.1.65

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (23) hide show
  1. package/docs/services/expression-parser-functions/README.md +9 -0
  2. package/docs/services/expression-parser-functions/beziercurvefit.md +57 -0
  3. package/docs/services/expression-parser-functions/bezierpoint.md +55 -0
  4. package/docs/services/expression-parser-functions/intercept.md +59 -0
  5. package/docs/services/expression-parser-functions/setvalue.md +55 -0
  6. package/docs/services/expression-parser-functions/slope.md +51 -0
  7. package/docs/services/expression-parser-functions/ternary.md +180 -0
  8. package/docs/services/expression-parser.md +131 -0
  9. package/example_applications/mathematical_playground/AppData.json +36 -1
  10. package/example_applications/mathematical_playground/Math-Solver-Harness.js +215 -0
  11. package/example_applications/mathematical_playground/TestSuite-AcidTest.json +178 -0
  12. package/example_applications/mathematical_playground/TestSuite-Identities.json +147 -0
  13. package/example_applications/mathematical_playground/TestSuite-Precision.json +127 -0
  14. package/example_applications/mathematical_playground/TestSuite-Spreadsheet.json +198 -0
  15. package/package.json +3 -2
  16. package/source/services/Fable-Service-ExpressionParser/Fable-Service-ExpressionParser-ExpressionTokenizer-DirectiveMutation.js +299 -2
  17. package/source/services/Fable-Service-ExpressionParser/Fable-Service-ExpressionParser-ExpressionTokenizer.js +1 -1
  18. package/source/services/Fable-Service-ExpressionParser/Fable-Service-ExpressionParser-FunctionMap.json +5 -4
  19. package/source/services/Fable-Service-ExpressionParser/Fable-Service-ExpressionParser-TokenMap.json +65 -1
  20. package/source/services/Fable-Service-ExpressionParser.js +144 -0
  21. package/source/services/Fable-Service-Logic.js +33 -0
  22. package/source/services/Fable-Service-Math.js +78 -0
  23. package/test/ExpressionParser_tests.js +1059 -0
@@ -112,6 +112,7 @@ class FableServiceExpressionParser extends libFableServiceBase
112
112
 
113
113
  // Wire each sub service into this instance of the solver.
114
114
  this.Tokenizer.connectExpressionParser(this);
115
+ this.Tokenizer.TokenizerDirectiveMutation.connectExpressionParser(this);
115
116
  this.Linter.connectExpressionParser(this);
116
117
  this.Postfix.connectExpressionParser(this);
117
118
  this.ValueMarshal.connectExpressionParser(this);
@@ -456,6 +457,149 @@ class FableServiceExpressionParser extends libFableServiceBase
456
457
 
457
458
  return tmpValueArray;
458
459
  }
460
+ else if (tmpResultsObject.SolverDirectives.Code == 'MULTIROWMAP')
461
+ {
462
+ // MULTIROWMAP iterates over an array of objects (rows), allowing each expression to reference
463
+ // the current row and any number of previous/future rows via configurable offsets.
464
+ // Optional SERIESSTART and SERIESSTEP control which rows are iterated and in what direction.
465
+ const tmpDirectiveValues = tmpResultsObject.SolverDirectives.Values;
466
+ const tmpDirectiveValueKeys = tmpResultsObject.SolverDirectives.ValueKeys;
467
+ const tmpRowsAddress = tmpResultsObject.SolverDirectives.RowsAddress;
468
+ let tmpValueArray = [];
469
+
470
+ // Resolve the rows array from the data source
471
+ let tmpRows = null;
472
+ if (tmpRowsAddress)
473
+ {
474
+ tmpRows = tmpManifest.getValueByHash(tmpDataSourceObject, tmpRowsAddress);
475
+ }
476
+
477
+ if (!Array.isArray(tmpRows) || tmpRows.length < 1)
478
+ {
479
+ tmpResultsObject.ExpressionParserLog.push(`ExpressionParser.solve detected invalid MULTIROWMAP directive parameters. ROWS FROM address must resolve to a non-empty array.`);
480
+ this.log.warn(tmpResultsObject.ExpressionParserLog[tmpResultsObject.ExpressionParserLog.length-1]);
481
+ return undefined;
482
+ }
483
+
484
+ // Resolve SeriesStart: default to 0 (first row). Negative values count from end.
485
+ let tmpSeriesStart = 0;
486
+ if (tmpResultsObject.SolverDirectives.SeriesStart !== null)
487
+ {
488
+ tmpSeriesStart = parseInt(tmpResultsObject.SolverDirectives.SeriesStart);
489
+ if (isNaN(tmpSeriesStart))
490
+ {
491
+ // Try to resolve as a variable from the data source
492
+ let tmpStartToken = this.fable.ExpressionParser.Postfix.getTokenContainerObject(tmpResultsObject.SolverDirectives.SeriesStart, 'Token.Symbol');
493
+ this.substituteValuesInTokenizedObjects([tmpStartToken], tmpDataSourceObject, tmpResultsObject, tmpManifest);
494
+ if (tmpStartToken.Resolved)
495
+ {
496
+ tmpSeriesStart = parseInt(tmpStartToken.Value);
497
+ }
498
+ if (isNaN(tmpSeriesStart))
499
+ {
500
+ tmpSeriesStart = 0;
501
+ }
502
+ }
503
+ }
504
+ // Handle negative start (count from end)
505
+ if (tmpSeriesStart < 0)
506
+ {
507
+ tmpSeriesStart = tmpRows.length + tmpSeriesStart;
508
+ }
509
+ // Clamp to valid range
510
+ tmpSeriesStart = Math.max(0, Math.min(tmpSeriesStart, tmpRows.length - 1));
511
+
512
+ // Resolve SeriesStep: default to 1
513
+ let tmpSeriesStep = 1;
514
+ if (tmpResultsObject.SolverDirectives.SeriesStep !== null)
515
+ {
516
+ tmpSeriesStep = parseInt(tmpResultsObject.SolverDirectives.SeriesStep);
517
+ if (isNaN(tmpSeriesStep))
518
+ {
519
+ // Try to resolve as a variable from the data source
520
+ let tmpStepToken = this.fable.ExpressionParser.Postfix.getTokenContainerObject(tmpResultsObject.SolverDirectives.SeriesStep, 'Token.Symbol');
521
+ this.substituteValuesInTokenizedObjects([tmpStepToken], tmpDataSourceObject, tmpResultsObject, tmpManifest);
522
+ if (tmpStepToken.Resolved)
523
+ {
524
+ tmpSeriesStep = parseInt(tmpStepToken.Value);
525
+ }
526
+ if (isNaN(tmpSeriesStep))
527
+ {
528
+ tmpSeriesStep = 1;
529
+ }
530
+ }
531
+ }
532
+ if (tmpSeriesStep === 0)
533
+ {
534
+ tmpResultsObject.ExpressionParserLog.push(`ExpressionParser.solve detected invalid MULTIROWMAP directive parameters. SERIESSTEP cannot be zero.`);
535
+ this.log.warn(tmpResultsObject.ExpressionParserLog[tmpResultsObject.ExpressionParserLog.length-1]);
536
+ return undefined;
537
+ }
538
+
539
+ let tmpStepCounter = 0;
540
+ for (let i = tmpSeriesStart; (tmpSeriesStep > 0) ? (i < tmpRows.length) : (i >= 0); i += tmpSeriesStep)
541
+ {
542
+ // Build the data source for this iteration with the mapped variables
543
+ let tmpRowStepDataSourceObject = Object.assign({}, tmpDataSourceObject);
544
+ tmpRowStepDataSourceObject.stepIndex = tmpStepCounter;
545
+ tmpRowStepDataSourceObject.rowIndex = i;
546
+
547
+ for (let j = 0; j < tmpDirectiveValueKeys.length; j++)
548
+ {
549
+ const tmpVariableKey = tmpDirectiveValueKeys[j];
550
+ const tmpVariableDescription = tmpDirectiveValues[tmpVariableKey];
551
+ const tmpTargetRowIndex = i + tmpVariableDescription.RowOffset;
552
+
553
+ // Check if the target row index is within bounds
554
+ if (tmpTargetRowIndex < 0 || tmpTargetRowIndex >= tmpRows.length)
555
+ {
556
+ // Out of bounds -- use the default value
557
+ tmpRowStepDataSourceObject[tmpVariableKey] = tmpVariableDescription.Default;
558
+ }
559
+ else
560
+ {
561
+ const tmpTargetRow = tmpRows[tmpTargetRowIndex];
562
+ if (tmpVariableDescription.Property === null)
563
+ {
564
+ // No property specified, use the whole row
565
+ tmpRowStepDataSourceObject[tmpVariableKey] = tmpTargetRow;
566
+ }
567
+ else
568
+ {
569
+ // Look up the property on the target row
570
+ let tmpValue = tmpManifest.getValueByHash(tmpTargetRow, tmpVariableDescription.Property);
571
+ if (tmpValue == null)
572
+ {
573
+ tmpValue = tmpVariableDescription.Default;
574
+ }
575
+ tmpRowStepDataSourceObject[tmpVariableKey] = tmpValue;
576
+ }
577
+ }
578
+ }
579
+
580
+ let tmpMutatedValues = this.substituteValuesInTokenizedObjects(tmpResultsObject.PostfixTokenObjects, tmpRowStepDataSourceObject, tmpResultsObject, tmpManifest);
581
+
582
+ tmpValueArray.push( this.solvePostfixedExpression( tmpResultsObject.PostfixSolveList, tmpDataDestinationObject, tmpResultsObject, tmpManifest) );
583
+
584
+ for (let j = 0; j < tmpMutatedValues.length; j++)
585
+ {
586
+ tmpMutatedValues[j].Resolved = false;
587
+ }
588
+
589
+ tmpStepCounter++;
590
+ }
591
+
592
+ // Do the assignment
593
+ let tmpAssignmentManifestHash = tmpResultsObject.PostfixedAssignmentAddress;
594
+ if ((tmpResultsObject.OriginalRawTokens[1] === '=') && (typeof(tmpResultsObject.OriginalRawTokens[0]) === 'string') && (tmpResultsObject.OriginalRawTokens[0].length > 0))
595
+ {
596
+ tmpAssignmentManifestHash = tmpResultsObject.OriginalRawTokens[0];
597
+ }
598
+
599
+ tmpManifest.setValueByHash(tmpDataDestinationObject, tmpAssignmentManifestHash, tmpValueArray);
600
+
601
+ return tmpValueArray;
602
+ }
459
603
  else if (tmpResultsObject.SolverDirectives.Code == 'MONTECARLO')
460
604
  {
461
605
  const [ tmpSampleCount ] = this._prepareDirectiveParameters([
@@ -126,6 +126,39 @@ class FableServiceLogic extends libFableServiceBase
126
126
  }
127
127
  return pOnTrue;
128
128
  }
129
+
130
+ /**
131
+ * Numeric-aware ternary selection for the expression parser.
132
+ * Treats 0, "0", "", null, undefined, false, NaN, empty arrays, and empty objects as falsy.
133
+ * Used by the ternary operator desugaring (condition ? trueVal :: falseVal).
134
+ *
135
+ * @param {any} pCondition - The condition to evaluate
136
+ * @param {any} pOnTrue - The value to return if the condition is truthy
137
+ * @param {any} [pOnFalse = ''] - The value to return if the condition is falsy
138
+ * @return {any} - The selected value
139
+ */
140
+ ternary(pCondition, pOnTrue, pOnFalse = '')
141
+ {
142
+ // Standard JS falsy check
143
+ if (!pCondition)
144
+ {
145
+ return pOnFalse;
146
+ }
147
+ // Numeric zero as a string (from comparison operators returning "0")
148
+ if (pCondition === '0')
149
+ {
150
+ return pOnFalse;
151
+ }
152
+ if (Array.isArray(pCondition) && pCondition.length < 1)
153
+ {
154
+ return pOnFalse;
155
+ }
156
+ if (typeof pCondition === 'object' && Object.keys(pCondition).length < 1)
157
+ {
158
+ return pOnFalse;
159
+ }
160
+ return pOnTrue;
161
+ }
129
162
  }
130
163
 
131
164
  module.exports = FableServiceLogic;
@@ -460,6 +460,84 @@ class FableServiceMath extends libFableServiceBase
460
460
  return tmpLeftArbitraryValue.lte(tmpRightValue);
461
461
  }
462
462
 
463
+ /**
464
+ * Comparison operator: returns "1" if left > right, "0" otherwise.
465
+ * For use as an infix operator in the expression parser.
466
+ *
467
+ * @param {number|string} pLeftValue - The left value to compare.
468
+ * @param {number|string} pRightValue - The right value to compare.
469
+ * @returns {string} - "1" if left > right, "0" otherwise.
470
+ */
471
+ greaterThanOperator(pLeftValue, pRightValue)
472
+ {
473
+ return this.gtPrecise(pLeftValue, pRightValue) ? '1' : '0';
474
+ }
475
+
476
+ /**
477
+ * Comparison operator: returns "1" if left >= right, "0" otherwise.
478
+ * For use as an infix operator in the expression parser.
479
+ *
480
+ * @param {number|string} pLeftValue - The left value to compare.
481
+ * @param {number|string} pRightValue - The right value to compare.
482
+ * @returns {string} - "1" if left >= right, "0" otherwise.
483
+ */
484
+ greaterThanOrEqualOperator(pLeftValue, pRightValue)
485
+ {
486
+ return this.gtePrecise(pLeftValue, pRightValue) ? '1' : '0';
487
+ }
488
+
489
+ /**
490
+ * Comparison operator: returns "1" if left < right, "0" otherwise.
491
+ * For use as an infix operator in the expression parser.
492
+ *
493
+ * @param {number|string} pLeftValue - The left value to compare.
494
+ * @param {number|string} pRightValue - The right value to compare.
495
+ * @returns {string} - "1" if left < right, "0" otherwise.
496
+ */
497
+ lessThanOperator(pLeftValue, pRightValue)
498
+ {
499
+ return this.ltPrecise(pLeftValue, pRightValue) ? '1' : '0';
500
+ }
501
+
502
+ /**
503
+ * Comparison operator: returns "1" if left <= right, "0" otherwise.
504
+ * For use as an infix operator in the expression parser.
505
+ *
506
+ * @param {number|string} pLeftValue - The left value to compare.
507
+ * @param {number|string} pRightValue - The right value to compare.
508
+ * @returns {string} - "1" if left <= right, "0" otherwise.
509
+ */
510
+ lessThanOrEqualOperator(pLeftValue, pRightValue)
511
+ {
512
+ return this.ltePrecise(pLeftValue, pRightValue) ? '1' : '0';
513
+ }
514
+
515
+ /**
516
+ * Comparison operator: returns "1" if left == right, "0" otherwise.
517
+ * For use as an infix operator in the expression parser.
518
+ *
519
+ * @param {number|string} pLeftValue - The left value to compare.
520
+ * @param {number|string} pRightValue - The right value to compare.
521
+ * @returns {string} - "1" if left == right, "0" otherwise.
522
+ */
523
+ equalOperator(pLeftValue, pRightValue)
524
+ {
525
+ return this.comparePrecise(pLeftValue, pRightValue) == 0 ? '1' : '0';
526
+ }
527
+
528
+ /**
529
+ * Comparison operator: returns "1" if left != right, "0" otherwise.
530
+ * For use as an infix operator in the expression parser.
531
+ *
532
+ * @param {number|string} pLeftValue - The left value to compare.
533
+ * @param {number|string} pRightValue - The right value to compare.
534
+ * @returns {string} - "1" if left != right, "0" otherwise.
535
+ */
536
+ notEqualOperator(pLeftValue, pRightValue)
537
+ {
538
+ return this.comparePrecise(pLeftValue, pRightValue) != 0 ? '1' : '0';
539
+ }
540
+
463
541
  /**
464
542
  * Converts degrees to radians with arbitrary precision.
465
543
  *