exprify 1.0.4 → 1.0.7

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 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,33 @@
1
- # Exprify
1
+ [![Exprify Banner](https://raw.githubusercontent.com/code-hemu/Exprify/refs/heads/main/docs/assets/capture.jpg)](https://github.com/code-hemu/Exprify)
2
2
 
3
- [![Exprify Social Banner](https://raw.githubusercontent.com/code-hemu/Exprify/refs/heads/main/docs/assets/capture.jpg)](https://github.com/code-hemu/Exprify)
4
3
 
5
- Exprify is a JavaScript expression parser and evaluator for math-heavy apps. It supports arithmetic, variables, custom functions, unit conversion, matrices, complex numbers, symbolic helpers, and a growing set of linear algebra utilities.
4
+ **Exprify** (Math **Expr**ession + Simp**lify**) is a fast, lightweight JavaScript expression parser and evaluator. It is designed for math applications, scientific computing, data visualization tools, calculators, and other complex workflows that run in the browser and in Node.js. It supports basic arithmetic, variables, user-defined functions, and built-in operators for comparison, logic, and string manipulation.
5
+
6
+
7
+ [![Version](https://img.shields.io/npm/v/exprify)](https://www.npmjs.com/package/exprify)
8
+ [![Downloads](https://img.shields.io/npm/dt/exprify)](https://www.npmjs.com/package/exprify)
9
+ [![License](https://img.shields.io/github/license/code-hemu/exprify)](https://github.com/code-hemu/exprify/blob/master/LICENSE)
10
+ [![jsDelivr](https://data.jsdelivr.com/v1/package/npm/exprify/badge?style=rounded)](https://www.jsdelivr.com/package/npm/exprify)
11
+ [![unpkg](https://img.shields.io/badge/CDN-unpkg-blue)](https://unpkg.com/exprify)
12
+ [![Issues](https://img.shields.io/github/issues/code-hemu/exprify)](https://github.com/code-hemu/exprify/issues)
13
+ [![Contributors](https://img.shields.io/github/contributors/code-hemu/exprify)](https://github.com/code-hemu/exprify/graphs/contributors)
14
+ [![Sponsor](https://img.shields.io/github/sponsors/code-hemu)](https://github.com/sponsors/code-hemu)
15
+ [![Open Source](https://badges.frapsoft.com/os/v1/open-source.svg?v=103)](https://github.com/code-hemu/exprify)
16
+
17
+ ## Features
18
+
19
+ - **Arithmetic & Variables** - `expr.evaluate("5 + 7 * 2")` · [docs](docs/datatypes/numbers.md)
20
+ - **Unit conversion** - `expr.evaluate("2 inch to cm")` · [docs](docs/datatypes/units.md)
21
+ - **Matrix operations** - `expr.evaluate("det([-1,2;3,1])")` · [docs](docs/datatypes/matrices.md)
22
+ - **Complex numbers** - `expr.evaluate("9/3 + 2i")` · [docs](docs/datatypes/complex_numbers.md)
23
+ - **Symbolic math** - `expr.evaluate('expand("(x+1)^2")')` · [docs](docs/expressions/algebra.md)
24
+ - **Arbitrary precision** - `expr.evaluate('bignumber("0.1") + bignumber("0.2")')` · [docs](docs/datatypes/bignumbers.md)
25
+ - **Exact fractions** - `expr.evaluate("fraction(1,3) + fraction(1,6)")` · [docs](docs/datatypes/fractions.md)
26
+ - **Calculus & statistics** - `expr.evaluate('integral("x^2", 0, 1)')` · [docs](docs/reference/functions.md)
27
+ - **Lambda expressions** - `expr.evaluate('map([1,2,3], x -> x^2)')` · [docs](docs/expressions/customization.md)
28
+ - **Expression chaining** - `c.evaluate("sqrt(x)").evaluate("ans * 2").done()` · [docs](docs/core/chaining.md)
29
+ - **State serialization** - `expr.exportState()` / `expr.importState(state)` · [docs](docs/core/serialization.md)
30
+ - **Degree-mode trig** - `expr.evaluate("sind(90)")` · [docs](docs/reference/functions.md)
6
31
 
7
32
  ## Installation
8
33
 
@@ -12,270 +37,178 @@ npm install exprify
12
37
 
13
38
  ## Quick Start
14
39
 
15
- **ESM** (Node.js, bundlers):
40
+ **ESM** (Node.js / bundlers):
16
41
 
17
42
  ```js
18
43
  import Exprify from "exprify";
19
44
 
20
45
  const expr = new Exprify();
21
46
 
22
- console.log(expr.evaluate("5 + 7 * 2"));
23
- // 19
47
+ expr.evaluate("5 + 7 * 2"); // 19
24
48
 
25
49
  expr.setVariable("x", 10);
26
- console.log(expr.evaluate("x + 5"));
27
- // 15
50
+ expr.evaluate("x + 5"); // 15
28
51
  ```
29
52
 
30
- **CommonJS** (`require`):
53
+ **CommonJS:**
31
54
 
32
55
  ```js
33
56
  const Exprify = require("exprify");
34
57
 
35
58
  const expr = new Exprify();
36
- console.log(expr.evaluate("5 + 7 * 2"));
37
- // 19
59
+ expr.evaluate("5 + 7 * 2"); // 19
38
60
  ```
39
61
 
40
- ## Browser Usage
62
+ **Browser (CDN):**
41
63
 
42
64
  ```html
43
65
  <script src="https://unpkg.com/exprify"></script>
44
66
  <script>
45
67
  const expr = new Exprify();
46
- console.log(expr.evaluate("(10 + 5) * 2"));
68
+ expr.evaluate("(10 + 5) * 2"); // 30
47
69
  </script>
48
70
  ```
49
71
 
50
- `unpkg` resolves to the browser bundle from `dist/exprify.min.js`.
51
-
52
- ## Module Formats
53
-
54
- The package ships a separate build for each consumer, and `package.json`'s `exports` field routes each import style to the right file:
55
-
56
- | Consumer | Resolved file | Notes |
57
- | --- | --- | --- |
58
- | `import Exprify from "exprify"` (ESM) | `dist/exprify.esm.js` | Default export is the `Exprify` class. |
59
- | `const Exprify = require("exprify")` (CJS) | `dist/exprify.cjs.cjs` | `module.exports` is the `Exprify` class. |
60
- | `<script src="https://unpkg.com/exprify">` | `dist/exprify.min.js` | UMD build; exposes `Exprify` as a global. |
61
- | `import "exprify/dist/exprify.js"` (UMD) | `dist/exprify.js` | Unminified UMD for bundlers that prefer it. |
62
-
63
- The `.cjs` extension on the CommonJS bundle keeps it loadable as CJS even though the package is `"type": "module"`, so `require()` and `import` both work without configuration.
64
-
65
- ## API
72
+ ## API Reference
66
73
 
67
74
  ### `new Exprify()`
68
75
 
69
- Creates a new evaluator instance with isolated state for:
76
+ Creates a new evaluator instance with fully isolated state. Each instance maintains its own independent registry of variables, custom functions, unit definitions, and a compiled-expression cache - so multiple instances never interfere with each other.
70
77
 
71
- - variables
72
- - functions
73
- - units
74
- - compiled-expression cache
78
+ ```js
79
+ const expr = new Exprify();
80
+ ```
75
81
 
76
- ### `expr.evaluate(expression)`
82
+ ### `expr.evaluate(expression, scope?)`
77
83
 
78
- Parses and evaluates an expression string.
84
+ Parses and evaluates an expression string, returning the computed result. An optional `scope` object lets you pass temporary variable values that apply only to that single call - they do not modify the instance's stored state.
79
85
 
80
86
  ```js
81
- expr.evaluate("10 + 5 * 2");
82
- // 20
87
+ expr.evaluate("10 + 5 * 2"); // 20
88
+
89
+ expr.setVariable("x", 100);
90
+ expr.evaluate("x + 1", { x: 5 }); // 6 (x = 100 is unchanged)
83
91
  ```
84
92
 
85
93
  ### `expr.parse(expression)`
86
94
 
87
- Returns `{ tokens, ast }`.
95
+ Parses an expression without evaluating it. Returns a `{ tokens, ast }` object containing the raw token list and the abstract syntax tree - useful for debugging, introspection, or building custom tooling on top of the parser.
88
96
 
89
97
  ```js
90
- const parsed = expr.parse("2 inch to cm");
91
- console.log(parsed.tokens);
92
- console.log(parsed.ast);
98
+ const { tokens, ast } = expr.parse("2 inch to cm");
99
+ // tokens: [...], ast: { type: "UnitConversion", ... }
93
100
  ```
94
101
 
95
102
  ### `expr.compile(expression)`
96
103
 
97
- Compiles an expression once and returns a reusable function.
104
+ Compiles an expression once and returns a reusable callable function. The compiled form skips parsing on every subsequent invocation, making this the right choice for hot paths or any expression evaluated repeatedly with different inputs.
98
105
 
99
106
  ```js
100
107
  const area = expr.compile("width * height");
101
-
102
- console.log(area({ width: 6, height: 4 }));
103
- // 24
108
+ area({ width: 6, height: 4 }); // 24
109
+ area({ width: 3, height: 9 }); // 27
104
110
  ```
105
111
 
106
112
  ### `expr.setVariable(name, value)` / `expr.getVariable(name)`
107
113
 
108
- Stores and reuses values across evaluations.
114
+ Stores a named value that persists across all future evaluations on this instance. `getVariable` retrieves a previously stored value by name.
109
115
 
110
116
  ```js
111
117
  expr.setVariable("x", 10);
112
118
  expr.setVariable("y", 5);
119
+ expr.evaluate("x + y * 2"); // 20
113
120
 
114
- console.log(expr.evaluate("x + y * 2"));
115
- // 20
121
+ expr.getVariable("x"); // 10
116
122
  ```
117
123
 
118
124
  ### `expr.addFunction(name, fn)`
119
125
 
120
- Registers a custom JavaScript function.
126
+ Registers a plain JavaScript function under a given name, making it available to call inside any expression evaluated on this instance. The function receives its arguments as individual parameters, exactly as written in the expression.
121
127
 
122
128
  ```js
123
129
  expr.addFunction("double", (n) => n * 2);
130
+ expr.evaluate("double(5) + 3"); // 13
124
131
 
125
- console.log(expr.evaluate("double(5) + 3"));
126
- // 13
127
- ```
128
-
129
- ### Inline Function Definitions
130
-
131
- You can define functions inside expressions.
132
-
133
- ```js
134
- expr.evaluate("hyp(a, b) = sqrt(a ^ 2 + b ^ 2)");
135
-
136
- console.log(expr.evaluate("hyp(3, 4)"));
137
- // 5
138
- ```
139
-
140
- ## Features
141
-
142
- ### Arithmetic
143
-
144
- ```js
145
- expr.evaluate("2 + 3 * 4");
146
- // 14
147
-
148
- expr.evaluate("(2 + 3) * 4");
149
- // 20
150
-
151
- expr.evaluate("11n ^ 2n");
152
- // 121n
132
+ expr.addFunction("clamp", (val, lo, hi) => Math.min(Math.max(val, lo), hi));
133
+ expr.evaluate("clamp(150, 0, 100)"); // 100
153
134
  ```
154
135
 
155
- ### Strings, Booleans, Complex Numbers
156
-
157
- ```js
158
- expr.evaluate('"Hello " + "World"');
159
- // "Hello World"
160
-
161
- expr.evaluate("true && false");
162
- // false
163
-
164
- expr.evaluate("9 / 3 + 2i");
165
- // "3 + 2i"
166
- ```
136
+ ### `expr.chain()`
167
137
 
168
- ### Unit Conversion
138
+ Returns a fluent `Chain` object for building multi-step calculations. Each call to `.evaluate()` on the chain stores its result in the special variable `ans`, which the next expression can reference directly. Call `.done()` at the end to extract the final value.
169
139
 
170
140
  ```js
171
- expr.evaluate("2 inch to cm");
172
- // "5.08 cm"
173
-
174
- expr.evaluate("5 cm + 2 inch");
175
- // "10.08 cm"
176
-
177
- expr.evaluate("5cm + 0.2 m in inch");
178
- // "9.84251968503937 inch"
179
-
180
- expr.evaluate("a = 5.08 cm + 2 inch");
181
- expr.evaluate("a to inch");
182
- // "4 inch"
141
+ const c = expr.chain();
142
+ c.setVariable("x", 25);
143
+ c.evaluate("sqrt(x) + 3"); // computes 8, stored as ans
144
+ c.evaluate("ans * 2"); // computes 16, stored as ans
145
+ c.done(); // 16
183
146
  ```
184
147
 
185
- ### Matrices
186
-
187
- Exprify supports matrix literals with `;` as row separators.
188
-
189
- ```js
190
- expr.evaluate("a = [1, 2, 3; 4, 5, 6]");
191
- // {"exprify":"DenseMatrix","data":[[1,2,3],[4,5,6]],"size":[2,3]}
192
-
193
- expr.evaluate("a[2, 3]");
194
- // 6
195
-
196
- expr.evaluate("a[1:2, 2]");
197
- // "2\n5"
198
-
199
- expr.evaluate("a[3, 1:3] = [7, 8, 9]");
200
- // "7\t8\t9"
148
+ ### `expr.exportState()` / `expr.importState(state)`
201
149
 
202
- expr.evaluate("det([-1, 2; 3, 1])");
203
- // -7
204
- ```
205
-
206
- ### Linear Algebra Helpers
150
+ Serializes the complete engine state - all variables, registered functions, and unit definitions - into a plain object that can be stored, transmitted, or restored later. `importState` loads a previously exported snapshot into a fresh instance, fully reconstructing that environment.
207
151
 
208
152
  ```js
209
- expr.evaluate("lup([[2, 1], [1, 4]])");
210
- // {"L":{"exprify":"DenseMatrix",...},"U":{"exprify":"DenseMatrix",...},"p":[0,1]}
211
-
212
- expr.evaluate("lyap([[-2, 0], [1, -4]], [[3, 1], [1, 3]])");
213
- // {"exprify":"DenseMatrix","data":[[0.75,0.2916666666666667],[0.2916666666666667,0.44791666666666663]],"size":[2,2]}
214
-
215
- expr.evaluate("qr([[1, -1, 4], [1, 4, -2], [1, 4, 2], [1, -1, 0]])");
216
- // {"Q":{"exprify":"DenseMatrix",...},"R":{"exprify":"DenseMatrix",...}}
153
+ const state = expr.exportState();
154
+ // {
155
+ // variables: { x: 10, y: 5 },
156
+ // functions: ["double", "clamp"],
157
+ // units: {...}
158
+ // }
217
159
 
218
- expr.evaluate("polynomialRoot(-6, 11, -6, 1)");
219
- // [1,3,2]
160
+ const expr2 = new Exprify();
161
+ expr2.importState(state);
162
+ expr2.evaluate("x + y"); // 15
220
163
  ```
221
164
 
222
- Available helpers currently include `det`, `lsolve`, `lup`, `lyap`, `qr`, and `polynomialRoot`.
165
+ ### Inline Function Definitions
223
166
 
224
- ### Algebra Helpers
167
+ Functions can be defined directly inside an expression using the `name(params) = body` syntax. Once defined, they behave exactly like functions registered via `addFunction` and remain available for the lifetime of the instance.
225
168
 
226
169
  ```js
227
- expr.evaluate('simplify("2x + x")');
228
- // "3 * x"
229
-
230
- expr.evaluate('derivative("2x^2 + 3x + 4", "x")');
231
- // "4 * x + 3"
232
-
233
- expr.evaluate('rationalize("2x/y - y/(x+1)", true)');
234
- // {"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)"}
170
+ expr.evaluate("hyp(a, b) = sqrt(a^2 + b^2)");
171
+ expr.evaluate("hyp(3, 4)"); // 5
172
+ expr.evaluate("hyp(5, 12)"); // 13
235
173
  ```
236
174
 
237
- ### Parse and AST Utilities
238
-
239
- ```js
240
- expr.evaluate('leafCount("e^(i*pi)-1")');
241
- // 4
242
-
243
- expr.evaluate('leafCount(parse("{a: 22/7, b: 10^(1/2)}"))');
244
- // 5
245
- ```
175
+ This is particularly convenient for one-off helpers that do not warrant a full `addFunction` call, or for expressions that define and immediately use a function in a single evaluation step.
246
176
 
247
- ### Built-in Functions
177
+ ## Built-in Functions (Selected)
248
178
 
249
- Common built-ins include:
179
+ | Function | Description | Example | Result |
180
+ |---|---|---|---|
181
+ | `abs` | Absolute value | `abs(-5)` | `5` |
182
+ | `round` | Round to nearest integer | `round(3.7)` | `4` |
183
+ | `floor` | Round down | `floor(3.7)` | `3` |
184
+ | `ceil` | Round up | `ceil(3.2)` | `4` |
250
185
 
251
- - `max`, `min`, `abs`, `round`, `floor`, `ceil`, `sqrt`, `pow`
252
- - `sin`, `cos`, `tan`, `asin`, `acos`, `atan`
253
- - `log`, `log10`, `exp`, `random`
254
- - `clamp`, `if`, `length`, `typeof`
255
- - `det`, `lsolve`, `lup`, `lyap`, `qr`, `polynomialRoot`
256
- - `simplify`, `derivative`, `rationalize`, `leafCount`, `parse`
186
+ > See the [full searchable function reference](docs/reference/functions.md) for all ~130 built-in functions.
257
187
 
258
188
  ## Return Types
259
189
 
260
- Depending on the expression, `evaluate()` may return:
190
+ | Type | Example expression | Result |
191
+ |---|---|---|
192
+ | Number / BigInt / Boolean | `2 + 2`, `true && false` | `4`, `false` |
193
+ | String | `"hello" + " world"` | `"hello world"` |
194
+ | Unit string | `2 inch to cm` | `"5.08 cm"` |
195
+ | Complex string | `3 + 2i` | `"3 + 2i"` |
196
+ | Matrix JSON | `[1,2;3,4]` | `{"exprify":"DenseMatrix",...}` |
197
+ | Structured JSON | `lup(...)`, `rationalize(..., true)` | JSON object string |
198
+ | Function | `x -> x^2` | Native JS function |
199
+ | Array | `1:5` | `[1,2,3,4,5]` |
261
200
 
262
- - numbers / bigint / booleans
263
- - strings
264
- - formatted unit strings like `"5.08 cm"`
265
- - formatted complex strings like `"3 + 2i"`
266
- - matrix wrapper JSON strings such as `{"exprify":"DenseMatrix",...}`
267
- - JSON strings for structured helper outputs like `lup()` or `rationalize(..., true)`
268
201
 
269
202
  ## Manual Build
270
203
 
271
204
  ```bash
272
- git clone https://github.com/code-hemu/Exprify.git
205
+ git clone https://github.com/code-hemu/exprify.git
273
206
  cd Exprify
274
207
  npm install
275
208
  npm run build
276
209
  ```
277
210
 
278
- Build output is written to `dist/`.
211
+ Output is written to `dist/`.
279
212
 
280
213
  ## Testing
281
214
 
@@ -283,21 +216,15 @@ Build output is written to `dist/`.
283
216
  npm test
284
217
  ```
285
218
 
286
- Tested in CI across Node 20 and 22 (see `.github/workflows/ci.yml`).
287
-
288
- ## Publishing
289
-
290
- The package is published to the public npm registry via `npm publish`. The `npm-publish.yml` workflow runs on GitHub release events. See `.github/workflows/npm-publish.yml` for details.
291
-
292
- `package-lock.json` is committed but is not shipped in the published tarball (see `.npmignore`); consumers generate their own lockfile when installing.
293
-
294
- ## License
295
-
296
- Exprify is licensed under GPL-3.0. Copyright (c) [Nirmal Paul](https://github.com/nirmalpaul383/).
219
+ Tested in CI across Node 20 and 22. See `.github/workflows/ci.yml` for details.
297
220
 
298
221
  ## Contributing
299
222
 
300
223
  1. Fork the repository.
301
224
  2. Create a branch: `git checkout -b feature/your-feature`
302
225
  3. Commit your changes: `git commit -m "Add your feature"`
303
- 4. Push your branch and open a pull request.
226
+ 4. Push and open a pull request.
227
+
228
+ ## License
229
+
230
+ 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.