@fw-components/formula-editor 2.0.7-formula-editor.22 → 2.0.7-formula-editor.23
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.
|
@@ -12,7 +12,7 @@ let SuggestionMenu = class SuggestionMenu extends LitElement {
|
|
|
12
12
|
super(...arguments);
|
|
13
13
|
this.recommendations = [];
|
|
14
14
|
this.onClickRecommendation = (recommendation) => { };
|
|
15
|
-
this._selectedRecommendationIndex =
|
|
15
|
+
this._selectedRecommendationIndex = -1;
|
|
16
16
|
}
|
|
17
17
|
scrollToSelectedRecommendation(index) {
|
|
18
18
|
const listItem = this.suggestionList?.querySelectorAll("li")[index];
|
|
@@ -3,14 +3,19 @@ import { Recommender } from "./recommendor.js";
|
|
|
3
3
|
import { Stack } from "./stack.js";
|
|
4
4
|
import { Queue } from "./queue.js";
|
|
5
5
|
import { Expectation } from "../types";
|
|
6
|
-
import { mathematicalOperators, operatorPrecedence } from "./constants.js";
|
|
6
|
+
import { mathematicalOperators, operatorPrecedence, unaryOperators } from "./constants.js";
|
|
7
7
|
export class Parser {
|
|
8
8
|
constructor(variables, minSuggestionLen) {
|
|
9
9
|
this.variables = variables;
|
|
10
10
|
this._recommender = new Recommender(Array.from(this.variables.keys()), minSuggestionLen);
|
|
11
11
|
}
|
|
12
|
+
getFormulaTokens(formulaString) {
|
|
13
|
+
if (!formulaString?.length)
|
|
14
|
+
return [];
|
|
15
|
+
return formulaString.match(/'[^']*'|\d+|[A-Za-z_][A-Za-z0-9_]*|[-+(),*^/:?\s]/g);
|
|
16
|
+
}
|
|
12
17
|
parseInput(formula, prevCurPos = null, recommendation = null) {
|
|
13
|
-
const tokens =
|
|
18
|
+
const tokens = this.getFormulaTokens(formula);
|
|
14
19
|
const parentheses = new Stack();
|
|
15
20
|
let expectation = Expectation.VARIABLE;
|
|
16
21
|
let currentPosition = 0;
|
|
@@ -80,7 +85,7 @@ export class Parser {
|
|
|
80
85
|
* No error for Unary `+` and `-` as they might represent a positive or negative number respectively
|
|
81
86
|
*/
|
|
82
87
|
else if (expectation === Expectation.VARIABLE && !isNumber && !isSpace && token != "("
|
|
83
|
-
&& !((token
|
|
88
|
+
&& !((unaryOperators.includes(token)) && (!parsedString.trim() || previousToken === "(" || mathematicalOperators.has(previousToken)))) {
|
|
84
89
|
parseOutput.errorString = `Expected variable/number at position ${currentPosition}`;
|
|
85
90
|
expectation = Expectation.UNDEFINED;
|
|
86
91
|
}
|
|
@@ -150,20 +155,16 @@ export class Parser {
|
|
|
150
155
|
return parseOutput;
|
|
151
156
|
}
|
|
152
157
|
buildRPN(formula) {
|
|
153
|
-
if (this.parseInput(formula).errorString)
|
|
158
|
+
if (this.parseInput(formula).errorString)
|
|
154
159
|
return null;
|
|
155
|
-
|
|
156
|
-
const tokens = formula
|
|
157
|
-
.match(/'[^']*'|\d+|[A-Za-z_][A-Za-z0-9_]*|[-+(),*^/:?\s]/g)
|
|
158
|
-
?.filter((el) => !/\s+/.test(el) && el !== "");
|
|
159
|
-
// Handling the special case of unary `-` and `+`.
|
|
160
|
+
const tokens = this.getFormulaTokens(formula)?.filter((el) => !/\s+/.test(el) && el !== "");
|
|
160
161
|
let previousToken = "";
|
|
161
162
|
let carriedToken = null;
|
|
162
163
|
const parsedTokens = [];
|
|
163
164
|
let currentTokens = "";
|
|
165
|
+
// Check if variables include unary operators `-` and `+`.
|
|
164
166
|
for (const token of tokens) {
|
|
165
|
-
if ((token
|
|
166
|
-
(!currentTokens.trim() || previousToken === "(" || mathematicalOperators.has(previousToken))) {
|
|
167
|
+
if ((unaryOperators.includes(token)) && (!currentTokens.trim() || previousToken === "(" || mathematicalOperators.has(previousToken))) {
|
|
167
168
|
carriedToken = token;
|
|
168
169
|
}
|
|
169
170
|
else if (carriedToken) {
|
|
@@ -192,9 +193,7 @@ export class Parser {
|
|
|
192
193
|
operatorStack.pop();
|
|
193
194
|
}
|
|
194
195
|
else if (mathematicalOperators.has(token)) {
|
|
195
|
-
while (mathematicalOperators.has(operatorStack.top()) &&
|
|
196
|
-
operatorPrecedence[token] <=
|
|
197
|
-
operatorPrecedence[operatorStack.top()]) {
|
|
196
|
+
while (mathematicalOperators.has(operatorStack.top()) && operatorPrecedence[token] <= operatorPrecedence[operatorStack.top()]) {
|
|
198
197
|
outputQueue.enqueue(operatorStack.pop());
|
|
199
198
|
}
|
|
200
199
|
operatorStack.push(token);
|
|
@@ -210,87 +209,67 @@ export class Parser {
|
|
|
210
209
|
}
|
|
211
210
|
addParentheses(formula) {
|
|
212
211
|
const rpn = this.buildRPN(formula);
|
|
213
|
-
if (!rpn)
|
|
212
|
+
if (!rpn)
|
|
214
213
|
return null;
|
|
215
|
-
}
|
|
216
214
|
const lexedRPN = [];
|
|
217
215
|
while (!rpn.isEmpty()) {
|
|
218
216
|
lexedRPN.push(rpn.dequeue());
|
|
219
217
|
}
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
// Stores the `results`, which are essentially individual groups
|
|
223
|
-
// of tokens showing a meaningful value.
|
|
224
|
-
let resultStack = new Stack();
|
|
218
|
+
const operatorStack = new Stack();
|
|
219
|
+
const resultStack = new Stack();
|
|
225
220
|
lexedRPN.forEach((symbol) => {
|
|
226
|
-
let parsedLeftExpression
|
|
227
|
-
|
|
228
|
-
//
|
|
229
|
-
|
|
230
|
-
if (((symbol[0] === "+" || symbol[0] === "-") && this.variables.has(symbol.substring(1))) ||
|
|
221
|
+
let parsedLeftExpression;
|
|
222
|
+
let parsedRightExpression;
|
|
223
|
+
// check if the symbol is a number or variable or unaryOperatorPreceded Variable
|
|
224
|
+
if (((unaryOperators.includes(symbol[0])) && this.variables.has(symbol.substring(1))) ||
|
|
231
225
|
this.variables.has(symbol) ||
|
|
232
226
|
(!isNaN(parseFloat(symbol)) && isFinite(parseFloat(symbol)))) {
|
|
233
227
|
resultStack.push(symbol);
|
|
234
228
|
operatorStack.push(null);
|
|
235
229
|
}
|
|
236
|
-
// If
|
|
237
|
-
// take out previous operators from the `operatorStack`, compare
|
|
238
|
-
// them with the current one, adds brackets accordingly to the `results`
|
|
239
|
-
// around it, and then finally add it to the `operatorStack` for
|
|
240
|
-
// future reference.
|
|
230
|
+
// If symbol is an operator, check operatorStack, adds brackets accordingly to the result and add it to operatorStack
|
|
241
231
|
else if (Object.keys(operatorPrecedence).includes(symbol)) {
|
|
242
|
-
|
|
232
|
+
const [rightExpression, leftExpression, operatorA, operatorB] = [
|
|
243
233
|
resultStack.pop(),
|
|
244
234
|
resultStack.pop(),
|
|
245
235
|
operatorStack.pop(),
|
|
246
236
|
operatorStack.pop(),
|
|
247
237
|
];
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
operatorPrecedence[symbol] ||
|
|
251
|
-
(operatorPrecedence[operatorB] ===
|
|
252
|
-
operatorPrecedence[symbol] &&
|
|
253
|
-
["/", "-"].includes(symbol))) {
|
|
238
|
+
if ((operatorPrecedence[operatorB] <= operatorPrecedence[symbol]) ||
|
|
239
|
+
(operatorPrecedence[operatorB] === operatorPrecedence[symbol] && ["/", "-"].includes(symbol))) {
|
|
254
240
|
parsedLeftExpression = `(${leftExpression})`;
|
|
255
241
|
}
|
|
256
242
|
else {
|
|
257
243
|
parsedLeftExpression = leftExpression;
|
|
258
244
|
}
|
|
259
|
-
if (operatorPrecedence[operatorA] <=
|
|
260
|
-
operatorPrecedence[symbol]
|
|
261
|
-
(operatorPrecedence[operatorA] ===
|
|
262
|
-
operatorPrecedence[symbol] &&
|
|
263
|
-
["/", "-"].includes(symbol))) {
|
|
245
|
+
if (operatorPrecedence[operatorA] <= operatorPrecedence[symbol] ||
|
|
246
|
+
(operatorPrecedence[operatorA] === operatorPrecedence[symbol] && ["/", "-"].includes(symbol))) {
|
|
264
247
|
parsedRightExpression = `(${rightExpression})`;
|
|
265
248
|
}
|
|
266
249
|
else {
|
|
267
250
|
parsedRightExpression = rightExpression;
|
|
268
251
|
}
|
|
269
|
-
// The bracket included expression is now itself a `result`
|
|
270
252
|
resultStack.push(`${parsedLeftExpression} ${symbol} ${parsedRightExpression}`);
|
|
271
253
|
operatorStack.push(symbol);
|
|
272
254
|
}
|
|
273
255
|
else
|
|
274
256
|
throw `${symbol} is not a recognized symbol`;
|
|
275
257
|
});
|
|
276
|
-
if (
|
|
277
|
-
return resultStack.pop();
|
|
278
|
-
}
|
|
279
|
-
else
|
|
258
|
+
if (resultStack.isEmpty())
|
|
280
259
|
throw `${lexedRPN} is not a correct RPN`;
|
|
260
|
+
return resultStack.pop();
|
|
281
261
|
}
|
|
282
262
|
calculate(formula) {
|
|
283
|
-
|
|
284
|
-
|
|
263
|
+
const formulaRPN = this.buildRPN(formula);
|
|
264
|
+
const calculationResult = {
|
|
285
265
|
result: undefined,
|
|
286
266
|
errorString: null,
|
|
287
267
|
};
|
|
288
|
-
if (!
|
|
268
|
+
if (!formulaRPN)
|
|
289
269
|
return calculationResult;
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
const frontItem = rpn.dequeue();
|
|
270
|
+
const calcStack = new Stack();
|
|
271
|
+
while (!formulaRPN.isEmpty()) {
|
|
272
|
+
const frontItem = formulaRPN.dequeue();
|
|
294
273
|
if (!mathematicalOperators.has(frontItem)) {
|
|
295
274
|
const [sign, variableKey] = /^[+-]/.test(frontItem) ? [frontItem[0], frontItem.slice(1)] : ["", frontItem];
|
|
296
275
|
const operandValue = Number.parseFloat(this.variables.get(variableKey)?.toString() ?? variableKey);
|
|
@@ -298,9 +277,9 @@ export class Parser {
|
|
|
298
277
|
calcStack.push(Big(number));
|
|
299
278
|
}
|
|
300
279
|
else {
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
280
|
+
const operator = frontItem;
|
|
281
|
+
const numB = calcStack.pop();
|
|
282
|
+
const numA = calcStack.pop();
|
|
304
283
|
try {
|
|
305
284
|
switch (operator) {
|
|
306
285
|
case "+":
|
|
@@ -319,8 +298,6 @@ export class Parser {
|
|
|
319
298
|
}
|
|
320
299
|
calcStack.push(Big(numA).div(Big(numB)));
|
|
321
300
|
break;
|
|
322
|
-
// Big.js doesn't support exponentiating a Big to a Big, which
|
|
323
|
-
// is obvious due to performance overheads. Use this case with care.
|
|
324
301
|
case "^":
|
|
325
302
|
calcStack.push(Big(numA).pow(parseFloat(Big(numB).toString())));
|
|
326
303
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fw-components/formula-editor",
|
|
3
|
-
"version": "2.0.7-formula-editor.
|
|
3
|
+
"version": "2.0.7-formula-editor.23",
|
|
4
4
|
"description": "A WYSIWYG type formula editor",
|
|
5
5
|
"main": "dist/formula-editor/src/formula-editor.js",
|
|
6
6
|
"exports": {
|
|
@@ -31,5 +31,5 @@
|
|
|
31
31
|
"@types/big.js": "^6.1.6",
|
|
32
32
|
"es-dev-server": "^2.1.0"
|
|
33
33
|
},
|
|
34
|
-
"gitHead": "
|
|
34
|
+
"gitHead": "aabc3f2d04b0d5e9fc5dfdbc5773407d35b15a18"
|
|
35
35
|
}
|