exprify 1.0.0
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/LICENSE +674 -0
- package/README.md +135 -0
- package/dist/exprify.cjs.js +519 -0
- package/dist/exprify.cjs.js.map +1 -0
- package/dist/exprify.esm.js +511 -0
- package/dist/exprify.esm.js.map +1 -0
- package/dist/exprify.js +532 -0
- package/dist/exprify.js.map +1 -0
- package/dist/exprify.min.js +3 -0
- package/dist/exprify.min.js.map +1 -0
- package/package.json +53 -0
- package/src/core/Exprify.js +70 -0
- package/src/functions/externalFunctions.js +19 -0
- package/src/functions/internalFunctions.js +53 -0
- package/src/index.js +38 -0
- package/src/math/operations.js +48 -0
- package/src/parser/evaluator.js +57 -0
- package/src/parser/infixToPostfix.js +78 -0
- package/src/parser/tokenizer.js +145 -0
- package/src/utils/typeConverter.js +63 -0
- package/src/variables/variables.js +28 -0
package/README.md
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
# Exprify — Math Expression Parser & Evaluator
|
|
2
|
+
|
|
3
|
+
[](https://github.com/code-hemu/Exprify)
|
|
4
|
+
|
|
5
|
+
Exprify is a JavaScript expression parser and evaluator supporting math operations, variables, and custom functions.
|
|
6
|
+
|
|
7
|
+
## 🔧 Manual Build
|
|
8
|
+
1. Clone the repository:
|
|
9
|
+
```bash
|
|
10
|
+
git clone https://github.com/code-hemu/Exprify.git
|
|
11
|
+
cd Exprify
|
|
12
|
+
|
|
13
|
+
2. Install dependencies and build:
|
|
14
|
+
```bash
|
|
15
|
+
npm install
|
|
16
|
+
npm run build
|
|
17
|
+
|
|
18
|
+
The output will be generated in a dist/ folder.
|
|
19
|
+
|
|
20
|
+
## 🚀 Quick Start
|
|
21
|
+
### Node.js / ES Modules
|
|
22
|
+
```Javascript
|
|
23
|
+
import Exprify from "exprify";
|
|
24
|
+
const expr = new Exprify();
|
|
25
|
+
console.log(expr.evaluate("5 + 7 * 2"));
|
|
26
|
+
// → 19
|
|
27
|
+
```
|
|
28
|
+
### Browser (UMD)
|
|
29
|
+
```html
|
|
30
|
+
<script src="exprify.js"></script>
|
|
31
|
+
<script>
|
|
32
|
+
const expr = new Exprify();
|
|
33
|
+
console.log(expr.evaluate("10 + 5 * 2"));
|
|
34
|
+
</script>
|
|
35
|
+
```
|
|
36
|
+
## 🧠 Examples
|
|
37
|
+
### ➕ Basic Math
|
|
38
|
+
```Javascript
|
|
39
|
+
expr.evaluate("10 + 5 * 2");
|
|
40
|
+
// → 20
|
|
41
|
+
```
|
|
42
|
+
### 🧮 Parentheses
|
|
43
|
+
```Javascript
|
|
44
|
+
expr.evaluate("(10 + 5) * 2");
|
|
45
|
+
// → 30
|
|
46
|
+
```
|
|
47
|
+
### 🔢 Variables
|
|
48
|
+
```Javascript
|
|
49
|
+
expr.setVariable("x", 10);
|
|
50
|
+
expr.setVariable("y", 5);
|
|
51
|
+
|
|
52
|
+
expr.evaluate("x + y * 2");
|
|
53
|
+
// → 20
|
|
54
|
+
```
|
|
55
|
+
### 🔧 Custom Functions
|
|
56
|
+
```Javascript
|
|
57
|
+
expr.addFunction("double", (x) => x * 2);
|
|
58
|
+
|
|
59
|
+
expr.evaluate("#double(5) + 3");
|
|
60
|
+
// → 13
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### 📊 Built-in Functions
|
|
64
|
+
```Javascript
|
|
65
|
+
expr.evaluate("#max(10, 25, 7)");
|
|
66
|
+
// → 25
|
|
67
|
+
|
|
68
|
+
expr.evaluate("#min(10, 25, 7)");
|
|
69
|
+
// → 7
|
|
70
|
+
```
|
|
71
|
+
## 🤿 Other Examples
|
|
72
|
+
```Javascript
|
|
73
|
+
//Create a new Exprify object with Exprify class
|
|
74
|
+
const expr = new Exprify();
|
|
75
|
+
|
|
76
|
+
//Simple Expression evaluation
|
|
77
|
+
console.log(expr.evaluate(`25+5*2`)); // → 35
|
|
78
|
+
|
|
79
|
+
//Nested expression evaluation
|
|
80
|
+
console.log(expr.evaluate(`((52/8+2)+56*((25/2)*4+(8-2)))*2`)); // → 6289
|
|
81
|
+
|
|
82
|
+
//BigInt Expression evaluation
|
|
83
|
+
console.log(expr.evaluate(`11n ^2n`)); // → 121n
|
|
84
|
+
|
|
85
|
+
//String concatenation: '+' Operator behaves as concatenation operator
|
|
86
|
+
console.log(expr.evaluate(`"Hello " + "World"`)); // → "Hello World"
|
|
87
|
+
|
|
88
|
+
//Invalid Expression: One operand is a string and another one is number
|
|
89
|
+
console.log(expr.evaluate(`"45" + 5`)); // → datatype error
|
|
90
|
+
|
|
91
|
+
//Invalid Expression: One operand is a number and another one is boolean
|
|
92
|
+
console.log(expr.evaluate(`45 * true`)); // → datatype error
|
|
93
|
+
|
|
94
|
+
//Invalid Expression: unclosed quoted text
|
|
95
|
+
console.log(expr.evaluate(`"Hello World `)); // → unclosed error
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## 🧩 Built-in Functions
|
|
99
|
+
Exprify has some built-in functions, here is a complete list
|
|
100
|
+
| Function | Details | Example |
|
|
101
|
+
| - | - |- |
|
|
102
|
+
|**#max(...)**| #max() function returns the largest number of the provided numerical arguments | ```"#max(45,50,20, 4+9*(6+4))" //returns 94 ```|
|
|
103
|
+
|**#min(...)**| #min() function returns the smallest number of the provided numerical arguments | ```"#min(45,50,20, 4+9*(6+4))" //returns 20 ```|
|
|
104
|
+
|**#and(...)** <br> or <br> **#&&**| **#and()** or **#&&** tests each of its arguments , if all are true then it will return true | ```"#and(true , true, false)" //returns false ``` <br> <br> ```"#&&(true , true, false)" //returns false ```|
|
|
105
|
+
|**#or(...)** <br> or <br> **#\|\|** | **#or()** or **#\|\|()** tests each of its arguments , if any of its arguments is true then it will return true | ```"#or(true , true, false)" //returns true ``` <br> <br> ```"#\|\|(true , true, false)" //returns true```|
|
|
106
|
+
|**#not(x)** <br> or <br> **#!**| **#not()** or **#!()** changes 'true' value to a 'false' value and 'false' value to a 'true' value | ```"#not(true)" //returns false ``` <br> <br> ```"#!(true)" //returns false ``` |
|
|
107
|
+
|**#greaterThan(...)** <br> or <br> **#>**| **#greaterThan()** or **#>()** takes 2 parameters and compare if that the 1st parameter is greater than the second parameter or not | ```"#greaterThan(67 , 5)" //returns true ``` <br> <br>```"#>(67 , 5)" //returns true ``` |
|
|
108
|
+
|**#lessThan(...)** <br> or <br> **#<**| **#lessThan()** or **#<()** takes 2 parameters and compare if that the 1st parameter is less than the second parameter or not | ```"#lessThan(67 , 5)" //returns false ``` <br> <br>```"#<(67 , 5)" //returns false ``` |
|
|
109
|
+
|**#isEqual(...)** <br> or <br> **#==**| **#isEqual()** or **#==()** takes 2 parameters and compare if that the both parameter is same numerical value or not | ```"#isEqual(60 , 50+10)" //returns true ``` <br> <br>```"#==(60 , 50+10)" //returns true ``` |
|
|
110
|
+
|**#if(...)**| **#if()** takes 3 parameters. 1st parameter is a condition parameter, if the condition is true then it returns 2nd parameter otherwise it returns 3rd parameter (if the 3rd parameter is not specified then its default value false will be return) | ```"#if(true , 5, 80)" //returns 80 ``` <br> <br> ```"#if(#<(50,100) , '50 is less than 100', '100 is less than 50')" //returns '50 is less than 100' ```|
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
## 📜 License
|
|
114
|
+
Exprify is freely distributable under the terms of the GPL-3.0 License. Copyright (c) [Nirmal Paul](https://github.com/nirmalpaul383/) (N Paul).
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
## 🤝 Contributing
|
|
118
|
+
|
|
119
|
+
Contributions are welcome!
|
|
120
|
+
|
|
121
|
+
1. Fork the repo
|
|
122
|
+
|
|
123
|
+
2. Create your branch:
|
|
124
|
+
```bash
|
|
125
|
+
git checkout -b feature/your-feature
|
|
126
|
+
|
|
127
|
+
3. Commit changes:
|
|
128
|
+
```bash
|
|
129
|
+
git commit -m "Add your feature"
|
|
130
|
+
|
|
131
|
+
4. Push and open a PR 🚀
|
|
132
|
+
|
|
133
|
+
## ⭐ Support
|
|
134
|
+
If you like [this project](https://github.com/code-hemu/Exprify), give it a ⭐ on GitHub! This project is originally made by [Nirmal Paul](https://github.com/nirmalpaul383/) (N Paul) and replicate by [ViewPoint](https://github.com/nirmalpaul383/ViewPoint). The Main Developer's (N Paul) [youtube page](https://www.youtube.com/channel/UCY6JY8bTlR7hZEvhy6Pldxg/), [facebook page](https://facebook.com/a.new.way.Technical/).
|
|
135
|
+
|
|
@@ -0,0 +1,519 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
|
+
|
|
5
|
+
function tokenize(expr, context) {
|
|
6
|
+
let tokens = [];
|
|
7
|
+
let current = "";
|
|
8
|
+
let quote = "";
|
|
9
|
+
|
|
10
|
+
for (let i = 0; i < expr.length; i++) {
|
|
11
|
+
|
|
12
|
+
let char = expr[i];
|
|
13
|
+
|
|
14
|
+
const isOperator =
|
|
15
|
+
char === '(' || char === ')' ||
|
|
16
|
+
char === '^' || char === '*' ||
|
|
17
|
+
char === '/' || char === '%' ||
|
|
18
|
+
char === '+' || char === '-';
|
|
19
|
+
|
|
20
|
+
const isQuote = char === '"' || char === "'" || char === "`";
|
|
21
|
+
|
|
22
|
+
if (isQuote) {
|
|
23
|
+
if (quote === "") {
|
|
24
|
+
quote = char;
|
|
25
|
+
current += char;
|
|
26
|
+
} else if (quote === char) {
|
|
27
|
+
current += char;
|
|
28
|
+
quote = "";
|
|
29
|
+
|
|
30
|
+
tokens.push(context.stringToJS(current, context.variablesDB));
|
|
31
|
+
current = "";
|
|
32
|
+
} else {
|
|
33
|
+
current += char;
|
|
34
|
+
}
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (quote !== "") {
|
|
39
|
+
current += char;
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (char === "#") {
|
|
44
|
+
|
|
45
|
+
let bracket = 0;
|
|
46
|
+
let funcName = "";
|
|
47
|
+
let arg = "";
|
|
48
|
+
let args = [];
|
|
49
|
+
let quoteFunc = "";
|
|
50
|
+
|
|
51
|
+
while (i < expr.length - 1) {
|
|
52
|
+
i++;
|
|
53
|
+
char = expr[i];
|
|
54
|
+
|
|
55
|
+
if (bracket === 0) {
|
|
56
|
+
if (char === "(") {
|
|
57
|
+
bracket++;
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (char === " ")
|
|
62
|
+
throw new Error("Function name cannot contain space");
|
|
63
|
+
|
|
64
|
+
if (isQuote)
|
|
65
|
+
throw new Error("Function name cannot contain quotes");
|
|
66
|
+
|
|
67
|
+
if (funcName === "" && /[0-9.]/.test(char))
|
|
68
|
+
throw new Error("Function name cannot start with number");
|
|
69
|
+
|
|
70
|
+
funcName += char;
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (isQuote) {
|
|
75
|
+
if (quoteFunc === "") quoteFunc = char;
|
|
76
|
+
else if (quoteFunc === char) quoteFunc = "";
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (quoteFunc === "") {
|
|
80
|
+
|
|
81
|
+
if (char === "(") bracket++;
|
|
82
|
+
else if (char === ")") {
|
|
83
|
+
bracket--;
|
|
84
|
+
|
|
85
|
+
if (bracket === 0) {
|
|
86
|
+
if (arg !== "") args.push(arg);
|
|
87
|
+
break;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (char === "," && bracket === 1) {
|
|
92
|
+
if (arg === "")
|
|
93
|
+
throw new Error(`Missing argument in #${funcName}()`);
|
|
94
|
+
|
|
95
|
+
args.push(arg);
|
|
96
|
+
arg = "";
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
arg += char;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
args = args.map(a => context.evaluate(a));
|
|
105
|
+
|
|
106
|
+
let fn =
|
|
107
|
+
context.func_DB_intrnl[funcName] ||
|
|
108
|
+
context.func_DB_extrnl[funcName];
|
|
109
|
+
|
|
110
|
+
if (!fn) {
|
|
111
|
+
throw new Error(`#${funcName}() not defined`);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
tokens.push(fn(...args));
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (isOperator) {
|
|
119
|
+
|
|
120
|
+
if (current !== "") {
|
|
121
|
+
tokens.push(context.stringToJS(current, context.variablesDB));
|
|
122
|
+
current = "";
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
tokens.push(char);
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (char === " ") {
|
|
130
|
+
if (current !== "") {
|
|
131
|
+
tokens.push(context.stringToJS(current, context.variablesDB));
|
|
132
|
+
current = "";
|
|
133
|
+
}
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
current += char;
|
|
138
|
+
|
|
139
|
+
if (i === expr.length - 1 && current !== "") {
|
|
140
|
+
tokens.push(context.stringToJS(current, context.variablesDB));
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (quote !== "") {
|
|
145
|
+
throw new Error("Unclosed string literal");
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return tokens;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function infixToPostfix(tokens, operator_precedence) {
|
|
152
|
+
let output = [];
|
|
153
|
+
let stack = [];
|
|
154
|
+
|
|
155
|
+
for (let i = 0; i < tokens.length; i++) {
|
|
156
|
+
|
|
157
|
+
let token = tokens[i];
|
|
158
|
+
|
|
159
|
+
const isOperator =
|
|
160
|
+
token === '^' || token === '*' ||
|
|
161
|
+
token === '/' || token === '%' ||
|
|
162
|
+
token === '+' || token === '-';
|
|
163
|
+
|
|
164
|
+
const isLeftParen = token === "(";
|
|
165
|
+
const isRightParen = token === ")";
|
|
166
|
+
|
|
167
|
+
const isOperand = !isOperator && !isLeftParen && !isRightParen;
|
|
168
|
+
|
|
169
|
+
if (isOperand) {
|
|
170
|
+
output.push(token);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
else if (isOperator) {
|
|
174
|
+
|
|
175
|
+
while (stack.length > 0) {
|
|
176
|
+
|
|
177
|
+
let top = stack[stack.length - 1];
|
|
178
|
+
|
|
179
|
+
if (top === "(") break;
|
|
180
|
+
|
|
181
|
+
let topPrec = operator_precedence[top] || 0;
|
|
182
|
+
let currPrec = operator_precedence[token];
|
|
183
|
+
|
|
184
|
+
// Right associativity for ^
|
|
185
|
+
if (
|
|
186
|
+
(token === '^' && currPrec < topPrec) ||
|
|
187
|
+
(token !== '^' && currPrec <= topPrec)
|
|
188
|
+
) {
|
|
189
|
+
output.push(stack.pop());
|
|
190
|
+
} else {
|
|
191
|
+
break;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
stack.push(token);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
else if (isLeftParen) {
|
|
199
|
+
stack.push(token);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
else if (isRightParen) {
|
|
203
|
+
|
|
204
|
+
while (stack.length > 0 && stack[stack.length - 1] !== "(") {
|
|
205
|
+
output.push(stack.pop());
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
if (stack.length === 0) {
|
|
209
|
+
throw new Error("Mismatched parentheses: missing '('");
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
stack.pop();
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
while (stack.length > 0) {
|
|
217
|
+
|
|
218
|
+
let top = stack.pop();
|
|
219
|
+
|
|
220
|
+
if (top === "(" || top === ")") {
|
|
221
|
+
throw new Error("Mismatched parentheses");
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
output.push(top);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
return output;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
function evaluatePostfix(postfix, mathOperations) {
|
|
231
|
+
|
|
232
|
+
let stack = [];
|
|
233
|
+
|
|
234
|
+
const isOperator = (val) =>
|
|
235
|
+
val === '^' || val === '*' ||
|
|
236
|
+
val === '/' || val === '%' ||
|
|
237
|
+
val === '+' || val === '-';
|
|
238
|
+
|
|
239
|
+
for (let i = 0; i < postfix.length; i++) {
|
|
240
|
+
|
|
241
|
+
let token = postfix[i];
|
|
242
|
+
|
|
243
|
+
if (!isOperator(token)) {
|
|
244
|
+
stack.push(token);
|
|
245
|
+
continue;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
if (stack.length < 2) {
|
|
249
|
+
throw new Error("Invalid expression: insufficient operands");
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
let b = stack.pop(); // second
|
|
253
|
+
let a = stack.pop(); // first
|
|
254
|
+
|
|
255
|
+
let result;
|
|
256
|
+
|
|
257
|
+
switch (token) {
|
|
258
|
+
case '^':
|
|
259
|
+
result = mathOperations.power(a, b);
|
|
260
|
+
break;
|
|
261
|
+
case '*':
|
|
262
|
+
result = mathOperations.multiply(a, b);
|
|
263
|
+
break;
|
|
264
|
+
case '/':
|
|
265
|
+
result = mathOperations.divide(a, b);
|
|
266
|
+
break;
|
|
267
|
+
case '%':
|
|
268
|
+
result = mathOperations.modulus(a, b);
|
|
269
|
+
break;
|
|
270
|
+
case '+':
|
|
271
|
+
result = mathOperations.add(a, b);
|
|
272
|
+
break;
|
|
273
|
+
case '-':
|
|
274
|
+
result = mathOperations.subtract(a, b);
|
|
275
|
+
break;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
stack.push(result);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
if (stack.length !== 1) {
|
|
282
|
+
throw new Error("Invalid expression: leftover values in stack");
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
return stack[0];
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
const isValidNumberPair = (a, b) =>
|
|
289
|
+
(typeof a === typeof b) &&
|
|
290
|
+
(typeof a === 'number' || typeof a === 'bigint');
|
|
291
|
+
|
|
292
|
+
const mathOperations = Object.freeze({
|
|
293
|
+
|
|
294
|
+
operator_precedence: {
|
|
295
|
+
'^': 4,
|
|
296
|
+
'*': 3,
|
|
297
|
+
'/': 3,
|
|
298
|
+
'%': 3,
|
|
299
|
+
'+': 1,
|
|
300
|
+
'-': 1,
|
|
301
|
+
},
|
|
302
|
+
|
|
303
|
+
power: function(a, b) {
|
|
304
|
+
if (isValidNumberPair(a, b)) return a ** b;
|
|
305
|
+
throw new Error("Invalid types for ^");
|
|
306
|
+
},
|
|
307
|
+
|
|
308
|
+
multiply: function(a, b) {
|
|
309
|
+
if (isValidNumberPair(a, b)) return a * b;
|
|
310
|
+
throw new Error("Invalid types for *");
|
|
311
|
+
},
|
|
312
|
+
|
|
313
|
+
divide: function(a, b) {
|
|
314
|
+
if (isValidNumberPair(a, b)) {
|
|
315
|
+
if (b === 0) throw new Error("Division by zero");
|
|
316
|
+
return a / b;
|
|
317
|
+
}
|
|
318
|
+
throw new Error("Invalid types for /");
|
|
319
|
+
},
|
|
320
|
+
|
|
321
|
+
add: function(a, b) {
|
|
322
|
+
if (isValidNumberPair(a, b)) return a + b;
|
|
323
|
+
if (typeof a === 'string' && typeof b === 'string') return a + b;
|
|
324
|
+
throw new Error("Invalid types for +");
|
|
325
|
+
},
|
|
326
|
+
subtract: function(a, b) {
|
|
327
|
+
if (isValidNumberPair(a, b)) return a - b;
|
|
328
|
+
throw new Error("Invalid types for -");
|
|
329
|
+
},
|
|
330
|
+
|
|
331
|
+
modulus: function(a, b) {
|
|
332
|
+
if (isValidNumberPair(a, b)) return a % b;
|
|
333
|
+
throw new Error("Invalid types for %");
|
|
334
|
+
}
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
const and = (...args) => args.every(Boolean);
|
|
338
|
+
const or = (...args) => args.some(Boolean);
|
|
339
|
+
const not = (val) => !val;
|
|
340
|
+
|
|
341
|
+
const gt = (a, b) => a > b;
|
|
342
|
+
const lt = (a, b) => a < b;
|
|
343
|
+
const eq = (a, b) => a === b;
|
|
344
|
+
const gte = (a, b) => a >= b;
|
|
345
|
+
const lte = (a, b) => a <= b;
|
|
346
|
+
|
|
347
|
+
const internalFunctions = Object.freeze({
|
|
348
|
+
|
|
349
|
+
max: (...args) => {
|
|
350
|
+
if (!args.every(v => typeof v === 'number')) {
|
|
351
|
+
throw new Error("max() expects numbers only");
|
|
352
|
+
}
|
|
353
|
+
return Math.max(...args);
|
|
354
|
+
},
|
|
355
|
+
|
|
356
|
+
min: (...args) => {
|
|
357
|
+
if (!args.every(v => typeof v === 'number')) {
|
|
358
|
+
throw new Error("min() expects numbers only");
|
|
359
|
+
}
|
|
360
|
+
return Math.min(...args);
|
|
361
|
+
},
|
|
362
|
+
|
|
363
|
+
and,
|
|
364
|
+
"&&": and,
|
|
365
|
+
|
|
366
|
+
or,
|
|
367
|
+
"||": or,
|
|
368
|
+
|
|
369
|
+
not,
|
|
370
|
+
"!": not,
|
|
371
|
+
|
|
372
|
+
greaterThan: gt,
|
|
373
|
+
">": gt,
|
|
374
|
+
|
|
375
|
+
lessThan: lt,
|
|
376
|
+
"<": lt,
|
|
377
|
+
|
|
378
|
+
isEqual: eq,
|
|
379
|
+
"==": eq,
|
|
380
|
+
|
|
381
|
+
greaterThanOrEqual: gte,
|
|
382
|
+
">=": gte,
|
|
383
|
+
|
|
384
|
+
lessThanOrEqual: lte,
|
|
385
|
+
"<=": lte,
|
|
386
|
+
|
|
387
|
+
if: (cond, t, f = false) => cond ? t : f
|
|
388
|
+
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
const externalFunctions = {};
|
|
392
|
+
|
|
393
|
+
const variablesDB = {};
|
|
394
|
+
|
|
395
|
+
function stringToJS(str, variablesDB) {
|
|
396
|
+
if (typeof str !== "string" || str.length === 0) {
|
|
397
|
+
throw new Error("Invalid input: expected a non-empty string.");
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
const firstChar = str[0];
|
|
401
|
+
const lastChar = str[str.length - 1];
|
|
402
|
+
|
|
403
|
+
// HEX (0x...)
|
|
404
|
+
if (/^0x[0-9a-fA-F]+n?$/.test(str)) {
|
|
405
|
+
|
|
406
|
+
// BigInt hex (0xFFn)
|
|
407
|
+
if (lastChar === 'n') {
|
|
408
|
+
return BigInt(str.slice(0, -1));
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
return Number(str);
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
if (/^[+-]?(\d+(\.\d+)?|\.\d+)(e[+-]?\d+)?n?$/i.test(str)) {
|
|
415
|
+
|
|
416
|
+
// BigInt
|
|
417
|
+
if (lastChar === 'n') {
|
|
418
|
+
const numPart = str.slice(0, -1);
|
|
419
|
+
|
|
420
|
+
if (numPart.includes('.') || /e/i.test(numPart)) {
|
|
421
|
+
throw new Error(`Invalid BigInt: ${str}`);
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
return BigInt(numPart);
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
return Number(str);
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
if (
|
|
431
|
+
(firstChar === '"' && lastChar === '"') ||
|
|
432
|
+
(firstChar === "'" && lastChar === "'") ||
|
|
433
|
+
(firstChar === '`' && lastChar === '`')
|
|
434
|
+
) {
|
|
435
|
+
return str.slice(1, -1);
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
if (
|
|
439
|
+
firstChar === '"' ||
|
|
440
|
+
firstChar === "'" ||
|
|
441
|
+
firstChar === '`'
|
|
442
|
+
) {
|
|
443
|
+
throw new Error(`Unmatched or missing quotes: ${str}`);
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
if (str === "true") return true;
|
|
447
|
+
if (str === "false") return false;
|
|
448
|
+
|
|
449
|
+
if (str in variablesDB) {
|
|
450
|
+
return variablesDB[str];
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
throw new Error(
|
|
454
|
+
`${str} is not defined. Use setVariable("${str}", value) first.`
|
|
455
|
+
);
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
class ViewPoint {
|
|
459
|
+
|
|
460
|
+
constructor() {
|
|
461
|
+
// Shared state
|
|
462
|
+
this.variablesDB = variablesDB;
|
|
463
|
+
this.func_DB_intrnl = internalFunctions;
|
|
464
|
+
this.func_DB_extrnl = externalFunctions;
|
|
465
|
+
|
|
466
|
+
this.operator_precedence = mathOperations.operator_precedence;
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
setVariable(name, value) {
|
|
470
|
+
this.variablesDB[name] = value;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
addFunction(name, fn) {
|
|
474
|
+
this.func_DB_extrnl[name] = fn;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
stringToJS(str) {
|
|
478
|
+
return stringToJS.call(this, str, this.variablesDB);
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
evaluate(expr) {
|
|
482
|
+
|
|
483
|
+
if (typeof expr !== "string") {
|
|
484
|
+
throw new Error("Expression must be a string");
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
const context = {
|
|
488
|
+
variablesDB: this.variablesDB,
|
|
489
|
+
func_DB_intrnl: this.func_DB_intrnl,
|
|
490
|
+
func_DB_extrnl: this.func_DB_extrnl,
|
|
491
|
+
stringToJS: this.stringToJS.bind(this),
|
|
492
|
+
evaluate: this.evaluate.bind(this)
|
|
493
|
+
};
|
|
494
|
+
|
|
495
|
+
// Step 1: Tokenize
|
|
496
|
+
const tokens = tokenize(expr, context);
|
|
497
|
+
|
|
498
|
+
// Step 2: Infix → Postfix
|
|
499
|
+
const postfix = infixToPostfix(
|
|
500
|
+
tokens,
|
|
501
|
+
this.operator_precedence
|
|
502
|
+
);
|
|
503
|
+
|
|
504
|
+
// Step 3: Evaluate Postfix
|
|
505
|
+
const result = evaluatePostfix(
|
|
506
|
+
postfix,
|
|
507
|
+
mathOperations
|
|
508
|
+
);
|
|
509
|
+
|
|
510
|
+
return result;
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
exports.Exprify = ViewPoint;
|
|
515
|
+
exports.externalFunctions = externalFunctions;
|
|
516
|
+
exports.internalFunctions = internalFunctions;
|
|
517
|
+
exports.mathOperations = mathOperations;
|
|
518
|
+
exports.variablesDB = variablesDB;
|
|
519
|
+
|