fable 3.0.134 → 3.0.136
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.compatible.js +137 -65
- package/dist/fable.compatible.min.js +2 -2
- package/dist/fable.compatible.min.js.map +1 -1
- package/dist/fable.js +106 -34
- package/dist/fable.min.js +2 -2
- package/dist/fable.min.js.map +1 -1
- package/example_applications/mathematical_playground/.vscode/launch.json +17 -0
- package/example_applications/mathematical_playground/Math-Solver-Harness.js +31 -34
- package/package.json +1 -1
- package/source/services/Fable-Service-ExpressionParser/Fable-Service-ExpressionParser-ExpressionTokenizer.js +0 -1
- package/source/services/Fable-Service-ExpressionParser/Fable-Service-ExpressionParser-Linter.js +0 -1
- package/source/services/Fable-Service-ExpressionParser/Fable-Service-ExpressionParser-Messaging.js +159 -0
- package/source/services/Fable-Service-ExpressionParser/Fable-Service-ExpressionParser-Postfix.js +7 -4
- package/source/services/Fable-Service-ExpressionParser/Fable-Service-ExpressionParser-SolvePostfixedExpression.js +18 -9
- package/source/services/Fable-Service-ExpressionParser/Fable-Service-ExpressionParser-ValueMarshal.js +146 -0
- package/source/services/Fable-Service-ExpressionParser.js +77 -127
|
@@ -14,7 +14,7 @@ const libFableServiceBase = require('fable-serviceproviderbase');
|
|
|
14
14
|
* Learned a lot from this npm package: https://www.npmjs.com/package/math-expression-evaluator
|
|
15
15
|
* And its related code at github: https://github.com/bugwheels94/math-expression-evaluator
|
|
16
16
|
*
|
|
17
|
-
* There were two problems with the codebase...
|
|
17
|
+
* There were two problems with the codebase above...
|
|
18
18
|
*
|
|
19
19
|
* First, the code was very unreadable and determining it was correct or extending it
|
|
20
20
|
* was out of the question.
|
|
@@ -22,178 +22,124 @@ const libFableServiceBase = require('fable-serviceproviderbase');
|
|
|
22
22
|
* Second, and this is a larger issue, is that we need the expressions to be parsed as
|
|
23
23
|
* arbitrary precision. When I determined that extending the library to use string-based
|
|
24
24
|
* numbers and an arbitrary precision library as the back-end would have taken a significantly
|
|
25
|
-
* longer amount of time than just writing the parser from scratch
|
|
25
|
+
* longer amount of time than just writing the parser from scratch... et voila.
|
|
26
26
|
*/
|
|
27
27
|
|
|
28
28
|
class FableServiceExpressionParser extends libFableServiceBase
|
|
29
29
|
{
|
|
30
|
+
/**
|
|
31
|
+
* Constructs a new instance of the ExpressionParser service.
|
|
32
|
+
* @param {Object} pFable - The Fable object.
|
|
33
|
+
* @param {Object} pOptions - The options for the service.
|
|
34
|
+
* @param {string} pServiceHash - The hash of the service.
|
|
35
|
+
*/
|
|
30
36
|
constructor(pFable, pOptions, pServiceHash)
|
|
31
37
|
{
|
|
32
38
|
super(pFable, pOptions, pServiceHash);
|
|
33
39
|
|
|
40
|
+
// The configuration for tokens that the solver recognizes, with precedence and friendly names.
|
|
34
41
|
this.tokenMap = require('./Fable-Service-ExpressionParser/Fable-Service-ExpressionParser-TokenMap.json');
|
|
35
|
-
// This precedence is higher than defined in our token map
|
|
36
|
-
this.tokenMaxPrecedence =
|
|
42
|
+
// This precedence is higher than defined in our token map. We could make this value dynamic
|
|
43
|
+
this.tokenMaxPrecedence = 10;
|
|
37
44
|
|
|
45
|
+
// The configuration for which functions are available to the solver.
|
|
38
46
|
this.functionMap = require('./Fable-Service-ExpressionParser/Fable-Service-ExpressionParser-FunctionMap.json');
|
|
39
47
|
|
|
40
48
|
this.serviceType = 'ExpressionParser';
|
|
41
49
|
|
|
50
|
+
// These are sub-services for the tokenizer, linter, compiler, marshaler and solver.
|
|
42
51
|
this.fable.addServiceTypeIfNotExists('ExpressionParser-Tokenizer', require('./Fable-Service-ExpressionParser/Fable-Service-ExpressionParser-ExpressionTokenizer.js'));
|
|
43
52
|
this.fable.addServiceTypeIfNotExists('ExpressionParser-Linter', require('./Fable-Service-ExpressionParser/Fable-Service-ExpressionParser-Linter.js'));
|
|
44
53
|
this.fable.addServiceTypeIfNotExists('ExpressionParser-Postfix', require('./Fable-Service-ExpressionParser/Fable-Service-ExpressionParser-Postfix.js'));
|
|
54
|
+
this.fable.addServiceTypeIfNotExists('ExpressionParser-ValueMarshal', require('./Fable-Service-ExpressionParser/Fable-Service-ExpressionParser-ValueMarshal.js'));
|
|
45
55
|
this.fable.addServiceTypeIfNotExists('ExpressionParser-Solver', require('./Fable-Service-ExpressionParser/Fable-Service-ExpressionParser-SolvePostfixedExpression.js'));
|
|
56
|
+
// And the sub-service for the friendly user messaging
|
|
57
|
+
this.fable.addServiceTypeIfNotExists('ExpressionParser-Messaging', require('./Fable-Service-ExpressionParser/Fable-Service-ExpressionParser-Messaging.js'));
|
|
58
|
+
|
|
46
59
|
|
|
60
|
+
// This code instantitates these fable services to child objects of this service, but does not pollute the main fable with them.
|
|
47
61
|
this.Tokenizer = this.fable.instantiateServiceProviderWithoutRegistration('ExpressionParser-Tokenizer');
|
|
48
62
|
this.Linter = this.fable.instantiateServiceProviderWithoutRegistration('ExpressionParser-Linter');
|
|
49
63
|
this.Postfix = this.fable.instantiateServiceProviderWithoutRegistration('ExpressionParser-Postfix');
|
|
64
|
+
this.ValueMarshal = this.fable.instantiateServiceProviderWithoutRegistration('ExpressionParser-ValueMarshal');
|
|
50
65
|
this.Solver = this.fable.instantiateServiceProviderWithoutRegistration('ExpressionParser-Solver');
|
|
66
|
+
this.Messaging = this.fable.instantiateServiceProviderWithoutRegistration('ExpressionParser-Messaging');
|
|
51
67
|
|
|
52
|
-
//
|
|
68
|
+
// Wire each sub service into this instance of the solver.
|
|
53
69
|
this.Tokenizer.connectExpressionParser(this);
|
|
54
70
|
this.Linter.connectExpressionParser(this);
|
|
55
71
|
this.Postfix.connectExpressionParser(this);
|
|
72
|
+
this.ValueMarshal.connectExpressionParser(this);
|
|
56
73
|
this.Solver.connectExpressionParser(this);
|
|
57
|
-
|
|
74
|
+
this.Messaging.connectExpressionParser(this);
|
|
58
75
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
if (!Array.isArray(pTokenizedObjects))
|
|
64
|
-
{
|
|
65
|
-
tmpResults.ExpressionParserLog.push(`ERROR: ExpressionParser.substituteValuesInTokenizedObjects was passed a non-array tokenized object list.`);
|
|
66
|
-
this.log.error(tmpResults.ExpressionParserLog[tmpResults.ExpressionParserLog.length-1]);
|
|
67
|
-
return pTokenizedObjects;
|
|
68
|
-
}
|
|
69
|
-
if (typeof(pDataSource) !== 'object')
|
|
70
|
-
{
|
|
71
|
-
tmpResults.ExpressionParserLog.push(`ERROR: ExpressionParser.substituteValuesInTokenizedObjects either was passed no data source, or was passed a non-object data source.`);
|
|
72
|
-
this.log.error(tmpResults.ExpressionParserLog[tmpResults.ExpressionParserLog.length-1]);
|
|
73
|
-
return pTokenizedObjects;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
let tmpDataSource = pDataSource;
|
|
77
|
-
|
|
78
|
-
let tmpManifest = (typeof(pManifest) == 'object') ? pManifest : this.fable.newManyfest(pManifest);
|
|
79
|
-
|
|
80
|
-
for (let i = 0; i < pTokenizedObjects.length; i++)
|
|
81
|
-
{
|
|
82
|
-
if (typeof(pTokenizedObjects[i]) !== 'object')
|
|
83
|
-
{
|
|
84
|
-
tmpResults.ExpressionParserLog.push(`WARNING: ExpressionParser.substituteValuesInTokenizedObjects found a non-object tokenized object at index ${i}`);
|
|
85
|
-
this.log.warn(tmpResults.ExpressionParserLog[tmpResults.ExpressionParserLog.length-1]);
|
|
86
|
-
continue;
|
|
87
|
-
}
|
|
88
|
-
let tmpToken = pTokenizedObjects[i];
|
|
89
|
-
if ((pTokenizedObjects[i].Type === 'Token.Symbol') && !tmpToken.Resolved)
|
|
90
|
-
{
|
|
91
|
-
// Symbols always look up values by hash first
|
|
92
|
-
let tmpValue = tmpManifest.getValueByHash(tmpDataSource, tmpToken.Token);
|
|
93
|
-
// if (!tmpValue)
|
|
94
|
-
// {
|
|
95
|
-
// // If no hash resolves, try by address.
|
|
96
|
-
// tmpValue = tmpManifest.getValueAtAddress(tmpToken.Token, tmpDataSource);
|
|
97
|
-
// }
|
|
98
|
-
if (!tmpValue)
|
|
99
|
-
{
|
|
100
|
-
tmpResults.ExpressionParserLog.push(`WARNING: ExpressionParser.substituteValuesInTokenizedObjects found no value for the symbol hash or address ${tmpToken.Token} at index ${i}`);
|
|
101
|
-
this.log.warn(tmpResults.ExpressionParserLog[tmpResults.ExpressionParserLog.length-1]);
|
|
102
|
-
continue;
|
|
103
|
-
}
|
|
104
|
-
else
|
|
105
|
-
{
|
|
106
|
-
tmpResults.ExpressionParserLog.push(`INFO: ExpressionParser.substituteValuesInTokenizedObjects found a value [${tmpValue}] for the state address ${tmpToken.Token} at index ${i}`);
|
|
107
|
-
this.log.info(tmpResults.ExpressionParserLog[tmpResults.ExpressionParserLog.length-1]);
|
|
108
|
-
try
|
|
109
|
-
{
|
|
110
|
-
let tmpValueParsed = new this.fable.Utility.bigNumber(tmpValue);
|
|
111
|
-
tmpToken.Resolved = true;
|
|
112
|
-
tmpToken.Value = tmpValueParsed.toString();
|
|
113
|
-
}
|
|
114
|
-
catch(pError)
|
|
115
|
-
{
|
|
116
|
-
// TODO: Should we allow this to be a function? Good god the complexity and beauty of that...
|
|
117
|
-
if (Array.isArray(tmpValue) || (typeof(tmpValue) === 'object'))
|
|
118
|
-
{
|
|
119
|
-
tmpToken.Resolved = true;
|
|
120
|
-
tmpToken.Value = tmpValue;
|
|
121
|
-
}
|
|
122
|
-
else
|
|
123
|
-
{
|
|
124
|
-
tmpResults.ExpressionParserLog.push(`INFO: ExpressionParser.substituteValuesInTokenizedObjects found a non-numeric, non-set value for the state address ${tmpToken.Token} at index ${i}`);
|
|
125
|
-
this.log.error(tmpResults.ExpressionParserLog[tmpResults.ExpressionParserLog.length-1]);
|
|
126
|
-
tmpToken.Resolved = false;
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
if ((pTokenizedObjects[i].Type === 'Token.StateAddress') && !tmpToken.Resolved)
|
|
132
|
-
{
|
|
133
|
-
// Symbols are always hashes. This gracefully works for simple shallow objects because hashes default to the address in Manyfest.
|
|
134
|
-
let tmpValue = tmpManifest.getValueAtAddress(tmpDataSource, tmpToken.Token);
|
|
135
|
-
if (!tmpValue)
|
|
136
|
-
{
|
|
137
|
-
tmpResults.ExpressionParserLog.push(`WARNING: ExpressionParser.substituteValuesInTokenizedObjects found no value for the state address ${tmpToken.Token} at index ${i}`);
|
|
138
|
-
this.log.warn(tmpResults.ExpressionParserLog[tmpResults.ExpressionParserLog.length-1]);
|
|
139
|
-
continue;
|
|
140
|
-
}
|
|
141
|
-
else
|
|
142
|
-
{
|
|
143
|
-
tmpResults.ExpressionParserLog.push(`INFO: ExpressionParser.substituteValuesInTokenizedObjects found a value [${tmpValue}] for the state address ${tmpToken.Token} at index ${i}`);
|
|
144
|
-
this.log.info(tmpResults.ExpressionParserLog[tmpResults.ExpressionParserLog.length-1]);
|
|
145
|
-
try
|
|
146
|
-
{
|
|
147
|
-
let tmpValueParsed = new this.fable.Utility.bigNumber(tmpValue);
|
|
148
|
-
tmpToken.Resolved = true;
|
|
149
|
-
tmpToken.Value = tmpValueParsed.toString();
|
|
150
|
-
}
|
|
151
|
-
catch(pError)
|
|
152
|
-
{
|
|
153
|
-
tmpResults.ExpressionParserLog.push(`ERROR: ExpressionParser.substituteValuesInTokenizedObjects found a non-numeric value for the state address ${tmpToken.Token} at index ${i}`);
|
|
154
|
-
this.log.error(tmpResults.ExpressionParserLog[tmpResults.ExpressionParserLog.length-1]);
|
|
155
|
-
tmpToken.Resolved = false;
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
if ((pTokenizedObjects[i].Type === 'Token.Constant') && !tmpToken.Resolved)
|
|
160
|
-
{
|
|
161
|
-
tmpResults.ExpressionParserLog.push(`INFO: ExpressionParser.substituteValuesInTokenizedObjects found a value [${tmpToken.Token}] for the constant ${tmpToken.Token} at index ${i}`);
|
|
162
|
-
this.log.info(tmpResults.ExpressionParserLog[tmpResults.ExpressionParserLog.length-1]);
|
|
163
|
-
try
|
|
164
|
-
{
|
|
165
|
-
let tmpValueParsed = new this.fable.Utility.bigNumber(tmpToken.Token);
|
|
166
|
-
tmpToken.Resolved = true;
|
|
167
|
-
tmpToken.Value = tmpValueParsed.toString();
|
|
168
|
-
}
|
|
169
|
-
catch(pError)
|
|
170
|
-
{
|
|
171
|
-
// This constant has the right symbols but apparently isn't a parsable number.
|
|
172
|
-
tmpResults.ExpressionParserLog.push(`ERROR: ExpressionParser.substituteValuesInTokenizedObjects found a non-numeric value for the state address ${tmpToken.Token} at index ${i}`);
|
|
173
|
-
this.log.error(tmpResults.ExpressionParserLog[tmpResults.ExpressionParserLog.length-1]);
|
|
174
|
-
tmpToken.Resolved = false;
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
return pTokenizedObjects;
|
|
76
|
+
this.GenericManifest = this.fable.newManyfest();
|
|
77
|
+
|
|
78
|
+
// This will look for a LogNoisiness on fable (or one that falls in from pict) and if it doesn't exist, set one for this service.
|
|
79
|
+
this.LogNoisiness = ('LogNoisiness' in this.fable) ? this.fable.LogNoisiness : 0;
|
|
180
80
|
}
|
|
181
81
|
|
|
82
|
+
/**
|
|
83
|
+
* Tokenizes the given mathematical expression string.
|
|
84
|
+
*
|
|
85
|
+
* @param {string} pExpression - The expression to tokenize.
|
|
86
|
+
* @param {object} pResultObject - The result object to store the tokenized expression.
|
|
87
|
+
* @returns {object} - The tokenized expression.
|
|
88
|
+
*/
|
|
182
89
|
tokenize(pExpression, pResultObject)
|
|
183
90
|
{
|
|
184
91
|
return this.Tokenizer.tokenize(pExpression, pResultObject);
|
|
185
92
|
}
|
|
186
93
|
|
|
94
|
+
/**
|
|
95
|
+
* Lints a tokenized expression.
|
|
96
|
+
*
|
|
97
|
+
* @param {Array} pTokenizedExpression - The tokenized expression to lint.
|
|
98
|
+
* @param {Object} pResultObject - The result object where we store the linting result.
|
|
99
|
+
* @returns {Object} - The linting result object.
|
|
100
|
+
*/
|
|
187
101
|
lintTokenizedExpression(pTokenizedExpression, pResultObject)
|
|
188
102
|
{
|
|
189
103
|
return this.Linter.lintTokenizedExpression(pTokenizedExpression, pResultObject);
|
|
190
104
|
}
|
|
191
105
|
|
|
106
|
+
/**
|
|
107
|
+
* Builds a postfix solve list for the given tokenized expression and result object.
|
|
108
|
+
*
|
|
109
|
+
* @param {Array} pTokenizedExpression - The tokenized expression.
|
|
110
|
+
* @param {Object} pResultObject - The result object where the algorithm "shows its work".
|
|
111
|
+
* @returns {Array} The postfix solve list.
|
|
112
|
+
*/
|
|
192
113
|
buildPostfixedSolveList(pTokenizedExpression, pResultObject)
|
|
193
114
|
{
|
|
194
115
|
return this.Postfix.buildPostfixedSolveList(pTokenizedExpression, pResultObject);
|
|
195
116
|
}
|
|
196
117
|
|
|
118
|
+
/**
|
|
119
|
+
* Substitutes values in tokenized objects.
|
|
120
|
+
*
|
|
121
|
+
* This means marshaling data from pDataSource into the array of objects with the passed in Manifest (or a generic manifest) to prepare for solving.
|
|
122
|
+
*
|
|
123
|
+
* @param {Array} pTokenizedObjects - The array of tokenized objects.
|
|
124
|
+
* @param {Object} pDataSource - The data source object.
|
|
125
|
+
* @param {Object} pResultObject - The result object.
|
|
126
|
+
* @param {Object} pManifest - The manifest object.
|
|
127
|
+
* @returns {Object} - The updated result object.
|
|
128
|
+
*/
|
|
129
|
+
substituteValuesInTokenizedObjects(pTokenizedObjects, pDataSource, pResultObject, pManifest)
|
|
130
|
+
{
|
|
131
|
+
return this.ValueMarshal.substituteValuesInTokenizedObjects(pTokenizedObjects, pDataSource, pResultObject, pManifest);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Solves a postfixed expression Array.
|
|
136
|
+
*
|
|
137
|
+
* @param {Array} pPostfixedExpression - The postfixed expression to solve.
|
|
138
|
+
* @param {object} pDataDestinationObject - The data destination object where data gets marshaled to after solving.
|
|
139
|
+
* @param {object} pResultObject - The result object where the algorithm "shows its work".
|
|
140
|
+
* @param {object} pManifest - The manifest object.
|
|
141
|
+
* @returns {any} The result of the solved expression.
|
|
142
|
+
*/
|
|
197
143
|
solvePostfixedExpression(pPostfixedExpression, pDataDestinationObject, pResultObject, pManifest)
|
|
198
144
|
{
|
|
199
145
|
return this.Solver.solvePostfixedExpression(pPostfixedExpression, pDataDestinationObject, pResultObject, pManifest);
|
|
@@ -215,11 +161,15 @@ class FableServiceExpressionParser extends libFableServiceBase
|
|
|
215
161
|
let tmpDataSourceObject = (typeof(pDataSourceObject) === 'object') ? pDataSourceObject : {};
|
|
216
162
|
let tmpDataDestinationObject = (typeof(pDataDestinationObject) === 'object') ? pDataDestinationObject : {};
|
|
217
163
|
|
|
164
|
+
// This is technically a "pre-compile" and we can keep this Results Object around to reuse for better performance. Not required.
|
|
218
165
|
this.tokenize(pExpression, tmpResultsObject);
|
|
219
166
|
this.lintTokenizedExpression(tmpResultsObject.RawTokens, tmpResultsObject);
|
|
220
167
|
this.buildPostfixedSolveList(tmpResultsObject.RawTokens, tmpResultsObject);
|
|
221
168
|
|
|
169
|
+
// This is where the data from variables gets marshaled into their symbols (from AppData or the like)
|
|
222
170
|
this.substituteValuesInTokenizedObjects(tmpResultsObject.PostfixTokenObjects, tmpDataSourceObject, tmpResultsObject, pManifest);
|
|
171
|
+
|
|
172
|
+
// Finally this is the expr solving method, which returns a string and also marshals it into tmpDataDestinationObject
|
|
223
173
|
return this.solvePostfixedExpression(tmpResultsObject.PostfixSolveList, tmpDataDestinationObject, tmpResultsObject, pManifest);
|
|
224
174
|
}
|
|
225
175
|
}
|