fable 3.1.64 → 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.
- package/docs/services/expression-parser-functions/README.md +9 -0
- package/docs/services/expression-parser-functions/beziercurvefit.md +57 -0
- package/docs/services/expression-parser-functions/bezierpoint.md +55 -0
- package/docs/services/expression-parser-functions/intercept.md +59 -0
- package/docs/services/expression-parser-functions/setvalue.md +55 -0
- package/docs/services/expression-parser-functions/slope.md +51 -0
- package/docs/services/expression-parser-functions/ternary.md +180 -0
- package/docs/services/expression-parser.md +72 -0
- package/example_applications/mathematical_playground/Math-Solver-Harness.js +108 -0
- package/example_applications/mathematical_playground/TestSuite-AcidTest.json +178 -0
- package/example_applications/mathematical_playground/TestSuite-Identities.json +147 -0
- package/example_applications/mathematical_playground/TestSuite-Precision.json +127 -0
- package/example_applications/mathematical_playground/TestSuite-Spreadsheet.json +198 -0
- package/package.json +1 -1
- package/source/services/Fable-Service-ExpressionParser/Fable-Service-ExpressionParser-ExpressionTokenizer-DirectiveMutation.js +143 -0
- package/source/services/Fable-Service-ExpressionParser/Fable-Service-ExpressionParser-ExpressionTokenizer.js +1 -1
- package/source/services/Fable-Service-ExpressionParser/Fable-Service-ExpressionParser-FunctionMap.json +5 -4
- package/source/services/Fable-Service-ExpressionParser/Fable-Service-ExpressionParser-TokenMap.json +65 -1
- package/source/services/Fable-Service-ExpressionParser.js +1 -0
- package/source/services/Fable-Service-Logic.js +33 -0
- package/source/services/Fable-Service-Math.js +78 -0
- package/test/ExpressionParser_tests.js +290 -0
|
@@ -339,10 +339,153 @@ class ExpressionTokenizerDirectiveMutation extends libExpressionParserOperationB
|
|
|
339
339
|
return tmpNewDirectiveDescription;
|
|
340
340
|
}
|
|
341
341
|
|
|
342
|
+
rewriteTernaryOperators(pResultObject)
|
|
343
|
+
{
|
|
344
|
+
let tmpTokens = pResultObject.RawTokens;
|
|
345
|
+
|
|
346
|
+
// Scan right-to-left so nested ternaries (innermost first) are handled correctly.
|
|
347
|
+
for (let i = tmpTokens.length - 1; i >= 0; i--)
|
|
348
|
+
{
|
|
349
|
+
if (tmpTokens[i] !== '?')
|
|
350
|
+
{
|
|
351
|
+
continue;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
// Found a ? token at index i.
|
|
355
|
+
// Walk left to find the start of the condition expression.
|
|
356
|
+
// The condition starts after the previous comma, open parenthesis, assignment operator, or beginning of tokens.
|
|
357
|
+
let tmpConditionStart = 0;
|
|
358
|
+
let tmpParenDepth = 0;
|
|
359
|
+
for (let j = i - 1; j >= 0; j--)
|
|
360
|
+
{
|
|
361
|
+
if (tmpTokens[j] === ')')
|
|
362
|
+
{
|
|
363
|
+
tmpParenDepth++;
|
|
364
|
+
}
|
|
365
|
+
else if (tmpTokens[j] === '(')
|
|
366
|
+
{
|
|
367
|
+
if (tmpParenDepth > 0)
|
|
368
|
+
{
|
|
369
|
+
tmpParenDepth--;
|
|
370
|
+
}
|
|
371
|
+
else
|
|
372
|
+
{
|
|
373
|
+
// We hit an unmatched open paren — condition starts after it
|
|
374
|
+
tmpConditionStart = j + 1;
|
|
375
|
+
break;
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
else if (tmpParenDepth === 0)
|
|
379
|
+
{
|
|
380
|
+
let tmpTokenDescriptor = this.ExpressionParser.tokenMap[tmpTokens[j]];
|
|
381
|
+
if (tmpTokenDescriptor && (tmpTokenDescriptor.Type === 'Assignment' || tmpTokens[j] === ','))
|
|
382
|
+
{
|
|
383
|
+
tmpConditionStart = j + 1;
|
|
384
|
+
break;
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
// Walk right from ? to find the matching :: at the same parenthesis depth.
|
|
390
|
+
let tmpSeparatorIndex = -1;
|
|
391
|
+
tmpParenDepth = 0;
|
|
392
|
+
for (let j = i + 1; j < tmpTokens.length; j++)
|
|
393
|
+
{
|
|
394
|
+
if (tmpTokens[j] === '(')
|
|
395
|
+
{
|
|
396
|
+
tmpParenDepth++;
|
|
397
|
+
}
|
|
398
|
+
else if (tmpTokens[j] === ')')
|
|
399
|
+
{
|
|
400
|
+
if (tmpParenDepth > 0)
|
|
401
|
+
{
|
|
402
|
+
tmpParenDepth--;
|
|
403
|
+
}
|
|
404
|
+
else
|
|
405
|
+
{
|
|
406
|
+
// Hit closing paren at our depth — no :: found in this group
|
|
407
|
+
break;
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
else if (tmpTokens[j] === '::' && tmpParenDepth === 0)
|
|
411
|
+
{
|
|
412
|
+
tmpSeparatorIndex = j;
|
|
413
|
+
break;
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
if (tmpSeparatorIndex === -1)
|
|
418
|
+
{
|
|
419
|
+
pResultObject.ExpressionParserLog.push(`ExpressionParser.rewriteTernaryOperators found a ? at token index ${i} with no matching :: separator.`);
|
|
420
|
+
this.log.warn(pResultObject.ExpressionParserLog[pResultObject.ExpressionParserLog.length - 1]);
|
|
421
|
+
continue;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
// Walk right from :: to find the end of the false branch.
|
|
425
|
+
// The false branch ends at the next comma, close parenthesis, or end of tokens at the same depth.
|
|
426
|
+
let tmpFalseBranchEnd = tmpTokens.length;
|
|
427
|
+
tmpParenDepth = 0;
|
|
428
|
+
for (let j = tmpSeparatorIndex + 1; j < tmpTokens.length; j++)
|
|
429
|
+
{
|
|
430
|
+
if (tmpTokens[j] === '(')
|
|
431
|
+
{
|
|
432
|
+
tmpParenDepth++;
|
|
433
|
+
}
|
|
434
|
+
else if (tmpTokens[j] === ')')
|
|
435
|
+
{
|
|
436
|
+
if (tmpParenDepth > 0)
|
|
437
|
+
{
|
|
438
|
+
tmpParenDepth--;
|
|
439
|
+
}
|
|
440
|
+
else
|
|
441
|
+
{
|
|
442
|
+
// Unmatched close paren — false branch ends here
|
|
443
|
+
tmpFalseBranchEnd = j;
|
|
444
|
+
break;
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
else if (tmpParenDepth === 0)
|
|
448
|
+
{
|
|
449
|
+
if (tmpTokens[j] === ',')
|
|
450
|
+
{
|
|
451
|
+
tmpFalseBranchEnd = j;
|
|
452
|
+
break;
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
// Extract the three segments
|
|
458
|
+
let tmpConditionTokens = tmpTokens.slice(tmpConditionStart, i);
|
|
459
|
+
let tmpTrueBranchTokens = tmpTokens.slice(i + 1, tmpSeparatorIndex);
|
|
460
|
+
let tmpFalseBranchTokens = tmpTokens.slice(tmpSeparatorIndex + 1, tmpFalseBranchEnd);
|
|
461
|
+
|
|
462
|
+
// Build the replacement: ternary((condition), trueBranch, falseBranch)
|
|
463
|
+
// The condition is wrapped in its own parentheses to ensure correct
|
|
464
|
+
// precedence grouping when arithmetic appears on both sides of a comparison.
|
|
465
|
+
let tmpReplacementTokens = ['ternary', '(', '('];
|
|
466
|
+
tmpReplacementTokens = tmpReplacementTokens.concat(tmpConditionTokens);
|
|
467
|
+
tmpReplacementTokens.push(')');
|
|
468
|
+
tmpReplacementTokens.push(',');
|
|
469
|
+
tmpReplacementTokens = tmpReplacementTokens.concat(tmpTrueBranchTokens);
|
|
470
|
+
tmpReplacementTokens.push(',');
|
|
471
|
+
tmpReplacementTokens = tmpReplacementTokens.concat(tmpFalseBranchTokens);
|
|
472
|
+
tmpReplacementTokens.push(')');
|
|
473
|
+
|
|
474
|
+
// Splice the replacement into the token array
|
|
475
|
+
tmpTokens.splice(tmpConditionStart, tmpFalseBranchEnd - tmpConditionStart, ...tmpReplacementTokens);
|
|
476
|
+
|
|
477
|
+
// Adjust i to re-scan from the start of what we just inserted (in case of nested ternaries in the condition)
|
|
478
|
+
i = tmpConditionStart;
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
|
|
342
482
|
parseDirectives(pResultObject)
|
|
343
483
|
{
|
|
344
484
|
let tmpResults = (typeof(pResultObject) === 'object') ? pResultObject : { ExpressionParserLog: [] };
|
|
345
485
|
|
|
486
|
+
// Rewrite ternary operators before directive parsing
|
|
487
|
+
this.rewriteTernaryOperators(tmpResults);
|
|
488
|
+
|
|
346
489
|
tmpResults.SolverDirectives = this.defaultDirective;
|
|
347
490
|
tmpResults.SolverDirectiveTokens = [];
|
|
348
491
|
|
|
@@ -215,7 +215,7 @@ class ExpressionTokenizer extends libExpressionParserOperationBase
|
|
|
215
215
|
{
|
|
216
216
|
if (tmpCurrentToken.length > 0)
|
|
217
217
|
{
|
|
218
|
-
tmpResults.RawTokens.push(
|
|
218
|
+
tmpResults.RawTokens.push(tmpCurrentToken);
|
|
219
219
|
}
|
|
220
220
|
tmpCurrentToken = '';
|
|
221
221
|
tmpCurrentTokenType = false;
|
|
@@ -285,6 +285,11 @@
|
|
|
285
285
|
"Address": "fable.Logic.when"
|
|
286
286
|
},
|
|
287
287
|
|
|
288
|
+
"ternary": {
|
|
289
|
+
"Name": "numeric-aware ternary selection (used by ? :: operator desugaring)",
|
|
290
|
+
"Address": "fable.Logic.ternary"
|
|
291
|
+
},
|
|
292
|
+
|
|
288
293
|
"entryinset": {
|
|
289
294
|
"Name": "Entry in Set",
|
|
290
295
|
"Address": "fable.Math.entryInSet"
|
|
@@ -465,10 +470,6 @@
|
|
|
465
470
|
"Address": "fable.Math.interceptPrecise"
|
|
466
471
|
},
|
|
467
472
|
|
|
468
|
-
"polynomialregression": {
|
|
469
|
-
"Name": "Perform an nth degree Polynomial Regression on a Set of X and Y Values",
|
|
470
|
-
"Address": "fable.Math.polynomialRegression"
|
|
471
|
-
},
|
|
472
473
|
"leastsquares": {
|
|
473
474
|
"Name": "Perform a Least Squares Regression on a Set of Independent Variable Vectors and a Dependent Variable Vector",
|
|
474
475
|
"Address": "fable.Math.leastSquares"
|
package/source/services/Fable-Service-ExpressionParser/Fable-Service-ExpressionParser-TokenMap.json
CHANGED
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
"Name": "Set Concatenate",
|
|
47
47
|
"Token": ",",
|
|
48
48
|
"Function": "fable.Math.setConcatenate",
|
|
49
|
-
"Precedence":
|
|
49
|
+
"Precedence": 6,
|
|
50
50
|
"Type": "Operator"
|
|
51
51
|
},
|
|
52
52
|
|
|
@@ -99,5 +99,69 @@
|
|
|
99
99
|
"Function": "fable.Math.subtractPrecise",
|
|
100
100
|
"Precedence": 4,
|
|
101
101
|
"Type": "Operator"
|
|
102
|
+
},
|
|
103
|
+
|
|
104
|
+
">":
|
|
105
|
+
{
|
|
106
|
+
"Name": "Greater Than",
|
|
107
|
+
"Token": ">",
|
|
108
|
+
"Function": "fable.Math.greaterThanOperator",
|
|
109
|
+
"Precedence": 5,
|
|
110
|
+
"Type": "Operator"
|
|
111
|
+
},
|
|
112
|
+
">=":
|
|
113
|
+
{
|
|
114
|
+
"Name": "Greater Than or Equal",
|
|
115
|
+
"Token": ">=",
|
|
116
|
+
"Function": "fable.Math.greaterThanOrEqualOperator",
|
|
117
|
+
"Precedence": 5,
|
|
118
|
+
"Type": "Operator"
|
|
119
|
+
},
|
|
120
|
+
"<":
|
|
121
|
+
{
|
|
122
|
+
"Name": "Less Than",
|
|
123
|
+
"Token": "<",
|
|
124
|
+
"Function": "fable.Math.lessThanOperator",
|
|
125
|
+
"Precedence": 5,
|
|
126
|
+
"Type": "Operator"
|
|
127
|
+
},
|
|
128
|
+
"<=":
|
|
129
|
+
{
|
|
130
|
+
"Name": "Less Than or Equal",
|
|
131
|
+
"Token": "<=",
|
|
132
|
+
"Function": "fable.Math.lessThanOrEqualOperator",
|
|
133
|
+
"Precedence": 5,
|
|
134
|
+
"Type": "Operator"
|
|
135
|
+
},
|
|
136
|
+
"==":
|
|
137
|
+
{
|
|
138
|
+
"Name": "Equal",
|
|
139
|
+
"Token": "==",
|
|
140
|
+
"Function": "fable.Math.equalOperator",
|
|
141
|
+
"Precedence": 5,
|
|
142
|
+
"Type": "Operator"
|
|
143
|
+
},
|
|
144
|
+
"!=":
|
|
145
|
+
{
|
|
146
|
+
"Name": "Not Equal",
|
|
147
|
+
"Token": "!=",
|
|
148
|
+
"Function": "fable.Math.notEqualOperator",
|
|
149
|
+
"Precedence": 5,
|
|
150
|
+
"Type": "Operator"
|
|
151
|
+
},
|
|
152
|
+
|
|
153
|
+
"?":
|
|
154
|
+
{
|
|
155
|
+
"Name": "Ternary Condition",
|
|
156
|
+
"Token": "?",
|
|
157
|
+
"Precedence": 0,
|
|
158
|
+
"Type": "Ternary"
|
|
159
|
+
},
|
|
160
|
+
"::":
|
|
161
|
+
{
|
|
162
|
+
"Name": "Ternary Separator",
|
|
163
|
+
"Token": "::",
|
|
164
|
+
"Precedence": 0,
|
|
165
|
+
"Type": "Ternary"
|
|
102
166
|
}
|
|
103
167
|
}
|
|
@@ -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);
|
|
@@ -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
|
*
|
|
@@ -2155,3 +2155,293 @@ suite
|
|
|
2155
2155
|
);
|
|
2156
2156
|
}
|
|
2157
2157
|
);
|
|
2158
|
+
|
|
2159
|
+
suite
|
|
2160
|
+
(
|
|
2161
|
+
'Comparison Operators',
|
|
2162
|
+
function ()
|
|
2163
|
+
{
|
|
2164
|
+
suite
|
|
2165
|
+
(
|
|
2166
|
+
'Greater Than',
|
|
2167
|
+
function ()
|
|
2168
|
+
{
|
|
2169
|
+
test
|
|
2170
|
+
(
|
|
2171
|
+
'Basic greater than comparisons.',
|
|
2172
|
+
(fDone) =>
|
|
2173
|
+
{
|
|
2174
|
+
let _Parser = getExpressionParser();
|
|
2175
|
+
Expect(_Parser.solve('Result = 5 > 3')).to.equal('1');
|
|
2176
|
+
Expect(_Parser.solve('Result = 3 > 5')).to.equal('0');
|
|
2177
|
+
Expect(_Parser.solve('Result = 5 > 5')).to.equal('0');
|
|
2178
|
+
return fDone();
|
|
2179
|
+
}
|
|
2180
|
+
);
|
|
2181
|
+
}
|
|
2182
|
+
);
|
|
2183
|
+
suite
|
|
2184
|
+
(
|
|
2185
|
+
'Greater Than or Equal',
|
|
2186
|
+
function ()
|
|
2187
|
+
{
|
|
2188
|
+
test
|
|
2189
|
+
(
|
|
2190
|
+
'Basic greater than or equal comparisons.',
|
|
2191
|
+
(fDone) =>
|
|
2192
|
+
{
|
|
2193
|
+
let _Parser = getExpressionParser();
|
|
2194
|
+
Expect(_Parser.solve('Result = 5 >= 3')).to.equal('1');
|
|
2195
|
+
Expect(_Parser.solve('Result = 3 >= 5')).to.equal('0');
|
|
2196
|
+
Expect(_Parser.solve('Result = 5 >= 5')).to.equal('1');
|
|
2197
|
+
return fDone();
|
|
2198
|
+
}
|
|
2199
|
+
);
|
|
2200
|
+
}
|
|
2201
|
+
);
|
|
2202
|
+
suite
|
|
2203
|
+
(
|
|
2204
|
+
'Less Than',
|
|
2205
|
+
function ()
|
|
2206
|
+
{
|
|
2207
|
+
test
|
|
2208
|
+
(
|
|
2209
|
+
'Basic less than comparisons.',
|
|
2210
|
+
(fDone) =>
|
|
2211
|
+
{
|
|
2212
|
+
let _Parser = getExpressionParser();
|
|
2213
|
+
Expect(_Parser.solve('Result = 3 < 5')).to.equal('1');
|
|
2214
|
+
Expect(_Parser.solve('Result = 5 < 3')).to.equal('0');
|
|
2215
|
+
Expect(_Parser.solve('Result = 5 < 5')).to.equal('0');
|
|
2216
|
+
return fDone();
|
|
2217
|
+
}
|
|
2218
|
+
);
|
|
2219
|
+
}
|
|
2220
|
+
);
|
|
2221
|
+
suite
|
|
2222
|
+
(
|
|
2223
|
+
'Less Than or Equal',
|
|
2224
|
+
function ()
|
|
2225
|
+
{
|
|
2226
|
+
test
|
|
2227
|
+
(
|
|
2228
|
+
'Basic less than or equal comparisons.',
|
|
2229
|
+
(fDone) =>
|
|
2230
|
+
{
|
|
2231
|
+
let _Parser = getExpressionParser();
|
|
2232
|
+
Expect(_Parser.solve('Result = 3 <= 5')).to.equal('1');
|
|
2233
|
+
Expect(_Parser.solve('Result = 5 <= 3')).to.equal('0');
|
|
2234
|
+
Expect(_Parser.solve('Result = 5 <= 5')).to.equal('1');
|
|
2235
|
+
return fDone();
|
|
2236
|
+
}
|
|
2237
|
+
);
|
|
2238
|
+
}
|
|
2239
|
+
);
|
|
2240
|
+
suite
|
|
2241
|
+
(
|
|
2242
|
+
'Equal',
|
|
2243
|
+
function ()
|
|
2244
|
+
{
|
|
2245
|
+
test
|
|
2246
|
+
(
|
|
2247
|
+
'Basic equality comparisons.',
|
|
2248
|
+
(fDone) =>
|
|
2249
|
+
{
|
|
2250
|
+
let _Parser = getExpressionParser();
|
|
2251
|
+
Expect(_Parser.solve('Result = 5 == 5')).to.equal('1');
|
|
2252
|
+
Expect(_Parser.solve('Result = 5 == 3')).to.equal('0');
|
|
2253
|
+
return fDone();
|
|
2254
|
+
}
|
|
2255
|
+
);
|
|
2256
|
+
}
|
|
2257
|
+
);
|
|
2258
|
+
suite
|
|
2259
|
+
(
|
|
2260
|
+
'Not Equal',
|
|
2261
|
+
function ()
|
|
2262
|
+
{
|
|
2263
|
+
test
|
|
2264
|
+
(
|
|
2265
|
+
'Basic inequality comparisons.',
|
|
2266
|
+
(fDone) =>
|
|
2267
|
+
{
|
|
2268
|
+
let _Parser = getExpressionParser();
|
|
2269
|
+
Expect(_Parser.solve('Result = 5 != 3')).to.equal('1');
|
|
2270
|
+
Expect(_Parser.solve('Result = 5 != 5')).to.equal('0');
|
|
2271
|
+
return fDone();
|
|
2272
|
+
}
|
|
2273
|
+
);
|
|
2274
|
+
}
|
|
2275
|
+
);
|
|
2276
|
+
suite
|
|
2277
|
+
(
|
|
2278
|
+
'Comparison with Arithmetic',
|
|
2279
|
+
function ()
|
|
2280
|
+
{
|
|
2281
|
+
test
|
|
2282
|
+
(
|
|
2283
|
+
'Arithmetic is evaluated before comparison.',
|
|
2284
|
+
(fDone) =>
|
|
2285
|
+
{
|
|
2286
|
+
let _Parser = getExpressionParser();
|
|
2287
|
+
// 2 + 3 = 5, 1 + 2 = 3, so 5 > 3 = 1
|
|
2288
|
+
Expect(_Parser.solve('Result = 2 + 3 > 1 + 2')).to.equal('1');
|
|
2289
|
+
// 1 + 1 = 2, 3 + 1 = 4, so 2 > 4 = 0
|
|
2290
|
+
Expect(_Parser.solve('Result = 1 + 1 > 3 + 1')).to.equal('0');
|
|
2291
|
+
// 10 - 5 = 5, 2 * 3 = 6, so 5 < 6 = 1
|
|
2292
|
+
Expect(_Parser.solve('Result = 10 - 5 < 2 * 3')).to.equal('1');
|
|
2293
|
+
return fDone();
|
|
2294
|
+
}
|
|
2295
|
+
);
|
|
2296
|
+
test
|
|
2297
|
+
(
|
|
2298
|
+
'Comparison with variables.',
|
|
2299
|
+
(fDone) =>
|
|
2300
|
+
{
|
|
2301
|
+
let _Parser = getExpressionParser();
|
|
2302
|
+
let tmpData = { Height: 10, Width: 5 };
|
|
2303
|
+
let tmpDestination = {};
|
|
2304
|
+
_Parser.solve('Result = Height > Width', tmpData, {}, false, tmpDestination);
|
|
2305
|
+
Expect(tmpDestination.Result).to.equal('1');
|
|
2306
|
+
_Parser.solve('Result = Width > Height', tmpData, {}, false, tmpDestination);
|
|
2307
|
+
Expect(tmpDestination.Result).to.equal('0');
|
|
2308
|
+
_Parser.solve('Result = Height == Width', tmpData, {}, false, tmpDestination);
|
|
2309
|
+
Expect(tmpDestination.Result).to.equal('0');
|
|
2310
|
+
_Parser.solve('Result = Height != Width', tmpData, {}, false, tmpDestination);
|
|
2311
|
+
Expect(tmpDestination.Result).to.equal('1');
|
|
2312
|
+
return fDone();
|
|
2313
|
+
}
|
|
2314
|
+
);
|
|
2315
|
+
}
|
|
2316
|
+
);
|
|
2317
|
+
}
|
|
2318
|
+
);
|
|
2319
|
+
|
|
2320
|
+
suite
|
|
2321
|
+
(
|
|
2322
|
+
'Ternary Operator',
|
|
2323
|
+
function ()
|
|
2324
|
+
{
|
|
2325
|
+
suite
|
|
2326
|
+
(
|
|
2327
|
+
'Basic Ternary',
|
|
2328
|
+
function ()
|
|
2329
|
+
{
|
|
2330
|
+
test
|
|
2331
|
+
(
|
|
2332
|
+
'True branch is selected when condition is truthy.',
|
|
2333
|
+
(fDone) =>
|
|
2334
|
+
{
|
|
2335
|
+
let _Parser = getExpressionParser();
|
|
2336
|
+
Expect(_Parser.solve('Result = 5 > 3 ? 100 :: 200')).to.equal('100');
|
|
2337
|
+
return fDone();
|
|
2338
|
+
}
|
|
2339
|
+
);
|
|
2340
|
+
test
|
|
2341
|
+
(
|
|
2342
|
+
'False branch is selected when condition is falsy.',
|
|
2343
|
+
(fDone) =>
|
|
2344
|
+
{
|
|
2345
|
+
let _Parser = getExpressionParser();
|
|
2346
|
+
Expect(_Parser.solve('Result = 3 > 5 ? 100 :: 200')).to.equal('200');
|
|
2347
|
+
return fDone();
|
|
2348
|
+
}
|
|
2349
|
+
);
|
|
2350
|
+
test
|
|
2351
|
+
(
|
|
2352
|
+
'Ternary with equality comparison.',
|
|
2353
|
+
(fDone) =>
|
|
2354
|
+
{
|
|
2355
|
+
let _Parser = getExpressionParser();
|
|
2356
|
+
Expect(_Parser.solve('Result = 5 == 5 ? 10 :: 20')).to.equal('10');
|
|
2357
|
+
Expect(_Parser.solve('Result = 5 == 3 ? 10 :: 20')).to.equal('20');
|
|
2358
|
+
return fDone();
|
|
2359
|
+
}
|
|
2360
|
+
);
|
|
2361
|
+
}
|
|
2362
|
+
);
|
|
2363
|
+
suite
|
|
2364
|
+
(
|
|
2365
|
+
'Ternary with Variables',
|
|
2366
|
+
function ()
|
|
2367
|
+
{
|
|
2368
|
+
test
|
|
2369
|
+
(
|
|
2370
|
+
'Ternary selects based on variable comparison.',
|
|
2371
|
+
(fDone) =>
|
|
2372
|
+
{
|
|
2373
|
+
let _Parser = getExpressionParser();
|
|
2374
|
+
let tmpData = { Height: 10, Width: 5, A: 42, B: 99 };
|
|
2375
|
+
let tmpDestination = {};
|
|
2376
|
+
_Parser.solve('SomeValue = Height > Width ? A :: B', tmpData, {}, false, tmpDestination);
|
|
2377
|
+
Expect(tmpDestination.SomeValue).to.equal('42');
|
|
2378
|
+
_Parser.solve('SomeValue = Width > Height ? A :: B', tmpData, {}, false, tmpDestination);
|
|
2379
|
+
Expect(tmpDestination.SomeValue).to.equal('99');
|
|
2380
|
+
return fDone();
|
|
2381
|
+
}
|
|
2382
|
+
);
|
|
2383
|
+
}
|
|
2384
|
+
);
|
|
2385
|
+
suite
|
|
2386
|
+
(
|
|
2387
|
+
'Ternary with Arithmetic in Branches',
|
|
2388
|
+
function ()
|
|
2389
|
+
{
|
|
2390
|
+
test
|
|
2391
|
+
(
|
|
2392
|
+
'Arithmetic expressions work in ternary branches.',
|
|
2393
|
+
(fDone) =>
|
|
2394
|
+
{
|
|
2395
|
+
let _Parser = getExpressionParser();
|
|
2396
|
+
let tmpData = { A: 10, B: 5 };
|
|
2397
|
+
let tmpDestination = {};
|
|
2398
|
+
// A > B is true, so A + 1 = 11
|
|
2399
|
+
_Parser.solve('Result = A > B ? A + 1 :: B + 1', tmpData, {}, false, tmpDestination);
|
|
2400
|
+
Expect(tmpDestination.Result).to.equal('11');
|
|
2401
|
+
// B > A is false, so B + 1 = 6
|
|
2402
|
+
_Parser.solve('Result = B > A ? A + 1 :: B + 1', tmpData, {}, false, tmpDestination);
|
|
2403
|
+
Expect(tmpDestination.Result).to.equal('6');
|
|
2404
|
+
return fDone();
|
|
2405
|
+
}
|
|
2406
|
+
);
|
|
2407
|
+
test
|
|
2408
|
+
(
|
|
2409
|
+
'Arithmetic expressions work in the ternary condition.',
|
|
2410
|
+
(fDone) =>
|
|
2411
|
+
{
|
|
2412
|
+
let _Parser = getExpressionParser();
|
|
2413
|
+
// 2 + 3 = 5, 1 + 2 = 3, so 5 > 3 = true, result is 100
|
|
2414
|
+
Expect(_Parser.solve('Result = 2 + 3 > 1 + 2 ? 100 :: 200')).to.equal('100');
|
|
2415
|
+
// 1 + 1 = 2, 3 + 1 = 4, so 2 > 4 = false, result is 200
|
|
2416
|
+
Expect(_Parser.solve('Result = 1 + 1 > 3 + 1 ? 100 :: 200')).to.equal('200');
|
|
2417
|
+
return fDone();
|
|
2418
|
+
}
|
|
2419
|
+
);
|
|
2420
|
+
}
|
|
2421
|
+
);
|
|
2422
|
+
suite
|
|
2423
|
+
(
|
|
2424
|
+
'Nested Ternary',
|
|
2425
|
+
function ()
|
|
2426
|
+
{
|
|
2427
|
+
test
|
|
2428
|
+
(
|
|
2429
|
+
'Nested ternary with parenthesized inner ternary.',
|
|
2430
|
+
(fDone) =>
|
|
2431
|
+
{
|
|
2432
|
+
let _Parser = getExpressionParser();
|
|
2433
|
+
let tmpData = { A: 5, B: 3 };
|
|
2434
|
+
let tmpDestination = {};
|
|
2435
|
+
// A > 0 is true, then B > 0 is true, so result is 1
|
|
2436
|
+
_Parser.solve('Result = A > 0 ? (B > 0 ? 1 :: 2) :: 3', tmpData, {}, false, tmpDestination);
|
|
2437
|
+
Expect(tmpDestination.Result).to.equal('1');
|
|
2438
|
+
// A > 0 is true, then B > 10 is false, so result is 2
|
|
2439
|
+
_Parser.solve('Result = A > 0 ? (B > 10 ? 1 :: 2) :: 3', tmpData, {}, false, tmpDestination);
|
|
2440
|
+
Expect(tmpDestination.Result).to.equal('2');
|
|
2441
|
+
return fDone();
|
|
2442
|
+
}
|
|
2443
|
+
);
|
|
2444
|
+
}
|
|
2445
|
+
);
|
|
2446
|
+
}
|
|
2447
|
+
);
|