exprify 1.0.3 → 1.0.6
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/HISTORY.md +49 -0
- package/README.md +113 -160
- package/SECURITY.md +18 -0
- package/bin/cli.mjs +234 -0
- package/dist/exprify.cjs.cjs +5341 -0
- package/dist/exprify.cjs.cjs.map +1 -0
- package/dist/exprify.esm.js +3558 -1220
- package/dist/exprify.esm.js.map +1 -1
- package/dist/exprify.js +3560 -1222
- package/dist/exprify.js.map +1 -1
- package/dist/exprify.min.js +2 -2
- package/dist/exprify.min.js.map +1 -1
- package/package.json +55 -19
- package/src/core/context.js +35 -27
- package/src/core/exprify.js +880 -0
- package/src/function/executor.js +29 -20
- package/src/function/internal.js +1150 -153
- package/src/function/registry.js +23 -16
- package/src/index.js +1 -1
- package/src/math/bignumber.js +31 -0
- package/src/math/fraction.js +112 -0
- package/src/math/operations.js +38 -24
- package/src/parser/astBuild.js +276 -214
- package/src/parser/evaluator.js +431 -171
- package/src/parser/tokenizer.js +179 -146
- package/src/utils/decimal.js +264 -0
- package/src/utils/globalUnits.js +43 -35
- package/src/utils/matrix.js +14 -14
- package/src/utils/store.js +69 -47
- package/src/variables/store.js +18 -15
- package/dist/exprify.cjs.js +0 -3003
- package/dist/exprify.cjs.js.map +0 -1
- package/src/assets/capture.jpg +0 -0
- package/src/core/Exprify.js +0 -369
package/HISTORY.md
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on Keep a Changelog, and this project follows semantic versioning.
|
|
6
|
+
|
|
7
|
+
## [1.0.5] - 2026-06-12
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- ESLint (v10 flat config) with strict rules, Prettier formatting, and TypeScript JSDoc type checking
|
|
12
|
+
- Husky pre-commit hook with lint-staged for automatic linting and formatting on commit
|
|
13
|
+
- Dependabot config for weekly automated dependency pull requests
|
|
14
|
+
- CodeQL security analysis workflow on push/PR/schedule
|
|
15
|
+
- Bundle size monitoring via size-limit (min.js: 30 KB, esm.js: 60 KB)
|
|
16
|
+
- Jest coverage reporting (`npm run test:coverage`) with CI artifact upload
|
|
17
|
+
- Node.js 24 to CI test matrix (previously 20 and 22 only)
|
|
18
|
+
- `.editorconfig` for cross-editor consistency
|
|
19
|
+
- `// @ts-check` annotations to all 14 source files
|
|
20
|
+
- `engines` field requiring Node >=18
|
|
21
|
+
|
|
22
|
+
### Changed
|
|
23
|
+
|
|
24
|
+
- Upgraded Rollup from v2 to v4 with updated plugin versions
|
|
25
|
+
- Replaced rimraf with built-in `fs.rmSync` for the clean script
|
|
26
|
+
- Replaced nodemon with Node.js built-in `--watch` flag for the dev script
|
|
27
|
+
- CI workflow now runs lint, typecheck, size check, and coverage in addition to tests
|
|
28
|
+
- Switched CI workflows to Node 24 for publish and audit jobs
|
|
29
|
+
- Added `--no-warnings` flag to test scripts to suppress ExperimentalWarning
|
|
30
|
+
|
|
31
|
+
### Fixed
|
|
32
|
+
|
|
33
|
+
- All ESLint errors (no-unused-vars, no-undef, eqeqeq, prefer-const, camelcase, etc.)
|
|
34
|
+
- All TypeScript type errors (err unknown type, null checks, variable store reference)
|
|
35
|
+
- lint-staged Windows ENOENT error by switching from package.json config to `lint-staged.config.js` with direct `node` invocation
|
|
36
|
+
|
|
37
|
+
### Removed
|
|
38
|
+
|
|
39
|
+
- Unused Babel dependencies (@babel/cli, @babel/core, @babel/preset-env)
|
|
40
|
+
- Unused rollup-plugin-strip-banner, glob, and rimraf dev dependencies
|
|
41
|
+
- `overrides` section (test-exclude, glob pins) — no longer needed with Jest 30
|
|
42
|
+
- `nodemon` devDependency (replaced by `node --watch`)
|
|
43
|
+
|
|
44
|
+
## [1.0.1] - 2026-04-05
|
|
45
|
+
|
|
46
|
+
### Fixed
|
|
47
|
+
|
|
48
|
+
- Corrected the import path in `src/index.js` to match the actual filename casing of `src/core/Exprify.js`.
|
|
49
|
+
- Resolved a cross-platform build issue where Rollup failed on Linux-based CI environments because of case-sensitive path resolution.
|
package/README.md
CHANGED
|
@@ -1,8 +1,32 @@
|
|
|
1
|
-
|
|
1
|
+
[](https://www.npmjs.com/package/exprify)
|
|
2
|
+
[](https://www.npmjs.com/package/exprify)
|
|
3
|
+
[](https://github.com/code-hemu/exprify/blob/master/LICENSE)
|
|
4
|
+
[](https://www.jsdelivr.com/package/npm/exprify)
|
|
5
|
+
[](https://unpkg.com/exprify)
|
|
6
|
+
[](https://github.com/code-hemu/exprify/issues)
|
|
7
|
+
[](https://github.com/code-hemu/exprify/graphs/contributors)
|
|
8
|
+
[](https://github.com/sponsors/code-hemu)
|
|
2
9
|
|
|
3
|
-
[](https://github.com/code-hemu/Exprify)
|
|
4
11
|
|
|
5
|
-
Exprify
|
|
12
|
+
**Exprify** (**Math Expr**ession + Simp**lify**) parses a string into an expression tree, evaluates it with a given set of variables, and lets you chain or compose operations together - in the browser and in Node.js.
|
|
13
|
+
|
|
14
|
+
## Features
|
|
15
|
+
|
|
16
|
+
| Capability | Example | Docs |
|
|
17
|
+
|---|---|---|
|
|
18
|
+
| **Arithmetic & Variables** | `expr.evaluate("5 + 7 * 2")` | [datatypes/numbers.md](docs/datatypes/numbers.md) |
|
|
19
|
+
| **Unit conversion** | `expr.evaluate("2 inch to cm")` | [datatypes/units.md](docs/datatypes/units.md) |
|
|
20
|
+
| **Matrix operations** | `expr.evaluate("det([-1,2;3,1])")` | [datatypes/matrices.md](docs/datatypes/matrices.md) |
|
|
21
|
+
| **Complex numbers** | `expr.evaluate("9/3 + 2i")` | [datatypes/complex.md](docs/datatypes/complex_numbers.md) |
|
|
22
|
+
| **Symbolic math** | `expr.evaluate('expand("(x+1)^2")')` | [expressions/algebra.md](docs/expressions/algebra.md) |
|
|
23
|
+
| **Arbitrary precision** | `expr.evaluate('bignumber("0.1") + bignumber("0.2")')` | [datatypes/bignumbers.md](docs/datatypes/bignumbers.md) |
|
|
24
|
+
| **Exact fractions** | `expr.evaluate("fraction(1,3) + fraction(1,6)")` | [datatypes/fractions.md](docs/datatypes/fractions.md) |
|
|
25
|
+
| **Calculus & statistics** | `expr.evaluate('integral("x^2", 0, 1)')` | [reference/functions.md](docs/reference/functions.md) |
|
|
26
|
+
| **Lambda expressions** | `expr.evaluate('map([1,2,3], x -> x^2)')` | [expressions/customization.md](docs/expressions/customization.md) |
|
|
27
|
+
| **Expression chaining** | `c.evaluate("sqrt(x)").evaluate("ans * 2").done()` | [core/chaining.md](docs/core/chaining.md) |
|
|
28
|
+
| **State serialization** | `expr.exportState()` / `expr.importState(state)` | [core/serialization.md](docs/core/serialization.md) |
|
|
29
|
+
| **Degree-mode trig** | `expr.evaluate("sind(90)")` | [reference/functions.md](docs/reference/functions.md) |
|
|
6
30
|
|
|
7
31
|
## Installation
|
|
8
32
|
|
|
@@ -12,245 +36,172 @@ npm install exprify
|
|
|
12
36
|
|
|
13
37
|
## Quick Start
|
|
14
38
|
|
|
39
|
+
**ESM** (Node.js / bundlers):
|
|
40
|
+
|
|
15
41
|
```js
|
|
16
42
|
import Exprify from "exprify";
|
|
17
43
|
|
|
18
44
|
const expr = new Exprify();
|
|
19
45
|
|
|
20
|
-
|
|
21
|
-
// 19
|
|
46
|
+
expr.evaluate("5 + 7 * 2"); // 19
|
|
22
47
|
|
|
23
48
|
expr.setVariable("x", 10);
|
|
24
|
-
|
|
25
|
-
// 15
|
|
49
|
+
expr.evaluate("x + 5"); // 15
|
|
26
50
|
```
|
|
27
51
|
|
|
28
|
-
|
|
52
|
+
**CommonJS:**
|
|
53
|
+
|
|
54
|
+
```js
|
|
55
|
+
const Exprify = require("exprify");
|
|
56
|
+
|
|
57
|
+
const expr = new Exprify();
|
|
58
|
+
expr.evaluate("5 + 7 * 2"); // 19
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
**Browser (CDN):**
|
|
29
62
|
|
|
30
63
|
```html
|
|
31
64
|
<script src="https://unpkg.com/exprify"></script>
|
|
32
65
|
<script>
|
|
33
66
|
const expr = new Exprify();
|
|
34
|
-
|
|
67
|
+
expr.evaluate("(10 + 5) * 2"); // 30
|
|
35
68
|
</script>
|
|
36
69
|
```
|
|
37
70
|
|
|
38
|
-
|
|
71
|
+
## Module Formats
|
|
72
|
+
|
|
73
|
+
`package.json`'s `exports` field routes each import style to the correct build automatically.
|
|
74
|
+
|
|
75
|
+
| Consumer | Resolved file | Notes |
|
|
76
|
+
|---|---|---|
|
|
77
|
+
| `import Exprify from "exprify"` | `dist/exprify.esm.js` | Default export is the `Exprify` class |
|
|
78
|
+
| `require("exprify")` | `dist/exprify.cjs.cjs` | `module.exports` is the `Exprify` class |
|
|
79
|
+
| `<script src="https://unpkg.com/exprify">` | `dist/exprify.min.js` | UMD build; exposes `Exprify` as a global |
|
|
80
|
+
| `import "exprify/dist/exprify.js"` | `dist/exprify.js` | Unminified UMD for bundlers that prefer it |
|
|
81
|
+
|
|
82
|
+
> The `.cjs` extension keeps the CommonJS bundle loadable via `require()` even though the package uses `"type": "module"`.
|
|
39
83
|
|
|
40
84
|
## API
|
|
41
85
|
|
|
42
86
|
### `new Exprify()`
|
|
43
87
|
|
|
44
|
-
Creates a new evaluator instance with isolated state for
|
|
88
|
+
Creates a new evaluator instance with isolated state for variables, functions, units, and a compiled-expression cache.
|
|
45
89
|
|
|
46
|
-
|
|
47
|
-
- functions
|
|
48
|
-
- units
|
|
49
|
-
- compiled-expression cache
|
|
90
|
+
### `expr.evaluate(expression, scope?)`
|
|
50
91
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
Parses and evaluates an expression string.
|
|
92
|
+
Parses and evaluates an expression string. An optional `scope` object overrides variables for that single call only.
|
|
54
93
|
|
|
55
94
|
```js
|
|
56
|
-
expr.evaluate("10 + 5 * 2");
|
|
57
|
-
|
|
95
|
+
expr.evaluate("10 + 5 * 2"); // 20
|
|
96
|
+
|
|
97
|
+
expr.setVariable("x", 100);
|
|
98
|
+
expr.evaluate("x + 1", { x: 5 }); // 6
|
|
58
99
|
```
|
|
59
100
|
|
|
60
101
|
### `expr.parse(expression)`
|
|
61
102
|
|
|
62
|
-
Returns `{ tokens, ast }
|
|
103
|
+
Returns `{ tokens, ast }` - the raw token list and abstract syntax tree.
|
|
63
104
|
|
|
64
105
|
```js
|
|
65
|
-
const
|
|
66
|
-
console.log(parsed.tokens);
|
|
67
|
-
console.log(parsed.ast);
|
|
106
|
+
const { tokens, ast } = expr.parse("2 inch to cm");
|
|
68
107
|
```
|
|
69
108
|
|
|
70
109
|
### `expr.compile(expression)`
|
|
71
110
|
|
|
72
|
-
Compiles an expression once and returns a reusable function.
|
|
111
|
+
Compiles an expression once and returns a reusable function - ideal for hot paths.
|
|
73
112
|
|
|
74
113
|
```js
|
|
75
114
|
const area = expr.compile("width * height");
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
// 24
|
|
115
|
+
area({ width: 6, height: 4 }); // 24
|
|
116
|
+
area({ width: 3, height: 9 }); // 27
|
|
79
117
|
```
|
|
80
118
|
|
|
81
119
|
### `expr.setVariable(name, value)` / `expr.getVariable(name)`
|
|
82
120
|
|
|
83
|
-
Stores and
|
|
121
|
+
Stores and retrieves named values that persist across evaluations.
|
|
84
122
|
|
|
85
123
|
```js
|
|
86
124
|
expr.setVariable("x", 10);
|
|
87
125
|
expr.setVariable("y", 5);
|
|
88
|
-
|
|
89
|
-
console.log(expr.evaluate("x + y * 2"));
|
|
90
|
-
// 20
|
|
126
|
+
expr.evaluate("x + y * 2"); // 20
|
|
91
127
|
```
|
|
92
128
|
|
|
93
129
|
### `expr.addFunction(name, fn)`
|
|
94
130
|
|
|
95
|
-
Registers a custom JavaScript function.
|
|
131
|
+
Registers a custom JavaScript function, making it callable inside expressions.
|
|
96
132
|
|
|
97
133
|
```js
|
|
98
134
|
expr.addFunction("double", (n) => n * 2);
|
|
99
|
-
|
|
100
|
-
console.log(expr.evaluate("double(5) + 3"));
|
|
101
|
-
// 13
|
|
135
|
+
expr.evaluate("double(5) + 3"); // 13
|
|
102
136
|
```
|
|
103
137
|
|
|
104
|
-
###
|
|
138
|
+
### `expr.chain()`
|
|
105
139
|
|
|
106
|
-
|
|
140
|
+
Returns a fluent `Chain` object. Each step stores its result as `ans` for the next expression.
|
|
107
141
|
|
|
108
142
|
```js
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
//
|
|
143
|
+
const c = expr.chain();
|
|
144
|
+
c.setVariable("x", 25);
|
|
145
|
+
c.evaluate("sqrt(x) + 3"); // ans = 8
|
|
146
|
+
c.evaluate("ans * 2"); // ans = 16
|
|
147
|
+
c.done(); // 16
|
|
113
148
|
```
|
|
114
149
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
### Arithmetic
|
|
118
|
-
|
|
119
|
-
```js
|
|
120
|
-
expr.evaluate("2 + 3 * 4");
|
|
121
|
-
// 14
|
|
122
|
-
|
|
123
|
-
expr.evaluate("(2 + 3) * 4");
|
|
124
|
-
// 20
|
|
150
|
+
### `expr.exportState()` / `expr.importState(state)`
|
|
125
151
|
|
|
126
|
-
|
|
127
|
-
// 121n
|
|
128
|
-
```
|
|
129
|
-
|
|
130
|
-
### Strings, Booleans, Complex Numbers
|
|
152
|
+
Serializes and restores the full engine state - variables, functions, and units.
|
|
131
153
|
|
|
132
154
|
```js
|
|
133
|
-
expr.
|
|
134
|
-
//
|
|
155
|
+
const state = expr.exportState();
|
|
156
|
+
// { variables: {...}, functions: [...], units: {...} }
|
|
135
157
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
expr.evaluate("9 / 3 + 2i");
|
|
140
|
-
// "3 + 2i"
|
|
158
|
+
const expr2 = new Exprify();
|
|
159
|
+
expr2.importState(state);
|
|
141
160
|
```
|
|
142
161
|
|
|
143
|
-
###
|
|
144
|
-
|
|
145
|
-
```js
|
|
146
|
-
expr.evaluate("2 inch to cm");
|
|
147
|
-
// "5.08 cm"
|
|
148
|
-
|
|
149
|
-
expr.evaluate("5 cm + 2 inch");
|
|
150
|
-
// "10.08 cm"
|
|
151
|
-
|
|
152
|
-
expr.evaluate("5cm + 0.2 m in inch");
|
|
153
|
-
// "9.84251968503937 inch"
|
|
154
|
-
|
|
155
|
-
expr.evaluate("a = 5.08 cm + 2 inch");
|
|
156
|
-
expr.evaluate("a to inch");
|
|
157
|
-
// "4 inch"
|
|
158
|
-
```
|
|
159
|
-
|
|
160
|
-
### Matrices
|
|
161
|
-
|
|
162
|
-
Exprify supports matrix literals with `;` as row separators.
|
|
163
|
-
|
|
164
|
-
```js
|
|
165
|
-
expr.evaluate("a = [1, 2, 3; 4, 5, 6]");
|
|
166
|
-
// {"exprify":"DenseMatrix","data":[[1,2,3],[4,5,6]],"size":[2,3]}
|
|
167
|
-
|
|
168
|
-
expr.evaluate("a[2, 3]");
|
|
169
|
-
// 6
|
|
170
|
-
|
|
171
|
-
expr.evaluate("a[1:2, 2]");
|
|
172
|
-
// "2\n5"
|
|
173
|
-
|
|
174
|
-
expr.evaluate("a[3, 1:3] = [7, 8, 9]");
|
|
175
|
-
// "7\t8\t9"
|
|
176
|
-
|
|
177
|
-
expr.evaluate("det([-1, 2; 3, 1])");
|
|
178
|
-
// -7
|
|
179
|
-
```
|
|
180
|
-
|
|
181
|
-
### Linear Algebra Helpers
|
|
182
|
-
|
|
183
|
-
```js
|
|
184
|
-
expr.evaluate("lup([[2, 1], [1, 4]])");
|
|
185
|
-
// {"L":{"exprify":"DenseMatrix",...},"U":{"exprify":"DenseMatrix",...},"p":[0,1]}
|
|
186
|
-
|
|
187
|
-
expr.evaluate("lyap([[-2, 0], [1, -4]], [[3, 1], [1, 3]])");
|
|
188
|
-
// {"exprify":"DenseMatrix","data":[[0.75,0.2916666666666667],[0.2916666666666667,0.44791666666666663]],"size":[2,2]}
|
|
189
|
-
|
|
190
|
-
expr.evaluate("qr([[1, -1, 4], [1, 4, -2], [1, 4, 2], [1, -1, 0]])");
|
|
191
|
-
// {"Q":{"exprify":"DenseMatrix",...},"R":{"exprify":"DenseMatrix",...}}
|
|
192
|
-
|
|
193
|
-
expr.evaluate("polynomialRoot(-6, 11, -6, 1)");
|
|
194
|
-
// [1,3,2]
|
|
195
|
-
```
|
|
196
|
-
|
|
197
|
-
Available helpers currently include `det`, `lsolve`, `lup`, `lyap`, `qr`, and `polynomialRoot`.
|
|
198
|
-
|
|
199
|
-
### Algebra Helpers
|
|
200
|
-
|
|
201
|
-
```js
|
|
202
|
-
expr.evaluate('simplify("2x + x")');
|
|
203
|
-
// "3 * x"
|
|
204
|
-
|
|
205
|
-
expr.evaluate('derivative("2x^2 + 3x + 4", "x")');
|
|
206
|
-
// "4 * x + 3"
|
|
207
|
-
|
|
208
|
-
expr.evaluate('rationalize("2x/y - y/(x+1)", true)');
|
|
209
|
-
// {"numerator":"2 * x ^ 2 + 2 * x - y ^ 2","denominator":"x * y + y","coefficients":[],"variables":["x","y"],"expression":"(2 * x ^ 2 + 2 * x - y ^ 2) / (x * y + y)"}
|
|
210
|
-
```
|
|
162
|
+
### Inline Function Definitions
|
|
211
163
|
|
|
212
|
-
|
|
164
|
+
Functions can be defined directly inside expressions and reused immediately.
|
|
213
165
|
|
|
214
166
|
```js
|
|
215
|
-
expr.evaluate(
|
|
216
|
-
//
|
|
217
|
-
|
|
218
|
-
expr.evaluate('leafCount(parse("{a: 22/7, b: 10^(1/2)}"))');
|
|
219
|
-
// 5
|
|
167
|
+
expr.evaluate("hyp(a, b) = sqrt(a^2 + b^2)");
|
|
168
|
+
expr.evaluate("hyp(3, 4)"); // 5
|
|
220
169
|
```
|
|
221
170
|
|
|
222
|
-
|
|
171
|
+
## Built-in Functions (Selected)
|
|
223
172
|
|
|
224
|
-
|
|
173
|
+
| Function | Description | Example | Result |
|
|
174
|
+
|---|---|---|---|
|
|
175
|
+
| `abs` | Absolute value | `abs(-5)` | `5` |
|
|
176
|
+
| `round` | Round to nearest integer | `round(3.7)` | `4` |
|
|
177
|
+
| `floor` | Round down | `floor(3.7)` | `3` |
|
|
178
|
+
| `ceil` | Round up | `ceil(3.2)` | `4` |
|
|
225
179
|
|
|
226
|
-
|
|
227
|
-
- `sin`, `cos`, `tan`, `asin`, `acos`, `atan`
|
|
228
|
-
- `log`, `log10`, `exp`, `random`
|
|
229
|
-
- `clamp`, `if`, `length`, `typeof`
|
|
230
|
-
- `det`, `lsolve`, `lup`, `lyap`, `qr`, `polynomialRoot`
|
|
231
|
-
- `simplify`, `derivative`, `rationalize`, `leafCount`, `parse`
|
|
180
|
+
> See the [full searchable function reference](docs/reference/functions.md) for all ~130 built-in functions.
|
|
232
181
|
|
|
233
182
|
## Return Types
|
|
234
183
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
184
|
+
| Type | Example expression | Result |
|
|
185
|
+
|---|---|---|
|
|
186
|
+
| Number / BigInt / Boolean | `2 + 2`, `true && false` | `4`, `false` |
|
|
187
|
+
| String | `"hello" + " world"` | `"hello world"` |
|
|
188
|
+
| Unit string | `2 inch to cm` | `"5.08 cm"` |
|
|
189
|
+
| Complex string | `3 + 2i` | `"3 + 2i"` |
|
|
190
|
+
| Matrix JSON | `[1,2;3,4]` | `{"exprify":"DenseMatrix",...}` |
|
|
191
|
+
| Structured JSON | `lup(...)`, `rationalize(..., true)` | JSON object string |
|
|
192
|
+
| Function | `x -> x^2` | Native JS function |
|
|
193
|
+
| Array | `1:5` | `[1,2,3,4,5]` |
|
|
243
194
|
|
|
244
195
|
## Manual Build
|
|
245
196
|
|
|
246
197
|
```bash
|
|
247
|
-
git clone https://github.com/code-hemu/
|
|
198
|
+
git clone https://github.com/code-hemu/exprify.git
|
|
248
199
|
cd Exprify
|
|
249
200
|
npm install
|
|
250
201
|
npm run build
|
|
251
202
|
```
|
|
252
203
|
|
|
253
|
-
|
|
204
|
+
Output is written to `dist/`.
|
|
254
205
|
|
|
255
206
|
## Testing
|
|
256
207
|
|
|
@@ -258,13 +209,15 @@ Build output is written to `dist/`.
|
|
|
258
209
|
npm test
|
|
259
210
|
```
|
|
260
211
|
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
Exprify is licensed under GPL-3.0. Copyright (c) [Nirmal Paul](https://github.com/nirmalpaul383/).
|
|
212
|
+
Tested in CI across Node 20 and 22. See `.github/workflows/ci.yml` for details.
|
|
264
213
|
|
|
265
214
|
## Contributing
|
|
266
215
|
|
|
267
216
|
1. Fork the repository.
|
|
268
217
|
2. Create a branch: `git checkout -b feature/your-feature`
|
|
269
218
|
3. Commit your changes: `git commit -m "Add your feature"`
|
|
270
|
-
4. Push
|
|
219
|
+
4. Push and open a pull request.
|
|
220
|
+
|
|
221
|
+
## License
|
|
222
|
+
|
|
223
|
+
Exprify is licensed under **GPL-3.0**. Copyright © [Nirmal Paul](https://github.com/nirmalpaul383/).
|
package/SECURITY.md
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# Security Policy
|
|
2
|
+
|
|
3
|
+
## Supported Versions
|
|
4
|
+
|
|
5
|
+
| Version | Supported |
|
|
6
|
+
| ------- | --------- |
|
|
7
|
+
| 1.0.5 | ❌ |
|
|
8
|
+
| 1.0.6 | ✅ |
|
|
9
|
+
|
|
10
|
+
## Reporting a Vulnerability
|
|
11
|
+
|
|
12
|
+
If you discover a security vulnerability in Exprify, please report it privately by opening a security advisory at:
|
|
13
|
+
|
|
14
|
+
https://github.com/code-hemu/exprify/security/advisories/new
|
|
15
|
+
|
|
16
|
+
You can expect an acknowledgment within 48 hours and a detailed response within 5 business days. If the vulnerability is accepted, a fix will be coordinated through a private release and published as a patch version. If declined, you will receive an explanation of why the issue does not constitute a security risk.
|
|
17
|
+
|
|
18
|
+
Please do **not** report security vulnerabilities through public GitHub issues, discussions, or pull requests.
|
package/bin/cli.mjs
ADDED
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { readFileSync } from 'node:fs';
|
|
4
|
+
import { createInterface } from 'node:readline';
|
|
5
|
+
import { stdin, stdout, stderr, exit, argv, version } from 'node:process';
|
|
6
|
+
import { fileURLToPath } from 'node:url';
|
|
7
|
+
import { dirname, resolve } from 'node:path';
|
|
8
|
+
|
|
9
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
10
|
+
|
|
11
|
+
let pkg;
|
|
12
|
+
try {
|
|
13
|
+
pkg = JSON.parse(readFileSync(resolve(__dirname, '../package.json'), 'utf-8'));
|
|
14
|
+
} catch {
|
|
15
|
+
pkg = { version: 'unknown' };
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
let Exprify;
|
|
19
|
+
try {
|
|
20
|
+
const mod = await import('../src/core/exprify.js');
|
|
21
|
+
Exprify = mod.default || mod.Exprify;
|
|
22
|
+
} catch {
|
|
23
|
+
stderr.write('Error: Could not load Exprify module\n');
|
|
24
|
+
exit(1);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const expr = new Exprify();
|
|
28
|
+
|
|
29
|
+
const USAGE = `Usage: exprify [options] [expression...]
|
|
30
|
+
|
|
31
|
+
Options:
|
|
32
|
+
--help Show this help message
|
|
33
|
+
--version Show version number
|
|
34
|
+
--parse <expr> Parse expression and show token/AST structure
|
|
35
|
+
--tokens <expr> Tokenize expression and show tokens
|
|
36
|
+
|
|
37
|
+
If no expression is provided and stdin is a TTY, starts interactive REPL.
|
|
38
|
+
If stdin is piped, reads expression from stdin.
|
|
39
|
+
|
|
40
|
+
Examples:
|
|
41
|
+
exprify "2 + 2"
|
|
42
|
+
exprify "sqrt(16)" "5 * 3"
|
|
43
|
+
exprify --parse "x ^ 2 + 2 * x + 1"
|
|
44
|
+
echo "2 + 2" | exprify
|
|
45
|
+
`;
|
|
46
|
+
|
|
47
|
+
const COLORS = {
|
|
48
|
+
reset: '\x1b[0m',
|
|
49
|
+
red: '\x1b[31m',
|
|
50
|
+
green: '\x1b[32m',
|
|
51
|
+
yellow: '\x1b[33m',
|
|
52
|
+
cyan: '\x1b[36m',
|
|
53
|
+
bold: '\x1b[1m',
|
|
54
|
+
dim: '\x1b[2m',
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
function formatResult(value) {
|
|
58
|
+
if (value === null) return 'null';
|
|
59
|
+
if (value === undefined) return 'undefined';
|
|
60
|
+
if (typeof value === 'object' || Array.isArray(value)) {
|
|
61
|
+
return JSON.stringify(value, null, 2);
|
|
62
|
+
}
|
|
63
|
+
return String(value);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function printError(msg) {
|
|
67
|
+
stderr.write(COLORS.red + 'Error: ' + COLORS.reset + msg + '\n');
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function evaluateAndPrint(expression, mode) {
|
|
71
|
+
try {
|
|
72
|
+
if (mode === 'parse') {
|
|
73
|
+
const result = expr.parse(expression);
|
|
74
|
+
console.log(JSON.stringify(result, null, 2));
|
|
75
|
+
} else if (mode === 'tokens') {
|
|
76
|
+
const result = expr.tokenize(expression);
|
|
77
|
+
console.log(JSON.stringify(result, null, 2));
|
|
78
|
+
} else {
|
|
79
|
+
const result = expr.evaluate(expression);
|
|
80
|
+
console.log(formatResult(result));
|
|
81
|
+
}
|
|
82
|
+
} catch (err) {
|
|
83
|
+
printError(err.message);
|
|
84
|
+
exit(1);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const args = argv.slice(2);
|
|
89
|
+
|
|
90
|
+
if (args.length === 0) {
|
|
91
|
+
if (stdin.isTTY) {
|
|
92
|
+
startREPL();
|
|
93
|
+
} else {
|
|
94
|
+
let input = '';
|
|
95
|
+
stdin.setEncoding('utf-8');
|
|
96
|
+
stdin.on('data', (chunk) => {
|
|
97
|
+
input += chunk;
|
|
98
|
+
});
|
|
99
|
+
stdin.on('end', () => {
|
|
100
|
+
const exprStr = input.trim();
|
|
101
|
+
if (exprStr) evaluateAndPrint(exprStr);
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
exit(0);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const flag = args[0];
|
|
108
|
+
|
|
109
|
+
if (flag === '--help' || flag === '-h') {
|
|
110
|
+
console.log(USAGE);
|
|
111
|
+
exit(0);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (flag === '--version' || flag === '-v') {
|
|
115
|
+
console.log(pkg.version);
|
|
116
|
+
exit(0);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (flag === '--parse' || flag === '--tokens') {
|
|
120
|
+
if (args.length < 2) {
|
|
121
|
+
printError('Missing expression argument');
|
|
122
|
+
console.log(USAGE);
|
|
123
|
+
exit(2);
|
|
124
|
+
}
|
|
125
|
+
const mode = flag.slice(2);
|
|
126
|
+
for (let i = 1; i < args.length; i++) {
|
|
127
|
+
evaluateAndPrint(args[i], mode);
|
|
128
|
+
}
|
|
129
|
+
exit(0);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
for (const arg of args) {
|
|
133
|
+
evaluateAndPrint(arg);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function startREPL() {
|
|
137
|
+
console.log(`Exprify v${pkg.version} - interactive REPL`);
|
|
138
|
+
console.log('Type an expression or .help for commands\n');
|
|
139
|
+
|
|
140
|
+
const rl = createInterface({
|
|
141
|
+
input: stdin,
|
|
142
|
+
output: stdout,
|
|
143
|
+
prompt: COLORS.cyan + '» ' + COLORS.reset,
|
|
144
|
+
completer: (line) => {
|
|
145
|
+
const completions = [
|
|
146
|
+
'help',
|
|
147
|
+
'.help',
|
|
148
|
+
'.exit',
|
|
149
|
+
'pi',
|
|
150
|
+
'e',
|
|
151
|
+
'i',
|
|
152
|
+
'PHI',
|
|
153
|
+
'TAU',
|
|
154
|
+
'INFINITY',
|
|
155
|
+
'NaN',
|
|
156
|
+
'sin',
|
|
157
|
+
'cos',
|
|
158
|
+
'tan',
|
|
159
|
+
'sqrt',
|
|
160
|
+
'abs',
|
|
161
|
+
'log',
|
|
162
|
+
'exp',
|
|
163
|
+
'map',
|
|
164
|
+
'filter',
|
|
165
|
+
'sum',
|
|
166
|
+
'prod',
|
|
167
|
+
'mean',
|
|
168
|
+
'max',
|
|
169
|
+
'min',
|
|
170
|
+
'if',
|
|
171
|
+
'parse',
|
|
172
|
+
'leafCount',
|
|
173
|
+
'random',
|
|
174
|
+
'simplify',
|
|
175
|
+
'expand',
|
|
176
|
+
'factor',
|
|
177
|
+
'solve',
|
|
178
|
+
'derivative',
|
|
179
|
+
'integral',
|
|
180
|
+
'sigma',
|
|
181
|
+
'limit',
|
|
182
|
+
'substitute',
|
|
183
|
+
'det',
|
|
184
|
+
'transpose',
|
|
185
|
+
'inverse',
|
|
186
|
+
'trace',
|
|
187
|
+
'rank',
|
|
188
|
+
];
|
|
189
|
+
const hits = completions.filter((c) => c.startsWith(line));
|
|
190
|
+
return [hits.length ? hits : completions, line];
|
|
191
|
+
},
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
rl.on('line', (line) => {
|
|
195
|
+
const input = line.trim();
|
|
196
|
+
|
|
197
|
+
if (!input) {
|
|
198
|
+
rl.prompt();
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
if (input === '.exit' || input === 'exit' || input === 'quit') {
|
|
203
|
+
rl.close();
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if (input === '.help' || input === 'help') {
|
|
208
|
+
console.log(`\n ${COLORS.bold}Commands:${COLORS.reset}`);
|
|
209
|
+
console.log(' .exit Exit the REPL');
|
|
210
|
+
console.log(' .help Show this message');
|
|
211
|
+
console.log(' <expr> Evaluate an expression');
|
|
212
|
+
console.log(' Ctrl+C Cancel / exit');
|
|
213
|
+
console.log('');
|
|
214
|
+
rl.prompt();
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
try {
|
|
219
|
+
const result = expr.evaluate(input);
|
|
220
|
+
console.log(COLORS.green + formatResult(result) + COLORS.reset);
|
|
221
|
+
} catch (err) {
|
|
222
|
+
console.log(COLORS.red + 'Error: ' + COLORS.reset + err.message);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
rl.prompt();
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
rl.on('close', () => {
|
|
229
|
+
console.log('');
|
|
230
|
+
exit(0);
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
rl.prompt();
|
|
234
|
+
}
|