fable 3.1.8 → 3.1.9

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 (26) hide show
  1. package/dist/fable.js +97 -52
  2. package/dist/fable.js.map +1 -1
  3. package/dist/fable.min.js +2 -2
  4. package/dist/fable.min.js.map +1 -1
  5. package/package.json +1 -1
  6. package/source/Fable.js +1 -0
  7. package/source/services/Fable-Service-DataFormat.js +65 -52
  8. package/source/services/Fable-Service-ExpressionParser/Fable-Service-ExpressionParser-ExpressionTokenizer.js +2 -2
  9. package/source/services/Fable-Service-ExpressionParser/Fable-Service-ExpressionParser-FunctionMap.json +24 -4
  10. package/source/services/Fable-Service-ExpressionParser/Fable-Service-ExpressionParser-Messaging.js +3 -3
  11. package/source/services/Fable-Service-ExpressionParser/Fable-Service-ExpressionParser-Postfix.js +25 -10
  12. package/source/services/Fable-Service-ExpressionParser/Fable-Service-ExpressionParser-SolvePostfixedExpression.js +20 -30
  13. package/source/services/Fable-Service-ExpressionParser/Fable-Service-ExpressionParser-TokenMap.json +2 -2
  14. package/source/services/Fable-Service-ExpressionParser/Fable-Service-ExpressionParser-ValueMarshal.js +2 -0
  15. package/source/services/Fable-Service-FilePersistence.js +3 -3
  16. package/source/services/Fable-Service-Logic.js +129 -0
  17. package/source/services/Fable-Service-Math.js +49 -11
  18. package/source/services/Fable-Service-Utility.js +35 -1
  19. package/source/services/Fable-SetConcatArray.js +25 -0
  20. package/test/CSVParser_tests.js +2 -2
  21. package/test/DataFormat-StringManipulation_tests.js +17 -1
  22. package/test/ExpressionParser_tests.js +154 -8
  23. package/test/FableOperation_tests.js +2 -2
  24. package/test/FilePersistence_tests.js +2 -2
  25. package/test/Math_test.js +4 -4
  26. package/test/MetaTemplating_tests.js +4 -4
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fable",
3
- "version": "3.1.8",
3
+ "version": "3.1.9",
4
4
  "description": "A service dependency injection, configuration and logging library.",
5
5
  "main": "source/Fable.js",
6
6
  "scripts": {
package/source/Fable.js CHANGED
@@ -75,6 +75,7 @@ class Fable extends libFableServiceBase.CoreServiceProviderBase
75
75
  this.addAndInstantiateServiceType('DataFormat', require('./services/Fable-Service-DataFormat.js'));
76
76
  this.addAndInstantiateServiceType('DataGeneration', require('./services/Fable-Service-DataGeneration.js'));
77
77
  this.addAndInstantiateServiceType('Utility', require('./services/Fable-Service-Utility.js'));
78
+ this.addAndInstantiateServiceType('Logic', require('./services/Fable-Service-Logic.js'));
78
79
  this.addAndInstantiateServiceType('Math', require('./services/Fable-Service-Math.js'));
79
80
  this.addServiceType('ExpressionParser', require('./services/Fable-Service-ExpressionParser.js'));
80
81
  this.addServiceType('RestClient', require('./services/Fable-Service-RestClient.js'));
@@ -22,6 +22,7 @@ class DataFormat extends libFableServiceProviderBase
22
22
  this._Regex_formatterDollarsRemoveCommas = /,/gi;
23
23
  this._Regex_formatterCleanNonAlphaChar = /[^a-zA-Z]/gi;
24
24
  this._Regex_formatterCapitalizeEachWord = /([a-zA-Z]+)/g;
25
+ this._Regex_matcherHTMLEntities = /&(#?[a-zA-Z0-9]+);/g;
25
26
 
26
27
  // TODO: Potentially pull these in from a configuration.
27
28
  // TODO: Use locale data for this if it's defaults all the way down.
@@ -157,6 +158,66 @@ class DataFormat extends libFableServiceProviderBase
157
158
  });
158
159
  }
159
160
 
161
+ /**
162
+ * @param {string} pString - The string to resolve
163
+ * @return {string} - The input string with all HTML entities resolved to their character counterparts
164
+ */
165
+ resolveHtmlEntities(pString)
166
+ {
167
+ if (typeof(pString) !== 'string')
168
+ {
169
+ return pString;
170
+ }
171
+
172
+ return pString.replace(this._Regex_matcherHTMLEntities, (pMatch, pEntity) =>
173
+ {
174
+ switch (pEntity)
175
+ {
176
+ case 'comma':
177
+ return ',';
178
+ case 'amp':
179
+ return '&';
180
+ case 'lt':
181
+ return '<';
182
+ case 'gt':
183
+ return '>';
184
+ case 'times':
185
+ return '×';
186
+ case 'divide':
187
+ return '÷';
188
+ case 'plus':
189
+ return '+';
190
+ case 'minus':
191
+ return '-';
192
+ case 'infin':
193
+ return '∞';
194
+ case 'ang':
195
+ return '∠';
196
+ case 'quot':
197
+ return '"';
198
+ case 'apos':
199
+ return '\'';
200
+ case 'nbsp':
201
+ return ' ';
202
+ case 'copy':
203
+ return '©';
204
+ case 'reg':
205
+ return '®';
206
+ case 'trade':
207
+ return '™';
208
+ case 'euro':
209
+ return '€';
210
+ default:
211
+ if (!pEntity.startsWith('#'))
212
+ {
213
+ return pMatch;
214
+ }
215
+ }
216
+ const tmpNumericalValue = parseInt(pEntity.substring(1), 10);
217
+ return String.fromCharCode(tmpNumericalValue);
218
+ });
219
+ }
220
+
160
221
  /**
161
222
  * Concatenate a list of strings together. Non-strings are excluded.
162
223
  *
@@ -177,19 +238,7 @@ class DataFormat extends libFableServiceProviderBase
177
238
  concatenateStringsInternal ()
178
239
  {
179
240
  const pParams = [ ...arguments ];
180
- const tmpArrayFlattener = (p) =>
181
- {
182
- if (Array.isArray(p))
183
- {
184
- return p.flatMap(tmpArrayFlattener);
185
- }
186
- if (typeof p === 'object')
187
- {
188
- return Object.values(p);
189
- }
190
- return [ p ];
191
- };
192
- const tmpFlattenedArrays = pParams.flatMap(tmpArrayFlattener);
241
+ const tmpFlattenedArrays = this.fable.Utility.flattenArrayOfSolverInputs(pParams);
193
242
 
194
243
  return this.concatenateStrings(...tmpFlattenedArrays);
195
244
  }
@@ -216,19 +265,7 @@ class DataFormat extends libFableServiceProviderBase
216
265
  joinStringsInternal()
217
266
  {
218
267
  const [ pJoinOn, ...pParams ] = arguments;
219
- const tmpArrayFlattener = (p) =>
220
- {
221
- if (Array.isArray(p))
222
- {
223
- return p.flatMap(tmpArrayFlattener);
224
- }
225
- if (typeof p === 'object')
226
- {
227
- return Object.values(p);
228
- }
229
- return [ p ];
230
- };
231
- const tmpFlattenedArrays = pParams.flatMap(tmpArrayFlattener);
268
+ const tmpFlattenedArrays = this.fable.Utility.flattenArrayOfSolverInputs(pParams);
232
269
 
233
270
  return this.joinStrings(pJoinOn, ...tmpFlattenedArrays);
234
271
  }
@@ -254,19 +291,7 @@ class DataFormat extends libFableServiceProviderBase
254
291
  concatenateStringsRawInternal (pValueObjectSetAddress)
255
292
  {
256
293
  const pParams = [ ...arguments ];
257
- const tmpArrayFlattener = (p) =>
258
- {
259
- if (Array.isArray(p))
260
- {
261
- return p.flatMap(tmpArrayFlattener);
262
- }
263
- if (typeof p === 'object')
264
- {
265
- return Object.values(p);
266
- }
267
- return [ p ];
268
- };
269
- const tmpFlattenedArrays = pParams.flatMap(tmpArrayFlattener);
294
+ const tmpFlattenedArrays = this.fable.Utility.flattenArrayOfSolverInputs(pParams);
270
295
 
271
296
  return this.concatenateStringsRaw(...tmpFlattenedArrays);
272
297
  }
@@ -293,19 +318,7 @@ class DataFormat extends libFableServiceProviderBase
293
318
  joinStringsRawInternal ()
294
319
  {
295
320
  const [ pJoinOn, ...pParams ] = arguments;
296
- const tmpArrayFlattener = (p) =>
297
- {
298
- if (Array.isArray(p))
299
- {
300
- return p.flatMap(tmpArrayFlattener);
301
- }
302
- if (typeof p === 'object')
303
- {
304
- return Object.values(p);
305
- }
306
- return [ p ];
307
- };
308
- const tmpFlattenedArrays = pParams.flatMap(tmpArrayFlattener);
321
+ const tmpFlattenedArrays = this.fable.Utility.flattenArrayOfSolverInputs(pParams);
309
322
 
310
323
  return this.joinStringsRaw(pJoinOn, ...tmpFlattenedArrays);
311
324
  }
@@ -45,8 +45,8 @@ class ExpressionTokenizer extends libExpressionParserOperationBase
45
45
  let tmpCharacter = pExpression[i];
46
46
 
47
47
  // [ WHITESPACE ]
48
- // 1. Space breaks tokens except when we're in an address that's been scoped by a {}
49
- if ((tmpCharacter === ' ') && ((tmpCurrentTokenType !== 'StateAddress') && (tmpCurrentTokenType !== 'String')))
48
+ // 1. Space breaks tokens except when we're in an address that's been scoped by a {} or ""
49
+ if ((tmpCharacter === ' ' || tmpCharacter === '\t') && ((tmpCurrentTokenType !== 'StateAddress') && (tmpCurrentTokenType !== 'String')))
50
50
  {
51
51
  if (tmpCurrentToken.length > 0)
52
52
  {
@@ -129,16 +129,26 @@
129
129
  "Address": "fable.Utility.getInternalValueByHash"
130
130
  },
131
131
 
132
+ "flatten": {
133
+ "Name": "flatten an array of values",
134
+ "Address": "fable.Utility.flattenArrayOfSolverInputs"
135
+ },
136
+
132
137
  "findfirstvaluebyexactmatch": {
133
- "Name": "find + map on array of objects?",
138
+ "Name": "find + map on array of objects",
134
139
  "Address": "fable.Utility.findFirstValueByExactMatchInternal"
135
140
  },
136
141
 
137
142
  "findfirstvaluebystringincludes": {
138
- "Name": "find + map on array of objects?",
143
+ "Name": "find + map on array of objects",
139
144
  "Address": "fable.Utility.findFirstValueByStringIncludesInternal"
140
145
  },
141
146
 
147
+ "resolvehtmlentities": {
148
+ "Name": "resolve HTML entities",
149
+ "Address": "fable.Utility.resolveHtmlEntities"
150
+ },
151
+
142
152
  "concat": {
143
153
  "Name": "concatenate an array of values and output a string",
144
154
  "Address": "fable.DataFormat.concatenateStringsInternal"
@@ -159,15 +169,25 @@
159
169
  "Address": "fable.DataFormat.joinStringsRawInternal"
160
170
  },
161
171
 
172
+ "if": {
173
+ "Name": "perform a conditional operator on two values, and choose one of two outcomes based on the result",
174
+ "Address": "fable.Logic.checkIf"
175
+ },
176
+
177
+ "when": {
178
+ "Name": "perform a 'truthy' check on one value, and return one of two outcomes based on the result",
179
+ "Address": "fable.Logic.when"
180
+ },
181
+
162
182
  "entryinset": {
163
183
  "Name": "Entry in Set",
164
184
  "Address": "fable.Math.entryInSet"
165
185
  },
166
- "smallestInSet": {
186
+ "smallestinset": {
167
187
  "Name": "Smallest in Set",
168
188
  "Address": "fable.Math.smallestInSet"
169
189
  },
170
- "largestInSet": {
190
+ "largestinset": {
171
191
  "Name": "Largest in Set",
172
192
  "Address": "fable.Math.largestInSet"
173
193
  },
@@ -25,7 +25,7 @@ class ExpressionParserMessaging extends libExpressionParserOperationBase
25
25
  {
26
26
  let tmpVirtualSymbol = this.getOperationVirtualSymbolName(pToken);
27
27
 
28
- if ((pToken.Type == 'Token.Constant') && (pToken.Value))
28
+ if ((pToken.Type == 'Token.Symbol' || pToken.Type == 'Token.Constant') && (pToken.Value))
29
29
  {
30
30
  return pToken.Value.toString();
31
31
  }
@@ -166,9 +166,9 @@ class ExpressionParserMessaging extends libExpressionParserOperationBase
166
166
  return;
167
167
  }
168
168
 
169
- for (let i = 0; i < tmpExpressionParseOutcome.PostfixSolveList.length; i++)
169
+ for (let i = 0; i < pResultObject.PostfixSolveList.length; i++)
170
170
  {
171
- let tmpToken = tmpExpressionParseOutcome.PostfixSolveList[i];
171
+ let tmpToken = pResultObject.PostfixSolveList[i];
172
172
  console.log(`${i}: ${tmpToken.VirtualSymbolName} = (${tmpToken.LeftValue.Token}::${tmpToken.LeftValue.Value}) ${tmpToken.Operation.Token} (${tmpToken.RightValue.Token}::${tmpToken.RightValue.Value}) `)
173
173
  }
174
174
 
@@ -243,6 +243,9 @@ class ExpressionParserPostfix extends libExpressionParserOperationBase
243
243
  // This maps layer stack addresses (which match parenthesis virtual symbol names) to the resultant value for that layer stack.
244
244
  // These values change as it solves but the last assignment is the proper assignment because math only reads forward in a line
245
245
  tmpResults.PostfixLayerstackMap = {};
246
+ //FIXME: vet these - do we need a suffix version?
247
+ const unaryEligibleOperationTokens = [ '+', '-' ];
248
+ const unaryOperationPrefixTriggerTypes = [ 'Token.Operator', 'Token.Assignment' ];
246
249
  for (let tmpSolveLayerIndex = 0; tmpSolveLayerIndex < tmpSolveLayerKeys.length; tmpSolveLayerIndex++)
247
250
  {
248
251
  let tmpSolveLayerTokens = tmpSolveLayerMap[tmpSolveLayerKeys[tmpSolveLayerIndex]];
@@ -253,11 +256,22 @@ class ExpressionParserPostfix extends libExpressionParserOperationBase
253
256
  // There is a recursive way to do this, but given the short length of even the most complex equations we're favoring readability.
254
257
  for (let i = 0; i < tmpSolveLayerTokens.length; i++)
255
258
  {
259
+ const tmpToken = tmpSolveLayerTokens[i];
260
+ if (unaryEligibleOperationTokens.includes(tmpToken.Token) &&
261
+ // promote to unary if:
262
+ // 1. we are the first token in our group
263
+ // 2. we are prefixed by a token type that is incompatible with us being binary
264
+ (i == 0 || unaryOperationPrefixTriggerTypes.includes(tmpSolveLayerTokens[i - 1].Type)))
265
+ {
266
+ //FIXME: slow, but don't break the static data
267
+ tmpToken.Descriptor = JSON.parse(JSON.stringify(tmpToken.Descriptor));
268
+ tmpToken.Descriptor.Precedence = 1;
269
+ }
270
+ //FIXME: handle operators with dynamic precedence (ex. unary vs. bunary + and -)
256
271
  // If the token is an operator and at the current precedence, add it to the postfix solve list and mutate the array.
257
272
  if ((tmpSolveLayerTokens[i].Type === 'Token.Operator') &&
258
- (tmpSolveLayerTokens[i].Descriptor.Precedence === tmpPrecedence))
273
+ (tmpToken.Descriptor.Precedence === tmpPrecedence))
259
274
  {
260
- let tmpToken = tmpSolveLayerTokens[i];
261
275
  // If there is a token and nothing else in this layer, then it's an error.
262
276
  if (tmpSolveLayerTokens.length === 1)
263
277
  {
@@ -265,6 +279,13 @@ class ExpressionParserPostfix extends libExpressionParserOperationBase
265
279
  this.log.error(tmpResults.ExpressionParserLog[tmpResults.ExpressionParserLog.length-1]);
266
280
  return tmpResults.PostfixSolveList;
267
281
  }
282
+ // The - at the beginning of an expression is a number line orientation modifier
283
+ else if ((i == 0) && (tmpToken.Token == '-' || tmpToken.Token == '+'))
284
+ {
285
+ tmpToken.VirtualSymbolName = `VNLO_${tmpVirtualSymbolIndex}`;
286
+ tmpResults.PostfixLayerstackMap[tmpToken.SolveLayerStack] = tmpToken.VirtualSymbolName;
287
+ tmpVirtualSymbolIndex++;
288
+ }
268
289
  // If the token is at the beginning of the expression and not a number line orientation modifier, it's an error.
269
290
  else if ((i == 0) && ((tmpToken.Token != '+') || (tmpToken.Token != '-')))
270
291
  {
@@ -280,13 +301,6 @@ class ExpressionParserPostfix extends libExpressionParserOperationBase
280
301
  return tmpResults.PostfixSolveList;
281
302
  }
282
303
 
283
- // The - at the beginning of an expression is a number line orientation modifier
284
- else if ((i == 0) && (tmpToken.Token == '-'))
285
- {
286
- tmpToken.VirtualSymbolName = `VNLO_${tmpVirtualSymbolIndex}`;
287
- tmpResults.PostfixLayerstackMap[tmpToken.SolveLayerStack] = tmpToken.VirtualSymbolName;
288
- tmpVirtualSymbolIndex++;
289
- }
290
304
  // The - after an operator or an open parenthesis is also a number line orientation modifier
291
305
  else if ((i > 0) && (tmpToken.Token == '-') && ((tmpSolveLayerTokens[i-1].Type === 'Token.Operator') || (tmpSolveLayerTokens[i-1].Token === '(')))
292
306
  {
@@ -306,7 +320,8 @@ class ExpressionParserPostfix extends libExpressionParserOperationBase
306
320
  }
307
321
 
308
322
  // If the token is next to another operator it's a parsing error
309
- else if ((tmpSolveLayerTokens[i-1].Type === 'Token.Operator') || (tmpSolveLayerTokens[i+1].Type === 'Token.Operator'))
323
+ else if (((tmpSolveLayerTokens[i-1].Type === 'Token.Operator') || (tmpSolveLayerTokens[i+1].Type === 'Token.Operator')) &&
324
+ (tmpSolveLayerTokens[i+1].Token != '-' && tmpSolveLayerTokens[i+1].Token != '+'))
310
325
  {
311
326
  tmpResults.ExpressionParserLog.push(`ERROR: ExpressionParser.buildPostfixedSolveList found an operator at token index ${i} that is not surrounded by two values.`);
312
327
  this.log.error(tmpResults.ExpressionParserLog[tmpResults.ExpressionParserLog.length-1]);
@@ -1,4 +1,5 @@
1
1
  const libExpressionParserOperationBase = require('./Fable-Service-ExpressionParser-Base.js');
2
+ const libSetConcatArray = require('../Fable-SetConcatArray.js');
2
3
 
3
4
  class ExpressionParserSolver extends libExpressionParserOperationBase
4
5
  {
@@ -41,6 +42,7 @@ class ExpressionParserSolver extends libExpressionParserOperationBase
41
42
 
42
43
  for (let i = 0; i < pPostfixedExpression.length; i++)
43
44
  {
45
+ // X = SUM(15, SUM(SIN(25), 10), (5 + 2), 3)
44
46
  if (pPostfixedExpression[i].Operation.Type === 'Token.SolverInstruction')
45
47
  {
46
48
  continue;
@@ -89,7 +91,7 @@ class ExpressionParserSolver extends libExpressionParserOperationBase
89
91
  {
90
92
  // TODO: This can be optimized. A lot. If necessary. Seems pretty fast honestly for even thousands of operations. Slowest part is arbitrary precision.
91
93
  // An operator always has a left and right value.
92
- let tmpFunctionAddress = false;
94
+ let tmpFunctionAddress;
93
95
  // Note: There are easier, passive ways of managing this state. But this is complex.
94
96
  let tmpIsFunction = false;
95
97
  if (tmpStepResultObject.ExpressionStep.Operation.Token in this.ExpressionParser.tokenMap)
@@ -106,39 +108,27 @@ class ExpressionParserSolver extends libExpressionParserOperationBase
106
108
  {
107
109
  try
108
110
  {
109
- //this.log.trace(`Solving Function Step ${i} [${tmpStepResultObject.ExpressionStep.VirtualSymbolName}] --> [${tmpStepResultObject.ExpressionStep.Operation.Token}]: ( ${tmpStepResultObject.ExpressionStep.LeftValue.Value} , ${tmpStepResultObject.ExpressionStep.RightValue.Value} )`);
110
- // Build the set of arguments to send to the functions.
111
- tmpStepResultObject.ExpressionStep.LeftValue.ArgumentString = '';
112
- if (typeof(tmpStepResultObject.ExpressionStep.LeftValue.Value) === 'undefined')
111
+ let tmpResult;
112
+ const tmpFunction = tmpManifest.getValueAtAddress(tmpStepResultObject, tmpFunctionAddress);
113
+ if (typeof tmpFunction === 'function')
113
114
  {
114
- tmpStepResultObject.ExpressionStep.LeftValue.Arguments = [];
115
- }
116
- else if (Array.isArray(tmpStepResultObject.ExpressionStep.LeftValue.Value))
117
- {
118
- // Allow for array-based math sets to just be pased through
119
- tmpStepResultObject.ExpressionStep.LeftValue.Arguments = tmpStepResultObject.ExpressionStep.LeftValue.Value;
120
- tmpStepResultObject.ExpressionStep.LeftValue.ArgumentString = 'ExpressionStep.LeftValue.Arguments';
121
- }
122
- else if (typeof(tmpStepResultObject.ExpressionStep.LeftValue.Value) === 'object')
123
- {
124
- // Allow for array-based math sets to just be pased through
125
- tmpStepResultObject.ExpressionStep.LeftValue.Arguments = tmpStepResultObject.ExpressionStep.LeftValue.Value;
126
- tmpStepResultObject.ExpressionStep.LeftValue.ArgumentString = 'ExpressionStep.LeftValue.Arguments';
127
- }
128
- else
129
- {
130
- // Allow for string-based math sets.
131
- tmpStepResultObject.ExpressionStep.LeftValue.Arguments = tmpStepResultObject.ExpressionStep.LeftValue.Value.toString().split(',');
132
- for (let j = 0; j < tmpStepResultObject.ExpressionStep.LeftValue.Arguments.length; j++)
115
+ let tmpFunctionBinding = null;
116
+ if (tmpFunctionAddress.includes('.'))
117
+ {
118
+ tmpFunctionBinding = tmpManifest.getValueAtAddress(tmpStepResultObject, tmpFunctionAddress.split('.').slice(0, -1).join('.'));
119
+ }
120
+ let tmpArguments = tmpStepResultObject.ExpressionStep.LeftValue.Value;
121
+ if (!(tmpArguments instanceof libSetConcatArray))
122
+ {
123
+ tmpArguments = [ tmpArguments ];
124
+ }
125
+ else
133
126
  {
134
- if (tmpStepResultObject.ExpressionStep.LeftValue.ArgumentString !== '')
135
- {
136
- tmpStepResultObject.ExpressionStep.LeftValue.ArgumentString += ',';
137
- }
138
- tmpStepResultObject.ExpressionStep.LeftValue.ArgumentString += `ExpressionStep.LeftValue.Arguments[${j}]`;
127
+ tmpArguments = tmpArguments.values;
139
128
  }
129
+ tmpResult = tmpFunction.apply(tmpFunctionBinding, tmpArguments);
140
130
  }
141
- tmpManifest.setValueAtAddress(tmpResults.VirtualSymbols, tmpStepResultObject.ExpressionStep.VirtualSymbolName, tmpManifest.getValueAtAddress(tmpStepResultObject, `${tmpFunctionAddress}(${tmpStepResultObject.ExpressionStep.LeftValue.ArgumentString})`));
131
+ tmpManifest.setValueAtAddress(tmpResults.VirtualSymbols, tmpStepResultObject.ExpressionStep.VirtualSymbolName, tmpResult);
142
132
  tmpResults.LastResult = tmpManifest.getValueAtAddress(tmpResults.VirtualSymbols, tmpStepResultObject.ExpressionStep.VirtualSymbolName);
143
133
  //this.log.trace(` ---> Step ${i}: ${tmpResults.VirtualSymbols[tmpStepResultObject.ExpressionStep.VirtualSymbolName]}`)
144
134
  }
@@ -63,7 +63,7 @@
63
63
  "Name": "Exponent",
64
64
  "Token": "^",
65
65
  "Function": "fable.Math.powerPrecise",
66
- "Precedence": 1,
66
+ "Precedence": 2,
67
67
  "Type": "Operator"
68
68
  },
69
69
  "%":
@@ -91,4 +91,4 @@
91
91
  "Precedence": 4,
92
92
  "Type": "Operator"
93
93
  }
94
- }
94
+ }
@@ -60,6 +60,8 @@ class ExpressionParserValueMarshal extends libExpressionParserOperationBase
60
60
  // }
61
61
  if (!tmpValue)
62
62
  {
63
+ tmpToken.Value = tmpValue;
64
+ tmpToken.Resolve = true;
63
65
  tmpResults.ExpressionParserLog.push(`WARNING: ExpressionParser.substituteValuesInTokenizedObjects found no value for the symbol hash or address ${tmpToken.Token} at index ${i}`);
64
66
  this.log.warn(tmpResults.ExpressionParserLog[tmpResults.ExpressionParserLog.length-1]);
65
67
  continue;
@@ -74,17 +74,17 @@ class FableServiceFilePersistence extends libFableServiceBase
74
74
  let tmpRecordFunction = (typeof(fRecordFunction) === 'function') ? fRecordFunction :
75
75
  (pRecord) =>
76
76
  {
77
- this.fable.log(`CSV Reader received line ${pRecord}`);
77
+ this.fable.log.trace(`CSV Reader received line ${pRecord}`);
78
78
  };
79
79
  let tmpCompleteFunction = (typeof(fCompleteFunction) === 'function') ? fCompleteFunction :
80
80
  () =>
81
81
  {
82
- this.fable.log(`CSV Read of ${pFilePath} complete.`);
82
+ this.fable.log.info(`CSV Read of ${pFilePath} complete.`);
83
83
  };
84
84
  let tmpErrorFunction = (typeof(fErrorFunction) === 'function') ? fErrorFunction :
85
85
  (pError) =>
86
86
  {
87
- this.fable.log(`CSV Read of ${pFilePath} Error: ${pError}`, pError);
87
+ this.fable.log.error(`CSV Read of ${pFilePath} Error: ${pError}`, pError);
88
88
  };
89
89
 
90
90
  return this.lineReaderFactory(pFilePath,
@@ -0,0 +1,129 @@
1
+ const libFableServiceBase = require('fable-serviceproviderbase');
2
+
3
+ class FableServiceLogic extends libFableServiceBase
4
+ {
5
+ /**
6
+ * @param {import('../Fable.js')} pFable - The fable object
7
+ * @param {Record<string, any>} [pOptions] - The options object
8
+ * @param {string} [pServiceHash] - The hash of the service
9
+ */
10
+ constructor(pFable, pOptions, pServiceHash)
11
+ {
12
+ super(pFable, pOptions, pServiceHash);
13
+ }
14
+
15
+ /**
16
+ * Find the first value in an object that contains a specific value
17
+ *
18
+ * @param {string|number} pLeft - The left value to check
19
+ * @param {string} pComparisonOperator - The comparison operator to use
20
+ * @param {string|number} pRight - The right value to check
21
+ * @param {any} pOnTrue - The value to return if the comparison is true
22
+ * @param {any} [pOnFalse = ''] - The value to return if the comparison is false
23
+ * @return {any} - The selected value
24
+ */
25
+ checkIf(pLeft, pComparisonOperator, pRight, pOnTrue, pOnFalse)
26
+ {
27
+ // precise numeric
28
+ // string (non-numeric)
29
+ let tmpMathLeft = this.fable.Math.parsePrecise(pLeft, null);
30
+ let tmpMathRight = this.fable.Math.parsePrecise(pRight, null);
31
+ let tmpCheckResult = false;
32
+ if (tmpMathLeft === null || tmpMathRight === null)
33
+ {
34
+ if (typeof pOnFalse === 'undefined')
35
+ {
36
+ pOnFalse = '';
37
+ }
38
+ switch (pComparisonOperator)
39
+ {
40
+ case '<':
41
+ case 'LT':
42
+ tmpCheckResult = pLeft < pRight;
43
+ break;
44
+ case '<=':
45
+ case 'LTE':
46
+ tmpCheckResult = pLeft <= pRight;
47
+ break;
48
+ case '>':
49
+ case 'GT':
50
+ tmpCheckResult = pLeft > pRight;
51
+ break;
52
+ case '>=':
53
+ case 'GTE':
54
+ tmpCheckResult = pLeft >= pRight;
55
+ break;
56
+ case '==':
57
+ tmpCheckResult = pLeft == pRight;
58
+ case '===':
59
+ tmpCheckResult = pLeft === pRight;
60
+ default:
61
+ this.fable.log.warn(`[FableServiceLogic.checkIf] Invalid comparison operator: ${pComparisonOperator}`);
62
+ tmpCheckResult = pLeft == pRight;
63
+ }
64
+ }
65
+ else
66
+ {
67
+ if (typeof pOnFalse === 'undefined')
68
+ {
69
+ pOnFalse = '0';
70
+ }
71
+ switch (pComparisonOperator)
72
+ {
73
+ case '<':
74
+ case 'LT':
75
+ tmpCheckResult = this.fable.Math.ltPrecise(tmpMathLeft, tmpMathRight);
76
+ break;
77
+ case '<=':
78
+ case 'LTE':
79
+ tmpCheckResult = this.fable.Math.ltePrecise(tmpMathLeft, tmpMathRight);
80
+ break;
81
+ case '>':
82
+ case 'GT':
83
+ tmpCheckResult = this.fable.Math.gtPrecise(tmpMathLeft, tmpMathRight);
84
+ break;
85
+ case '>=':
86
+ case 'GTE':
87
+ tmpCheckResult = this.fable.Math.gtePrecise(tmpMathLeft, tmpMathRight);
88
+ break;
89
+ case '==':
90
+ tmpCheckResult = this.fable.Math.comparePreciseWithin(tmpMathLeft, tmpMathRight, '0.000001') == 0;
91
+ break;
92
+ case '===':
93
+ tmpCheckResult = this.fable.Math.comparePrecise(tmpMathLeft, tmpMathRight) == 0;
94
+ break;
95
+ default:
96
+ this.fable.log.warn(`[FableServiceLogic.checkIf] Invalid comparison operator: ${pComparisonOperator}`);
97
+ tmpCheckResult = pLeft == pRight ? pOnTrue : pOnFalse;
98
+ }
99
+ }
100
+ return tmpCheckResult ? pOnTrue : pOnFalse;
101
+ }
102
+
103
+ /**
104
+ * Find the first value in an object that contains a specific value
105
+ *
106
+ * @param {any} pCheckForTruthy - The object to check
107
+ * @param {any} pOnTrue - The value to return if the object is truthy
108
+ * @param {any} [pOnFalse = ''] - The value to return if the object is falsy
109
+ * @return {any} - The value from the object
110
+ */
111
+ when(pCheckForTruthy, pOnTrue, pOnFalse = '')
112
+ {
113
+ if (!pCheckForTruthy)
114
+ {
115
+ return pOnFalse;
116
+ }
117
+ if (Array.isArray(pCheckForTruthy) && pCheckForTruthy.length < 1)
118
+ {
119
+ return pOnFalse;
120
+ }
121
+ if (typeof pCheckForTruthy === 'object' && Object.keys(pCheckForTruthy).length < 1)
122
+ {
123
+ return pOnFalse;
124
+ }
125
+ return pOnTrue;
126
+ }
127
+ }
128
+
129
+ module.exports = FableServiceLogic;