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
@@ -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
@@ -264,6 +336,65 @@ parser.solve('Result = MONTECARLO SAMPLECOUNT 5000 VAR x PT x -3 PT x 3 VAR y PT
264
336
  {}, {}, manifest, dest);
265
337
  ```
266
338
 
339
+ ### MULTIROWMAP Expressions
340
+
341
+ Iterate over an array of objects (rows) with multi-row lookback and lookahead. Each variable can reference the current row or any row at a relative offset, with configurable defaults for out-of-bounds access.
342
+
343
+ ```javascript
344
+ // Syntax:
345
+ // Result = MULTIROWMAP ROWS FROM <address>
346
+ // [SERIESSTART <n>] [SERIESSTEP <n>]
347
+ // VAR <name> FROM <property> [OFFSET <n>] [DEFAULT <value>]
348
+ // ... : <expression>
349
+
350
+ // Basic: compute area from each row's Width and Height
351
+ parser.solve('Areas = MULTIROWMAP ROWS FROM Rows VAR w FROM Width VAR h FROM Height : w * h',
352
+ { Rows: [{Width:10,Height:20}, {Width:30,Height:40}] }, {}, manifest, dest);
353
+ // dest.Areas === ['200', '1200']
354
+
355
+ // Previous row lookback (day-over-day change)
356
+ parser.solve('Change = MULTIROWMAP ROWS FROM Prices VAR Today FROM Close VAR Yesterday FROM Close OFFSET -1 DEFAULT 0 : Today - Yesterday',
357
+ data, {}, manifest, dest);
358
+
359
+ // Two-row lookback (Fibonacci verification)
360
+ parser.solve('Check = MULTIROWMAP ROWS FROM FibRows VAR Cur FROM Fib VAR P1 FROM Fib OFFSET -1 DEFAULT 0 VAR P2 FROM Fib OFFSET -2 DEFAULT 0 : Cur - (P1 + P2)',
361
+ data, {}, manifest, dest);
362
+
363
+ // Forward lookback (next row reference)
364
+ parser.solve('NextDiff = MULTIROWMAP ROWS FROM Rows VAR Cur FROM Value VAR Next FROM Value OFFSET 1 DEFAULT 0 : Next - Cur',
365
+ data, {}, manifest, dest);
366
+
367
+ // Central difference (both directions)
368
+ parser.solve('Deriv = MULTIROWMAP ROWS FROM Rows VAR Prev FROM Value OFFSET -1 DEFAULT 0 VAR Next FROM Value OFFSET 1 DEFAULT 0 : (Next - Prev) / 2',
369
+ data, {}, manifest, dest);
370
+
371
+ // SERIESSTART: skip initial rows (start from row 2)
372
+ parser.solve('MA = MULTIROWMAP ROWS FROM Prices SERIESSTART 2 VAR c0 FROM Close VAR c1 FROM Close OFFSET -1 DEFAULT 0 VAR c2 FROM Close OFFSET -2 DEFAULT 0 : (c0 + c1 + c2) / 3',
373
+ data, {}, manifest, dest);
374
+
375
+ // Negative SERIESSTART: start from 2 rows before end
376
+ parser.solve('LastTwo = MULTIROWMAP ROWS FROM Rows SERIESSTART -2 VAR v FROM Value : v + 0',
377
+ data, {}, manifest, dest);
378
+
379
+ // SERIESSTEP -1: iterate backwards
380
+ parser.solve('Reversed = MULTIROWMAP ROWS FROM Rows SERIESSTART -1 SERIESSTEP -1 VAR v FROM Value : v + 0',
381
+ data, {}, manifest, dest);
382
+
383
+ // SERIESSTEP 2: every other row
384
+ parser.solve('Sampled = MULTIROWMAP ROWS FROM Rows SERIESSTEP 2 VAR v FROM Value : v + 0',
385
+ data, {}, manifest, dest);
386
+ ```
387
+
388
+ **Available variables in MULTIROWMAP expressions:**
389
+ - `stepIndex` — iteration counter (0, 1, 2, ...)
390
+ - `rowIndex` — actual array index of the current row
391
+ - Any VAR-mapped variable names
392
+
393
+ **OFFSET values:**
394
+ - `0` (default) — current row
395
+ - `-1` — previous row, `-2` — two rows back, `-3` — three back, etc.
396
+ - `1` — next row, `2` — two ahead, etc.
397
+
267
398
  ### ITERATIVESERIES
268
399
 
269
400
  Run cumulative computations over arrays:
@@ -22,5 +22,40 @@
22
22
  "BezierYValues": [0, 1, 4, 9, 16],
23
23
 
24
24
  "SalesMonths": [1, 2, 3, 4, 5, 6],
25
- "SalesRevenue": [150, 200, 250, 310, 350, 400]
25
+ "SalesRevenue": [150, 200, 250, 310, 350, 400],
26
+
27
+ "FibonacciRows": [
28
+ { "Fib": 0 },
29
+ { "Fib": 1 },
30
+ { "Fib": 1 },
31
+ { "Fib": 2 },
32
+ { "Fib": 3 },
33
+ { "Fib": 5 },
34
+ { "Fib": 8 },
35
+ { "Fib": 13 },
36
+ { "Fib": 21 },
37
+ { "Fib": 34 }
38
+ ],
39
+
40
+ "StockPrices": [
41
+ { "Date": "2025-01-01", "Close": 100.00, "Volume": 5000 },
42
+ { "Date": "2025-01-02", "Close": 102.50, "Volume": 6200 },
43
+ { "Date": "2025-01-03", "Close": 101.00, "Volume": 4800 },
44
+ { "Date": "2025-01-06", "Close": 105.00, "Volume": 7100 },
45
+ { "Date": "2025-01-07", "Close": 108.50, "Volume": 8300 },
46
+ { "Date": "2025-01-08", "Close": 107.00, "Volume": 5500 },
47
+ { "Date": "2025-01-09", "Close": 110.00, "Volume": 6800 },
48
+ { "Date": "2025-01-10", "Close": 112.50, "Volume": 7200 }
49
+ ],
50
+
51
+ "TemperatureReadings": [
52
+ { "Hour": 0, "Temp": 58.0 },
53
+ { "Hour": 3, "Temp": 55.5 },
54
+ { "Hour": 6, "Temp": 54.0 },
55
+ { "Hour": 9, "Temp": 60.5 },
56
+ { "Hour": 12, "Temp": 68.0 },
57
+ { "Hour": 15, "Temp": 72.5 },
58
+ { "Hour": 18, "Temp": 67.0 },
59
+ { "Hour": 21, "Temp": 61.5 }
60
+ ]
26
61
  }