formulakit 0.1.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 +21 -0
- package/README.md +216 -0
- package/dist/index.cjs +404 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +73 -0
- package/dist/index.d.ts +73 -0
- package/dist/index.js +369 -0
- package/dist/index.js.map +1 -0
- package/package.json +53 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 trananhtung
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
# formulakit
|
|
2
|
+
|
|
3
|
+
[](#contributors-)
|
|
4
|
+
|
|
5
|
+
> Zero-dependency safe arithmetic expression evaluator for TypeScript. Parse and evaluate math formulas with variables, custom functions, and ternary logic — no `eval()`. Port of Python `simpleeval` / Go `expr` / C# `DynamicExpresso`.
|
|
6
|
+
|
|
7
|
+
[](https://www.npmjs.com/package/formulakit)
|
|
8
|
+
[](LICENSE)
|
|
9
|
+
|
|
10
|
+
## Install
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
npm install formulakit
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Quick start
|
|
17
|
+
|
|
18
|
+
```typescript
|
|
19
|
+
import { evaluate, compile } from "formulakit";
|
|
20
|
+
|
|
21
|
+
// one-shot evaluation
|
|
22
|
+
evaluate("2 + 3 * 4"); // 14
|
|
23
|
+
evaluate("sqrt(a^2 + b^2)", { a: 3, b: 4 }); // 5
|
|
24
|
+
|
|
25
|
+
// compile once, evaluate many times (faster)
|
|
26
|
+
const bmi = compile("weight / (height * height)");
|
|
27
|
+
bmi.evaluate({ weight: 70, height: 1.75 }); // 22.86
|
|
28
|
+
bmi.evaluate({ weight: 90, height: 1.80 }); // 27.78
|
|
29
|
+
|
|
30
|
+
// ternary / conditional
|
|
31
|
+
evaluate("qty >= 100 ? price * 0.8 : price", { qty: 150, price: 10 }); // 8
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Why formulakit?
|
|
35
|
+
|
|
36
|
+
| Package | Downloads/week | Status | TypeScript |
|
|
37
|
+
|---|---|---|---|
|
|
38
|
+
| `expr-eval` | ~200k | **Abandoned March 2021** | ❌ |
|
|
39
|
+
| `mathjs` | ~1.2M | Active | ✅ | Has 3 deps, 180KB |
|
|
40
|
+
| **`formulakit`** | — | **Active** | ✅ native |
|
|
41
|
+
|
|
42
|
+
`expr-eval` is the de-facto choice for safe expression evaluation but has been abandoned for 3+ years and has no TypeScript types. `mathjs` is excellent but ships 180KB of linear algebra, units, and symbolic math — overkill if you just need `"2 * x + 1"`. `formulakit` fills the gap: 0 dependencies, native TypeScript, ESM+CJS.
|
|
43
|
+
|
|
44
|
+
## Features
|
|
45
|
+
|
|
46
|
+
- **Safe** — pure AST evaluator, never calls `eval()` or `Function()`
|
|
47
|
+
- **Variables** — pass any `Record<string, number>` as context
|
|
48
|
+
- **Custom functions** — extend with your own named functions
|
|
49
|
+
- **Ternary operator** — `condition ? a : b`
|
|
50
|
+
- **Comparison & logical operators** — `==`, `!=`, `<`, `<=`, `>`, `>=`, `&&`, `||`, `!`
|
|
51
|
+
- **Right-associative `^`** — `2^3^2` = `2^9` = 512 (standard math)
|
|
52
|
+
- **Short-circuit** — `&&` and `||` skip the right side when not needed
|
|
53
|
+
- **compile()** — parse once, evaluate many times
|
|
54
|
+
- **variables()** — introspect which variables a formula uses
|
|
55
|
+
|
|
56
|
+
## API
|
|
57
|
+
|
|
58
|
+
### `evaluate(expr, vars?, opts?)`
|
|
59
|
+
|
|
60
|
+
```typescript
|
|
61
|
+
evaluate("x^2 + y^2", { x: 3, y: 4 }) // 25
|
|
62
|
+
evaluate("sin(PI/6)") // 0.5
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### `tryEvaluate(expr, vars?, opts?)`
|
|
66
|
+
|
|
67
|
+
Returns `null` instead of throwing on parse/evaluation errors.
|
|
68
|
+
|
|
69
|
+
```typescript
|
|
70
|
+
tryEvaluate("1 + 2") // 3
|
|
71
|
+
tryEvaluate("unknown_var") // null
|
|
72
|
+
tryEvaluate("2 @ 3") // null (bad syntax)
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### `compile(expr): CompiledFormula`
|
|
76
|
+
|
|
77
|
+
Pre-parse an expression for repeated use:
|
|
78
|
+
|
|
79
|
+
```typescript
|
|
80
|
+
const f = compile("a * b + c");
|
|
81
|
+
f.evaluate({ a: 1, b: 2, c: 3 }) // 5
|
|
82
|
+
f.evaluate({ a: 10, b: 20, c: 30 }) // 230
|
|
83
|
+
|
|
84
|
+
f.variables() // ["a", "b", "c"] — sorted list of identifiers
|
|
85
|
+
f.source // "a * b + c"
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### `parse(expr): ASTNode` / `evalAST(ast, opts?)`
|
|
89
|
+
|
|
90
|
+
Low-level access to the parse tree:
|
|
91
|
+
|
|
92
|
+
```typescript
|
|
93
|
+
import { parse, evalAST } from "formulakit";
|
|
94
|
+
const ast = parse("x + y");
|
|
95
|
+
evalAST(ast, { vars: { x: 10, y: 20 } }); // 30
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## Operators
|
|
99
|
+
|
|
100
|
+
| Operator | Description | Example |
|
|
101
|
+
|---|---|---|
|
|
102
|
+
| `+` `-` | Addition / subtraction | `2 + 3`, `5 - 1` |
|
|
103
|
+
| `*` `/` | Multiplication / division | `3 * 4`, `10 / 2` |
|
|
104
|
+
| `%` | Modulo | `7 % 3` → 1 |
|
|
105
|
+
| `^` | Power (right-associative) | `2^3^2` = `2^9` = 512 |
|
|
106
|
+
| `==` `!=` | Equality (returns 0 or 1) | `x == y` |
|
|
107
|
+
| `<` `<=` `>` `>=` | Comparison | `a < b` |
|
|
108
|
+
| `&&` | Logical AND (short-circuit) | `x > 0 && x < 10` |
|
|
109
|
+
| `\|\|` | Logical OR (short-circuit) | `x < 0 \|\| x > 100` |
|
|
110
|
+
| `!` | Logical NOT | `!0` → 1 |
|
|
111
|
+
| `? :` | Ternary | `x > 0 ? x : -x` |
|
|
112
|
+
| `(` `)` | Grouping | `(a + b) * c` |
|
|
113
|
+
|
|
114
|
+
## Built-in constants
|
|
115
|
+
|
|
116
|
+
`PI`, `E`, `LN2`, `LN10`, `LOG2E`, `LOG10E`, `SQRT2`, `Infinity`, `NaN`
|
|
117
|
+
|
|
118
|
+
## Built-in functions
|
|
119
|
+
|
|
120
|
+
| Function | Description |
|
|
121
|
+
|---|---|
|
|
122
|
+
| `abs(x)` | Absolute value |
|
|
123
|
+
| `ceil(x)` / `floor(x)` / `round(x)` / `trunc(x)` | Rounding |
|
|
124
|
+
| `sign(x)` | Sign (-1 / 0 / 1) |
|
|
125
|
+
| `sqrt(x)` / `cbrt(x)` | Square/cube root |
|
|
126
|
+
| `pow(x, y)` | Power (same as `x^y`) |
|
|
127
|
+
| `exp(x)` | e^x |
|
|
128
|
+
| `log(x)` / `log2(x)` / `log10(x)` | Logarithms |
|
|
129
|
+
| `sin(x)` / `cos(x)` / `tan(x)` | Trig (radians) |
|
|
130
|
+
| `asin(x)` / `acos(x)` / `atan(x)` / `atan2(y,x)` | Inverse trig |
|
|
131
|
+
| `sinh(x)` / `cosh(x)` / `tanh(x)` | Hyperbolic |
|
|
132
|
+
| `min(...)` / `max(...)` | Min/max of N args |
|
|
133
|
+
| `clamp(x, lo, hi)` | Clamp to [lo, hi] |
|
|
134
|
+
| `hypot(x, y, ...)` | Hypotenuse |
|
|
135
|
+
| `isNaN(x)` / `isFinite(x)` | Tests (returns 0 or 1) |
|
|
136
|
+
|
|
137
|
+
## Options
|
|
138
|
+
|
|
139
|
+
```typescript
|
|
140
|
+
evaluate(expr, vars, {
|
|
141
|
+
functions: { double: (x) => x * 2 }, // custom functions
|
|
142
|
+
maxDepth: 200, // max AST recursion (default 200)
|
|
143
|
+
});
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
## Examples
|
|
147
|
+
|
|
148
|
+
### BMI calculator
|
|
149
|
+
|
|
150
|
+
```typescript
|
|
151
|
+
const bmi = compile("weight / height^2");
|
|
152
|
+
bmi.evaluate({ weight: 70, height: 1.75 }); // 22.86
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
### Compound interest
|
|
156
|
+
|
|
157
|
+
```typescript
|
|
158
|
+
evaluate("P * (1 + r/n) ^ (n * t)", { P: 1000, r: 0.05, n: 12, t: 10 });
|
|
159
|
+
// 1647.01
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
### Spreadsheet-style formula
|
|
163
|
+
|
|
164
|
+
```typescript
|
|
165
|
+
const formula = compile("IF > threshold ? value * multiplier : value");
|
|
166
|
+
// ... or with ternary:
|
|
167
|
+
compile("value > threshold ? value * multiplier : value")
|
|
168
|
+
.evaluate({ value: 200, threshold: 100, multiplier: 1.5 }); // 300
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
### Dynamic pricing
|
|
172
|
+
|
|
173
|
+
```typescript
|
|
174
|
+
const price = compile("qty >= 100 ? base * 0.8 : qty >= 50 ? base * 0.9 : base");
|
|
175
|
+
price.evaluate({ qty: 150, base: 10 }); // 8
|
|
176
|
+
price.evaluate({ qty: 75, base: 10 }); // 9
|
|
177
|
+
price.evaluate({ qty: 10, base: 10 }); // 10
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
### Custom functions
|
|
181
|
+
|
|
182
|
+
```typescript
|
|
183
|
+
import { evaluate } from "formulakit";
|
|
184
|
+
|
|
185
|
+
evaluate("toCelsius(98.6)", {}, {
|
|
186
|
+
functions: {
|
|
187
|
+
toCelsius: (f) => (f - 32) * 5/9,
|
|
188
|
+
},
|
|
189
|
+
}); // 37
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
## Contributors ✨
|
|
193
|
+
|
|
194
|
+
This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind are welcome — code, docs, bug reports, ideas, reviews! See the [emoji key](https://allcontributors.org/docs/en/emoji-key) for how each contribution is recognized, and open a PR or issue to get involved.
|
|
195
|
+
|
|
196
|
+
Thanks goes to these wonderful people:
|
|
197
|
+
|
|
198
|
+
<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->
|
|
199
|
+
<!-- prettier-ignore-start -->
|
|
200
|
+
<!-- markdownlint-disable -->
|
|
201
|
+
<table>
|
|
202
|
+
<tbody>
|
|
203
|
+
<tr>
|
|
204
|
+
<td align="center" valign="top" width="14.28%"><a href="https://github.com/trananhtung"><img src="https://avatars.githubusercontent.com/u/30992229?v=4?s=100" width="100px;" alt="Tung Tran"/><br /><sub><b>Tung Tran</b></sub></a><br /><a href="https://github.com/trananhtung/formulakit/commits?author=trananhtung" title="Code">💻</a> <a href="#maintenance-trananhtung" title="Maintenance">🚧</a></td>
|
|
205
|
+
</tr>
|
|
206
|
+
</tbody>
|
|
207
|
+
</table>
|
|
208
|
+
|
|
209
|
+
<!-- markdownlint-restore -->
|
|
210
|
+
<!-- prettier-ignore-end -->
|
|
211
|
+
|
|
212
|
+
<!-- ALL-CONTRIBUTORS-LIST:END -->
|
|
213
|
+
|
|
214
|
+
## License
|
|
215
|
+
|
|
216
|
+
MIT © [trananhtung](https://github.com/trananhtung)
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,404 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
BUILTIN_FUNCTIONS: () => BUILTIN_FUNCTIONS,
|
|
24
|
+
BUILTIN_VARS: () => BUILTIN_VARS,
|
|
25
|
+
CompiledFormula: () => CompiledFormula,
|
|
26
|
+
FormulaError: () => FormulaError,
|
|
27
|
+
compile: () => compile,
|
|
28
|
+
evalAST: () => evalAST,
|
|
29
|
+
evaluate: () => evaluate,
|
|
30
|
+
parse: () => parse,
|
|
31
|
+
tryEvaluate: () => tryEvaluate
|
|
32
|
+
});
|
|
33
|
+
module.exports = __toCommonJS(index_exports);
|
|
34
|
+
|
|
35
|
+
// src/types.ts
|
|
36
|
+
var FormulaError = class extends Error {
|
|
37
|
+
constructor(message, position) {
|
|
38
|
+
super(message);
|
|
39
|
+
this.position = position;
|
|
40
|
+
this.name = "FormulaError";
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
// src/lexer.ts
|
|
45
|
+
function tokenize(src) {
|
|
46
|
+
const tokens = [];
|
|
47
|
+
let i = 0;
|
|
48
|
+
while (i < src.length) {
|
|
49
|
+
if (/\s/.test(src[i])) {
|
|
50
|
+
i++;
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
const pos = i;
|
|
54
|
+
const ch = src[i];
|
|
55
|
+
if (/[0-9]/.test(ch) || ch === "." && /[0-9]/.test(src[i + 1] ?? "")) {
|
|
56
|
+
let raw = "";
|
|
57
|
+
while (i < src.length && /[0-9.]/.test(src[i])) raw += src[i++];
|
|
58
|
+
if (src[i] === "e" || src[i] === "E") {
|
|
59
|
+
raw += src[i++];
|
|
60
|
+
if (src[i] === "+" || src[i] === "-") raw += src[i++];
|
|
61
|
+
while (i < src.length && /[0-9]/.test(src[i])) raw += src[i++];
|
|
62
|
+
}
|
|
63
|
+
tokens.push({ type: "Number" /* Number */, raw, pos });
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
if (/[a-zA-Z_]/.test(ch)) {
|
|
67
|
+
let raw = "";
|
|
68
|
+
while (i < src.length && /[a-zA-Z0-9_]/.test(src[i])) raw += src[i++];
|
|
69
|
+
tokens.push({ type: "Ident" /* Ident */, raw, pos });
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
const two = src.slice(i, i + 2);
|
|
73
|
+
if (two === "==" || two === "!=" || two === "<=" || two === ">=" || two === "&&" || two === "||") {
|
|
74
|
+
tokens.push({ type: two, raw: two, pos });
|
|
75
|
+
i += 2;
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
const singles = {
|
|
79
|
+
"+": "+" /* Plus */,
|
|
80
|
+
"-": "-" /* Minus */,
|
|
81
|
+
"*": "*" /* Star */,
|
|
82
|
+
"/": "/" /* Slash */,
|
|
83
|
+
"%": "%" /* Percent */,
|
|
84
|
+
"^": "^" /* Caret */,
|
|
85
|
+
"(": "(" /* LParen */,
|
|
86
|
+
")": ")" /* RParen */,
|
|
87
|
+
",": "," /* Comma */,
|
|
88
|
+
"?": "?" /* Question */,
|
|
89
|
+
":": ":" /* Colon */,
|
|
90
|
+
"<": "<" /* Lt */,
|
|
91
|
+
">": ">" /* Gt */,
|
|
92
|
+
"!": "!" /* Not */
|
|
93
|
+
};
|
|
94
|
+
if (ch in singles) {
|
|
95
|
+
tokens.push({ type: singles[ch], raw: ch, pos });
|
|
96
|
+
i++;
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
throw new FormulaError(`Unexpected character '${ch}'`, pos);
|
|
100
|
+
}
|
|
101
|
+
tokens.push({ type: "EOF" /* EOF */, raw: "", pos: i });
|
|
102
|
+
return tokens;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// src/parser.ts
|
|
106
|
+
var BP = {
|
|
107
|
+
["||" /* Or */]: 10,
|
|
108
|
+
["&&" /* And */]: 20,
|
|
109
|
+
["==" /* Eq */]: 30,
|
|
110
|
+
["!=" /* NEq */]: 30,
|
|
111
|
+
["<" /* Lt */]: 40,
|
|
112
|
+
["<=" /* Lte */]: 40,
|
|
113
|
+
[">" /* Gt */]: 40,
|
|
114
|
+
[">=" /* Gte */]: 40,
|
|
115
|
+
["+" /* Plus */]: 50,
|
|
116
|
+
["-" /* Minus */]: 50,
|
|
117
|
+
["*" /* Star */]: 60,
|
|
118
|
+
["/" /* Slash */]: 60,
|
|
119
|
+
["%" /* Percent */]: 60,
|
|
120
|
+
["^" /* Caret */]: 70
|
|
121
|
+
// right-assoc → parse at BP-1
|
|
122
|
+
};
|
|
123
|
+
var Parser = class {
|
|
124
|
+
constructor(src) {
|
|
125
|
+
this.pos = 0;
|
|
126
|
+
this.tokens = tokenize(src);
|
|
127
|
+
}
|
|
128
|
+
peek() {
|
|
129
|
+
return this.tokens[this.pos];
|
|
130
|
+
}
|
|
131
|
+
consume() {
|
|
132
|
+
return this.tokens[this.pos++];
|
|
133
|
+
}
|
|
134
|
+
expect(type) {
|
|
135
|
+
const t = this.consume();
|
|
136
|
+
if (t.type !== type) throw new FormulaError(`Expected '${type}' but got '${t.raw}'`, t.pos);
|
|
137
|
+
return t;
|
|
138
|
+
}
|
|
139
|
+
parse() {
|
|
140
|
+
const node = this.expr(0);
|
|
141
|
+
if (this.peek().type !== "EOF" /* EOF */) {
|
|
142
|
+
const t = this.peek();
|
|
143
|
+
throw new FormulaError(`Unexpected token '${t.raw}'`, t.pos);
|
|
144
|
+
}
|
|
145
|
+
return node;
|
|
146
|
+
}
|
|
147
|
+
expr(minBP) {
|
|
148
|
+
let left = this.prefix();
|
|
149
|
+
while (true) {
|
|
150
|
+
const t = this.peek();
|
|
151
|
+
if (t.type === "?" /* Question */ && minBP < 5) {
|
|
152
|
+
this.consume();
|
|
153
|
+
const consequent = this.expr(0);
|
|
154
|
+
this.expect(":" /* Colon */);
|
|
155
|
+
const alternate = this.expr(0);
|
|
156
|
+
left = { kind: "conditional", condition: left, consequent, alternate };
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
const bp = BP[t.type];
|
|
160
|
+
if (bp === void 0 || bp <= minBP) break;
|
|
161
|
+
this.consume();
|
|
162
|
+
const rightBP = t.type === "^" /* Caret */ ? bp - 1 : bp;
|
|
163
|
+
const right = this.expr(rightBP);
|
|
164
|
+
left = { kind: "binary", op: t.type, left, right };
|
|
165
|
+
}
|
|
166
|
+
return left;
|
|
167
|
+
}
|
|
168
|
+
prefix() {
|
|
169
|
+
const t = this.peek();
|
|
170
|
+
if (t.type === "Number" /* Number */) {
|
|
171
|
+
this.consume();
|
|
172
|
+
return { kind: "number", value: parseFloat(t.raw) };
|
|
173
|
+
}
|
|
174
|
+
if (t.type === "Ident" /* Ident */) {
|
|
175
|
+
this.consume();
|
|
176
|
+
if (this.peek().type === "(" /* LParen */) {
|
|
177
|
+
this.consume();
|
|
178
|
+
const args = [];
|
|
179
|
+
if (this.peek().type !== ")" /* RParen */) {
|
|
180
|
+
args.push(this.expr(0));
|
|
181
|
+
while (this.peek().type === "," /* Comma */) {
|
|
182
|
+
this.consume();
|
|
183
|
+
args.push(this.expr(0));
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
this.expect(")" /* RParen */);
|
|
187
|
+
return { kind: "call", name: t.raw, args };
|
|
188
|
+
}
|
|
189
|
+
return { kind: "identifier", name: t.raw };
|
|
190
|
+
}
|
|
191
|
+
if (t.type === "-" /* Minus */) {
|
|
192
|
+
this.consume();
|
|
193
|
+
return { kind: "unary", op: "-", operand: this.expr(65) };
|
|
194
|
+
}
|
|
195
|
+
if (t.type === "!" /* Not */) {
|
|
196
|
+
this.consume();
|
|
197
|
+
return { kind: "unary", op: "!", operand: this.expr(65) };
|
|
198
|
+
}
|
|
199
|
+
if (t.type === "+" /* Plus */) {
|
|
200
|
+
this.consume();
|
|
201
|
+
return this.expr(65);
|
|
202
|
+
}
|
|
203
|
+
if (t.type === "(" /* LParen */) {
|
|
204
|
+
this.consume();
|
|
205
|
+
const inner = this.expr(0);
|
|
206
|
+
this.expect(")" /* RParen */);
|
|
207
|
+
return inner;
|
|
208
|
+
}
|
|
209
|
+
throw new FormulaError(`Unexpected token '${t.raw}'`, t.pos);
|
|
210
|
+
}
|
|
211
|
+
};
|
|
212
|
+
function parse(src) {
|
|
213
|
+
return new Parser(src).parse();
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// src/builtins.ts
|
|
217
|
+
var BUILTIN_VARS = {
|
|
218
|
+
PI: Math.PI,
|
|
219
|
+
E: Math.E,
|
|
220
|
+
LN2: Math.LN2,
|
|
221
|
+
LN10: Math.LN10,
|
|
222
|
+
LOG2E: Math.LOG2E,
|
|
223
|
+
LOG10E: Math.LOG10E,
|
|
224
|
+
SQRT2: Math.SQRT2,
|
|
225
|
+
Infinity: Infinity,
|
|
226
|
+
NaN: NaN
|
|
227
|
+
};
|
|
228
|
+
var BUILTIN_FUNCTIONS = {
|
|
229
|
+
abs: (x) => Math.abs(x),
|
|
230
|
+
ceil: (x) => Math.ceil(x),
|
|
231
|
+
floor: (x) => Math.floor(x),
|
|
232
|
+
round: (x) => Math.round(x),
|
|
233
|
+
trunc: (x) => Math.trunc(x),
|
|
234
|
+
sign: (x) => Math.sign(x),
|
|
235
|
+
sqrt: (x) => Math.sqrt(x),
|
|
236
|
+
cbrt: (x) => Math.cbrt(x),
|
|
237
|
+
pow: (x, y) => Math.pow(x, y),
|
|
238
|
+
exp: (x) => Math.exp(x),
|
|
239
|
+
log: (x) => Math.log(x),
|
|
240
|
+
log2: (x) => Math.log2(x),
|
|
241
|
+
log10: (x) => Math.log10(x),
|
|
242
|
+
sin: (x) => Math.sin(x),
|
|
243
|
+
cos: (x) => Math.cos(x),
|
|
244
|
+
tan: (x) => Math.tan(x),
|
|
245
|
+
asin: (x) => Math.asin(x),
|
|
246
|
+
acos: (x) => Math.acos(x),
|
|
247
|
+
atan: (x) => Math.atan(x),
|
|
248
|
+
atan2: (y, x) => Math.atan2(y, x),
|
|
249
|
+
sinh: (x) => Math.sinh(x),
|
|
250
|
+
cosh: (x) => Math.cosh(x),
|
|
251
|
+
tanh: (x) => Math.tanh(x),
|
|
252
|
+
min: (...args) => Math.min(...args),
|
|
253
|
+
max: (...args) => Math.max(...args),
|
|
254
|
+
clamp: (x, lo, hi) => Math.min(Math.max(x, lo), hi),
|
|
255
|
+
hypot: (...args) => Math.hypot(...args),
|
|
256
|
+
random: () => Math.random(),
|
|
257
|
+
isNaN: (x) => isNaN(x) ? 1 : 0,
|
|
258
|
+
isFinite: (x) => isFinite(x) ? 1 : 0
|
|
259
|
+
};
|
|
260
|
+
|
|
261
|
+
// src/evaluator.ts
|
|
262
|
+
function evalNode(node, opts, depth) {
|
|
263
|
+
if (depth > opts.maxDepth) throw new FormulaError("Maximum recursion depth exceeded");
|
|
264
|
+
const d = depth + 1;
|
|
265
|
+
switch (node.kind) {
|
|
266
|
+
case "number":
|
|
267
|
+
return node.value;
|
|
268
|
+
case "identifier": {
|
|
269
|
+
const name = node.name;
|
|
270
|
+
if (name in opts.vars) return opts.vars[name];
|
|
271
|
+
if (name in BUILTIN_VARS) return BUILTIN_VARS[name];
|
|
272
|
+
throw new FormulaError(`Unknown variable '${name}'`);
|
|
273
|
+
}
|
|
274
|
+
case "unary": {
|
|
275
|
+
const v = evalNode(node.operand, opts, d);
|
|
276
|
+
if (node.op === "-") return -v;
|
|
277
|
+
if (node.op === "!") return v === 0 ? 1 : 0;
|
|
278
|
+
throw new FormulaError(`Unknown unary operator '${node.op}'`);
|
|
279
|
+
}
|
|
280
|
+
case "binary": {
|
|
281
|
+
const l = evalNode(node.left, opts, d);
|
|
282
|
+
if (node.op === "&&") return l !== 0 ? evalNode(node.right, opts, d) : 0;
|
|
283
|
+
if (node.op === "||") return l !== 0 ? l : evalNode(node.right, opts, d);
|
|
284
|
+
const r = evalNode(node.right, opts, d);
|
|
285
|
+
switch (node.op) {
|
|
286
|
+
case "+":
|
|
287
|
+
return l + r;
|
|
288
|
+
case "-":
|
|
289
|
+
return l - r;
|
|
290
|
+
case "*":
|
|
291
|
+
return l * r;
|
|
292
|
+
case "/":
|
|
293
|
+
if (r === 0) throw new FormulaError("Division by zero");
|
|
294
|
+
return l / r;
|
|
295
|
+
case "%":
|
|
296
|
+
if (r === 0) throw new FormulaError("Modulo by zero");
|
|
297
|
+
return l % r;
|
|
298
|
+
case "^":
|
|
299
|
+
return Math.pow(l, r);
|
|
300
|
+
case "==":
|
|
301
|
+
return l === r ? 1 : 0;
|
|
302
|
+
case "!=":
|
|
303
|
+
return l !== r ? 1 : 0;
|
|
304
|
+
case "<":
|
|
305
|
+
return l < r ? 1 : 0;
|
|
306
|
+
case "<=":
|
|
307
|
+
return l <= r ? 1 : 0;
|
|
308
|
+
case ">":
|
|
309
|
+
return l > r ? 1 : 0;
|
|
310
|
+
case ">=":
|
|
311
|
+
return l >= r ? 1 : 0;
|
|
312
|
+
default:
|
|
313
|
+
throw new FormulaError(`Unknown operator '${node.op}'`);
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
case "call": {
|
|
317
|
+
const fn = opts.functions[node.name] ?? BUILTIN_FUNCTIONS[node.name];
|
|
318
|
+
if (!fn) throw new FormulaError(`Unknown function '${node.name}'`);
|
|
319
|
+
const args = node.args.map((a) => evalNode(a, opts, d));
|
|
320
|
+
return fn(...args);
|
|
321
|
+
}
|
|
322
|
+
case "conditional": {
|
|
323
|
+
const cond = evalNode(node.condition, opts, d);
|
|
324
|
+
return cond !== 0 ? evalNode(node.consequent, opts, d) : evalNode(node.alternate, opts, d);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
function evalAST(node, opts = {}) {
|
|
329
|
+
const resolved = {
|
|
330
|
+
vars: opts.vars ?? {},
|
|
331
|
+
functions: opts.functions ?? {},
|
|
332
|
+
maxDepth: opts.maxDepth ?? 200
|
|
333
|
+
};
|
|
334
|
+
return evalNode(node, resolved, 0);
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// src/formula.ts
|
|
338
|
+
function compile(expr) {
|
|
339
|
+
const ast = parse(expr);
|
|
340
|
+
return new CompiledFormula(ast, expr);
|
|
341
|
+
}
|
|
342
|
+
function evaluate(expr, vars, opts) {
|
|
343
|
+
const ast = parse(expr);
|
|
344
|
+
return evalAST(ast, { ...opts, vars });
|
|
345
|
+
}
|
|
346
|
+
function tryEvaluate(expr, vars, opts) {
|
|
347
|
+
try {
|
|
348
|
+
return evaluate(expr, vars, opts);
|
|
349
|
+
} catch {
|
|
350
|
+
return null;
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
var CompiledFormula = class {
|
|
354
|
+
constructor(ast, source) {
|
|
355
|
+
this._ast = ast;
|
|
356
|
+
this.source = source;
|
|
357
|
+
}
|
|
358
|
+
evaluate(vars, opts) {
|
|
359
|
+
return evalAST(this._ast, { ...opts, vars });
|
|
360
|
+
}
|
|
361
|
+
/** Return all variable names referenced in this formula. */
|
|
362
|
+
variables() {
|
|
363
|
+
const names = /* @__PURE__ */ new Set();
|
|
364
|
+
collectVars(this._ast, names);
|
|
365
|
+
return [...names].sort();
|
|
366
|
+
}
|
|
367
|
+
};
|
|
368
|
+
function collectVars(node, out) {
|
|
369
|
+
switch (node.kind) {
|
|
370
|
+
case "number":
|
|
371
|
+
break;
|
|
372
|
+
case "identifier":
|
|
373
|
+
out.add(node.name);
|
|
374
|
+
break;
|
|
375
|
+
case "unary":
|
|
376
|
+
collectVars(node.operand, out);
|
|
377
|
+
break;
|
|
378
|
+
case "binary":
|
|
379
|
+
collectVars(node.left, out);
|
|
380
|
+
collectVars(node.right, out);
|
|
381
|
+
break;
|
|
382
|
+
case "call":
|
|
383
|
+
node.args.forEach((a) => collectVars(a, out));
|
|
384
|
+
break;
|
|
385
|
+
case "conditional":
|
|
386
|
+
collectVars(node.condition, out);
|
|
387
|
+
collectVars(node.consequent, out);
|
|
388
|
+
collectVars(node.alternate, out);
|
|
389
|
+
break;
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
393
|
+
0 && (module.exports = {
|
|
394
|
+
BUILTIN_FUNCTIONS,
|
|
395
|
+
BUILTIN_VARS,
|
|
396
|
+
CompiledFormula,
|
|
397
|
+
FormulaError,
|
|
398
|
+
compile,
|
|
399
|
+
evalAST,
|
|
400
|
+
evaluate,
|
|
401
|
+
parse,
|
|
402
|
+
tryEvaluate
|
|
403
|
+
});
|
|
404
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/types.ts","../src/lexer.ts","../src/parser.ts","../src/builtins.ts","../src/evaluator.ts","../src/formula.ts"],"sourcesContent":["export type { Value, Variables, FunctionMap, EvalOptions, ASTNode } from \"./types.js\";\nexport { FormulaError } from \"./types.js\";\nexport { parse } from \"./parser.js\";\nexport { evalAST } from \"./evaluator.js\";\nexport { evaluate, tryEvaluate, compile, CompiledFormula } from \"./formula.js\";\nexport { BUILTIN_VARS, BUILTIN_FUNCTIONS } from \"./builtins.js\";\n","export type Value = number;\n\nexport type Variables = Record<string, Value>;\n\nexport type FunctionMap = Record<string, (...args: Value[]) => Value>;\n\nexport interface EvalOptions {\n vars?: Variables;\n functions?: FunctionMap;\n maxDepth?: number;\n}\n\nexport class FormulaError extends Error {\n constructor(\n message: string,\n public readonly position?: number\n ) {\n super(message);\n this.name = \"FormulaError\";\n }\n}\n\n// ── AST node types ────────────────────────────────────────────────────────────\n\nexport type NodeKind =\n | \"number\"\n | \"identifier\"\n | \"binary\"\n | \"unary\"\n | \"call\"\n | \"conditional\";\n\nexport interface NumberNode {\n kind: \"number\";\n value: number;\n}\n\nexport interface IdentifierNode {\n kind: \"identifier\";\n name: string;\n}\n\nexport interface BinaryNode {\n kind: \"binary\";\n op: string;\n left: ASTNode;\n right: ASTNode;\n}\n\nexport interface UnaryNode {\n kind: \"unary\";\n op: string;\n operand: ASTNode;\n}\n\nexport interface CallNode {\n kind: \"call\";\n name: string;\n args: ASTNode[];\n}\n\nexport interface ConditionalNode {\n kind: \"conditional\";\n condition: ASTNode;\n consequent: ASTNode;\n alternate: ASTNode;\n}\n\nexport type ASTNode =\n | NumberNode\n | IdentifierNode\n | BinaryNode\n | UnaryNode\n | CallNode\n | ConditionalNode;\n","import { FormulaError } from \"./types.js\";\n\nexport const enum TT {\n Number = \"Number\",\n Ident = \"Ident\",\n Plus = \"+\",\n Minus = \"-\",\n Star = \"*\",\n Slash = \"/\",\n Percent = \"%\",\n Caret = \"^\",\n LParen = \"(\",\n RParen = \")\",\n Comma = \",\",\n Question = \"?\",\n Colon = \":\",\n Eq = \"==\",\n NEq = \"!=\",\n Lt = \"<\",\n Lte = \"<=\",\n Gt = \">\",\n Gte = \">=\",\n And = \"&&\",\n Or = \"||\",\n Not = \"!\",\n EOF = \"EOF\",\n}\n\nexport interface Token {\n type: TT;\n raw: string;\n pos: number;\n}\n\nexport function tokenize(src: string): Token[] {\n const tokens: Token[] = [];\n let i = 0;\n\n while (i < src.length) {\n // skip whitespace\n if (/\\s/.test(src[i])) { i++; continue; }\n\n const pos = i;\n const ch = src[i];\n\n // numbers (int or float, optional exponent)\n if (/[0-9]/.test(ch) || (ch === \".\" && /[0-9]/.test(src[i + 1] ?? \"\"))) {\n let raw = \"\";\n while (i < src.length && /[0-9.]/.test(src[i])) raw += src[i++];\n if (src[i] === \"e\" || src[i] === \"E\") {\n raw += src[i++];\n if (src[i] === \"+\" || src[i] === \"-\") raw += src[i++];\n while (i < src.length && /[0-9]/.test(src[i])) raw += src[i++];\n }\n tokens.push({ type: TT.Number, raw, pos });\n continue;\n }\n\n // identifiers and keywords (only PI/E)\n if (/[a-zA-Z_]/.test(ch)) {\n let raw = \"\";\n while (i < src.length && /[a-zA-Z0-9_]/.test(src[i])) raw += src[i++];\n tokens.push({ type: TT.Ident, raw, pos });\n continue;\n }\n\n // two-char operators\n const two = src.slice(i, i + 2);\n if (two === \"==\" || two === \"!=\" || two === \"<=\" || two === \">=\" || two === \"&&\" || two === \"||\") {\n tokens.push({ type: two as TT, raw: two, pos });\n i += 2;\n continue;\n }\n\n // one-char operators\n const singles: Record<string, TT> = {\n \"+\": TT.Plus, \"-\": TT.Minus, \"*\": TT.Star, \"/\": TT.Slash,\n \"%\": TT.Percent, \"^\": TT.Caret,\n \"(\": TT.LParen, \")\": TT.RParen, \",\": TT.Comma,\n \"?\": TT.Question, \":\": TT.Colon,\n \"<\": TT.Lt, \">\": TT.Gt, \"!\": TT.Not,\n };\n if (ch in singles) {\n tokens.push({ type: singles[ch], raw: ch, pos });\n i++;\n continue;\n }\n\n throw new FormulaError(`Unexpected character '${ch}'`, pos);\n }\n\n tokens.push({ type: TT.EOF, raw: \"\", pos: i });\n return tokens;\n}\n","import { tokenize, Token, TT } from \"./lexer.js\";\nimport {\n ASTNode, NumberNode, IdentifierNode, BinaryNode,\n UnaryNode, CallNode, ConditionalNode, FormulaError,\n} from \"./types.js\";\n\n// Pratt parser (top-down operator precedence)\n\nconst BP: Partial<Record<TT, number>> = {\n [TT.Or]: 10,\n [TT.And]: 20,\n [TT.Eq]: 30, [TT.NEq]: 30,\n [TT.Lt]: 40, [TT.Lte]: 40, [TT.Gt]: 40, [TT.Gte]: 40,\n [TT.Plus]: 50, [TT.Minus]: 50,\n [TT.Star]: 60, [TT.Slash]: 60, [TT.Percent]: 60,\n [TT.Caret]: 70, // right-assoc → parse at BP-1\n};\n\nclass Parser {\n private tokens: Token[];\n private pos: number = 0;\n\n constructor(src: string) {\n this.tokens = tokenize(src);\n }\n\n private peek(): Token { return this.tokens[this.pos]; }\n private consume(): Token { return this.tokens[this.pos++]; }\n private expect(type: TT): Token {\n const t = this.consume();\n if (t.type !== type) throw new FormulaError(`Expected '${type}' but got '${t.raw}'`, t.pos);\n return t;\n }\n\n parse(): ASTNode {\n const node = this.expr(0);\n if (this.peek().type !== TT.EOF) {\n const t = this.peek();\n throw new FormulaError(`Unexpected token '${t.raw}'`, t.pos);\n }\n return node;\n }\n\n private expr(minBP: number): ASTNode {\n let left = this.prefix();\n\n while (true) {\n const t = this.peek();\n\n // ternary ? :\n if (t.type === TT.Question && minBP < 5) {\n this.consume();\n const consequent = this.expr(0);\n this.expect(TT.Colon);\n const alternate = this.expr(0);\n left = { kind: \"conditional\", condition: left, consequent, alternate } as ConditionalNode;\n continue;\n }\n\n const bp = BP[t.type as TT];\n if (bp === undefined || bp <= minBP) break;\n\n this.consume();\n const rightBP = t.type === TT.Caret ? bp - 1 : bp; // ^ is right-assoc\n const right = this.expr(rightBP);\n left = { kind: \"binary\", op: t.type, left, right } as BinaryNode;\n }\n\n return left;\n }\n\n private prefix(): ASTNode {\n const t = this.peek();\n\n if (t.type === TT.Number) {\n this.consume();\n return { kind: \"number\", value: parseFloat(t.raw) } as NumberNode;\n }\n\n if (t.type === TT.Ident) {\n this.consume();\n // function call?\n if (this.peek().type === TT.LParen) {\n this.consume(); // (\n const args: ASTNode[] = [];\n if (this.peek().type !== TT.RParen) {\n args.push(this.expr(0));\n while (this.peek().type === TT.Comma) {\n this.consume();\n args.push(this.expr(0));\n }\n }\n this.expect(TT.RParen);\n return { kind: \"call\", name: t.raw, args } as CallNode;\n }\n return { kind: \"identifier\", name: t.raw } as IdentifierNode;\n }\n\n if (t.type === TT.Minus) {\n this.consume();\n return { kind: \"unary\", op: \"-\", operand: this.expr(65) } as UnaryNode;\n }\n\n if (t.type === TT.Not) {\n this.consume();\n return { kind: \"unary\", op: \"!\", operand: this.expr(65) } as UnaryNode;\n }\n\n if (t.type === TT.Plus) {\n this.consume();\n return this.expr(65); // unary +\n }\n\n if (t.type === TT.LParen) {\n this.consume();\n const inner = this.expr(0);\n this.expect(TT.RParen);\n return inner;\n }\n\n throw new FormulaError(`Unexpected token '${t.raw}'`, t.pos);\n }\n}\n\nexport function parse(src: string): ASTNode {\n return new Parser(src).parse();\n}\n","import { FunctionMap, Variables } from \"./types.js\";\n\nexport const BUILTIN_VARS: Variables = {\n PI: Math.PI,\n E: Math.E,\n LN2: Math.LN2,\n LN10: Math.LN10,\n LOG2E: Math.LOG2E,\n LOG10E: Math.LOG10E,\n SQRT2: Math.SQRT2,\n Infinity: Infinity,\n NaN: NaN,\n};\n\nexport const BUILTIN_FUNCTIONS: FunctionMap = {\n abs: (x) => Math.abs(x),\n ceil: (x) => Math.ceil(x),\n floor: (x) => Math.floor(x),\n round: (x) => Math.round(x),\n trunc: (x) => Math.trunc(x),\n sign: (x) => Math.sign(x),\n\n sqrt: (x) => Math.sqrt(x),\n cbrt: (x) => Math.cbrt(x),\n pow: (x, y) => Math.pow(x, y),\n exp: (x) => Math.exp(x),\n log: (x) => Math.log(x),\n log2: (x) => Math.log2(x),\n log10: (x) => Math.log10(x),\n\n sin: (x) => Math.sin(x),\n cos: (x) => Math.cos(x),\n tan: (x) => Math.tan(x),\n asin: (x) => Math.asin(x),\n acos: (x) => Math.acos(x),\n atan: (x) => Math.atan(x),\n atan2: (y, x) => Math.atan2(y, x),\n sinh: (x) => Math.sinh(x),\n cosh: (x) => Math.cosh(x),\n tanh: (x) => Math.tanh(x),\n\n min: (...args) => Math.min(...args),\n max: (...args) => Math.max(...args),\n clamp: (x, lo, hi) => Math.min(Math.max(x, lo), hi),\n\n hypot: (...args) => Math.hypot(...args),\n\n random: () => Math.random(),\n\n isNaN: (x) => (isNaN(x) ? 1 : 0),\n isFinite: (x) => (isFinite(x) ? 1 : 0),\n};\n","import { ASTNode, EvalOptions, FormulaError, Value } from \"./types.js\";\nimport { BUILTIN_VARS, BUILTIN_FUNCTIONS } from \"./builtins.js\";\n\nfunction evalNode(node: ASTNode, opts: Required<EvalOptions>, depth: number): Value {\n if (depth > opts.maxDepth) throw new FormulaError(\"Maximum recursion depth exceeded\");\n\n const d = depth + 1;\n\n switch (node.kind) {\n case \"number\":\n return node.value;\n\n case \"identifier\": {\n const name = node.name;\n if (name in opts.vars) return opts.vars[name];\n if (name in BUILTIN_VARS) return BUILTIN_VARS[name];\n throw new FormulaError(`Unknown variable '${name}'`);\n }\n\n case \"unary\": {\n const v = evalNode(node.operand, opts, d);\n if (node.op === \"-\") return -v;\n if (node.op === \"!\") return v === 0 ? 1 : 0;\n throw new FormulaError(`Unknown unary operator '${node.op}'`);\n }\n\n case \"binary\": {\n const l = evalNode(node.left, opts, d);\n // short-circuit for && / ||\n if (node.op === \"&&\") return (l !== 0 ? evalNode(node.right, opts, d) : 0);\n if (node.op === \"||\") return (l !== 0 ? l : evalNode(node.right, opts, d));\n\n const r = evalNode(node.right, opts, d);\n switch (node.op) {\n case \"+\": return l + r;\n case \"-\": return l - r;\n case \"*\": return l * r;\n case \"/\":\n if (r === 0) throw new FormulaError(\"Division by zero\");\n return l / r;\n case \"%\":\n if (r === 0) throw new FormulaError(\"Modulo by zero\");\n return l % r;\n case \"^\": return Math.pow(l, r);\n case \"==\": return l === r ? 1 : 0;\n case \"!=\": return l !== r ? 1 : 0;\n case \"<\": return l < r ? 1 : 0;\n case \"<=\": return l <= r ? 1 : 0;\n case \">\": return l > r ? 1 : 0;\n case \">=\": return l >= r ? 1 : 0;\n default:\n throw new FormulaError(`Unknown operator '${node.op}'`);\n }\n }\n\n case \"call\": {\n const fn = opts.functions[node.name] ?? BUILTIN_FUNCTIONS[node.name];\n if (!fn) throw new FormulaError(`Unknown function '${node.name}'`);\n const args = node.args.map((a) => evalNode(a, opts, d));\n return fn(...args);\n }\n\n case \"conditional\": {\n const cond = evalNode(node.condition, opts, d);\n return cond !== 0\n ? evalNode(node.consequent, opts, d)\n : evalNode(node.alternate, opts, d);\n }\n }\n}\n\nexport function evalAST(node: ASTNode, opts: EvalOptions = {}): Value {\n const resolved: Required<EvalOptions> = {\n vars: opts.vars ?? {},\n functions: opts.functions ?? {},\n maxDepth: opts.maxDepth ?? 200,\n };\n return evalNode(node, resolved, 0);\n}\n","import { parse } from \"./parser.js\";\nimport { evalAST } from \"./evaluator.js\";\nimport { ASTNode, EvalOptions, Value } from \"./types.js\";\n\n/**\n * Parse an expression string into an AST (reuse for repeated evaluations).\n */\nexport function compile(expr: string): CompiledFormula {\n const ast = parse(expr);\n return new CompiledFormula(ast, expr);\n}\n\n/**\n * Parse and evaluate an expression in one step.\n */\nexport function evaluate(expr: string, vars?: Record<string, Value>, opts?: Omit<EvalOptions, \"vars\">): Value {\n const ast = parse(expr);\n return evalAST(ast, { ...opts, vars });\n}\n\n/**\n * Parse and evaluate, returning null instead of throwing on parse/eval errors.\n */\nexport function tryEvaluate(expr: string, vars?: Record<string, Value>, opts?: Omit<EvalOptions, \"vars\">): Value | null {\n try { return evaluate(expr, vars, opts); }\n catch { return null; }\n}\n\nexport class CompiledFormula {\n readonly source: string;\n private readonly _ast: ASTNode;\n\n constructor(ast: ASTNode, source: string) {\n this._ast = ast;\n this.source = source;\n }\n\n evaluate(vars?: Record<string, Value>, opts?: Omit<EvalOptions, \"vars\">): Value {\n return evalAST(this._ast, { ...opts, vars });\n }\n\n /** Return all variable names referenced in this formula. */\n variables(): string[] {\n const names = new Set<string>();\n collectVars(this._ast, names);\n return [...names].sort();\n }\n}\n\nfunction collectVars(node: ASTNode, out: Set<string>): void {\n switch (node.kind) {\n case \"number\": break;\n case \"identifier\": out.add(node.name); break;\n case \"unary\": collectVars(node.operand, out); break;\n case \"binary\": collectVars(node.left, out); collectVars(node.right, out); break;\n case \"call\": node.args.forEach((a) => collectVars(a, out)); break;\n case \"conditional\":\n collectVars(node.condition, out);\n collectVars(node.consequent, out);\n collectVars(node.alternate, out);\n break;\n }\n}\n"],"mappings":"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","names":[]}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
type Value = number;
|
|
2
|
+
type Variables = Record<string, Value>;
|
|
3
|
+
type FunctionMap = Record<string, (...args: Value[]) => Value>;
|
|
4
|
+
interface EvalOptions {
|
|
5
|
+
vars?: Variables;
|
|
6
|
+
functions?: FunctionMap;
|
|
7
|
+
maxDepth?: number;
|
|
8
|
+
}
|
|
9
|
+
declare class FormulaError extends Error {
|
|
10
|
+
readonly position?: number | undefined;
|
|
11
|
+
constructor(message: string, position?: number | undefined);
|
|
12
|
+
}
|
|
13
|
+
interface NumberNode {
|
|
14
|
+
kind: "number";
|
|
15
|
+
value: number;
|
|
16
|
+
}
|
|
17
|
+
interface IdentifierNode {
|
|
18
|
+
kind: "identifier";
|
|
19
|
+
name: string;
|
|
20
|
+
}
|
|
21
|
+
interface BinaryNode {
|
|
22
|
+
kind: "binary";
|
|
23
|
+
op: string;
|
|
24
|
+
left: ASTNode;
|
|
25
|
+
right: ASTNode;
|
|
26
|
+
}
|
|
27
|
+
interface UnaryNode {
|
|
28
|
+
kind: "unary";
|
|
29
|
+
op: string;
|
|
30
|
+
operand: ASTNode;
|
|
31
|
+
}
|
|
32
|
+
interface CallNode {
|
|
33
|
+
kind: "call";
|
|
34
|
+
name: string;
|
|
35
|
+
args: ASTNode[];
|
|
36
|
+
}
|
|
37
|
+
interface ConditionalNode {
|
|
38
|
+
kind: "conditional";
|
|
39
|
+
condition: ASTNode;
|
|
40
|
+
consequent: ASTNode;
|
|
41
|
+
alternate: ASTNode;
|
|
42
|
+
}
|
|
43
|
+
type ASTNode = NumberNode | IdentifierNode | BinaryNode | UnaryNode | CallNode | ConditionalNode;
|
|
44
|
+
|
|
45
|
+
declare function parse(src: string): ASTNode;
|
|
46
|
+
|
|
47
|
+
declare function evalAST(node: ASTNode, opts?: EvalOptions): Value;
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Parse an expression string into an AST (reuse for repeated evaluations).
|
|
51
|
+
*/
|
|
52
|
+
declare function compile(expr: string): CompiledFormula;
|
|
53
|
+
/**
|
|
54
|
+
* Parse and evaluate an expression in one step.
|
|
55
|
+
*/
|
|
56
|
+
declare function evaluate(expr: string, vars?: Record<string, Value>, opts?: Omit<EvalOptions, "vars">): Value;
|
|
57
|
+
/**
|
|
58
|
+
* Parse and evaluate, returning null instead of throwing on parse/eval errors.
|
|
59
|
+
*/
|
|
60
|
+
declare function tryEvaluate(expr: string, vars?: Record<string, Value>, opts?: Omit<EvalOptions, "vars">): Value | null;
|
|
61
|
+
declare class CompiledFormula {
|
|
62
|
+
readonly source: string;
|
|
63
|
+
private readonly _ast;
|
|
64
|
+
constructor(ast: ASTNode, source: string);
|
|
65
|
+
evaluate(vars?: Record<string, Value>, opts?: Omit<EvalOptions, "vars">): Value;
|
|
66
|
+
/** Return all variable names referenced in this formula. */
|
|
67
|
+
variables(): string[];
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
declare const BUILTIN_VARS: Variables;
|
|
71
|
+
declare const BUILTIN_FUNCTIONS: FunctionMap;
|
|
72
|
+
|
|
73
|
+
export { type ASTNode, BUILTIN_FUNCTIONS, BUILTIN_VARS, CompiledFormula, type EvalOptions, FormulaError, type FunctionMap, type Value, type Variables, compile, evalAST, evaluate, parse, tryEvaluate };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
type Value = number;
|
|
2
|
+
type Variables = Record<string, Value>;
|
|
3
|
+
type FunctionMap = Record<string, (...args: Value[]) => Value>;
|
|
4
|
+
interface EvalOptions {
|
|
5
|
+
vars?: Variables;
|
|
6
|
+
functions?: FunctionMap;
|
|
7
|
+
maxDepth?: number;
|
|
8
|
+
}
|
|
9
|
+
declare class FormulaError extends Error {
|
|
10
|
+
readonly position?: number | undefined;
|
|
11
|
+
constructor(message: string, position?: number | undefined);
|
|
12
|
+
}
|
|
13
|
+
interface NumberNode {
|
|
14
|
+
kind: "number";
|
|
15
|
+
value: number;
|
|
16
|
+
}
|
|
17
|
+
interface IdentifierNode {
|
|
18
|
+
kind: "identifier";
|
|
19
|
+
name: string;
|
|
20
|
+
}
|
|
21
|
+
interface BinaryNode {
|
|
22
|
+
kind: "binary";
|
|
23
|
+
op: string;
|
|
24
|
+
left: ASTNode;
|
|
25
|
+
right: ASTNode;
|
|
26
|
+
}
|
|
27
|
+
interface UnaryNode {
|
|
28
|
+
kind: "unary";
|
|
29
|
+
op: string;
|
|
30
|
+
operand: ASTNode;
|
|
31
|
+
}
|
|
32
|
+
interface CallNode {
|
|
33
|
+
kind: "call";
|
|
34
|
+
name: string;
|
|
35
|
+
args: ASTNode[];
|
|
36
|
+
}
|
|
37
|
+
interface ConditionalNode {
|
|
38
|
+
kind: "conditional";
|
|
39
|
+
condition: ASTNode;
|
|
40
|
+
consequent: ASTNode;
|
|
41
|
+
alternate: ASTNode;
|
|
42
|
+
}
|
|
43
|
+
type ASTNode = NumberNode | IdentifierNode | BinaryNode | UnaryNode | CallNode | ConditionalNode;
|
|
44
|
+
|
|
45
|
+
declare function parse(src: string): ASTNode;
|
|
46
|
+
|
|
47
|
+
declare function evalAST(node: ASTNode, opts?: EvalOptions): Value;
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Parse an expression string into an AST (reuse for repeated evaluations).
|
|
51
|
+
*/
|
|
52
|
+
declare function compile(expr: string): CompiledFormula;
|
|
53
|
+
/**
|
|
54
|
+
* Parse and evaluate an expression in one step.
|
|
55
|
+
*/
|
|
56
|
+
declare function evaluate(expr: string, vars?: Record<string, Value>, opts?: Omit<EvalOptions, "vars">): Value;
|
|
57
|
+
/**
|
|
58
|
+
* Parse and evaluate, returning null instead of throwing on parse/eval errors.
|
|
59
|
+
*/
|
|
60
|
+
declare function tryEvaluate(expr: string, vars?: Record<string, Value>, opts?: Omit<EvalOptions, "vars">): Value | null;
|
|
61
|
+
declare class CompiledFormula {
|
|
62
|
+
readonly source: string;
|
|
63
|
+
private readonly _ast;
|
|
64
|
+
constructor(ast: ASTNode, source: string);
|
|
65
|
+
evaluate(vars?: Record<string, Value>, opts?: Omit<EvalOptions, "vars">): Value;
|
|
66
|
+
/** Return all variable names referenced in this formula. */
|
|
67
|
+
variables(): string[];
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
declare const BUILTIN_VARS: Variables;
|
|
71
|
+
declare const BUILTIN_FUNCTIONS: FunctionMap;
|
|
72
|
+
|
|
73
|
+
export { type ASTNode, BUILTIN_FUNCTIONS, BUILTIN_VARS, CompiledFormula, type EvalOptions, FormulaError, type FunctionMap, type Value, type Variables, compile, evalAST, evaluate, parse, tryEvaluate };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,369 @@
|
|
|
1
|
+
// src/types.ts
|
|
2
|
+
var FormulaError = class extends Error {
|
|
3
|
+
constructor(message, position) {
|
|
4
|
+
super(message);
|
|
5
|
+
this.position = position;
|
|
6
|
+
this.name = "FormulaError";
|
|
7
|
+
}
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
// src/lexer.ts
|
|
11
|
+
function tokenize(src) {
|
|
12
|
+
const tokens = [];
|
|
13
|
+
let i = 0;
|
|
14
|
+
while (i < src.length) {
|
|
15
|
+
if (/\s/.test(src[i])) {
|
|
16
|
+
i++;
|
|
17
|
+
continue;
|
|
18
|
+
}
|
|
19
|
+
const pos = i;
|
|
20
|
+
const ch = src[i];
|
|
21
|
+
if (/[0-9]/.test(ch) || ch === "." && /[0-9]/.test(src[i + 1] ?? "")) {
|
|
22
|
+
let raw = "";
|
|
23
|
+
while (i < src.length && /[0-9.]/.test(src[i])) raw += src[i++];
|
|
24
|
+
if (src[i] === "e" || src[i] === "E") {
|
|
25
|
+
raw += src[i++];
|
|
26
|
+
if (src[i] === "+" || src[i] === "-") raw += src[i++];
|
|
27
|
+
while (i < src.length && /[0-9]/.test(src[i])) raw += src[i++];
|
|
28
|
+
}
|
|
29
|
+
tokens.push({ type: "Number" /* Number */, raw, pos });
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
if (/[a-zA-Z_]/.test(ch)) {
|
|
33
|
+
let raw = "";
|
|
34
|
+
while (i < src.length && /[a-zA-Z0-9_]/.test(src[i])) raw += src[i++];
|
|
35
|
+
tokens.push({ type: "Ident" /* Ident */, raw, pos });
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
const two = src.slice(i, i + 2);
|
|
39
|
+
if (two === "==" || two === "!=" || two === "<=" || two === ">=" || two === "&&" || two === "||") {
|
|
40
|
+
tokens.push({ type: two, raw: two, pos });
|
|
41
|
+
i += 2;
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
const singles = {
|
|
45
|
+
"+": "+" /* Plus */,
|
|
46
|
+
"-": "-" /* Minus */,
|
|
47
|
+
"*": "*" /* Star */,
|
|
48
|
+
"/": "/" /* Slash */,
|
|
49
|
+
"%": "%" /* Percent */,
|
|
50
|
+
"^": "^" /* Caret */,
|
|
51
|
+
"(": "(" /* LParen */,
|
|
52
|
+
")": ")" /* RParen */,
|
|
53
|
+
",": "," /* Comma */,
|
|
54
|
+
"?": "?" /* Question */,
|
|
55
|
+
":": ":" /* Colon */,
|
|
56
|
+
"<": "<" /* Lt */,
|
|
57
|
+
">": ">" /* Gt */,
|
|
58
|
+
"!": "!" /* Not */
|
|
59
|
+
};
|
|
60
|
+
if (ch in singles) {
|
|
61
|
+
tokens.push({ type: singles[ch], raw: ch, pos });
|
|
62
|
+
i++;
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
throw new FormulaError(`Unexpected character '${ch}'`, pos);
|
|
66
|
+
}
|
|
67
|
+
tokens.push({ type: "EOF" /* EOF */, raw: "", pos: i });
|
|
68
|
+
return tokens;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// src/parser.ts
|
|
72
|
+
var BP = {
|
|
73
|
+
["||" /* Or */]: 10,
|
|
74
|
+
["&&" /* And */]: 20,
|
|
75
|
+
["==" /* Eq */]: 30,
|
|
76
|
+
["!=" /* NEq */]: 30,
|
|
77
|
+
["<" /* Lt */]: 40,
|
|
78
|
+
["<=" /* Lte */]: 40,
|
|
79
|
+
[">" /* Gt */]: 40,
|
|
80
|
+
[">=" /* Gte */]: 40,
|
|
81
|
+
["+" /* Plus */]: 50,
|
|
82
|
+
["-" /* Minus */]: 50,
|
|
83
|
+
["*" /* Star */]: 60,
|
|
84
|
+
["/" /* Slash */]: 60,
|
|
85
|
+
["%" /* Percent */]: 60,
|
|
86
|
+
["^" /* Caret */]: 70
|
|
87
|
+
// right-assoc → parse at BP-1
|
|
88
|
+
};
|
|
89
|
+
var Parser = class {
|
|
90
|
+
constructor(src) {
|
|
91
|
+
this.pos = 0;
|
|
92
|
+
this.tokens = tokenize(src);
|
|
93
|
+
}
|
|
94
|
+
peek() {
|
|
95
|
+
return this.tokens[this.pos];
|
|
96
|
+
}
|
|
97
|
+
consume() {
|
|
98
|
+
return this.tokens[this.pos++];
|
|
99
|
+
}
|
|
100
|
+
expect(type) {
|
|
101
|
+
const t = this.consume();
|
|
102
|
+
if (t.type !== type) throw new FormulaError(`Expected '${type}' but got '${t.raw}'`, t.pos);
|
|
103
|
+
return t;
|
|
104
|
+
}
|
|
105
|
+
parse() {
|
|
106
|
+
const node = this.expr(0);
|
|
107
|
+
if (this.peek().type !== "EOF" /* EOF */) {
|
|
108
|
+
const t = this.peek();
|
|
109
|
+
throw new FormulaError(`Unexpected token '${t.raw}'`, t.pos);
|
|
110
|
+
}
|
|
111
|
+
return node;
|
|
112
|
+
}
|
|
113
|
+
expr(minBP) {
|
|
114
|
+
let left = this.prefix();
|
|
115
|
+
while (true) {
|
|
116
|
+
const t = this.peek();
|
|
117
|
+
if (t.type === "?" /* Question */ && minBP < 5) {
|
|
118
|
+
this.consume();
|
|
119
|
+
const consequent = this.expr(0);
|
|
120
|
+
this.expect(":" /* Colon */);
|
|
121
|
+
const alternate = this.expr(0);
|
|
122
|
+
left = { kind: "conditional", condition: left, consequent, alternate };
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
const bp = BP[t.type];
|
|
126
|
+
if (bp === void 0 || bp <= minBP) break;
|
|
127
|
+
this.consume();
|
|
128
|
+
const rightBP = t.type === "^" /* Caret */ ? bp - 1 : bp;
|
|
129
|
+
const right = this.expr(rightBP);
|
|
130
|
+
left = { kind: "binary", op: t.type, left, right };
|
|
131
|
+
}
|
|
132
|
+
return left;
|
|
133
|
+
}
|
|
134
|
+
prefix() {
|
|
135
|
+
const t = this.peek();
|
|
136
|
+
if (t.type === "Number" /* Number */) {
|
|
137
|
+
this.consume();
|
|
138
|
+
return { kind: "number", value: parseFloat(t.raw) };
|
|
139
|
+
}
|
|
140
|
+
if (t.type === "Ident" /* Ident */) {
|
|
141
|
+
this.consume();
|
|
142
|
+
if (this.peek().type === "(" /* LParen */) {
|
|
143
|
+
this.consume();
|
|
144
|
+
const args = [];
|
|
145
|
+
if (this.peek().type !== ")" /* RParen */) {
|
|
146
|
+
args.push(this.expr(0));
|
|
147
|
+
while (this.peek().type === "," /* Comma */) {
|
|
148
|
+
this.consume();
|
|
149
|
+
args.push(this.expr(0));
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
this.expect(")" /* RParen */);
|
|
153
|
+
return { kind: "call", name: t.raw, args };
|
|
154
|
+
}
|
|
155
|
+
return { kind: "identifier", name: t.raw };
|
|
156
|
+
}
|
|
157
|
+
if (t.type === "-" /* Minus */) {
|
|
158
|
+
this.consume();
|
|
159
|
+
return { kind: "unary", op: "-", operand: this.expr(65) };
|
|
160
|
+
}
|
|
161
|
+
if (t.type === "!" /* Not */) {
|
|
162
|
+
this.consume();
|
|
163
|
+
return { kind: "unary", op: "!", operand: this.expr(65) };
|
|
164
|
+
}
|
|
165
|
+
if (t.type === "+" /* Plus */) {
|
|
166
|
+
this.consume();
|
|
167
|
+
return this.expr(65);
|
|
168
|
+
}
|
|
169
|
+
if (t.type === "(" /* LParen */) {
|
|
170
|
+
this.consume();
|
|
171
|
+
const inner = this.expr(0);
|
|
172
|
+
this.expect(")" /* RParen */);
|
|
173
|
+
return inner;
|
|
174
|
+
}
|
|
175
|
+
throw new FormulaError(`Unexpected token '${t.raw}'`, t.pos);
|
|
176
|
+
}
|
|
177
|
+
};
|
|
178
|
+
function parse(src) {
|
|
179
|
+
return new Parser(src).parse();
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// src/builtins.ts
|
|
183
|
+
var BUILTIN_VARS = {
|
|
184
|
+
PI: Math.PI,
|
|
185
|
+
E: Math.E,
|
|
186
|
+
LN2: Math.LN2,
|
|
187
|
+
LN10: Math.LN10,
|
|
188
|
+
LOG2E: Math.LOG2E,
|
|
189
|
+
LOG10E: Math.LOG10E,
|
|
190
|
+
SQRT2: Math.SQRT2,
|
|
191
|
+
Infinity: Infinity,
|
|
192
|
+
NaN: NaN
|
|
193
|
+
};
|
|
194
|
+
var BUILTIN_FUNCTIONS = {
|
|
195
|
+
abs: (x) => Math.abs(x),
|
|
196
|
+
ceil: (x) => Math.ceil(x),
|
|
197
|
+
floor: (x) => Math.floor(x),
|
|
198
|
+
round: (x) => Math.round(x),
|
|
199
|
+
trunc: (x) => Math.trunc(x),
|
|
200
|
+
sign: (x) => Math.sign(x),
|
|
201
|
+
sqrt: (x) => Math.sqrt(x),
|
|
202
|
+
cbrt: (x) => Math.cbrt(x),
|
|
203
|
+
pow: (x, y) => Math.pow(x, y),
|
|
204
|
+
exp: (x) => Math.exp(x),
|
|
205
|
+
log: (x) => Math.log(x),
|
|
206
|
+
log2: (x) => Math.log2(x),
|
|
207
|
+
log10: (x) => Math.log10(x),
|
|
208
|
+
sin: (x) => Math.sin(x),
|
|
209
|
+
cos: (x) => Math.cos(x),
|
|
210
|
+
tan: (x) => Math.tan(x),
|
|
211
|
+
asin: (x) => Math.asin(x),
|
|
212
|
+
acos: (x) => Math.acos(x),
|
|
213
|
+
atan: (x) => Math.atan(x),
|
|
214
|
+
atan2: (y, x) => Math.atan2(y, x),
|
|
215
|
+
sinh: (x) => Math.sinh(x),
|
|
216
|
+
cosh: (x) => Math.cosh(x),
|
|
217
|
+
tanh: (x) => Math.tanh(x),
|
|
218
|
+
min: (...args) => Math.min(...args),
|
|
219
|
+
max: (...args) => Math.max(...args),
|
|
220
|
+
clamp: (x, lo, hi) => Math.min(Math.max(x, lo), hi),
|
|
221
|
+
hypot: (...args) => Math.hypot(...args),
|
|
222
|
+
random: () => Math.random(),
|
|
223
|
+
isNaN: (x) => isNaN(x) ? 1 : 0,
|
|
224
|
+
isFinite: (x) => isFinite(x) ? 1 : 0
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
// src/evaluator.ts
|
|
228
|
+
function evalNode(node, opts, depth) {
|
|
229
|
+
if (depth > opts.maxDepth) throw new FormulaError("Maximum recursion depth exceeded");
|
|
230
|
+
const d = depth + 1;
|
|
231
|
+
switch (node.kind) {
|
|
232
|
+
case "number":
|
|
233
|
+
return node.value;
|
|
234
|
+
case "identifier": {
|
|
235
|
+
const name = node.name;
|
|
236
|
+
if (name in opts.vars) return opts.vars[name];
|
|
237
|
+
if (name in BUILTIN_VARS) return BUILTIN_VARS[name];
|
|
238
|
+
throw new FormulaError(`Unknown variable '${name}'`);
|
|
239
|
+
}
|
|
240
|
+
case "unary": {
|
|
241
|
+
const v = evalNode(node.operand, opts, d);
|
|
242
|
+
if (node.op === "-") return -v;
|
|
243
|
+
if (node.op === "!") return v === 0 ? 1 : 0;
|
|
244
|
+
throw new FormulaError(`Unknown unary operator '${node.op}'`);
|
|
245
|
+
}
|
|
246
|
+
case "binary": {
|
|
247
|
+
const l = evalNode(node.left, opts, d);
|
|
248
|
+
if (node.op === "&&") return l !== 0 ? evalNode(node.right, opts, d) : 0;
|
|
249
|
+
if (node.op === "||") return l !== 0 ? l : evalNode(node.right, opts, d);
|
|
250
|
+
const r = evalNode(node.right, opts, d);
|
|
251
|
+
switch (node.op) {
|
|
252
|
+
case "+":
|
|
253
|
+
return l + r;
|
|
254
|
+
case "-":
|
|
255
|
+
return l - r;
|
|
256
|
+
case "*":
|
|
257
|
+
return l * r;
|
|
258
|
+
case "/":
|
|
259
|
+
if (r === 0) throw new FormulaError("Division by zero");
|
|
260
|
+
return l / r;
|
|
261
|
+
case "%":
|
|
262
|
+
if (r === 0) throw new FormulaError("Modulo by zero");
|
|
263
|
+
return l % r;
|
|
264
|
+
case "^":
|
|
265
|
+
return Math.pow(l, r);
|
|
266
|
+
case "==":
|
|
267
|
+
return l === r ? 1 : 0;
|
|
268
|
+
case "!=":
|
|
269
|
+
return l !== r ? 1 : 0;
|
|
270
|
+
case "<":
|
|
271
|
+
return l < r ? 1 : 0;
|
|
272
|
+
case "<=":
|
|
273
|
+
return l <= r ? 1 : 0;
|
|
274
|
+
case ">":
|
|
275
|
+
return l > r ? 1 : 0;
|
|
276
|
+
case ">=":
|
|
277
|
+
return l >= r ? 1 : 0;
|
|
278
|
+
default:
|
|
279
|
+
throw new FormulaError(`Unknown operator '${node.op}'`);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
case "call": {
|
|
283
|
+
const fn = opts.functions[node.name] ?? BUILTIN_FUNCTIONS[node.name];
|
|
284
|
+
if (!fn) throw new FormulaError(`Unknown function '${node.name}'`);
|
|
285
|
+
const args = node.args.map((a) => evalNode(a, opts, d));
|
|
286
|
+
return fn(...args);
|
|
287
|
+
}
|
|
288
|
+
case "conditional": {
|
|
289
|
+
const cond = evalNode(node.condition, opts, d);
|
|
290
|
+
return cond !== 0 ? evalNode(node.consequent, opts, d) : evalNode(node.alternate, opts, d);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
function evalAST(node, opts = {}) {
|
|
295
|
+
const resolved = {
|
|
296
|
+
vars: opts.vars ?? {},
|
|
297
|
+
functions: opts.functions ?? {},
|
|
298
|
+
maxDepth: opts.maxDepth ?? 200
|
|
299
|
+
};
|
|
300
|
+
return evalNode(node, resolved, 0);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// src/formula.ts
|
|
304
|
+
function compile(expr) {
|
|
305
|
+
const ast = parse(expr);
|
|
306
|
+
return new CompiledFormula(ast, expr);
|
|
307
|
+
}
|
|
308
|
+
function evaluate(expr, vars, opts) {
|
|
309
|
+
const ast = parse(expr);
|
|
310
|
+
return evalAST(ast, { ...opts, vars });
|
|
311
|
+
}
|
|
312
|
+
function tryEvaluate(expr, vars, opts) {
|
|
313
|
+
try {
|
|
314
|
+
return evaluate(expr, vars, opts);
|
|
315
|
+
} catch {
|
|
316
|
+
return null;
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
var CompiledFormula = class {
|
|
320
|
+
constructor(ast, source) {
|
|
321
|
+
this._ast = ast;
|
|
322
|
+
this.source = source;
|
|
323
|
+
}
|
|
324
|
+
evaluate(vars, opts) {
|
|
325
|
+
return evalAST(this._ast, { ...opts, vars });
|
|
326
|
+
}
|
|
327
|
+
/** Return all variable names referenced in this formula. */
|
|
328
|
+
variables() {
|
|
329
|
+
const names = /* @__PURE__ */ new Set();
|
|
330
|
+
collectVars(this._ast, names);
|
|
331
|
+
return [...names].sort();
|
|
332
|
+
}
|
|
333
|
+
};
|
|
334
|
+
function collectVars(node, out) {
|
|
335
|
+
switch (node.kind) {
|
|
336
|
+
case "number":
|
|
337
|
+
break;
|
|
338
|
+
case "identifier":
|
|
339
|
+
out.add(node.name);
|
|
340
|
+
break;
|
|
341
|
+
case "unary":
|
|
342
|
+
collectVars(node.operand, out);
|
|
343
|
+
break;
|
|
344
|
+
case "binary":
|
|
345
|
+
collectVars(node.left, out);
|
|
346
|
+
collectVars(node.right, out);
|
|
347
|
+
break;
|
|
348
|
+
case "call":
|
|
349
|
+
node.args.forEach((a) => collectVars(a, out));
|
|
350
|
+
break;
|
|
351
|
+
case "conditional":
|
|
352
|
+
collectVars(node.condition, out);
|
|
353
|
+
collectVars(node.consequent, out);
|
|
354
|
+
collectVars(node.alternate, out);
|
|
355
|
+
break;
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
export {
|
|
359
|
+
BUILTIN_FUNCTIONS,
|
|
360
|
+
BUILTIN_VARS,
|
|
361
|
+
CompiledFormula,
|
|
362
|
+
FormulaError,
|
|
363
|
+
compile,
|
|
364
|
+
evalAST,
|
|
365
|
+
evaluate,
|
|
366
|
+
parse,
|
|
367
|
+
tryEvaluate
|
|
368
|
+
};
|
|
369
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/types.ts","../src/lexer.ts","../src/parser.ts","../src/builtins.ts","../src/evaluator.ts","../src/formula.ts"],"sourcesContent":["export type Value = number;\n\nexport type Variables = Record<string, Value>;\n\nexport type FunctionMap = Record<string, (...args: Value[]) => Value>;\n\nexport interface EvalOptions {\n vars?: Variables;\n functions?: FunctionMap;\n maxDepth?: number;\n}\n\nexport class FormulaError extends Error {\n constructor(\n message: string,\n public readonly position?: number\n ) {\n super(message);\n this.name = \"FormulaError\";\n }\n}\n\n// ── AST node types ────────────────────────────────────────────────────────────\n\nexport type NodeKind =\n | \"number\"\n | \"identifier\"\n | \"binary\"\n | \"unary\"\n | \"call\"\n | \"conditional\";\n\nexport interface NumberNode {\n kind: \"number\";\n value: number;\n}\n\nexport interface IdentifierNode {\n kind: \"identifier\";\n name: string;\n}\n\nexport interface BinaryNode {\n kind: \"binary\";\n op: string;\n left: ASTNode;\n right: ASTNode;\n}\n\nexport interface UnaryNode {\n kind: \"unary\";\n op: string;\n operand: ASTNode;\n}\n\nexport interface CallNode {\n kind: \"call\";\n name: string;\n args: ASTNode[];\n}\n\nexport interface ConditionalNode {\n kind: \"conditional\";\n condition: ASTNode;\n consequent: ASTNode;\n alternate: ASTNode;\n}\n\nexport type ASTNode =\n | NumberNode\n | IdentifierNode\n | BinaryNode\n | UnaryNode\n | CallNode\n | ConditionalNode;\n","import { FormulaError } from \"./types.js\";\n\nexport const enum TT {\n Number = \"Number\",\n Ident = \"Ident\",\n Plus = \"+\",\n Minus = \"-\",\n Star = \"*\",\n Slash = \"/\",\n Percent = \"%\",\n Caret = \"^\",\n LParen = \"(\",\n RParen = \")\",\n Comma = \",\",\n Question = \"?\",\n Colon = \":\",\n Eq = \"==\",\n NEq = \"!=\",\n Lt = \"<\",\n Lte = \"<=\",\n Gt = \">\",\n Gte = \">=\",\n And = \"&&\",\n Or = \"||\",\n Not = \"!\",\n EOF = \"EOF\",\n}\n\nexport interface Token {\n type: TT;\n raw: string;\n pos: number;\n}\n\nexport function tokenize(src: string): Token[] {\n const tokens: Token[] = [];\n let i = 0;\n\n while (i < src.length) {\n // skip whitespace\n if (/\\s/.test(src[i])) { i++; continue; }\n\n const pos = i;\n const ch = src[i];\n\n // numbers (int or float, optional exponent)\n if (/[0-9]/.test(ch) || (ch === \".\" && /[0-9]/.test(src[i + 1] ?? \"\"))) {\n let raw = \"\";\n while (i < src.length && /[0-9.]/.test(src[i])) raw += src[i++];\n if (src[i] === \"e\" || src[i] === \"E\") {\n raw += src[i++];\n if (src[i] === \"+\" || src[i] === \"-\") raw += src[i++];\n while (i < src.length && /[0-9]/.test(src[i])) raw += src[i++];\n }\n tokens.push({ type: TT.Number, raw, pos });\n continue;\n }\n\n // identifiers and keywords (only PI/E)\n if (/[a-zA-Z_]/.test(ch)) {\n let raw = \"\";\n while (i < src.length && /[a-zA-Z0-9_]/.test(src[i])) raw += src[i++];\n tokens.push({ type: TT.Ident, raw, pos });\n continue;\n }\n\n // two-char operators\n const two = src.slice(i, i + 2);\n if (two === \"==\" || two === \"!=\" || two === \"<=\" || two === \">=\" || two === \"&&\" || two === \"||\") {\n tokens.push({ type: two as TT, raw: two, pos });\n i += 2;\n continue;\n }\n\n // one-char operators\n const singles: Record<string, TT> = {\n \"+\": TT.Plus, \"-\": TT.Minus, \"*\": TT.Star, \"/\": TT.Slash,\n \"%\": TT.Percent, \"^\": TT.Caret,\n \"(\": TT.LParen, \")\": TT.RParen, \",\": TT.Comma,\n \"?\": TT.Question, \":\": TT.Colon,\n \"<\": TT.Lt, \">\": TT.Gt, \"!\": TT.Not,\n };\n if (ch in singles) {\n tokens.push({ type: singles[ch], raw: ch, pos });\n i++;\n continue;\n }\n\n throw new FormulaError(`Unexpected character '${ch}'`, pos);\n }\n\n tokens.push({ type: TT.EOF, raw: \"\", pos: i });\n return tokens;\n}\n","import { tokenize, Token, TT } from \"./lexer.js\";\nimport {\n ASTNode, NumberNode, IdentifierNode, BinaryNode,\n UnaryNode, CallNode, ConditionalNode, FormulaError,\n} from \"./types.js\";\n\n// Pratt parser (top-down operator precedence)\n\nconst BP: Partial<Record<TT, number>> = {\n [TT.Or]: 10,\n [TT.And]: 20,\n [TT.Eq]: 30, [TT.NEq]: 30,\n [TT.Lt]: 40, [TT.Lte]: 40, [TT.Gt]: 40, [TT.Gte]: 40,\n [TT.Plus]: 50, [TT.Minus]: 50,\n [TT.Star]: 60, [TT.Slash]: 60, [TT.Percent]: 60,\n [TT.Caret]: 70, // right-assoc → parse at BP-1\n};\n\nclass Parser {\n private tokens: Token[];\n private pos: number = 0;\n\n constructor(src: string) {\n this.tokens = tokenize(src);\n }\n\n private peek(): Token { return this.tokens[this.pos]; }\n private consume(): Token { return this.tokens[this.pos++]; }\n private expect(type: TT): Token {\n const t = this.consume();\n if (t.type !== type) throw new FormulaError(`Expected '${type}' but got '${t.raw}'`, t.pos);\n return t;\n }\n\n parse(): ASTNode {\n const node = this.expr(0);\n if (this.peek().type !== TT.EOF) {\n const t = this.peek();\n throw new FormulaError(`Unexpected token '${t.raw}'`, t.pos);\n }\n return node;\n }\n\n private expr(minBP: number): ASTNode {\n let left = this.prefix();\n\n while (true) {\n const t = this.peek();\n\n // ternary ? :\n if (t.type === TT.Question && minBP < 5) {\n this.consume();\n const consequent = this.expr(0);\n this.expect(TT.Colon);\n const alternate = this.expr(0);\n left = { kind: \"conditional\", condition: left, consequent, alternate } as ConditionalNode;\n continue;\n }\n\n const bp = BP[t.type as TT];\n if (bp === undefined || bp <= minBP) break;\n\n this.consume();\n const rightBP = t.type === TT.Caret ? bp - 1 : bp; // ^ is right-assoc\n const right = this.expr(rightBP);\n left = { kind: \"binary\", op: t.type, left, right } as BinaryNode;\n }\n\n return left;\n }\n\n private prefix(): ASTNode {\n const t = this.peek();\n\n if (t.type === TT.Number) {\n this.consume();\n return { kind: \"number\", value: parseFloat(t.raw) } as NumberNode;\n }\n\n if (t.type === TT.Ident) {\n this.consume();\n // function call?\n if (this.peek().type === TT.LParen) {\n this.consume(); // (\n const args: ASTNode[] = [];\n if (this.peek().type !== TT.RParen) {\n args.push(this.expr(0));\n while (this.peek().type === TT.Comma) {\n this.consume();\n args.push(this.expr(0));\n }\n }\n this.expect(TT.RParen);\n return { kind: \"call\", name: t.raw, args } as CallNode;\n }\n return { kind: \"identifier\", name: t.raw } as IdentifierNode;\n }\n\n if (t.type === TT.Minus) {\n this.consume();\n return { kind: \"unary\", op: \"-\", operand: this.expr(65) } as UnaryNode;\n }\n\n if (t.type === TT.Not) {\n this.consume();\n return { kind: \"unary\", op: \"!\", operand: this.expr(65) } as UnaryNode;\n }\n\n if (t.type === TT.Plus) {\n this.consume();\n return this.expr(65); // unary +\n }\n\n if (t.type === TT.LParen) {\n this.consume();\n const inner = this.expr(0);\n this.expect(TT.RParen);\n return inner;\n }\n\n throw new FormulaError(`Unexpected token '${t.raw}'`, t.pos);\n }\n}\n\nexport function parse(src: string): ASTNode {\n return new Parser(src).parse();\n}\n","import { FunctionMap, Variables } from \"./types.js\";\n\nexport const BUILTIN_VARS: Variables = {\n PI: Math.PI,\n E: Math.E,\n LN2: Math.LN2,\n LN10: Math.LN10,\n LOG2E: Math.LOG2E,\n LOG10E: Math.LOG10E,\n SQRT2: Math.SQRT2,\n Infinity: Infinity,\n NaN: NaN,\n};\n\nexport const BUILTIN_FUNCTIONS: FunctionMap = {\n abs: (x) => Math.abs(x),\n ceil: (x) => Math.ceil(x),\n floor: (x) => Math.floor(x),\n round: (x) => Math.round(x),\n trunc: (x) => Math.trunc(x),\n sign: (x) => Math.sign(x),\n\n sqrt: (x) => Math.sqrt(x),\n cbrt: (x) => Math.cbrt(x),\n pow: (x, y) => Math.pow(x, y),\n exp: (x) => Math.exp(x),\n log: (x) => Math.log(x),\n log2: (x) => Math.log2(x),\n log10: (x) => Math.log10(x),\n\n sin: (x) => Math.sin(x),\n cos: (x) => Math.cos(x),\n tan: (x) => Math.tan(x),\n asin: (x) => Math.asin(x),\n acos: (x) => Math.acos(x),\n atan: (x) => Math.atan(x),\n atan2: (y, x) => Math.atan2(y, x),\n sinh: (x) => Math.sinh(x),\n cosh: (x) => Math.cosh(x),\n tanh: (x) => Math.tanh(x),\n\n min: (...args) => Math.min(...args),\n max: (...args) => Math.max(...args),\n clamp: (x, lo, hi) => Math.min(Math.max(x, lo), hi),\n\n hypot: (...args) => Math.hypot(...args),\n\n random: () => Math.random(),\n\n isNaN: (x) => (isNaN(x) ? 1 : 0),\n isFinite: (x) => (isFinite(x) ? 1 : 0),\n};\n","import { ASTNode, EvalOptions, FormulaError, Value } from \"./types.js\";\nimport { BUILTIN_VARS, BUILTIN_FUNCTIONS } from \"./builtins.js\";\n\nfunction evalNode(node: ASTNode, opts: Required<EvalOptions>, depth: number): Value {\n if (depth > opts.maxDepth) throw new FormulaError(\"Maximum recursion depth exceeded\");\n\n const d = depth + 1;\n\n switch (node.kind) {\n case \"number\":\n return node.value;\n\n case \"identifier\": {\n const name = node.name;\n if (name in opts.vars) return opts.vars[name];\n if (name in BUILTIN_VARS) return BUILTIN_VARS[name];\n throw new FormulaError(`Unknown variable '${name}'`);\n }\n\n case \"unary\": {\n const v = evalNode(node.operand, opts, d);\n if (node.op === \"-\") return -v;\n if (node.op === \"!\") return v === 0 ? 1 : 0;\n throw new FormulaError(`Unknown unary operator '${node.op}'`);\n }\n\n case \"binary\": {\n const l = evalNode(node.left, opts, d);\n // short-circuit for && / ||\n if (node.op === \"&&\") return (l !== 0 ? evalNode(node.right, opts, d) : 0);\n if (node.op === \"||\") return (l !== 0 ? l : evalNode(node.right, opts, d));\n\n const r = evalNode(node.right, opts, d);\n switch (node.op) {\n case \"+\": return l + r;\n case \"-\": return l - r;\n case \"*\": return l * r;\n case \"/\":\n if (r === 0) throw new FormulaError(\"Division by zero\");\n return l / r;\n case \"%\":\n if (r === 0) throw new FormulaError(\"Modulo by zero\");\n return l % r;\n case \"^\": return Math.pow(l, r);\n case \"==\": return l === r ? 1 : 0;\n case \"!=\": return l !== r ? 1 : 0;\n case \"<\": return l < r ? 1 : 0;\n case \"<=\": return l <= r ? 1 : 0;\n case \">\": return l > r ? 1 : 0;\n case \">=\": return l >= r ? 1 : 0;\n default:\n throw new FormulaError(`Unknown operator '${node.op}'`);\n }\n }\n\n case \"call\": {\n const fn = opts.functions[node.name] ?? BUILTIN_FUNCTIONS[node.name];\n if (!fn) throw new FormulaError(`Unknown function '${node.name}'`);\n const args = node.args.map((a) => evalNode(a, opts, d));\n return fn(...args);\n }\n\n case \"conditional\": {\n const cond = evalNode(node.condition, opts, d);\n return cond !== 0\n ? evalNode(node.consequent, opts, d)\n : evalNode(node.alternate, opts, d);\n }\n }\n}\n\nexport function evalAST(node: ASTNode, opts: EvalOptions = {}): Value {\n const resolved: Required<EvalOptions> = {\n vars: opts.vars ?? {},\n functions: opts.functions ?? {},\n maxDepth: opts.maxDepth ?? 200,\n };\n return evalNode(node, resolved, 0);\n}\n","import { parse } from \"./parser.js\";\nimport { evalAST } from \"./evaluator.js\";\nimport { ASTNode, EvalOptions, Value } from \"./types.js\";\n\n/**\n * Parse an expression string into an AST (reuse for repeated evaluations).\n */\nexport function compile(expr: string): CompiledFormula {\n const ast = parse(expr);\n return new CompiledFormula(ast, expr);\n}\n\n/**\n * Parse and evaluate an expression in one step.\n */\nexport function evaluate(expr: string, vars?: Record<string, Value>, opts?: Omit<EvalOptions, \"vars\">): Value {\n const ast = parse(expr);\n return evalAST(ast, { ...opts, vars });\n}\n\n/**\n * Parse and evaluate, returning null instead of throwing on parse/eval errors.\n */\nexport function tryEvaluate(expr: string, vars?: Record<string, Value>, opts?: Omit<EvalOptions, \"vars\">): Value | null {\n try { return evaluate(expr, vars, opts); }\n catch { return null; }\n}\n\nexport class CompiledFormula {\n readonly source: string;\n private readonly _ast: ASTNode;\n\n constructor(ast: ASTNode, source: string) {\n this._ast = ast;\n this.source = source;\n }\n\n evaluate(vars?: Record<string, Value>, opts?: Omit<EvalOptions, \"vars\">): Value {\n return evalAST(this._ast, { ...opts, vars });\n }\n\n /** Return all variable names referenced in this formula. */\n variables(): string[] {\n const names = new Set<string>();\n collectVars(this._ast, names);\n return [...names].sort();\n }\n}\n\nfunction collectVars(node: ASTNode, out: Set<string>): void {\n switch (node.kind) {\n case \"number\": break;\n case \"identifier\": out.add(node.name); break;\n case \"unary\": collectVars(node.operand, out); break;\n case \"binary\": collectVars(node.left, out); collectVars(node.right, out); break;\n case \"call\": node.args.forEach((a) => collectVars(a, out)); break;\n case \"conditional\":\n collectVars(node.condition, out);\n collectVars(node.consequent, out);\n collectVars(node.alternate, out);\n break;\n }\n}\n"],"mappings":"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","names":[]}
|
package/package.json
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "formulakit",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Zero-dependency safe arithmetic expression evaluator: parse/evaluate math formulas with variables and functions, no eval(). TypeScript-first, ESM+CJS. Port of Python simpleeval / Go expr / C# DynamicExpresso.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.cjs",
|
|
7
|
+
"module": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"import": "./dist/index.js",
|
|
13
|
+
"require": "./dist/index.cjs"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"dist",
|
|
18
|
+
"README.md",
|
|
19
|
+
"LICENSE"
|
|
20
|
+
],
|
|
21
|
+
"scripts": {
|
|
22
|
+
"build": "tsup",
|
|
23
|
+
"typecheck": "tsc --noEmit",
|
|
24
|
+
"test": "node --experimental-vm-modules node_modules/.bin/jest --forceExit",
|
|
25
|
+
"prepublishOnly": "npm run typecheck && npm test && npm run build"
|
|
26
|
+
},
|
|
27
|
+
"keywords": [
|
|
28
|
+
"expression",
|
|
29
|
+
"evaluator",
|
|
30
|
+
"formula",
|
|
31
|
+
"math",
|
|
32
|
+
"parser",
|
|
33
|
+
"arithmetic",
|
|
34
|
+
"safe-eval",
|
|
35
|
+
"calculator",
|
|
36
|
+
"variables",
|
|
37
|
+
"typescript",
|
|
38
|
+
"zero-dependencies"
|
|
39
|
+
],
|
|
40
|
+
"author": "trananhtung",
|
|
41
|
+
"license": "MIT",
|
|
42
|
+
"repository": {
|
|
43
|
+
"type": "git",
|
|
44
|
+
"url": "https://github.com/trananhtung/formulakit.git"
|
|
45
|
+
},
|
|
46
|
+
"devDependencies": {
|
|
47
|
+
"@types/jest": "^30.0.0",
|
|
48
|
+
"jest": "^30.4.2",
|
|
49
|
+
"ts-jest": "^29.4.11",
|
|
50
|
+
"tsup": "^8.5.1",
|
|
51
|
+
"typescript": "^6.0.3"
|
|
52
|
+
}
|
|
53
|
+
}
|