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.
Files changed (23) hide show
  1. package/debug/Harness.js +48 -24
  2. package/dist/fable.compatible.js +329 -55
  3. package/dist/fable.compatible.min.js +2 -2
  4. package/dist/fable.compatible.min.js.map +1 -1
  5. package/dist/fable.js +304 -30
  6. package/dist/fable.min.js +2 -2
  7. package/dist/fable.min.js.map +1 -1
  8. package/package.json +2 -2
  9. package/source/Fable.js +18 -0
  10. package/source/services/Fable-Service-ExpressionParser/Fable-Service-ExpressionParser-Base.js +55 -0
  11. package/source/services/Fable-Service-ExpressionParser/Fable-Service-ExpressionParser-ExpressionTokenizer.js +158 -0
  12. package/source/services/Fable-Service-ExpressionParser/Fable-Service-ExpressionParser-FunctionMap.json +22 -0
  13. package/source/services/Fable-Service-ExpressionParser/Fable-Service-ExpressionParser-Linter.js +180 -0
  14. package/source/services/Fable-Service-ExpressionParser/Fable-Service-ExpressionParser-Postfix.js +626 -0
  15. package/source/services/Fable-Service-ExpressionParser/Fable-Service-ExpressionParser-SolvePostfixedExpression.js +125 -0
  16. package/source/services/Fable-Service-ExpressionParser/Fable-Service-ExpressionParser-TokenMap.json +75 -0
  17. package/source/services/Fable-Service-ExpressionParser.js +210 -0
  18. package/source/services/Fable-Service-Math.js +30 -1
  19. package/source/services/Fable-Service-MetaTemplate/MetaTemplate-StringParser.js +23 -14
  20. package/source/services/Fable-Service-MetaTemplate.js +4 -2
  21. package/source/services/Fable-Service-Template.js +1 -1
  22. package/test/ExpressionParser_tests.js +184 -0
  23. package/test/MetaTemplating_tests.js +41 -1
@@ -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;