fable 3.0.146 → 3.0.148
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/dist/fable.js +256 -181
- package/dist/fable.min.js +2 -2
- package/dist/fable.min.js.map +1 -1
- package/example_applications/mathematical_playground/Math-Solver-Harness.js +3 -2
- package/package.json +1 -1
- package/source/Fable.js +21 -1
- package/source/services/Fable-Service-DataGeneration.js +164 -59
- package/source/services/Fable-Service-ExpressionParser/Fable-Service-ExpressionParser-ExpressionTokenizer.js +34 -7
- package/source/services/Fable-Service-ExpressionParser/Fable-Service-ExpressionParser-FunctionMap.json +33 -3
- package/source/services/Fable-Service-ExpressionParser/Fable-Service-ExpressionParser-Linter.js +2 -2
- package/source/services/Fable-Service-ExpressionParser/Fable-Service-ExpressionParser-Postfix.js +15 -10
- package/source/services/Fable-Service-ExpressionParser/Fable-Service-ExpressionParser-SolvePostfixedExpression.js +23 -2
- package/source/services/Fable-Service-ExpressionParser/Fable-Service-ExpressionParser-TokenMap.json +11 -1
- package/source/services/Fable-Service-ExpressionParser.js +41 -2
- package/source/services/Fable-Service-Math.js +1 -23
- package/source/services/Fable-Service-Utility.js +45 -0
- package/test/DataGeneration_tests.js +14 -0
- package/test/ExpressionParser_tests.js +120 -3
- package/test/Fable_tests.js +11 -0
|
@@ -40,7 +40,8 @@ for (let i = 0; i < _Equations.Expressions.length; i++)
|
|
|
40
40
|
let tmpResultObject = {};
|
|
41
41
|
let tmpResultValue = _ExpressionParser.solve(_Equations.Expressions[i].Equation, _AppData, tmpResultObject, tmpManifest);
|
|
42
42
|
console.log(`Expression [${i}]: [${_Equations.Expressions[i].Equation}] ==> ${tmpResultValue}`);
|
|
43
|
-
|
|
43
|
+
console.log(` Expected: ${_Equations.Expressions[i].ExpectedResult}`);
|
|
44
|
+
//_Fable.ExpressionParser.Messaging.logFunctionOutcome(tmpResultObject);
|
|
44
45
|
|
|
45
46
|
if (tmpResultValue !== _Equations.Expressions[i].ExpectedResult)
|
|
46
47
|
{
|
|
@@ -81,7 +82,7 @@ _ExpressionParser.substituteValuesInTokenizedObjects(tmpExpressionParseOutcome.P
|
|
|
81
82
|
let tmpResultValue = _ExpressionParser.solvePostfixedExpression(tmpExpressionParseOutcome.PostfixSolveList, tmpSolverResultsObject, tmpExpressionParseOutcome, _FruitManifest);
|
|
82
83
|
|
|
83
84
|
// Now that we have a solved expression and the mapped-in values, show the user the solution
|
|
84
|
-
_Fable.ExpressionParser.Messaging.logFunctionOutcome(tmpExpressionParseOutcome);
|
|
85
|
+
//_Fable.ExpressionParser.Messaging.logFunctionOutcome(tmpExpressionParseOutcome);
|
|
85
86
|
|
|
86
87
|
// Step 6: Look at the results.
|
|
87
88
|
console.log(`Outcome object: ${JSON.stringify(tmpSolverResultsObject)}`);
|
package/package.json
CHANGED
package/source/Fable.js
CHANGED
|
@@ -310,7 +310,27 @@ class Fable extends libFableServiceBase.CoreServiceProviderBase
|
|
|
310
310
|
}
|
|
311
311
|
|
|
312
312
|
return false;
|
|
313
|
-
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* Generate a safe string to use in filenames for a date. Useful for log file uniqueness and temporary outputs.
|
|
318
|
+
*
|
|
319
|
+
* @static
|
|
320
|
+
* @param {Date} pDate - An optional javascript Date object to generate a datestamp for.
|
|
321
|
+
* @returns {string} - A string formatted as YYYY-MM-DD-HH-MM-SS
|
|
322
|
+
*/
|
|
323
|
+
static generateFileNameDateStamp(pDate)
|
|
324
|
+
{
|
|
325
|
+
const tmpDate = pDate || new Date();
|
|
326
|
+
const tmpYear = tmpDate.getFullYear();
|
|
327
|
+
const tmpMonth = String(tmpDate.getMonth() + 1).padStart(2, '0');
|
|
328
|
+
const tmpDay = String(tmpDate.getDate()).padStart(2, '0');
|
|
329
|
+
const tmpHour = String(tmpDate.getHours()).padStart(2, '0');
|
|
330
|
+
const tmpMinute = String(tmpDate.getMinutes()).padStart(2, '0');
|
|
331
|
+
const tmpSecond = String(tmpDate.getSeconds()).padStart(2, '0');
|
|
332
|
+
return `${tmpYear}-${tmpMonth}-${tmpDay}-${tmpHour}-${tmpMinute}-${tmpSecond}`;
|
|
333
|
+
}
|
|
314
334
|
}
|
|
315
335
|
|
|
316
336
|
// This is for backwards compatibility
|
|
@@ -1,66 +1,171 @@
|
|
|
1
1
|
const libFableServiceBase = require('fable-serviceproviderbase');
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* FableServiceDataGeneration class provides various methods for generating random data.
|
|
5
|
+
*
|
|
6
|
+
* @extends libFableServiceBase
|
|
7
|
+
*/
|
|
3
8
|
class FableServiceDataGeneration extends libFableServiceBase
|
|
4
9
|
{
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
10
|
+
constructor(pFable, pOptions, pServiceHash)
|
|
11
|
+
{
|
|
12
|
+
super(pFable, pOptions, pServiceHash);
|
|
13
|
+
|
|
14
|
+
this.serviceType = 'DataGeneration';
|
|
15
|
+
|
|
16
|
+
this.defaultData = require('./Fable-Service-DataGeneration-DefaultValues.json');
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Generates a random integer between the specified minimum and maximum values.
|
|
21
|
+
*
|
|
22
|
+
* @param {number} pMinimum - The minimum value (inclusive).
|
|
23
|
+
* @param {number} pMaximum - The maximum value (exclusive).
|
|
24
|
+
* @returns {number} A random integer between pMinimum and pMaximum.
|
|
25
|
+
*/
|
|
26
|
+
randomIntegerBetween(pMinimum, pMaximum)
|
|
27
|
+
{
|
|
28
|
+
try
|
|
29
|
+
{
|
|
30
|
+
let tmpMinimum = parseInt(pMinimum, 10);
|
|
31
|
+
let tmpMaximum = parseInt(pMaximum, 10);
|
|
32
|
+
return Math.floor(Math.random() * (tmpMaximum - tmpMinimum)) + tmpMinimum;
|
|
33
|
+
}
|
|
34
|
+
catch (pError)
|
|
35
|
+
{
|
|
36
|
+
this.fable.log.error('Error in randomIntegerBetween', pError, { 'Minimum': pMinimum, 'Maximum': pMaximum });
|
|
37
|
+
return NaN;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Generates a random integer between 0 (inclusive) and the specified maximum value (exclusive).
|
|
43
|
+
*
|
|
44
|
+
* @param {number} pMaximum - The maximum value (exclusive).
|
|
45
|
+
* @returns {number} A random integer between 0 and pMaximum.
|
|
46
|
+
*/
|
|
47
|
+
randomIntegerUpTo(pMaximum)
|
|
48
|
+
{
|
|
49
|
+
return this.randomIntegerBetween(0, pMaximum);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Generates a random integer between 0 (inclusive) and the default maximum value.
|
|
54
|
+
*
|
|
55
|
+
* @returns {number} A random integer between 0 and the default maximum value.
|
|
56
|
+
*/
|
|
57
|
+
randomInteger()
|
|
58
|
+
{
|
|
59
|
+
return Math.floor(Math.random() * this.defaultData.DefaultIntegerMaximum);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Generates a random float between the specified minimum and maximum values.
|
|
64
|
+
*
|
|
65
|
+
* @param {number} pMinimum - The minimum value (inclusive).
|
|
66
|
+
* @param {number} pMaximum - The maximum value (exclusive).
|
|
67
|
+
* @returns {number} A random float between pMinimum and pMaximum.
|
|
68
|
+
*/
|
|
69
|
+
randomFloatBetween(pMinimum, pMaximum)
|
|
70
|
+
{
|
|
71
|
+
try
|
|
72
|
+
{
|
|
73
|
+
let tmpMinimum = parseFloat(pMinimum);
|
|
74
|
+
let tmpMaximum = parseFloat(pMaximum);
|
|
75
|
+
return this.fable.Math.addPrecise(this.fable.Math.multiplyPrecise(Math.random(), this.fable.Math.subtractPrecise(tmpMaximum, tmpMinimum)), tmpMinimum);
|
|
76
|
+
}
|
|
77
|
+
catch (pError)
|
|
78
|
+
{
|
|
79
|
+
this.fable.log.error('Error in randomFloatBetween', pError, { 'Minimum': pMinimum, 'Maximum': pMaximum });
|
|
80
|
+
return NaN;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Generates a random float between 0 (inclusive) and the specified maximum value (exclusive).
|
|
86
|
+
*
|
|
87
|
+
* @param {number} pMaximum - The maximum value (exclusive).
|
|
88
|
+
* @returns {number} A random float between 0 and pMaximum.
|
|
89
|
+
*/
|
|
90
|
+
randomFloatUpTo(pMaximum)
|
|
91
|
+
{
|
|
92
|
+
return this.randomFloatBetween(0, pMaximum);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Generates a random float between 0 (inclusive) and 1 (exclusive).
|
|
97
|
+
*
|
|
98
|
+
* @returns {number} A random float between 0 and 1.
|
|
99
|
+
*/
|
|
100
|
+
randomFloat()
|
|
101
|
+
{
|
|
102
|
+
return Math.random();
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Generates a random numeric string of the specified length.
|
|
107
|
+
*
|
|
108
|
+
* @param {number} pLength - The length of the numeric string.
|
|
109
|
+
* @param {number} pMaxNumber - The maximum number to generate.
|
|
110
|
+
* @returns {string} A random numeric string of the specified length.
|
|
111
|
+
*/
|
|
112
|
+
randomNumericString(pLength, pMaxNumber)
|
|
113
|
+
{
|
|
114
|
+
let tmpLength = (typeof(pLength) === 'undefined') ? 10 : pLength;
|
|
115
|
+
let tmpMaxNumber = (typeof(pMaxNumber) === 'undefined') ? 9999999999 : pMaxNumber;
|
|
116
|
+
|
|
117
|
+
return this.services.DataFormat.stringPadStart(this.randomIntegerUpTo(tmpMaxNumber), pLength, '0');
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Generates a random month from the default month set.
|
|
122
|
+
*
|
|
123
|
+
* @returns {string} A random month.
|
|
124
|
+
*/
|
|
125
|
+
randomMonth()
|
|
126
|
+
{
|
|
127
|
+
return this.defaultData.MonthSet[this.randomIntegerUpTo(this.defaultData.MonthSet.length - 1)];
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Generates a random day of the week from the default week day set.
|
|
132
|
+
*
|
|
133
|
+
* @returns {string} A random day of the week.
|
|
134
|
+
*/
|
|
135
|
+
randomDayOfWeek()
|
|
136
|
+
{
|
|
137
|
+
return this.defaultData.WeekDaySet[this.randomIntegerUpTo(this.defaultData.WeekDaySet.length - 1)];
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Generates a random color from the default color set.
|
|
142
|
+
*
|
|
143
|
+
* @returns {string} A random color.
|
|
144
|
+
*/
|
|
145
|
+
randomColor()
|
|
146
|
+
{
|
|
147
|
+
return this.defaultData.ColorSet[this.randomIntegerUpTo(this.defaultData.ColorSet.length - 1)];
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Generates a random name from the default name set.
|
|
152
|
+
*
|
|
153
|
+
* @returns {string} A random name.
|
|
154
|
+
*/
|
|
155
|
+
randomName()
|
|
156
|
+
{
|
|
157
|
+
return this.defaultData.NameSet[this.randomIntegerUpTo(this.defaultData.NameSet.length - 1)];
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Generates a random surname from the default surname set.
|
|
162
|
+
*
|
|
163
|
+
* @returns {string} A random surname.
|
|
164
|
+
*/
|
|
165
|
+
randomSurname()
|
|
166
|
+
{
|
|
167
|
+
return this.defaultData.SurNameSet[this.randomIntegerUpTo(this.defaultData.SurNameSet.length - 1)];
|
|
168
|
+
}
|
|
64
169
|
}
|
|
65
170
|
|
|
66
171
|
module.exports = FableServiceDataGeneration;
|
|
@@ -99,16 +99,43 @@ class ExpressionTokenizer extends libExpressionParserOperationBase
|
|
|
99
99
|
}
|
|
100
100
|
|
|
101
101
|
// [ TOKENS ]
|
|
102
|
-
if (tmpCharacter in this.ExpressionParser.
|
|
102
|
+
if (tmpCharacter in this.ExpressionParser.tokenRadix)
|
|
103
103
|
{
|
|
104
|
-
|
|
104
|
+
let tmpTokenRadix = this.ExpressionParser.tokenRadix[tmpCharacter];
|
|
105
|
+
// If the token is a literal and has only one entry, it is a single character token and we can just safely add it.
|
|
106
|
+
if (tmpTokenRadix.TokenCount == 1 && tmpTokenRadix.Literal)
|
|
105
107
|
{
|
|
106
|
-
|
|
108
|
+
if (tmpCurrentToken.length > 0)
|
|
109
|
+
{
|
|
110
|
+
tmpResults.RawTokens.push(tmpCurrentToken);
|
|
111
|
+
}
|
|
112
|
+
tmpCurrentToken = '';
|
|
113
|
+
tmpCurrentTokenType = false;
|
|
114
|
+
tmpResults.RawTokens.push(tmpCharacter);
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
else
|
|
118
|
+
{
|
|
119
|
+
// This one has multiple options, so literals don't matter. We need to check the token map.
|
|
120
|
+
// The token radix TokenKeys array is sorted longest to shortest
|
|
121
|
+
for (let j = 0; j < tmpTokenRadix.TokenKeys.length; j++)
|
|
122
|
+
{
|
|
123
|
+
let tmpTokenKey = tmpTokenRadix.TokenKeys[j];
|
|
124
|
+
if (pExpression.substr(i, tmpTokenKey.length) == tmpTokenKey)
|
|
125
|
+
{
|
|
126
|
+
if (tmpCurrentToken.length > 0)
|
|
127
|
+
{
|
|
128
|
+
tmpResults.RawTokens.push(tmpTokenKey);
|
|
129
|
+
}
|
|
130
|
+
tmpCurrentToken = '';
|
|
131
|
+
tmpCurrentTokenType = false;
|
|
132
|
+
tmpResults.RawTokens.push(tmpTokenKey);
|
|
133
|
+
i += tmpTokenKey.length - 1;
|
|
134
|
+
break;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
continue;
|
|
107
138
|
}
|
|
108
|
-
tmpCurrentToken = '';
|
|
109
|
-
tmpCurrentTokenType = false;
|
|
110
|
-
tmpResults.RawTokens.push(tmpCharacter);
|
|
111
|
-
continue;
|
|
112
139
|
}
|
|
113
140
|
|
|
114
141
|
// If it's not an operator, it's a number or address.
|
|
@@ -83,8 +83,38 @@
|
|
|
83
83
|
"Name": "Round",
|
|
84
84
|
"Address": "fable.Math.roundPrecise"
|
|
85
85
|
},
|
|
86
|
-
"
|
|
87
|
-
"Name": "Histogram
|
|
88
|
-
"Address": "fable.Math.
|
|
86
|
+
"countSetElements": {
|
|
87
|
+
"Name": "Count Set Elements in a Histogram or Value Map",
|
|
88
|
+
"Address": "fable.Math.countSetElements"
|
|
89
|
+
},
|
|
90
|
+
|
|
91
|
+
"getvalue": {
|
|
92
|
+
"Name": "Get Value from Application State or Services (AppData, etc.)",
|
|
93
|
+
"Address": "fable.Utility.getValue"
|
|
94
|
+
},
|
|
95
|
+
|
|
96
|
+
"randominteger": {
|
|
97
|
+
"Name": "Random Integer",
|
|
98
|
+
"Address": "fable.DataGeneration.randomInteger"
|
|
99
|
+
},
|
|
100
|
+
"randomintegerbetween": {
|
|
101
|
+
"Name": "Random Integer Between Two Numbers",
|
|
102
|
+
"Address": "fable.DataGeneration.randomIntegerBetween"
|
|
103
|
+
},
|
|
104
|
+
"randomintegerupto": {
|
|
105
|
+
"Name": "Random Integer",
|
|
106
|
+
"Address": "fable.DataGeneration.randomIntegerUpTo"
|
|
107
|
+
},
|
|
108
|
+
"randomfloat": {
|
|
109
|
+
"Name": "Random Float",
|
|
110
|
+
"Address": "fable.DataGeneration.randomFloat"
|
|
111
|
+
},
|
|
112
|
+
"randomfloatbetween": {
|
|
113
|
+
"Name": "Random Float",
|
|
114
|
+
"Address": "fable.DataGeneration.randomFloatBetween"
|
|
115
|
+
},
|
|
116
|
+
"randomfloatupto": {
|
|
117
|
+
"Name": "Random Float",
|
|
118
|
+
"Address": "fable.DataGeneration.randomFloatUpTo"
|
|
89
119
|
}
|
|
90
120
|
}
|
package/source/services/Fable-Service-ExpressionParser/Fable-Service-ExpressionParser-Linter.js
CHANGED
|
@@ -103,14 +103,14 @@ class ExpressionParserLinter extends libExpressionParserOperationBase
|
|
|
103
103
|
let tmpEqualityAssignmentIndex = false;
|
|
104
104
|
for (let i = 0; i < pTokenizedExpression.length; i++)
|
|
105
105
|
{
|
|
106
|
-
if (pTokenizedExpression[i] === '
|
|
106
|
+
if ((this.ExpressionParser.tokenMap[pTokenizedExpression[i]]) && (this.ExpressionParser.tokenMap[pTokenizedExpression[i]].Type === 'Assignment'))
|
|
107
107
|
{
|
|
108
108
|
tmpEqualityAssignmentCount++;
|
|
109
109
|
tmpEqualityAssignmentIndex = i;
|
|
110
110
|
|
|
111
111
|
if (tmpEqualityAssignmentCount > 1)
|
|
112
112
|
{
|
|
113
|
-
tmpResults.ExpressionParserLog.push(`ERROR: ExpressionParser.lintTokenizedExpression found multiple equality assignments in the tokenized expression; equality assignment #${tmpEqualityAssignmentCount} at token index ${i}.`);
|
|
113
|
+
tmpResults.ExpressionParserLog.push(`ERROR: ExpressionParser.lintTokenizedExpression found multiple equality assignments in the tokenized expression; equality assignment #${tmpEqualityAssignmentCount} operator '${pTokenizedExpression[i]}' at token index ${i}.`);
|
|
114
114
|
tmpResults.LinterResults.push(tmpResults.ExpressionParserLog[tmpResults.ExpressionParserLog.length-1]);
|
|
115
115
|
this.log.error(tmpResults.ExpressionParserLog[tmpResults.ExpressionParserLog.length-1]);
|
|
116
116
|
}
|
package/source/services/Fable-Service-ExpressionParser/Fable-Service-ExpressionParser-Postfix.js
CHANGED
|
@@ -70,6 +70,7 @@ class ExpressionParserPostfix extends libExpressionParserOperationBase
|
|
|
70
70
|
let tmpResults = (typeof(pResultObject) === 'object') ? pResultObject : { ExpressionParserLog: [] };
|
|
71
71
|
|
|
72
72
|
tmpResults.PostfixedAssignmentAddress = 'Result'
|
|
73
|
+
tmpResults.PostfixedAssignmentOperator = this.ExpressionParser.tokenMap['=']; // This is the default assignment operator
|
|
73
74
|
tmpResults.PostfixTokenObjects = [];
|
|
74
75
|
tmpResults.PostfixSolveList = [];
|
|
75
76
|
|
|
@@ -84,16 +85,19 @@ class ExpressionParserPostfix extends libExpressionParserOperationBase
|
|
|
84
85
|
let tmpEqualsIndex = -1;
|
|
85
86
|
for (let i = 0; i < pTokenizedExpression.length; i++)
|
|
86
87
|
{
|
|
87
|
-
if ((pTokenizedExpression[i]
|
|
88
|
+
if ((this.ExpressionParser.tokenMap[pTokenizedExpression[i]]) && (this.ExpressionParser.tokenMap[pTokenizedExpression[i]].Type === 'Assignment'))
|
|
88
89
|
{
|
|
89
|
-
tmpEqualsIndex
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
90
|
+
if (tmpEqualsIndex < 0)
|
|
91
|
+
{
|
|
92
|
+
tmpEqualsIndex = i;
|
|
93
|
+
tmpResults.PostfixedAssignmentOperator = this.ExpressionParser.tokenMap[pTokenizedExpression[i]];
|
|
94
|
+
}
|
|
95
|
+
else
|
|
96
|
+
{
|
|
97
|
+
tmpResults.ExpressionParserLog.push(`ERROR: ExpressionParser.buildPostfixedSolveList found multiple assignment operators in the tokenized expression; assignment operator '${pTokenizedExpression[i]}' #${tmpEqualsIndex} at token index ${i}.`);
|
|
98
|
+
this.log.error(tmpResults.ExpressionParserLog[tmpResults.ExpressionParserLog.length-1]);
|
|
99
|
+
return tmpResults.PostfixTokenObjects;
|
|
100
|
+
}
|
|
97
101
|
}
|
|
98
102
|
}
|
|
99
103
|
|
|
@@ -563,6 +567,7 @@ class ExpressionParserPostfix extends libExpressionParserOperationBase
|
|
|
563
567
|
// If this is a layer with one value, presume it's an assignment.
|
|
564
568
|
if (tmpSolveLayerTokens.length === 1)
|
|
565
569
|
{
|
|
570
|
+
// TODO: I think this is correct but with the addition of multiple assignment operators it's less clear.
|
|
566
571
|
let tmpAbstractAssignToken = this.getTokenContainerObject('=');
|
|
567
572
|
tmpAbstractAssignToken.VirtualSymbolName = tmpResults.PostfixLayerstackMap[tmpSolveLayerTokens[0].SolveLayerStack];
|
|
568
573
|
// If this doesn't have a matching solvelayerstack, get the virtual symbol name from the parenthesis group it's in
|
|
@@ -637,7 +642,7 @@ class ExpressionParserPostfix extends libExpressionParserOperationBase
|
|
|
637
642
|
}
|
|
638
643
|
|
|
639
644
|
// 7. Lastly set the assignment address.
|
|
640
|
-
let tmpAbstractAssignToken = this.getTokenContainerObject('=');
|
|
645
|
+
let tmpAbstractAssignToken = ('PostfixedAssignmentOperator' in tmpResults) ? this.getTokenContainerObject(tmpResults.PostfixedAssignmentOperator.Token) : this.getTokenContainerObject('=');
|
|
641
646
|
// The address we are assigning to
|
|
642
647
|
tmpAbstractAssignToken.VirtualSymbolName = tmpResults.PostfixedAssignmentAddress;
|
|
643
648
|
// The address it's coming from
|
|
@@ -178,8 +178,22 @@ class ExpressionParserSolver extends libExpressionParserOperationBase
|
|
|
178
178
|
{
|
|
179
179
|
if (pPostfixedExpression[i].RightValue.Type === 'Token.SolverMarshal')
|
|
180
180
|
{
|
|
181
|
+
// Set the result in the virtual symbols
|
|
181
182
|
tmpManifest.setValueAtAddress(tmpResults.VirtualSymbols, pPostfixedExpression[i].VirtualSymbolName, tmpSolverResultValue);
|
|
182
|
-
|
|
183
|
+
// Set the value in the destination object
|
|
184
|
+
if (pPostfixedExpression[i].Operation.Descriptor.OnlyEmpty)
|
|
185
|
+
{
|
|
186
|
+
// If it is only on "empty" values, check if the value is empty before assigning
|
|
187
|
+
if (this.fable.Utility.addressIsNullOrEmpty(tmpDataDestinationObject, pPostfixedExpression[i].VirtualSymbolName))
|
|
188
|
+
{
|
|
189
|
+
tmpManifest.setValueByHash(tmpDataDestinationObject, pPostfixedExpression[i].VirtualSymbolName, tmpSolverResultValue);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
else
|
|
193
|
+
{
|
|
194
|
+
// Otherwise, just assign it.
|
|
195
|
+
tmpManifest.setValueByHash(tmpDataDestinationObject, pPostfixedExpression[i].VirtualSymbolName, tmpSolverResultValue);
|
|
196
|
+
}
|
|
183
197
|
}
|
|
184
198
|
}
|
|
185
199
|
tmpResults.RawResult = tmpSolverResultValue;
|
|
@@ -190,7 +204,14 @@ class ExpressionParserSolver extends libExpressionParserOperationBase
|
|
|
190
204
|
delete tmpResults.fable;
|
|
191
205
|
}
|
|
192
206
|
|
|
193
|
-
|
|
207
|
+
if (typeof(tmpSolverResultValue) !== 'undefined')
|
|
208
|
+
{
|
|
209
|
+
return tmpSolverResultValue.toString();
|
|
210
|
+
}
|
|
211
|
+
else
|
|
212
|
+
{
|
|
213
|
+
return tmpSolverResultValue;
|
|
214
|
+
}
|
|
194
215
|
}
|
|
195
216
|
}
|
|
196
217
|
|
package/source/services/Fable-Service-ExpressionParser/Fable-Service-ExpressionParser-TokenMap.json
CHANGED
|
@@ -1,13 +1,23 @@
|
|
|
1
1
|
{
|
|
2
2
|
"=":
|
|
3
3
|
{
|
|
4
|
-
"Name": "
|
|
4
|
+
"Name": "Assign Value",
|
|
5
5
|
"Token": "=",
|
|
6
6
|
"Function": "fable.Math.assignValue",
|
|
7
7
|
"Precedence": 0,
|
|
8
8
|
"Type": "Assignment"
|
|
9
9
|
},
|
|
10
10
|
|
|
11
|
+
"?=":
|
|
12
|
+
{
|
|
13
|
+
"Name": "Null or Empty Coalescing Assign Value",
|
|
14
|
+
"Token": "?=",
|
|
15
|
+
"Function": "fable.Math.assignValue",
|
|
16
|
+
"OnlyEmpty": true,
|
|
17
|
+
"Precedence": 0,
|
|
18
|
+
"Type": "Assignment"
|
|
19
|
+
},
|
|
20
|
+
|
|
11
21
|
"(":
|
|
12
22
|
{
|
|
13
23
|
"Name": "Left Parenthesis",
|
|
@@ -39,8 +39,47 @@ class FableServiceExpressionParser extends libFableServiceBase
|
|
|
39
39
|
|
|
40
40
|
// The configuration for tokens that the solver recognizes, with precedence and friendly names.
|
|
41
41
|
this.tokenMap = require('./Fable-Service-ExpressionParser/Fable-Service-ExpressionParser-TokenMap.json');
|
|
42
|
-
|
|
43
|
-
|
|
42
|
+
|
|
43
|
+
// Keep track of maximum token precedence
|
|
44
|
+
this.tokenMaxPrecedence = 4;
|
|
45
|
+
// This isn't exactly a radix tree but close enough. It's a map of the first character of the token to the token.
|
|
46
|
+
this.tokenRadix = {};
|
|
47
|
+
let tmpTokenKeys = Object.keys(this.tokenMap);
|
|
48
|
+
for (let i = 0; i < tmpTokenKeys.length; i++)
|
|
49
|
+
{
|
|
50
|
+
let tmpTokenKey = tmpTokenKeys[i];
|
|
51
|
+
let tmpToken = this.tokenMap[tmpTokenKey];
|
|
52
|
+
|
|
53
|
+
tmpToken.Token = tmpTokenKey;
|
|
54
|
+
tmpToken.Length = tmpTokenKey.length;
|
|
55
|
+
|
|
56
|
+
let tmpTokenStartCharacter = tmpToken.Token[0];
|
|
57
|
+
if (!(tmpTokenStartCharacter in this.tokenRadix))
|
|
58
|
+
{
|
|
59
|
+
// With a token count of 1 and a literal of true, we can assume it being in the radix is the token.
|
|
60
|
+
this.tokenRadix[tmpTokenStartCharacter] = (
|
|
61
|
+
{
|
|
62
|
+
TokenCount: 0,
|
|
63
|
+
Literal: false,
|
|
64
|
+
TokenKeys: [],
|
|
65
|
+
TokenMap: {}
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
this.tokenRadix[tmpTokenStartCharacter].TokenCount++;
|
|
70
|
+
if (tmpTokenKey == tmpTokenStartCharacter)
|
|
71
|
+
{
|
|
72
|
+
this.tokenRadix[tmpTokenStartCharacter].Literal = true;
|
|
73
|
+
}
|
|
74
|
+
this.tokenRadix[tmpTokenStartCharacter].TokenMap[tmpToken.Token] = tmpToken;
|
|
75
|
+
this.tokenRadix[tmpTokenStartCharacter].TokenKeys.push(tmpTokenKey);
|
|
76
|
+
this.tokenRadix[tmpTokenStartCharacter].TokenKeys.sort((pLeft, pRight) => pRight.length - pLeft.length);
|
|
77
|
+
|
|
78
|
+
if (this.tokenMaxPrecedence < tmpToken.Precedence)
|
|
79
|
+
{
|
|
80
|
+
this.tokenMaxPrecedence = tmpToken.Precedence;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
44
83
|
|
|
45
84
|
// The configuration for which functions are available to the solver.
|
|
46
85
|
this.functionMap = require('./Fable-Service-ExpressionParser/Fable-Service-ExpressionParser-FunctionMap.json');
|
|
@@ -126,7 +126,7 @@ class FableServiceMath extends libFableServiceBase
|
|
|
126
126
|
{
|
|
127
127
|
let tmpValue = isNaN(pValue) ? 0 : pValue;
|
|
128
128
|
let tmpDecimals = isNaN(pDecimals) ? 0 : parseInt(pDecimals, 10);
|
|
129
|
-
let tmpRoundingMethod = (typeof (pRoundingMethod) === 'undefined') ? this.roundHalfUp : pRoundingMethod;
|
|
129
|
+
let tmpRoundingMethod = (typeof (pRoundingMethod) === 'undefined') ? this.roundHalfUp : parseInt(pRoundingMethod, 10);
|
|
130
130
|
|
|
131
131
|
let tmpArbitraryValue = new this.fable.Utility.bigNumber(tmpValue);
|
|
132
132
|
let tmpResult = tmpArbitraryValue.round(tmpDecimals, tmpRoundingMethod);
|
|
@@ -561,27 +561,6 @@ class FableServiceMath extends libFableServiceBase
|
|
|
561
561
|
return this.bucketSetPrecise(pValueSet);
|
|
562
562
|
}
|
|
563
563
|
|
|
564
|
-
/**
|
|
565
|
-
*
|
|
566
|
-
* @param {Array<Object>} pValueSet - An array of objects
|
|
567
|
-
* @param {string} pValueMapAddress - The address in each object to find the value to count in the histogram. Undefined will be 'Unknown'.
|
|
568
|
-
*/
|
|
569
|
-
valueMapCountPrecise(pValueSet, pValueMapAddress)
|
|
570
|
-
{
|
|
571
|
-
let tmpHistogram = {};
|
|
572
|
-
|
|
573
|
-
this.log.info(`ValueSet is a ${typeof(pValueSet)}`);
|
|
574
|
-
this.log.info(`ValueMapAddress is ${pValueMapAddress}`);
|
|
575
|
-
|
|
576
|
-
return tmpHistogram;
|
|
577
|
-
}
|
|
578
|
-
|
|
579
|
-
valueMapSumPrecise(pValueSet, pValueMapAddress, pValueMapAmount)
|
|
580
|
-
{
|
|
581
|
-
|
|
582
|
-
}
|
|
583
|
-
|
|
584
|
-
|
|
585
564
|
/**
|
|
586
565
|
* Sorts the histogram object in ascending order based on the frequencies of the buckets.
|
|
587
566
|
*
|
|
@@ -601,7 +580,6 @@ class FableServiceMath extends libFableServiceBase
|
|
|
601
580
|
}
|
|
602
581
|
|
|
603
582
|
return tmpSortedHistogram;
|
|
604
|
-
|
|
605
583
|
}
|
|
606
584
|
|
|
607
585
|
/**
|
|
@@ -92,6 +92,51 @@ class FableServiceUtility extends libFableServiceBase
|
|
|
92
92
|
|
|
93
93
|
return tmpChunkCache;
|
|
94
94
|
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Get a value from fable/pict by hash/address
|
|
98
|
+
* @param {string} pValueAddress - The manyfest hash/address of the value to get
|
|
99
|
+
*/
|
|
100
|
+
getValue(pValueAddress)
|
|
101
|
+
{
|
|
102
|
+
// Lazily create a manifest if it doesn't exist
|
|
103
|
+
if (!this.manifest)
|
|
104
|
+
{
|
|
105
|
+
this.manifest = this.fable.newManyfest();
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Get the value from the internal manifest and return it
|
|
109
|
+
return this.manifest.getValueByHash(this.fable, pValueAddress);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Check if a value is null or empty
|
|
114
|
+
* @param {object} pObject - The object to check
|
|
115
|
+
* @param {string} pValueAddress - The manyfest hash/address to check
|
|
116
|
+
*/
|
|
117
|
+
addressIsNullOrEmpty(pObject, pValueAddress)
|
|
118
|
+
{
|
|
119
|
+
// Lazily create a manifest if it doesn't exist
|
|
120
|
+
if (!this.manifest)
|
|
121
|
+
{
|
|
122
|
+
this.manifest = this.fable.newManyfest();
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// If it doesn't exist, it is null or empty.
|
|
126
|
+
if (!this.manifest.checkAddressExists(pObject, pValueAddress))
|
|
127
|
+
{
|
|
128
|
+
return true;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Get the value from the internal manifest and return it
|
|
132
|
+
let tmpValue = this.manifest.getValueByHash(pObject, pValueAddress);
|
|
133
|
+
if (tmpValue === null || tmpValue === '')
|
|
134
|
+
{
|
|
135
|
+
return true;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return false;
|
|
139
|
+
}
|
|
95
140
|
|
|
96
141
|
// Convert an ISO string to a javascript date object
|
|
97
142
|
// Adapted from https://stackoverflow.com/a/54751179
|