fable 3.1.64 → 3.1.66
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 +3 -3
- 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/source/services/Fable-Service-RestClient.js +105 -0
- package/test/ExpressionParser_tests.js +290 -0
- package/test/RestClientBinaryUpload_test.js +537 -0
|
@@ -84,6 +84,7 @@ Basic math operations with arbitrary precision:
|
|
|
84
84
|
### Value Access
|
|
85
85
|
|
|
86
86
|
- [getvalue](./getvalue.md) - Get value from application state
|
|
87
|
+
- [setvalue](./setvalue.md) - Set value to application state
|
|
87
88
|
- [getvaluearray](./getvaluearray.md) - Get array of values
|
|
88
89
|
- [getvalueobject](./getvalueobject.md) - Get value object
|
|
89
90
|
- [createvalueobjectbyhashes](./createvalueobjectbyhashes.md) - Create object from hashes
|
|
@@ -110,6 +111,7 @@ Basic math operations with arbitrary precision:
|
|
|
110
111
|
|
|
111
112
|
- [if](./if.md) - Conditional comparison
|
|
112
113
|
- [when](./when.md) - Truthy check
|
|
114
|
+
- [ternary](./ternary.md) - Numeric-aware ternary selection (`? ::` operator)
|
|
113
115
|
|
|
114
116
|
### Date Functions
|
|
115
117
|
|
|
@@ -143,6 +145,8 @@ Basic math operations with arbitrary precision:
|
|
|
143
145
|
|
|
144
146
|
### Regression and Matrix Functions
|
|
145
147
|
|
|
148
|
+
- [slope](./slope.md) - Slope of linear regression line
|
|
149
|
+
- [intercept](./intercept.md) - Y-intercept of linear regression line
|
|
146
150
|
- [polynomialregression](./polynomialregression.md) - Polynomial regression
|
|
147
151
|
- [leastsquares](./leastsquares.md) / [linest](./linest.md) - Least squares regression
|
|
148
152
|
- [predict](./predict.md) - Predict from regression model
|
|
@@ -152,6 +156,11 @@ Basic math operations with arbitrary precision:
|
|
|
152
156
|
- [matrixinverse](./matrixinverse.md) - Inverse matrix
|
|
153
157
|
- [gaussianelimination](./gaussianelimination.md) - Gaussian elimination
|
|
154
158
|
|
|
159
|
+
### Bezier Curves
|
|
160
|
+
|
|
161
|
+
- [bezierpoint](./bezierpoint.md) - Evaluate point on cubic Bezier curve
|
|
162
|
+
- [beziercurvefit](./beziercurvefit.md) - Fit cubic Bezier curve to data points
|
|
163
|
+
|
|
155
164
|
### Other Utilities
|
|
156
165
|
|
|
157
166
|
- [generatearrayofobjectsfromsets](./generatearrayofobjectsfromsets.md) - Generate objects from sets
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# beziercurvefit
|
|
2
|
+
|
|
3
|
+
Fits a cubic Bezier curve to a set of data points.
|
|
4
|
+
|
|
5
|
+
## Syntax
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
beziercurvefit(dataPoints)
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Parameters
|
|
12
|
+
|
|
13
|
+
| Parameter | Type | Description |
|
|
14
|
+
|-----------|------|-------------|
|
|
15
|
+
| `dataPoints` | Array | Array of numeric values to fit the curve to |
|
|
16
|
+
|
|
17
|
+
## Returns
|
|
18
|
+
|
|
19
|
+
Array - The four control points (P0, P1, P2, P3) of the fitted cubic Bezier curve.
|
|
20
|
+
|
|
21
|
+
## Description
|
|
22
|
+
|
|
23
|
+
The `beziercurvefit` function computes the four control points of a cubic Bezier curve that best fits the provided data points. This is useful for creating smooth curve approximations of discrete data.
|
|
24
|
+
|
|
25
|
+
## Examples
|
|
26
|
+
|
|
27
|
+
### Basic Usage
|
|
28
|
+
|
|
29
|
+
```expression
|
|
30
|
+
Curve = beziercurvefit(DataPoints)
|
|
31
|
+
// Returns array of 4 control points defining the fitted Bezier curve
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### With FLATTEN
|
|
35
|
+
|
|
36
|
+
```expression
|
|
37
|
+
Curve = beziercurvefit(FLATTEN(AppData.Measurements[].value))
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### Using Fitted Curve
|
|
41
|
+
|
|
42
|
+
```expression
|
|
43
|
+
// First fit the curve
|
|
44
|
+
ControlPoints = beziercurvefit(Data)
|
|
45
|
+
|
|
46
|
+
// Then evaluate points along the fitted curve
|
|
47
|
+
Point = bezierpoint(ENTRYINSET(ControlPoints, 0), ENTRYINSET(ControlPoints, 1), ENTRYINSET(ControlPoints, 2), ENTRYINSET(ControlPoints, 3), 0.5)
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Related Functions
|
|
51
|
+
|
|
52
|
+
- [bezierpoint](./bezierpoint.md) - Evaluate point on cubic Bezier curve
|
|
53
|
+
- [polynomialregression](./polynomialregression.md) - Polynomial curve fitting
|
|
54
|
+
|
|
55
|
+
## Notes
|
|
56
|
+
|
|
57
|
+
- Uses the Math service's `bezierCurveFit` method internally
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# bezierpoint
|
|
2
|
+
|
|
3
|
+
Evaluates a point on a cubic Bezier curve at a given parameter t.
|
|
4
|
+
|
|
5
|
+
## Syntax
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
bezierpoint(p0, p1, p2, p3, t)
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Parameters
|
|
12
|
+
|
|
13
|
+
| Parameter | Type | Description |
|
|
14
|
+
|-----------|------|-------------|
|
|
15
|
+
| `p0` | Number/String | Start point |
|
|
16
|
+
| `p1` | Number/String | First control point |
|
|
17
|
+
| `p2` | Number/String | Second control point |
|
|
18
|
+
| `p3` | Number/String | End point |
|
|
19
|
+
| `t` | Number/String | Parameter value (0 to 1) |
|
|
20
|
+
|
|
21
|
+
## Returns
|
|
22
|
+
|
|
23
|
+
String - The computed point on the cubic Bezier curve at parameter t.
|
|
24
|
+
|
|
25
|
+
## Description
|
|
26
|
+
|
|
27
|
+
The `bezierpoint` function evaluates the cubic Bezier curve defined by four control points at a given parameter value t. When t=0 the result is the start point, when t=1 the result is the end point, and values in between trace the curve.
|
|
28
|
+
|
|
29
|
+
## Examples
|
|
30
|
+
|
|
31
|
+
### Basic Usage
|
|
32
|
+
|
|
33
|
+
```expression
|
|
34
|
+
Result = bezierpoint(0, 0.25, 0.75, 1, 0.5)
|
|
35
|
+
// Evaluate midpoint of a cubic Bezier curve
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### Endpoint Values
|
|
39
|
+
|
|
40
|
+
```expression
|
|
41
|
+
Start = bezierpoint(10, 20, 80, 100, 0)
|
|
42
|
+
// Result: "10" (t=0 returns start point)
|
|
43
|
+
|
|
44
|
+
End = bezierpoint(10, 20, 80, 100, 1)
|
|
45
|
+
// Result: "100" (t=1 returns end point)
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Related Functions
|
|
49
|
+
|
|
50
|
+
- [beziercurvefit](./beziercurvefit.md) - Fit a cubic Bezier curve to data points
|
|
51
|
+
|
|
52
|
+
## Notes
|
|
53
|
+
|
|
54
|
+
- Uses the Math service's `bezierPoint` method internally
|
|
55
|
+
- Parameter t should be between 0 and 1
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# intercept
|
|
2
|
+
|
|
3
|
+
Calculates the Y-intercept of a linear regression line from a set of X and Y values. Equivalent to Excel's INTERCEPT function.
|
|
4
|
+
|
|
5
|
+
## Syntax
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
intercept(yValues, xValues)
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Parameters
|
|
12
|
+
|
|
13
|
+
| Parameter | Type | Description |
|
|
14
|
+
|-----------|------|-------------|
|
|
15
|
+
| `yValues` | Array | The dependent data points (Y values) |
|
|
16
|
+
| `xValues` | Array | The independent data points (X values) |
|
|
17
|
+
|
|
18
|
+
## Returns
|
|
19
|
+
|
|
20
|
+
String - The Y-intercept (b) of the least-squares regression line through the data points.
|
|
21
|
+
|
|
22
|
+
## Description
|
|
23
|
+
|
|
24
|
+
The `intercept` function computes the Y-intercept (b) of the best-fit linear regression line y = mx + b for a given set of data points. This is equivalent to Excel's INTERCEPT function. Uses arbitrary precision arithmetic.
|
|
25
|
+
|
|
26
|
+
## Examples
|
|
27
|
+
|
|
28
|
+
### Basic Usage
|
|
29
|
+
|
|
30
|
+
```expression
|
|
31
|
+
Result = intercept(YValues, XValues)
|
|
32
|
+
// With YValues=[2, 4, 6, 8], XValues=[1, 2, 3, 4]
|
|
33
|
+
// Result: "0" (line passes through origin)
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### With FLATTEN for Internal Data
|
|
37
|
+
|
|
38
|
+
```expression
|
|
39
|
+
YIntercept = intercept(FLATTEN(AppData.Points[].y), FLATTEN(AppData.Points[].x))
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### Combined with Slope
|
|
43
|
+
|
|
44
|
+
```expression
|
|
45
|
+
// Compute full regression: y = slope * x + intercept
|
|
46
|
+
M = slope(YValues, XValues)
|
|
47
|
+
B = intercept(YValues, XValues)
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Related Functions
|
|
51
|
+
|
|
52
|
+
- [slope](./slope.md) - Slope of linear regression line
|
|
53
|
+
- [linest](./linest.md) - Full least squares regression
|
|
54
|
+
- [predict](./predict.md) - Predict from regression model
|
|
55
|
+
|
|
56
|
+
## Notes
|
|
57
|
+
|
|
58
|
+
- Both arrays must have the same length
|
|
59
|
+
- Uses the Math service's `interceptPrecise` method internally
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# setvalue
|
|
2
|
+
|
|
3
|
+
Sets a value in application state or services (AppData, etc.).
|
|
4
|
+
|
|
5
|
+
## Syntax
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
setvalue(address, value)
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Parameters
|
|
12
|
+
|
|
13
|
+
| Parameter | Type | Description |
|
|
14
|
+
|-----------|------|-------------|
|
|
15
|
+
| `address` | String | The address path to set (e.g. `"AppData.Users[0].Name"`) |
|
|
16
|
+
| `value` | Any | The value to set at the given address |
|
|
17
|
+
|
|
18
|
+
## Returns
|
|
19
|
+
|
|
20
|
+
The value that was set.
|
|
21
|
+
|
|
22
|
+
## Description
|
|
23
|
+
|
|
24
|
+
The `setvalue` function writes a value to an internal application state address. It is the counterpart to `getvalue`. The address is resolved using Manyfest path notation, allowing deep property access including array indexing.
|
|
25
|
+
|
|
26
|
+
## Examples
|
|
27
|
+
|
|
28
|
+
### Basic Usage
|
|
29
|
+
|
|
30
|
+
```expression
|
|
31
|
+
setvalue("AppData.CurrentUser", "Alice")
|
|
32
|
+
// Sets fable.AppData.CurrentUser to "Alice"
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### With Computed Values
|
|
36
|
+
|
|
37
|
+
```expression
|
|
38
|
+
setvalue("AppData.Total", X + Y)
|
|
39
|
+
// With X=10, Y=20: sets AppData.Total to "30"
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### Array Access
|
|
43
|
+
|
|
44
|
+
```expression
|
|
45
|
+
setvalue("AppData.Items[0].Price", 29.99)
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Related Functions
|
|
49
|
+
|
|
50
|
+
- [getvalue](./getvalue.md) - Get value from application state
|
|
51
|
+
|
|
52
|
+
## Notes
|
|
53
|
+
|
|
54
|
+
- The first parameter is treated as an address string and resolved internally
|
|
55
|
+
- Uses the Utility service's `setInternalValueByHash` method
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# slope
|
|
2
|
+
|
|
3
|
+
Calculates the slope of a linear regression line from a set of X and Y values. Equivalent to Excel's SLOPE function.
|
|
4
|
+
|
|
5
|
+
## Syntax
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
slope(yValues, xValues)
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Parameters
|
|
12
|
+
|
|
13
|
+
| Parameter | Type | Description |
|
|
14
|
+
|-----------|------|-------------|
|
|
15
|
+
| `yValues` | Array | The dependent data points (Y values) |
|
|
16
|
+
| `xValues` | Array | The independent data points (X values) |
|
|
17
|
+
|
|
18
|
+
## Returns
|
|
19
|
+
|
|
20
|
+
String - The slope of the least-squares regression line through the data points.
|
|
21
|
+
|
|
22
|
+
## Description
|
|
23
|
+
|
|
24
|
+
The `slope` function computes the slope (m) of the best-fit linear regression line y = mx + b for a given set of data points. This is equivalent to Excel's SLOPE function. Uses arbitrary precision arithmetic.
|
|
25
|
+
|
|
26
|
+
## Examples
|
|
27
|
+
|
|
28
|
+
### Basic Usage
|
|
29
|
+
|
|
30
|
+
```expression
|
|
31
|
+
Result = slope(YValues, XValues)
|
|
32
|
+
// With YValues=[2, 4, 6, 8], XValues=[1, 2, 3, 4]
|
|
33
|
+
// Result: "2" (perfect linear relationship)
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### With FLATTEN for Internal Data
|
|
37
|
+
|
|
38
|
+
```expression
|
|
39
|
+
Slope = slope(FLATTEN(AppData.Points[].y), FLATTEN(AppData.Points[].x))
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Related Functions
|
|
43
|
+
|
|
44
|
+
- [intercept](./intercept.md) - Y-intercept of linear regression line
|
|
45
|
+
- [linest](./linest.md) - Full least squares regression
|
|
46
|
+
- [polynomialregression](./polynomialregression.md) - Polynomial regression
|
|
47
|
+
|
|
48
|
+
## Notes
|
|
49
|
+
|
|
50
|
+
- Both arrays must have the same length
|
|
51
|
+
- Uses the Math service's `slopePrecise` method internally
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
# ternary
|
|
2
|
+
|
|
3
|
+
Numeric-aware conditional selection. This is the function behind the `? ::` ternary operator syntax, and can also be called directly.
|
|
4
|
+
|
|
5
|
+
## Ternary Operator Syntax
|
|
6
|
+
|
|
7
|
+
The ternary operator provides inline if/else logic within expressions:
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
condition ? trueValue :: falseValue
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
This is syntactic sugar that gets rewritten to a `ternary()` function call before evaluation. The double colon `::` is used instead of a single `:` because `:` is already reserved for directive syntax (MAP, SERIES, etc.).
|
|
14
|
+
|
|
15
|
+
### How It Works
|
|
16
|
+
|
|
17
|
+
When the expression parser encounters `? ... ::`, it rewrites the expression into a function call:
|
|
18
|
+
|
|
19
|
+
```
|
|
20
|
+
Height > Width ? Height :: Width
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Becomes:
|
|
24
|
+
|
|
25
|
+
```
|
|
26
|
+
ternary((Height > Width), Height, Width)
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
The condition is automatically wrapped in parentheses to ensure correct precedence when combined with arithmetic. This means **you never need to worry about operator precedence** in ternary conditions -- it just works.
|
|
30
|
+
|
|
31
|
+
## Function Syntax
|
|
32
|
+
|
|
33
|
+
```
|
|
34
|
+
ternary(condition, onTrue)
|
|
35
|
+
ternary(condition, onTrue, onFalse)
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Parameters
|
|
39
|
+
|
|
40
|
+
| Parameter | Type | Default | Description |
|
|
41
|
+
|-----------|------|---------|-------------|
|
|
42
|
+
| `condition` | Any | required | Value to evaluate for truthiness |
|
|
43
|
+
| `onTrue` | Any | required | Value returned if condition is truthy |
|
|
44
|
+
| `onFalse` | Any | `""` | Value returned if condition is falsy |
|
|
45
|
+
|
|
46
|
+
## Returns
|
|
47
|
+
|
|
48
|
+
The `onTrue` value if `condition` is truthy, otherwise `onFalse`.
|
|
49
|
+
|
|
50
|
+
## Truthiness
|
|
51
|
+
|
|
52
|
+
The `ternary` function uses numeric-aware truthiness, designed to work correctly with comparison operators that return `"1"` and `"0"` as strings:
|
|
53
|
+
|
|
54
|
+
### Falsy values
|
|
55
|
+
- `false`, `null`, `undefined`, `NaN`
|
|
56
|
+
- `0` (numeric zero)
|
|
57
|
+
- `""` (empty string)
|
|
58
|
+
- `"0"` (string zero -- this is the key difference from `when`)
|
|
59
|
+
- `[]` (empty array)
|
|
60
|
+
- `{}` (empty object)
|
|
61
|
+
|
|
62
|
+
### Truthy values
|
|
63
|
+
- `"1"` or any non-zero number/string
|
|
64
|
+
- Any non-empty string (except `"0"`)
|
|
65
|
+
- Any non-empty array or object
|
|
66
|
+
|
|
67
|
+
## Examples
|
|
68
|
+
|
|
69
|
+
### Basic Ternary
|
|
70
|
+
|
|
71
|
+
```expression
|
|
72
|
+
Result = 5 > 3 ? 100 :: 200
|
|
73
|
+
// 5 > 3 evaluates to "1" (truthy)
|
|
74
|
+
// Result: "100"
|
|
75
|
+
|
|
76
|
+
Result = 3 > 5 ? 100 :: 200
|
|
77
|
+
// 3 > 5 evaluates to "0" (falsy)
|
|
78
|
+
// Result: "200"
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### With Variables
|
|
82
|
+
|
|
83
|
+
```expression
|
|
84
|
+
Winner = Height > Width ? Height :: Width
|
|
85
|
+
// With data: { Height: 10, Width: 5 }
|
|
86
|
+
// 10 > 5 evaluates to "1" (truthy)
|
|
87
|
+
// Result: "10"
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### Arithmetic in Branches
|
|
91
|
+
|
|
92
|
+
```expression
|
|
93
|
+
Result = A > B ? A + 1 :: B + 1
|
|
94
|
+
// With data: { A: 10, B: 5 }
|
|
95
|
+
// A > B is truthy, so A + 1 = 11
|
|
96
|
+
// Result: "11"
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### Arithmetic in Condition
|
|
100
|
+
|
|
101
|
+
```expression
|
|
102
|
+
Result = 2 + 3 > 1 + 2 ? 100 :: 200
|
|
103
|
+
// (2 + 3) > (1 + 2) => 5 > 3 => "1" (truthy)
|
|
104
|
+
// Result: "100"
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### With Assignment
|
|
108
|
+
|
|
109
|
+
```expression
|
|
110
|
+
SomeValue = Height > Width ? A :: B
|
|
111
|
+
// With data: { Height: 10, Width: 5, A: 42, B: 99 }
|
|
112
|
+
// Height > Width is truthy, selects A
|
|
113
|
+
// SomeValue: "42"
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### Equality Checks
|
|
117
|
+
|
|
118
|
+
```expression
|
|
119
|
+
Label = Score == 100 ? "Perfect" :: "Keep trying"
|
|
120
|
+
// With data: { Score: 100 }
|
|
121
|
+
// Result: "Perfect"
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### Nested Ternary
|
|
125
|
+
|
|
126
|
+
Use parentheses to nest ternary expressions:
|
|
127
|
+
|
|
128
|
+
```expression
|
|
129
|
+
Grade = Score >= 90 ? "A" :: (Score >= 80 ? "B" :: (Score >= 70 ? "C" :: "F"))
|
|
130
|
+
// With data: { Score: 85 }
|
|
131
|
+
// Score >= 90 is false, check inner: Score >= 80 is true
|
|
132
|
+
// Result: "B"
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### Inside MAP Expressions
|
|
136
|
+
|
|
137
|
+
```expression
|
|
138
|
+
Labels = MAP VAR x FROM Values : x > 0 ? "positive" :: "non-positive"
|
|
139
|
+
// With data: { Values: [5, -3, 0, 10] }
|
|
140
|
+
// Result: ["positive", "non-positive", "non-positive", "positive"]
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
## Comparison with `if` and `when`
|
|
144
|
+
|
|
145
|
+
| Feature | `ternary` / `? ::` | `if()` | `when()` |
|
|
146
|
+
|---------|---------------------|--------|----------|
|
|
147
|
+
| Syntax style | Infix operator | Function call | Function call |
|
|
148
|
+
| Condition | Truthiness check | Explicit comparison | Truthiness check |
|
|
149
|
+
| `"0"` handling | Falsy | N/A (uses operators) | Truthy |
|
|
150
|
+
| Parameters | 2-3 | 5 | 2-3 |
|
|
151
|
+
| Best for | Inline conditionals | Comparing two values | Null/empty checking |
|
|
152
|
+
|
|
153
|
+
### When to use each
|
|
154
|
+
|
|
155
|
+
**Use `? ::`** for inline conditionals, especially with comparison operators:
|
|
156
|
+
```expression
|
|
157
|
+
Price = Quantity > 100 ? BulkPrice :: RetailPrice
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
**Use `if()`** when you need the comparison operator as a parameter (useful in MAP expressions with dynamic operators):
|
|
161
|
+
```expression
|
|
162
|
+
Result = If(Score, ">=", Threshold, "Pass", "Fail")
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
**Use `when()`** for simple existence/null checks where `"0"` should be truthy:
|
|
166
|
+
```expression
|
|
167
|
+
Name = When(UserName, UserName, "Anonymous")
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
## Notes
|
|
171
|
+
|
|
172
|
+
- The `::` separator must be used (not `:`) to avoid conflicts with directive syntax
|
|
173
|
+
- Comparison operators (`>`, `>=`, `<`, `<=`, `==`, `!=`) return `"1"` or `"0"`, which the ternary function interprets correctly
|
|
174
|
+
- The ternary operator desugaring happens before expression evaluation, so it works seamlessly with all other expression features
|
|
175
|
+
- Nested ternary expressions require parentheses around the inner ternary
|
|
176
|
+
|
|
177
|
+
## Related Functions
|
|
178
|
+
|
|
179
|
+
- [if](./if.md) - Conditional comparison with explicit operator
|
|
180
|
+
- [when](./when.md) - Truthy check (treats `"0"` as truthy)
|
|
@@ -64,6 +64,67 @@ parser.solve('Name ?= GETVALUE("AppData.Students[0]")', data, {}, false, dest);
|
|
|
64
64
|
| `*` | Multiplication | `5 * 3` → `'15'` |
|
|
65
65
|
| `/` | Division | `15 / 3` → `'5'` |
|
|
66
66
|
| `^` | Power | `2 ^ 3` → `'8'` |
|
|
67
|
+
| `%` | Modulus | `10 % 3` → `'1'` |
|
|
68
|
+
|
|
69
|
+
### Comparison Operators
|
|
70
|
+
|
|
71
|
+
Comparison operators evaluate to `'1'` (true) or `'0'` (false). They bind looser than arithmetic, so `2 + 3 > 1 + 2` evaluates the math first.
|
|
72
|
+
|
|
73
|
+
| Operator | Description | Example |
|
|
74
|
+
|----------|-------------|---------|
|
|
75
|
+
| `>` | Greater than | `5 > 3` → `'1'` |
|
|
76
|
+
| `>=` | Greater than or equal | `5 >= 5` → `'1'` |
|
|
77
|
+
| `<` | Less than | `3 < 5` → `'1'` |
|
|
78
|
+
| `<=` | Less than or equal | `3 <= 3` → `'1'` |
|
|
79
|
+
| `==` | Equal | `5 == 5` → `'1'` |
|
|
80
|
+
| `!=` | Not equal | `5 != 3` → `'1'` |
|
|
81
|
+
|
|
82
|
+
```javascript
|
|
83
|
+
parser.solve('Result = 5 > 3', {}, {}, false, dest);
|
|
84
|
+
// dest.Result === '1'
|
|
85
|
+
|
|
86
|
+
parser.solve('Result = 2 + 3 > 1 + 2', {}, {}, false, dest);
|
|
87
|
+
// dest.Result === '1' (evaluates as (2+3) > (1+2), i.e. 5 > 3)
|
|
88
|
+
|
|
89
|
+
parser.solve('Result = Height > Width', { Height: 10, Width: 5 }, {}, false, dest);
|
|
90
|
+
// dest.Result === '1'
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### Ternary Operator
|
|
94
|
+
|
|
95
|
+
The ternary operator provides inline conditional expressions using `?` and `::` syntax:
|
|
96
|
+
|
|
97
|
+
```
|
|
98
|
+
condition ? trueValue :: falseValue
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
The condition is typically a comparison expression. If it evaluates to a truthy value (non-zero, non-empty), the true branch is returned; otherwise the false branch is returned.
|
|
102
|
+
|
|
103
|
+
```javascript
|
|
104
|
+
parser.solve('Result = 5 > 3 ? 100 :: 200', {}, {}, false, dest);
|
|
105
|
+
// dest.Result === '100'
|
|
106
|
+
|
|
107
|
+
parser.solve('Result = 3 > 5 ? 100 :: 200', {}, {}, false, dest);
|
|
108
|
+
// dest.Result === '200'
|
|
109
|
+
|
|
110
|
+
parser.solve('Winner = Height > Width ? Height :: Width',
|
|
111
|
+
{ Height: 10, Width: 5 }, {}, false, dest);
|
|
112
|
+
// dest.Winner === '10'
|
|
113
|
+
|
|
114
|
+
// Arithmetic in branches
|
|
115
|
+
parser.solve('Result = A > B ? A + 1 :: B + 1',
|
|
116
|
+
{ A: 10, B: 5 }, {}, false, dest);
|
|
117
|
+
// dest.Result === '11'
|
|
118
|
+
|
|
119
|
+
// Nested ternary (use parentheses for inner ternary)
|
|
120
|
+
parser.solve('Result = A > 0 ? (B > 0 ? 1 :: 2) :: 3',
|
|
121
|
+
{ A: 1, B: 1 }, {}, false, dest);
|
|
122
|
+
// dest.Result === '1'
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
The ternary operator uses `::` (double colon) instead of `:` for the false branch separator because `:` is already used as the Expression Begin token in directives like `MAP`, `SERIES`, etc.
|
|
126
|
+
|
|
127
|
+
Internally, `condition ? trueValue :: falseValue` is desugared to `ternary((condition), trueValue, falseValue)` before evaluation. See the [ternary function documentation](./expression-parser-functions/ternary.md) for details.
|
|
67
128
|
|
|
68
129
|
### Order of Operations
|
|
69
130
|
|
|
@@ -152,6 +213,17 @@ parser.solve('NameList = STRINGGETSEGMENTS(Names, ",")', { Names: 'Jane,John' })
|
|
|
152
213
|
|
|
153
214
|
### Conditional Functions
|
|
154
215
|
|
|
216
|
+
#### Ternary Operator (inline conditional)
|
|
217
|
+
|
|
218
|
+
The preferred way to write inline conditionals is with the ternary operator:
|
|
219
|
+
|
|
220
|
+
```javascript
|
|
221
|
+
parser.solve('Label = Score >= 70 ? "Pass" :: "Fail"', { Score: 85 }, {}, false, dest);
|
|
222
|
+
// dest.Label === 'Pass'
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
See [Ternary Operator](#ternary-operator) above and the [ternary function reference](./expression-parser-functions/ternary.md).
|
|
226
|
+
|
|
155
227
|
#### When (truthy check)
|
|
156
228
|
|
|
157
229
|
```javascript
|
|
@@ -223,3 +223,111 @@ console.log(`\nWeighted volume change (3-day lookback with price ratio):`);
|
|
|
223
223
|
console.log(` WeightedVolChange: [${tmpMultiRowResults.WeightedVolChange.join(', ')}]`);
|
|
224
224
|
|
|
225
225
|
console.log('QED');
|
|
226
|
+
|
|
227
|
+
/* * * * * * * * * * * * * * * * *
|
|
228
|
+
*
|
|
229
|
+
* Third-Party Test Suite Runner
|
|
230
|
+
*
|
|
231
|
+
* Run with: node Math-Solver-Harness.js --test-suite <name>
|
|
232
|
+
* Valid names: identities, spreadsheet, precision, acidtest, all
|
|
233
|
+
*
|
|
234
|
+
*/
|
|
235
|
+
let SUITE_MAP =
|
|
236
|
+
{
|
|
237
|
+
'identities': 'TestSuite-Identities.json',
|
|
238
|
+
'spreadsheet': 'TestSuite-Spreadsheet.json',
|
|
239
|
+
'precision': 'TestSuite-Precision.json',
|
|
240
|
+
'acidtest': 'TestSuite-AcidTest.json'
|
|
241
|
+
};
|
|
242
|
+
|
|
243
|
+
let tmpRequestedSuites = [];
|
|
244
|
+
for (let i = 0; i < process.argv.length; i++)
|
|
245
|
+
{
|
|
246
|
+
if (process.argv[i] === '--test-suite' && i + 1 < process.argv.length)
|
|
247
|
+
{
|
|
248
|
+
let tmpSuiteName = process.argv[i + 1].toLowerCase();
|
|
249
|
+
if (tmpSuiteName === 'all')
|
|
250
|
+
{
|
|
251
|
+
tmpRequestedSuites = Object.keys(SUITE_MAP);
|
|
252
|
+
}
|
|
253
|
+
else if (tmpSuiteName in SUITE_MAP)
|
|
254
|
+
{
|
|
255
|
+
tmpRequestedSuites.push(tmpSuiteName);
|
|
256
|
+
}
|
|
257
|
+
else
|
|
258
|
+
{
|
|
259
|
+
console.log(`\x1b[31mUnknown test suite: ${tmpSuiteName}\x1b[0m`);
|
|
260
|
+
console.log(`Valid suites: ${Object.keys(SUITE_MAP).join(', ')}, all`);
|
|
261
|
+
}
|
|
262
|
+
i++;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
if (tmpRequestedSuites.length > 0)
|
|
267
|
+
{
|
|
268
|
+
let tmpTotalPass = 0;
|
|
269
|
+
let tmpTotalFail = 0;
|
|
270
|
+
let tmpSuiteSummaries = [];
|
|
271
|
+
|
|
272
|
+
for (let s = 0; s < tmpRequestedSuites.length; s++)
|
|
273
|
+
{
|
|
274
|
+
let tmpSuiteKey = tmpRequestedSuites[s];
|
|
275
|
+
let tmpSuiteData = require(`./${SUITE_MAP[tmpSuiteKey]}`);
|
|
276
|
+
let tmpSuitePass = 0;
|
|
277
|
+
let tmpSuiteFail = 0;
|
|
278
|
+
let tmpCurrentCategory = '';
|
|
279
|
+
|
|
280
|
+
console.log(`\n\x1b[1m=== ${tmpSuiteData.Name} ===\x1b[0m`);
|
|
281
|
+
console.log(`${tmpSuiteData.Description}\n`);
|
|
282
|
+
|
|
283
|
+
for (let i = 0; i < tmpSuiteData.Expressions.length; i++)
|
|
284
|
+
{
|
|
285
|
+
let tmpTest = tmpSuiteData.Expressions[i];
|
|
286
|
+
if (tmpTest.Category && tmpTest.Category !== tmpCurrentCategory)
|
|
287
|
+
{
|
|
288
|
+
tmpCurrentCategory = tmpTest.Category;
|
|
289
|
+
console.log(` \x1b[4m${tmpCurrentCategory}\x1b[0m`);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
let tmpData = tmpTest.Data || _AppData;
|
|
293
|
+
let tmpResultObject = {};
|
|
294
|
+
let tmpResultValue;
|
|
295
|
+
try
|
|
296
|
+
{
|
|
297
|
+
tmpResultValue = _ExpressionParser.solve(tmpTest.Equation, tmpData, tmpResultObject, false);
|
|
298
|
+
}
|
|
299
|
+
catch (pError)
|
|
300
|
+
{
|
|
301
|
+
tmpResultValue = `ERROR: ${pError.message}`;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
let tmpPassed = (String(tmpResultValue) === String(tmpTest.ExpectedResult));
|
|
305
|
+
if (tmpPassed)
|
|
306
|
+
{
|
|
307
|
+
tmpSuitePass++;
|
|
308
|
+
console.log(` \x1b[32m PASS\x1b[0m ${tmpTest.Description}`);
|
|
309
|
+
}
|
|
310
|
+
else
|
|
311
|
+
{
|
|
312
|
+
tmpSuiteFail++;
|
|
313
|
+
console.log(` \x1b[31m FAIL\x1b[0m ${tmpTest.Description}`);
|
|
314
|
+
console.log(` Expected: ${tmpTest.ExpectedResult}`);
|
|
315
|
+
console.log(` Got: ${tmpResultValue}`);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
tmpTotalPass += tmpSuitePass;
|
|
320
|
+
tmpTotalFail += tmpSuiteFail;
|
|
321
|
+
tmpSuiteSummaries.push({ Name: tmpSuiteData.Name, Pass: tmpSuitePass, Fail: tmpSuiteFail, Total: tmpSuitePass + tmpSuiteFail });
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
console.log(`\n\x1b[1m=== Test Suite Summary ===\x1b[0m`);
|
|
325
|
+
for (let i = 0; i < tmpSuiteSummaries.length; i++)
|
|
326
|
+
{
|
|
327
|
+
let tmpSummary = tmpSuiteSummaries[i];
|
|
328
|
+
let tmpColor = tmpSummary.Fail > 0 ? '\x1b[31m' : '\x1b[32m';
|
|
329
|
+
console.log(` ${tmpColor}${tmpSummary.Pass}/${tmpSummary.Total}\x1b[0m ${tmpSummary.Name}`);
|
|
330
|
+
}
|
|
331
|
+
let tmpOverallColor = tmpTotalFail > 0 ? '\x1b[31m' : '\x1b[32m';
|
|
332
|
+
console.log(`\n ${tmpOverallColor}Total: ${tmpTotalPass}/${tmpTotalPass + tmpTotalFail} passed\x1b[0m`);
|
|
333
|
+
}
|