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 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
- # Exprify
1
+ [![Version](https://img.shields.io/npm/v/exprify)](https://www.npmjs.com/package/exprify)
2
+ [![Downloads](https://img.shields.io/npm/dt/exprify)](https://www.npmjs.com/package/exprify)
3
+ [![License](https://img.shields.io/github/license/code-hemu/exprify)](https://github.com/code-hemu/exprify/blob/master/LICENSE)
4
+ [![jsDelivr](https://data.jsdelivr.com/v1/package/npm/exprify/badge?style=rounded)](https://www.jsdelivr.com/package/npm/exprify)
5
+ [![unpkg](https://img.shields.io/badge/CDN-unpkg-blue)](https://unpkg.com/exprify)
6
+ [![Issues](https://img.shields.io/github/issues/code-hemu/exprify)](https://github.com/code-hemu/exprify/issues)
7
+ [![Contributors](https://img.shields.io/github/contributors/code-hemu/exprify)](https://github.com/code-hemu/exprify/graphs/contributors)
8
+ [![Sponsor](https://img.shields.io/github/sponsors/code-hemu)](https://github.com/sponsors/code-hemu)
2
9
 
3
- [![Exprify Social Banner](https://raw.githubusercontent.com/code-hemu/Exprify/refs/heads/main/src/assets/capture.jpg)](https://github.com/code-hemu/Exprify)
10
+ [![Exprify Banner](https://raw.githubusercontent.com/code-hemu/Exprify/refs/heads/main/docs/assets/capture.jpg)](https://github.com/code-hemu/Exprify)
4
11
 
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.
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
- console.log(expr.evaluate("5 + 7 * 2"));
21
- // 19
46
+ expr.evaluate("5 + 7 * 2"); // 19
22
47
 
23
48
  expr.setVariable("x", 10);
24
- console.log(expr.evaluate("x + 5"));
25
- // 15
49
+ expr.evaluate("x + 5"); // 15
26
50
  ```
27
51
 
28
- ## Browser Usage
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
- console.log(expr.evaluate("(10 + 5) * 2"));
67
+ expr.evaluate("(10 + 5) * 2"); // 30
35
68
  </script>
36
69
  ```
37
70
 
38
- `unpkg` resolves to the browser bundle from `dist/exprify.min.js`.
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
- - variables
47
- - functions
48
- - units
49
- - compiled-expression cache
90
+ ### `expr.evaluate(expression, scope?)`
50
91
 
51
- ### `expr.evaluate(expression)`
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
- // 20
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 parsed = expr.parse("2 inch to cm");
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
- console.log(area({ width: 6, height: 4 }));
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 reuses values across evaluations.
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
- ### Inline Function Definitions
138
+ ### `expr.chain()`
105
139
 
106
- You can define functions inside expressions.
140
+ Returns a fluent `Chain` object. Each step stores its result as `ans` for the next expression.
107
141
 
108
142
  ```js
109
- expr.evaluate("hyp(a, b) = sqrt(a ^ 2 + b ^ 2)");
110
-
111
- console.log(expr.evaluate("hyp(3, 4)"));
112
- // 5
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
- ## Features
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
- expr.evaluate("11n ^ 2n");
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.evaluate('"Hello " + "World"');
134
- // "Hello World"
155
+ const state = expr.exportState();
156
+ // { variables: {...}, functions: [...], units: {...} }
135
157
 
136
- expr.evaluate("true && false");
137
- // false
138
-
139
- expr.evaluate("9 / 3 + 2i");
140
- // "3 + 2i"
158
+ const expr2 = new Exprify();
159
+ expr2.importState(state);
141
160
  ```
142
161
 
143
- ### Unit Conversion
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
- ### Parse and AST Utilities
164
+ Functions can be defined directly inside expressions and reused immediately.
213
165
 
214
166
  ```js
215
- expr.evaluate('leafCount("e^(i*pi)-1")');
216
- // 4
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
- ### Built-in Functions
171
+ ## Built-in Functions (Selected)
223
172
 
224
- Common built-ins include:
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
- - `max`, `min`, `abs`, `round`, `floor`, `ceil`, `sqrt`, `pow`
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
- Depending on the expression, `evaluate()` may return:
236
-
237
- - numbers / bigint / booleans
238
- - strings
239
- - formatted unit strings like `"5.08 cm"`
240
- - formatted complex strings like `"3 + 2i"`
241
- - matrix wrapper JSON strings such as `{"exprify":"DenseMatrix",...}`
242
- - JSON strings for structured helper outputs like `lup()` or `rationalize(..., true)`
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/Exprify.git
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
- Build output is written to `dist/`.
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
- ## License
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 your branch and open a pull request.
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
+ }