fable 3.0.124 → 3.0.126
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/debug/Harness.js +48 -24
- package/dist/fable.compatible.js +329 -55
- package/dist/fable.compatible.min.js +2 -2
- package/dist/fable.compatible.min.js.map +1 -1
- package/dist/fable.js +304 -30
- package/dist/fable.min.js +2 -2
- package/dist/fable.min.js.map +1 -1
- package/package.json +2 -2
- package/source/Fable.js +18 -0
- package/source/services/Fable-Service-ExpressionParser/Fable-Service-ExpressionParser-Base.js +55 -0
- package/source/services/Fable-Service-ExpressionParser/Fable-Service-ExpressionParser-ExpressionTokenizer.js +158 -0
- package/source/services/Fable-Service-ExpressionParser/Fable-Service-ExpressionParser-FunctionMap.json +22 -0
- package/source/services/Fable-Service-ExpressionParser/Fable-Service-ExpressionParser-Linter.js +180 -0
- package/source/services/Fable-Service-ExpressionParser/Fable-Service-ExpressionParser-Postfix.js +626 -0
- package/source/services/Fable-Service-ExpressionParser/Fable-Service-ExpressionParser-SolvePostfixedExpression.js +125 -0
- package/source/services/Fable-Service-ExpressionParser/Fable-Service-ExpressionParser-TokenMap.json +75 -0
- package/source/services/Fable-Service-ExpressionParser.js +210 -0
- package/source/services/Fable-Service-Math.js +30 -1
- package/source/services/Fable-Service-MetaTemplate/MetaTemplate-StringParser.js +23 -14
- package/source/services/Fable-Service-MetaTemplate.js +4 -2
- package/source/services/Fable-Service-Template.js +1 -1
- package/test/ExpressionParser_tests.js +184 -0
- package/test/MetaTemplating_tests.js +41 -1
package/source/services/Fable-Service-ExpressionParser/Fable-Service-ExpressionParser-Postfix.js
ADDED
|
@@ -0,0 +1,626 @@
|
|
|
1
|
+
const libExpressionParserOperationBase = require('./Fable-Service-ExpressionParser-Base.js');
|
|
2
|
+
|
|
3
|
+
class ExpressionParserPostfix extends libExpressionParserOperationBase
|
|
4
|
+
{
|
|
5
|
+
constructor(pFable, pOptions, pServiceHash)
|
|
6
|
+
{
|
|
7
|
+
super(pFable, pOptions, pServiceHash);
|
|
8
|
+
|
|
9
|
+
this.serviceType = 'ExpressionParser-Postfix';
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
getPosfixSolveListOperation(pOperation, pLeftValue, pRightValue, pDepthSolveList, pDepthSolveIndex)
|
|
13
|
+
{
|
|
14
|
+
let tmpOperation = ({
|
|
15
|
+
VirtualSymbolName: pOperation.VirtualSymbolName,
|
|
16
|
+
Operation: pOperation,
|
|
17
|
+
LeftValue: pLeftValue,
|
|
18
|
+
RightValue: pRightValue
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
let tmpDepthSolveList = Array.isArray(pDepthSolveList) ? pDepthSolveList : false;
|
|
22
|
+
|
|
23
|
+
// // Although we have a `Token.Type == "Parenthesis"` option to check on, keeping these explicit means the solver won't
|
|
24
|
+
// // allow users to pass in parenthesis in the wrong order.
|
|
25
|
+
// // The linter does blow up as does the postfix, but, just in case we'll leave these explicit.
|
|
26
|
+
// // It really doesn't hurt anything.
|
|
27
|
+
// if (pLeftValue.Token === ')')
|
|
28
|
+
// {
|
|
29
|
+
// // We have found a close parenthesis which needs to pull the proper virtual symbol for the last operation on this stack.
|
|
30
|
+
// // This ensures we are not expressing the parenthesis virtual symbols to the solver.
|
|
31
|
+
// pLeftValue.VirtualSymbolName = pLayerStackMap[pLeftValue.SolveLayerStack];
|
|
32
|
+
// this.log.error(`ERROR: ExpressionParser.getPosfixSolveListOperation found a close parenthesis in the left value of an operation.`);
|
|
33
|
+
// }
|
|
34
|
+
// else if (pRightValue.Token === '(')
|
|
35
|
+
// {
|
|
36
|
+
// // We have found a close parenthesis which needs to pull the proper virtual symbol for the last operation on this stack.
|
|
37
|
+
// // This ensures we are not expressing the parenthesis virtual symbols to the solver.
|
|
38
|
+
// pRightValue.VirtualSymbolName = pLayerStackMap[pRightValue.SolveLayerStack];
|
|
39
|
+
// this.log.error(`ERROR: ExpressionParser.getPosfixSolveListOperation found a close parenthesis in the left value of an operation.`);
|
|
40
|
+
// }
|
|
41
|
+
|
|
42
|
+
// // Set the layer stack map virtual symbol name to the last operation performed on this stack.
|
|
43
|
+
// pLayerStackMap[pOperation.SolveLayerStack] = pOperation.VirtualSymbolName;
|
|
44
|
+
|
|
45
|
+
/* These two if blocks are very complex -- they basically provide a
|
|
46
|
+
* way to deal with recursion that can be expressed to the user in
|
|
47
|
+
* a meaningful way.
|
|
48
|
+
*
|
|
49
|
+
* The reason we are doing it as such is to show exactly how the
|
|
50
|
+
* solver resolves the passed-in tokens into a solvable expression.
|
|
51
|
+
*/
|
|
52
|
+
if (!tmpOperation.LeftValue.VirtualSymbolName)
|
|
53
|
+
{
|
|
54
|
+
tmpOperation.LeftValue.VirtualSymbolName = tmpOperation.VirtualSymbolName;
|
|
55
|
+
}
|
|
56
|
+
else
|
|
57
|
+
{
|
|
58
|
+
// We need to set the left value to a virtual symbol instead of the looked up value if it's already used in another operation
|
|
59
|
+
tmpOperation.LeftValue = this.getTokenContainerObject(tmpOperation.LeftValue.VirtualSymbolName, 'Token.VirtualSymbol');
|
|
60
|
+
// Now walk backwards and see if we need to update a previous symbol for a previously unparsed operator
|
|
61
|
+
if (tmpDepthSolveList)
|
|
62
|
+
{
|
|
63
|
+
for (let i = pDepthSolveIndex - 1; i >= 0; i--)
|
|
64
|
+
{
|
|
65
|
+
if ((tmpDepthSolveList[i].Type === 'Token.Operator') && (!tmpDepthSolveList[i].Parsed) &&
|
|
66
|
+
// When walking backward, we only want to mutate if the .
|
|
67
|
+
tmpDepthSolveList[i].hasOwnProperty('Descriptor') && tmpOperation.Operation.hasOwnProperty('Descriptor') &&
|
|
68
|
+
// Anything >3 does not have commutative properties
|
|
69
|
+
(tmpDepthSolveList[i].Descriptor.Precedence > 3))
|
|
70
|
+
{
|
|
71
|
+
// If the symbol to its right is not the same as this operation
|
|
72
|
+
if (tmpDepthSolveList[i+1].VirtualSymbolName !== tmpOperation.VirtualSymbolName)
|
|
73
|
+
{
|
|
74
|
+
// This is the recursive "shunting" being simulated
|
|
75
|
+
tmpDepthSolveList[i+1].VirtualSymbolName = tmpOperation.VirtualSymbolName;
|
|
76
|
+
}
|
|
77
|
+
break;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
if (!tmpOperation.RightValue.VirtualSymbolName)
|
|
83
|
+
{
|
|
84
|
+
tmpOperation.RightValue.VirtualSymbolName = tmpOperation.VirtualSymbolName;
|
|
85
|
+
}
|
|
86
|
+
else
|
|
87
|
+
{
|
|
88
|
+
// We need to set the right value to a virtual symbol instead of the looked up value if it's already used in another operation
|
|
89
|
+
tmpOperation.RightValue = this.getTokenContainerObject(tmpOperation.RightValue.VirtualSymbolName, 'Token.VirtualSymbol');
|
|
90
|
+
// Now walk forwards and see if we need to update an upcoming symbol for a previously unparsed operator
|
|
91
|
+
if (tmpDepthSolveList)
|
|
92
|
+
{
|
|
93
|
+
for (let i = pDepthSolveIndex + 1; i < tmpDepthSolveList.length; i++)
|
|
94
|
+
{
|
|
95
|
+
if ((tmpDepthSolveList[i].Type === 'Token.Operator') && (!tmpDepthSolveList[i].Parsed) &&
|
|
96
|
+
// When walking forward, we only want to mutate if the precedence hasn't been solved.
|
|
97
|
+
tmpDepthSolveList[i].hasOwnProperty('Descriptor') && tmpOperation.Operation.hasOwnProperty('Descriptor') &&
|
|
98
|
+
// Anything >3 does not have commutative properties
|
|
99
|
+
(tmpDepthSolveList[i].Descriptor.Precedence > 3))
|
|
100
|
+
{
|
|
101
|
+
// If the symbol to its right is not the same as this operation
|
|
102
|
+
if (tmpDepthSolveList[i-1].VirtualSymbolName !== tmpOperation.VirtualSymbolName)
|
|
103
|
+
{
|
|
104
|
+
// This is the recursive "shunting" being simulated
|
|
105
|
+
tmpDepthSolveList[i-1].VirtualSymbolName = tmpOperation.VirtualSymbolName;
|
|
106
|
+
}
|
|
107
|
+
break;
|
|
108
|
+
}
|
|
109
|
+
else if ((tmpDepthSolveList[i].Type === 'Token.Operator') && (!tmpDepthSolveList[i].Parsed))
|
|
110
|
+
{
|
|
111
|
+
break;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
tmpOperation.Operation.Parsed = true;
|
|
118
|
+
|
|
119
|
+
return tmpOperation;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
buildPostfixedSolveList(pTokenizedExpression, pResultObject)
|
|
123
|
+
{
|
|
124
|
+
let tmpResults = (typeof(pResultObject) === 'object') ? pResultObject : { ExpressionParserLog: [] };
|
|
125
|
+
|
|
126
|
+
tmpResults.PostfixedAssignmentAddress = 'Result'
|
|
127
|
+
tmpResults.PostfixTokenObjects = [];
|
|
128
|
+
tmpResults.PostfixSolveList = [];
|
|
129
|
+
|
|
130
|
+
if (pTokenizedExpression.length < 3)
|
|
131
|
+
{
|
|
132
|
+
tmpResults.ExpressionParserLog.push(`ERROR: ExpressionParser.buildPostfixedSolveList was passed a tokenized expression with less than three tokens.`);
|
|
133
|
+
this.log.error(tmpResults.ExpressionParserLog[tmpResults.ExpressionParserLog.length-1]);
|
|
134
|
+
return tmpResults.PostfixTokenObjects;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// 1. Figure out the Equality Assignment
|
|
138
|
+
let tmpEqualsIndex = -1;
|
|
139
|
+
for (let i = 0; i < pTokenizedExpression.length; i++)
|
|
140
|
+
{
|
|
141
|
+
if ((pTokenizedExpression[i] === '=') && (tmpEqualsIndex < 0))
|
|
142
|
+
{
|
|
143
|
+
tmpEqualsIndex = i;
|
|
144
|
+
}
|
|
145
|
+
// If there are two equality assignments, error and bail out.
|
|
146
|
+
else if (pTokenizedExpression[i] === '=')
|
|
147
|
+
{
|
|
148
|
+
tmpResults.ExpressionParserLog.push(`ERROR: ExpressionParser.buildPostfixedSolveList found multiple equality assignments in the tokenized expression; equality assignment #${tmpEqualsIndex} at token index ${i}.`);
|
|
149
|
+
this.log.error(tmpResults.ExpressionParserLog[tmpResults.ExpressionParserLog.length-1]);
|
|
150
|
+
return tmpResults.PostfixTokenObjects;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (tmpEqualsIndex == -1)
|
|
155
|
+
{
|
|
156
|
+
tmpResults.ExpressionParserLog.push(`WARNING: ExpressionParser.buildPostfixedSolveList found no equality assignment in the tokenized expression; defaulting to Result`);
|
|
157
|
+
this.log.warn(tmpResults.ExpressionParserLog[tmpResults.ExpressionParserLog.length-1]);
|
|
158
|
+
}
|
|
159
|
+
else if (tmpEqualsIndex > 1)
|
|
160
|
+
{
|
|
161
|
+
tmpResults.ExpressionParserLog.push(`WARNING: ExpressionParser.buildPostfixedSolveList found an equality assignment in the tokenized expression at an unexpected location (token index ${tmpEqualsIndex}); the expression cannot be parsed.`);
|
|
162
|
+
this.log.warn(tmpResults.ExpressionParserLog[tmpResults.ExpressionParserLog.length-1]);
|
|
163
|
+
}
|
|
164
|
+
else if (tmpEqualsIndex === 0)
|
|
165
|
+
{
|
|
166
|
+
// This is an implicit function -- just go to result and return the value.
|
|
167
|
+
// That is... the user entered something like "= 5 + 3" so we should just return 8, and use the default Result quietly.
|
|
168
|
+
}
|
|
169
|
+
else
|
|
170
|
+
{
|
|
171
|
+
tmpResults.PostfixedAssignmentAddress = pTokenizedExpression[0];
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// 2. Categorize tokens in the expression, put them in the "expression list" as a token object
|
|
175
|
+
for (let i = tmpEqualsIndex + 1; i < pTokenizedExpression.length; i++)
|
|
176
|
+
{
|
|
177
|
+
tmpResults.PostfixTokenObjects.push(this.getTokenContainerObject(pTokenizedExpression[i]));
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// 3. Decorate mathematical parsing depth and detect functions at the same time
|
|
181
|
+
// Having written this a few times now, it's easier to detect functions *while* parsing depth.
|
|
182
|
+
// Especially if we want our system to be able to communicate with the user when there is an issue.
|
|
183
|
+
let tmpDepth = 0;
|
|
184
|
+
// The virtual symbol index is used for the abstract interim values that are generated at each step of the solve
|
|
185
|
+
let tmpVirtualParenthesisIndex = 0;
|
|
186
|
+
let tmpSolveLayerStack = [];
|
|
187
|
+
// Kick off the solve layer stack with the first solve set identifier
|
|
188
|
+
tmpSolveLayerStack.push(`SolveSet_${tmpVirtualParenthesisIndex}_D_${tmpDepth}`);
|
|
189
|
+
for (let i = 0; i < tmpResults.PostfixTokenObjects.length; i++)
|
|
190
|
+
{
|
|
191
|
+
// 1. If it's an open parenthesis, set the parenthesis at the current depth and increment the depth
|
|
192
|
+
if (tmpResults.PostfixTokenObjects[i].Token === '(')
|
|
193
|
+
{
|
|
194
|
+
// Set the depth of the open parenthesis to the current solution depth
|
|
195
|
+
tmpResults.PostfixTokenObjects[i].Depth = tmpDepth;
|
|
196
|
+
// Generate the virtual symbol name for user output
|
|
197
|
+
tmpResults.PostfixTokenObjects[i].VirtualSymbolName = `Pr_${tmpVirtualParenthesisIndex}_D_${tmpDepth}`;
|
|
198
|
+
|
|
199
|
+
// 1a. Detect if this parenthesis is signaling a function
|
|
200
|
+
// If the previous token is a Symbol (e.g. it say sin(x) or sqrt(3+5) or something) then the parser will interpret it as a function
|
|
201
|
+
if (i > 0)
|
|
202
|
+
{
|
|
203
|
+
if (tmpResults.PostfixTokenObjects[i-1].Type === 'Token.Symbol')
|
|
204
|
+
{
|
|
205
|
+
// Set the type of this to be a function
|
|
206
|
+
tmpResults.PostfixTokenObjects[i-1].Type = 'Token.Function';
|
|
207
|
+
// tmpResults.PostfixTokenObjects[i-1].Descriptor = this.ExpressionParser.tokenMap[pTokenizedExpression[i-1]];
|
|
208
|
+
// Rename the virtual symbol n ame to include the function
|
|
209
|
+
// tmpResults.PostfixTokenObjects[i].VirtualSymbolName = `Fn_${tmpVirtualParenthesisIndex}_D_${tmpDepth}_${this.fable.DataFormat.cleanNonAlphaCharacters(tmpResults.PostfixTokenObjects[i-1].Token)}`;
|
|
210
|
+
// The function and the parenthesis are at the same depth and virtual symbol
|
|
211
|
+
// tmpResults.PostfixTokenObjects[i-1].VirtualSymbolName = tmpResults.PostfixTokenObjects[i].VirtualSymbolName;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Parenthesis manage the solve layer stack
|
|
216
|
+
// For adding a new parenthesis solve layer, we put the parenthesis in the stack we are in and then make all the contained tokens be within the stack of the parenthesis
|
|
217
|
+
tmpResults.PostfixTokenObjects[i].SolveLayerStack = tmpSolveLayerStack[tmpSolveLayerStack.length-1];
|
|
218
|
+
tmpSolveLayerStack.push(tmpResults.PostfixTokenObjects[i].VirtualSymbolName);
|
|
219
|
+
|
|
220
|
+
tmpVirtualParenthesisIndex++;
|
|
221
|
+
tmpDepth++;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// 2. If it's a closed parenthesis, decrease the depth
|
|
225
|
+
else if (tmpResults.PostfixTokenObjects[i].Token === ')')
|
|
226
|
+
{
|
|
227
|
+
tmpDepth--;
|
|
228
|
+
|
|
229
|
+
tmpResults.PostfixTokenObjects[i].Depth = tmpDepth;
|
|
230
|
+
|
|
231
|
+
// Check to see that the depth of the closed parenthesis is greater than 0
|
|
232
|
+
if (tmpDepth < 0)
|
|
233
|
+
{
|
|
234
|
+
tmpResults.ExpressionParserLog.push(`ERROR: ExpressionParser.buildPostfixedSolveList found a closing parenthesis at token index ${i} with no corresponding opening parenthesis.`);
|
|
235
|
+
this.log.error(tmpResults.ExpressionParserLog[tmpResults.ExpressionParserLog.length-1]);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Parenthesis manage the solve layer stack
|
|
239
|
+
// For closing parenthesis solve layer with a close paren, we put it in the same stack as the opening parenthesis.
|
|
240
|
+
// Give the closing parenthesis the same virtual symbol name as the opening parenthesis
|
|
241
|
+
// (do the both above at the same time)
|
|
242
|
+
tmpResults.PostfixTokenObjects[i].VirtualSymbolName = tmpSolveLayerStack.pop();
|
|
243
|
+
tmpResults.PostfixTokenObjects[i].SolveLayerStack = tmpSolveLayerStack[tmpSolveLayerStack.length-1];
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// 3. If it's a state address or Symbol, set depth
|
|
247
|
+
// It was much more complex later on solving these as virtual symbols of their own.
|
|
248
|
+
// We are saving the value resolution for the very end.
|
|
249
|
+
else if ((tmpResults.PostfixTokenObjects[i].Type === 'Token.Symbol'))
|
|
250
|
+
{
|
|
251
|
+
// Set the depth of the current solution depth
|
|
252
|
+
tmpResults.PostfixTokenObjects[i].Depth = tmpDepth;
|
|
253
|
+
tmpResults.PostfixTokenObjects[i].SolveLayerStack = tmpSolveLayerStack[tmpSolveLayerStack.length-1];
|
|
254
|
+
// Generate a virtual symbol name that's somewhat human readable
|
|
255
|
+
//tmpResults.PostfixTokenObjects[i].VirtualSymbolName = `Sm_${tmpVirtualParenthesisIndex}_D_${tmpDepth}_${this.fable.DataFormat.cleanNonAlphaCharacters(tmpResults.PostfixTokenObjects[i].Token)}`;
|
|
256
|
+
|
|
257
|
+
// We've used up this virtual symbol index so increment it
|
|
258
|
+
// The reason we only use these once is to make sure if we use, say, sin(x) twice at the same depth we still have unique names for each virtual solution
|
|
259
|
+
//tmpVirtualParenthesisIndex++;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// 4. If it's an operator or constant or comment, just set the depth
|
|
263
|
+
else
|
|
264
|
+
{
|
|
265
|
+
tmpResults.PostfixTokenObjects[i].Depth = tmpDepth;
|
|
266
|
+
tmpResults.PostfixTokenObjects[i].SolveLayerStack = tmpSolveLayerStack[tmpSolveLayerStack.length-1];
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// 4. Walk through the decorated symbols and generate the postfix solve list
|
|
271
|
+
// We are going to start by creating a map of the solve layers:
|
|
272
|
+
let tmpSolveLayerMap = {};
|
|
273
|
+
let tmpSolveLayerMaxDepth = 0;
|
|
274
|
+
for (let i = 0; i < tmpResults.PostfixTokenObjects.length; i++)
|
|
275
|
+
{
|
|
276
|
+
if (!tmpSolveLayerMap.hasOwnProperty(tmpResults.PostfixTokenObjects[i].SolveLayerStack))
|
|
277
|
+
{
|
|
278
|
+
tmpSolveLayerMap[tmpResults.PostfixTokenObjects[i].SolveLayerStack] = [];
|
|
279
|
+
}
|
|
280
|
+
tmpSolveLayerMap[tmpResults.PostfixTokenObjects[i].SolveLayerStack].push(tmpResults.PostfixTokenObjects[i]);
|
|
281
|
+
|
|
282
|
+
// See what our max depth is. This is super important to the postfix operation
|
|
283
|
+
// The programmer in me thinks it would be funny to not track this and just use the map key length as the max size, which would work (logically impossible to have a depth > key length) but it would be quite a bit more confusing to grok the algorithm.
|
|
284
|
+
if (tmpResults.PostfixTokenObjects[i].Depth > tmpSolveLayerMaxDepth)
|
|
285
|
+
{
|
|
286
|
+
tmpSolveLayerMaxDepth = tmpResults.PostfixTokenObjects[i].Depth;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
let tmpSolveLayerKeys = Object.keys(tmpSolveLayerMap);
|
|
291
|
+
// Reset the virtual symbol index -- it was used above for uniquenes when creating abstract symbols for parenthesis and layer stacks.
|
|
292
|
+
let tmpVirtualSymbolIndex = 0;
|
|
293
|
+
tmpSolveLayerKeys.sort(
|
|
294
|
+
// Sort the solve layers by depth.
|
|
295
|
+
(pLeftLayer, pRightLayer)=>
|
|
296
|
+
{
|
|
297
|
+
// It is impossible to have a layer with no entries in it.
|
|
298
|
+
// If that ever happens, the bug is actually above and we actively want this to blow up.
|
|
299
|
+
if (tmpSolveLayerMap[pLeftLayer][0].Depth < tmpSolveLayerMap[pRightLayer][0].Depth)
|
|
300
|
+
{
|
|
301
|
+
return 1;
|
|
302
|
+
}
|
|
303
|
+
if (tmpSolveLayerMap[pLeftLayer][0].Depth > tmpSolveLayerMap[pRightLayer][0].Depth)
|
|
304
|
+
{
|
|
305
|
+
return -1;
|
|
306
|
+
}
|
|
307
|
+
return 0;
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
// 5. Generate the postfix solve list
|
|
311
|
+
// The most important thing is going backwards in the depth order (a la reverse polish).
|
|
312
|
+
// Specifically not using shunting yard to provide in-depth "show your work" notes
|
|
313
|
+
// Yes it is possible to do a somewhat similar thing with shunting yard but the code is almost unreadable
|
|
314
|
+
|
|
315
|
+
// 5.1 Build the Virtual Symbol Names
|
|
316
|
+
// This maps layer stack addresses (which match parenthesis virtual symbol names) to the resultant value for that layer stack.
|
|
317
|
+
// These values change as it solves but the last assignment is the proper assignment because math only reads forward in a line
|
|
318
|
+
tmpResults.PostfixLayerstackMap = {};
|
|
319
|
+
for (let tmpSolveLayerIndex = 0; tmpSolveLayerIndex < tmpSolveLayerKeys.length; tmpSolveLayerIndex++)
|
|
320
|
+
{
|
|
321
|
+
let tmpSolveLayerTokens = tmpSolveLayerMap[tmpSolveLayerKeys[tmpSolveLayerIndex]];
|
|
322
|
+
// For each precedence (this isn't strictly required here but makes the outcome for the user more readable)
|
|
323
|
+
for (let tmpPrecedence = 0; tmpPrecedence <= this.ExpressionParser.tokenMaxPrecedence; tmpPrecedence++)
|
|
324
|
+
{
|
|
325
|
+
// Enumerate all tokens in a layer's expression.
|
|
326
|
+
// There is a recursive way to do this, but given the short length of even the most complex equations we're favoring readability.
|
|
327
|
+
for (let i = 0; i < tmpSolveLayerTokens.length; i++)
|
|
328
|
+
{
|
|
329
|
+
// If the token is an operator and at the current precedence, add it to the postfix solve list and mutate the array.
|
|
330
|
+
if ((tmpSolveLayerTokens[i].Type === 'Token.Operator') &&
|
|
331
|
+
(tmpSolveLayerTokens[i].Descriptor.Precedence === tmpPrecedence))
|
|
332
|
+
{
|
|
333
|
+
let tmpToken = tmpSolveLayerTokens[i];
|
|
334
|
+
// If there is a token and nothing else in this layer, then it's an error.
|
|
335
|
+
if (tmpSolveLayerTokens.length === 1)
|
|
336
|
+
{
|
|
337
|
+
tmpResults.ExpressionParserLog.push(`ERROR: ExpressionParser.buildPostfixedSolveList found a single operator in a solve layer expression at token index ${i}`);
|
|
338
|
+
this.log.error(tmpResults.ExpressionParserLog[tmpResults.ExpressionParserLog.length-1]);
|
|
339
|
+
return tmpResults.PostfixSolveList;
|
|
340
|
+
}
|
|
341
|
+
// If the token is at the beginning of the expression and not a number line orientation modifier, it's an error.
|
|
342
|
+
else if ((i == 0) && ((tmpToken.Token != '+') || (tmpToken.Token != '-')))
|
|
343
|
+
{
|
|
344
|
+
tmpResults.ExpressionParserLog.push(`ERROR: ExpressionParser.buildPostfixedSolveList found an operator at the beginning of a solve layer expression at token index ${i}`);
|
|
345
|
+
this.log.error(tmpResults.ExpressionParserLog[tmpResults.ExpressionParserLog.length-1]);
|
|
346
|
+
return tmpResults.PostfixSolveList;
|
|
347
|
+
}
|
|
348
|
+
// If the token is at the end of the expression, it is an error.
|
|
349
|
+
else if (i == tmpSolveLayerTokens.length - 1)
|
|
350
|
+
{
|
|
351
|
+
tmpResults.ExpressionParserLog.push(`ERROR: ExpressionParser.buildPostfixedSolveList found an operator at the end of a solve layer expression at token index ${i}`);
|
|
352
|
+
this.log.error(tmpResults.ExpressionParserLog[tmpResults.ExpressionParserLog.length-1]);
|
|
353
|
+
return tmpResults.PostfixSolveList;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
// The - at the beginning of an expression is a number line orientation modifier
|
|
357
|
+
else if ((i == 0) && (tmpToken.Token == '-'))
|
|
358
|
+
{
|
|
359
|
+
tmpToken.VirtualSymbolName = `V_${tmpVirtualSymbolIndex}`;
|
|
360
|
+
tmpResults.PostfixLayerstackMap[tmpToken.SolveLayerStack] = tmpToken.VirtualSymbolName;
|
|
361
|
+
tmpVirtualSymbolIndex++;
|
|
362
|
+
}
|
|
363
|
+
// The - after an operator or an open parenthesis is also a number line orientation modifier
|
|
364
|
+
else if ((i > 0) && (tmpToken.Token == '-') && ((tmpSolveLayerTokens[i-1].Type === 'Token.Operator') || (tmpSolveLayerTokens[i-1].Token === '(')))
|
|
365
|
+
{
|
|
366
|
+
// The number line negation operator is a special case that generates a virtual constant (-1.0) and multiplies it by the next token
|
|
367
|
+
tmpToken.VirtualSymbolName = `V_${tmpVirtualSymbolIndex}`;
|
|
368
|
+
tmpVirtualSymbolIndex++;
|
|
369
|
+
}
|
|
370
|
+
// The + at the beginning is also a number line orientation modifier ... THAT WE IGNORE
|
|
371
|
+
else if ((i == 0) && (tmpToken.Token == '+'))
|
|
372
|
+
{
|
|
373
|
+
continue;
|
|
374
|
+
}
|
|
375
|
+
// The + after an operator or a parenthesis is also a number line orientation modifier ... THAT WE IGNORE
|
|
376
|
+
else if ((i > 0) && (tmpToken.Token == '+') && ((tmpSolveLayerTokens[i-1].Type === 'Token.Operator') || (tmpSolveLayerTokens[i-1].Token === '(')))
|
|
377
|
+
{
|
|
378
|
+
continue;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
// If the token is next to another operator it's a parsing error
|
|
382
|
+
else if ((tmpSolveLayerTokens[i-1].Type === 'Token.Operator') || (tmpSolveLayerTokens[i+1].Type === 'Token.Operator'))
|
|
383
|
+
{
|
|
384
|
+
tmpResults.ExpressionParserLog.push(`ERROR: ExpressionParser.buildPostfixedSolveList found an operator at token index ${i} that is not surrounded by two values.`);
|
|
385
|
+
this.log.error(tmpResults.ExpressionParserLog[tmpResults.ExpressionParserLog.length-1]);
|
|
386
|
+
return tmpResults.PostfixSolveList;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
// Finally add a virtual symbol name to the dang thing.
|
|
390
|
+
else
|
|
391
|
+
{
|
|
392
|
+
tmpToken.VirtualSymbolName = `V_${tmpVirtualSymbolIndex}`;
|
|
393
|
+
tmpResults.PostfixLayerstackMap[tmpToken.SolveLayerStack] = tmpToken.VirtualSymbolName;
|
|
394
|
+
tmpVirtualSymbolIndex++;
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
else if ((tmpSolveLayerTokens[i].Type === 'Token.Function') && (tmpPrecedence === 0))
|
|
398
|
+
{
|
|
399
|
+
let tmpToken = tmpSolveLayerTokens[i];
|
|
400
|
+
tmpToken.VirtualSymbolName = `V_${tmpVirtualSymbolIndex}`;
|
|
401
|
+
tmpVirtualSymbolIndex++;
|
|
402
|
+
tmpResults.PostfixLayerstackMap[tmpToken.SolveLayerStack] = tmpToken.VirtualSymbolName;
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
// 5.2: Decorate the Parenthesis with Virtual Symbol Names
|
|
409
|
+
// ... this gets funny because of precedence of operations surrounding them, parenthesis and functions.
|
|
410
|
+
let tmpParenthesisCacheLIFOStack = [];
|
|
411
|
+
for (let i = 0; i < tmpResults.PostfixTokenObjects.length; i++)
|
|
412
|
+
{
|
|
413
|
+
let tmpPostfixTokenObject = tmpResults.PostfixTokenObjects[i];
|
|
414
|
+
|
|
415
|
+
if (tmpPostfixTokenObject.Type === 'Token.Parenthesis')
|
|
416
|
+
{
|
|
417
|
+
// This is just to track the parenthesis stack level in User feedback
|
|
418
|
+
tmpPostfixTokenObject.ParenthesisStack = tmpPostfixTokenObject.VirtualSymbolName;
|
|
419
|
+
|
|
420
|
+
if (tmpPostfixTokenObject.Token === '(')
|
|
421
|
+
{
|
|
422
|
+
// It's an open parenthesis. If the previous token was an operator, get its precedence.
|
|
423
|
+
if (i > 0)
|
|
424
|
+
{
|
|
425
|
+
if (tmpResults.PostfixTokenObjects[i-1].Type === 'Token.Operator')
|
|
426
|
+
{
|
|
427
|
+
tmpPostfixTokenObject.PreviousPrecedence = tmpResults.PostfixTokenObjects[i-1].Descriptor.Precedence;
|
|
428
|
+
tmpPostfixTokenObject.IsFunction = false;
|
|
429
|
+
tmpPostfixTokenObject.PreviousVirtualSymbolName = tmpResults.PostfixTokenObjects[i-1].VirtualSymbolName;
|
|
430
|
+
tmpPostfixTokenObject.VirtualSymbolName = tmpResults.PostfixLayerstackMap[tmpPostfixTokenObject.VirtualSymbolName];
|
|
431
|
+
}
|
|
432
|
+
// This is a function, we will create a virtual symbol for the discrete parenthesis
|
|
433
|
+
else if (tmpResults.PostfixTokenObjects[i-1].Type === 'Token.Function')
|
|
434
|
+
{
|
|
435
|
+
tmpPostfixTokenObject.PreviousPrecedence = 0;
|
|
436
|
+
tmpPostfixTokenObject.IsFunction = true;
|
|
437
|
+
tmpPostfixTokenObject.PreviousVirtualSymbolName = tmpResults.PostfixTokenObjects[i-1].VirtualSymbolName;
|
|
438
|
+
let tmpVirtualSymbolName = tmpResults.PostfixLayerstackMap[tmpPostfixTokenObject.VirtualSymbolName];
|
|
439
|
+
if (!tmpVirtualSymbolName)
|
|
440
|
+
{
|
|
441
|
+
// This is a parenthesis group with no operators in it; make a virtual symbol name.
|
|
442
|
+
tmpVirtualSymbolName = `V_${tmpVirtualSymbolIndex}`;
|
|
443
|
+
tmpVirtualSymbolIndex++;
|
|
444
|
+
}
|
|
445
|
+
tmpPostfixTokenObject.VirtualSymbolName = tmpVirtualSymbolName;
|
|
446
|
+
|
|
447
|
+
if (i > 1)
|
|
448
|
+
{
|
|
449
|
+
let tmpTokenBeforeFunction = tmpResults.PostfixTokenObjects[i-2];
|
|
450
|
+
if (tmpTokenBeforeFunction.Type === 'Token.Operator')
|
|
451
|
+
{
|
|
452
|
+
tmpPostfixTokenObject.PreviousVirtualSymbolName = tmpResults.PostfixTokenObjects[i-2].VirtualSymbolName;
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
else
|
|
458
|
+
{
|
|
459
|
+
tmpPostfixTokenObject.VirtualSymbolName = tmpResults.PostfixLayerstackMap[tmpPostfixTokenObject.VirtualSymbolName];
|
|
460
|
+
}
|
|
461
|
+
tmpParenthesisCacheLIFOStack.push(tmpPostfixTokenObject);
|
|
462
|
+
}
|
|
463
|
+
if (tmpPostfixTokenObject.Token === ')')
|
|
464
|
+
{
|
|
465
|
+
// There are three options for assigning this:
|
|
466
|
+
let tmpOpenParenthesis = tmpParenthesisCacheLIFOStack.pop();
|
|
467
|
+
// It's at the end of the tokens -- use the stack's identifier
|
|
468
|
+
if (i >= tmpResults.PostfixTokenObjects.length - 1)
|
|
469
|
+
{
|
|
470
|
+
if (tmpOpenParenthesis.IsFunction)
|
|
471
|
+
{
|
|
472
|
+
tmpPostfixTokenObject.VirtualSymbolName = tmpOpenParenthesis.PreviousVirtualSymbolName;
|
|
473
|
+
}
|
|
474
|
+
else
|
|
475
|
+
{
|
|
476
|
+
tmpPostfixTokenObject.VirtualSymbolName = tmpOpenParenthesis.VirtualSymbolName;
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
else
|
|
480
|
+
{
|
|
481
|
+
// Get the next token
|
|
482
|
+
let tmpPeekedNextToken = tmpResults.PostfixTokenObjects[i+1];
|
|
483
|
+
if (tmpPeekedNextToken.Type == 'Token.Operator' && tmpOpenParenthesis.IsFunction)
|
|
484
|
+
{
|
|
485
|
+
// This is the most complex case -- the next token is an operator AND this is a function.
|
|
486
|
+
// The following is just pointer math.
|
|
487
|
+
// If the operater is at the same precedence or higher than the open parenthesis previous operator, use the previous operator's identifier
|
|
488
|
+
// NOTE: This line of code is insanely complex
|
|
489
|
+
tmpPostfixTokenObject.VirtualSymbolName = tmpOpenParenthesis.PreviousVirtualSymbolName;
|
|
490
|
+
|
|
491
|
+
// if (tmpPeekedNextToken.Descriptor.Precedence <= tmpOpenParenthesis.PreviousPrecedence)
|
|
492
|
+
// {
|
|
493
|
+
// tmpPostfixTokenObject.VirtualSymbolName = tmpOpenParenthesis.PreviousVirtualSymbolName;
|
|
494
|
+
// }
|
|
495
|
+
// // Otherwise use this one -- it is the higher precedence. And update the previous parenthesis operator's virtual symbol to be the peeked token's virtual symbol.
|
|
496
|
+
// else
|
|
497
|
+
// {
|
|
498
|
+
// tmpPostfixTokenObject.VirtualSymbolName = tmpResults.PostfixLayerstackMap[tmpPostfixTokenObject.VirtualSymbolName];
|
|
499
|
+
// tmpOpenParenthesis.VirtualSymbolName = tmpPeekedNextToken.VirtualSymbolName;
|
|
500
|
+
// }
|
|
501
|
+
}
|
|
502
|
+
else if (tmpPeekedNextToken.Type == 'Token.Operator' && tmpOpenParenthesis.hasOwnProperty('PreviousPrecedence'))
|
|
503
|
+
{
|
|
504
|
+
// This is the second most complex case -- the next token is an operator.
|
|
505
|
+
// If the operater is at the same precedence or higher than the open parenthesis previous operator, use the previous operator's identifier
|
|
506
|
+
// NOTE: This line of code is insanely complex
|
|
507
|
+
if (tmpPeekedNextToken.Descriptor.Precedence <= tmpOpenParenthesis.PreviousPrecedence)
|
|
508
|
+
{
|
|
509
|
+
tmpPostfixTokenObject.VirtualSymbolName = tmpOpenParenthesis.PreviousVirtualSymbolName;
|
|
510
|
+
}
|
|
511
|
+
// Otherwise use this one -- it is the higher precedence. And update the previous parenthesis operator's virtual symbol to be the peeked token's virtual symbol.
|
|
512
|
+
else
|
|
513
|
+
{
|
|
514
|
+
tmpPostfixTokenObject.VirtualSymbolName = tmpResults.PostfixLayerstackMap[tmpPostfixTokenObject.VirtualSymbolName];
|
|
515
|
+
tmpOpenParenthesis.VirtualSymbolName = tmpPeekedNextToken.VirtualSymbolName;
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
else
|
|
519
|
+
{
|
|
520
|
+
// If this is a function, dereference the function's previous virtual symbol name
|
|
521
|
+
if (tmpOpenParenthesis.IsFunction)
|
|
522
|
+
{
|
|
523
|
+
tmpPostfixTokenObject.VirtualSymbolName = tmpOpenParenthesis.PreviousVirtualSymbolName;
|
|
524
|
+
}
|
|
525
|
+
else
|
|
526
|
+
{
|
|
527
|
+
tmpPostfixTokenObject.VirtualSymbolName = tmpResults.PostfixLayerstackMap[tmpPostfixTokenObject.VirtualSymbolName];
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
// If there isn't an operator in the layer stack, push forward the assignment
|
|
533
|
+
if (!tmpResults.PostfixLayerstackMap[tmpOpenParenthesis.ParenthesisStack])
|
|
534
|
+
{
|
|
535
|
+
tmpResults.PostfixLayerstackMap[tmpOpenParenthesis.ParenthesisStack] = tmpOpenParenthesis.VirtualSymbolName;
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
for (let tmpSolveLayerIndex = 0; tmpSolveLayerIndex < tmpSolveLayerKeys.length; tmpSolveLayerIndex++)
|
|
542
|
+
{
|
|
543
|
+
let tmpSolveLayerTokens = tmpSolveLayerMap[tmpSolveLayerKeys[tmpSolveLayerIndex]];
|
|
544
|
+
|
|
545
|
+
if (tmpSolveLayerTokens.length === 1)
|
|
546
|
+
{
|
|
547
|
+
// This is just a simple value assignment -- use a simple addition virtual operation.
|
|
548
|
+
// We often see these inside functions.
|
|
549
|
+
let tmpAbstractAddToken = this.getTokenContainerObject('+');
|
|
550
|
+
//let tmpVirtualSymbolName = tmpResults.PostfixLayerstackMap[tmpSolveLayerTokens[0].SolveLayerStack];
|
|
551
|
+
tmpAbstractAddToken.VirtualSymbolName = tmpResults.PostfixLayerstackMap[tmpSolveLayerTokens[0].SolveLayerStack];
|
|
552
|
+
tmpResults.PostfixSolveList.push(this.getPosfixSolveListOperation(tmpAbstractAddToken, this.getTokenContainerObject('0.0'), tmpSolveLayerTokens[0]));
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
// 5.3: Generate the Postfix Solve List
|
|
557
|
+
for (let tmpSolveLayerIndex = 0; tmpSolveLayerIndex < tmpSolveLayerKeys.length; tmpSolveLayerIndex++)
|
|
558
|
+
{
|
|
559
|
+
let tmpSolveLayerTokens = tmpSolveLayerMap[tmpSolveLayerKeys[tmpSolveLayerIndex]];
|
|
560
|
+
|
|
561
|
+
// For each precedence level in the layer
|
|
562
|
+
for (let tmpPrecedence = 0; tmpPrecedence <= this.ExpressionParser.tokenMaxPrecedence; tmpPrecedence++)
|
|
563
|
+
{
|
|
564
|
+
// Enumerate all tokens in a layer's expression.
|
|
565
|
+
// There is a recursive way to do this, but given the short length of even the most complex equations we're favoring readability.
|
|
566
|
+
for (let i = 0; i < tmpSolveLayerTokens.length; i++)
|
|
567
|
+
{
|
|
568
|
+
// If the token is an operator and at the current precedence, add it to the postfix solve list and mutate the array.
|
|
569
|
+
if ((tmpSolveLayerTokens[i].Type === 'Token.Operator') &&
|
|
570
|
+
(tmpSolveLayerTokens[i].Descriptor.Precedence === tmpPrecedence))
|
|
571
|
+
{
|
|
572
|
+
let tmpToken = tmpSolveLayerTokens[i];
|
|
573
|
+
// The - at the beginning of an expression is a number line orientation modifier
|
|
574
|
+
if ((i == 0) && (tmpToken.Token == '-'))
|
|
575
|
+
{
|
|
576
|
+
// The number line negation operator is a special case that generates a virtual constant (-1.0) and multiplies it by the next token
|
|
577
|
+
// This is an abstract operation that isn't in the expression.
|
|
578
|
+
let tmpAbstractMultiplyToken = this.getTokenContianerObject('*');
|
|
579
|
+
tmpAbstractMultiplyToken.VirtualSymbolName = tmpToken.VirtualSymbolName;
|
|
580
|
+
tmpResults.PostfixSolveList.push(this.getPosfixSolveListOperation(tmpAbstractMultiplyToken, this.getTokenContainerObject('-1.0'), tmpSolveLayerTokens[i+1]));
|
|
581
|
+
}
|
|
582
|
+
// The - after an operator or an open parenthesis is also a number line orientation modifier
|
|
583
|
+
else if ((i > 0) && (tmpToken.Token == '-') && ((tmpSolveLayerTokens[i-1].Type === 'Token.Operator') || (tmpSolveLayerTokens[i-1].Token === '(')))
|
|
584
|
+
{
|
|
585
|
+
// The number line negation operator is a special case that generates a virtual constant (-1.0) and multiplies it by the next token
|
|
586
|
+
let tmpAbstractMultiplyToken = this.getTokenContianerObject('*');
|
|
587
|
+
tmpAbstractMultiplyToken.VirtualSymbolName = tmpToken.VirtualSymbolName;
|
|
588
|
+
tmpResults.PostfixSolveList.push(this.getPosfixSolveListOperation(tmpAbstractMultiplyToken, this.getTokenContainerObject('-1.0'), tmpSolveLayerTokens[i+1]));
|
|
589
|
+
}
|
|
590
|
+
// The + at the beginning is also a number line orientation modifier ... THAT WE IGNORE
|
|
591
|
+
else if ((i == 0) && (tmpToken.Token == '+'))
|
|
592
|
+
{
|
|
593
|
+
continue;
|
|
594
|
+
}
|
|
595
|
+
// The + after an operator or a parenthesis is also a number line orientation modifier ... THAT WE IGNORE
|
|
596
|
+
else if ((i > 0) && (tmpToken.Token == '+') && ((tmpSolveLayerTokens[i-1].Type === 'Token.Operator') || (tmpSolveLayerTokens[i-1].Token === '(')))
|
|
597
|
+
{
|
|
598
|
+
continue;
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
// Finally add the dang thing.
|
|
602
|
+
else
|
|
603
|
+
{
|
|
604
|
+
tmpResults.PostfixSolveList.push(this.getPosfixSolveListOperation(tmpToken, tmpSolveLayerTokens[i-1], tmpSolveLayerTokens[i+1], tmpSolveLayerTokens, i));
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
else if ((tmpSolveLayerTokens[i].Type === 'Token.Function') && (tmpPrecedence === 0))
|
|
608
|
+
{
|
|
609
|
+
let tmpToken = tmpSolveLayerTokens[i];
|
|
610
|
+
// Not sure what to do with the other token.
|
|
611
|
+
tmpResults.PostfixSolveList.push(this.getPosfixSolveListOperation(tmpToken, tmpSolveLayerTokens[i+1], this.getTokenContainerObject('0.0')));
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
// Now set the assignment address.
|
|
618
|
+
let tmpAssignmentInstruction = this.getPosfixSolveListOperation(this.getTokenContainerObject('Assign', 'Token.SolverInstruction'), this.getTokenContainerObject('DestinationHash', 'Token.SolverInstruction'), this.getTokenContainerObject('Resulting', 'Token.SolverInstruction'));
|
|
619
|
+
tmpAssignmentInstruction.VirtualSymbolName = tmpResults.PostfixedAssignmentAddress;
|
|
620
|
+
tmpResults.PostfixSolveList.push(tmpAssignmentInstruction);
|
|
621
|
+
|
|
622
|
+
return tmpResults.PostfixSolveList;
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
module.exports = ExpressionParserPostfix;
|