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.
@@ -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, et voila.
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 = 5;
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
- // Now wire each of these up. Not in love with this pattern but better than a giant file here.
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
- substituteValuesInTokenizedObjects(pTokenizedObjects, pDataSource, pResultObject, pManifest)
60
- {
61
- let tmpResults = (typeof(pResultObject) === 'object') ? pResultObject : { ExpressionParserLog: [] };
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
  }