sci-calc-pro 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,119 @@
1
+ # sci-calc-pro
2
+
3
+ Professional scientific calculator CLI for Node.js. Zero dependencies, pure JavaScript.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install -g sci-calc-pro
9
+ ```
10
+
11
+ Or use directly in a project:
12
+
13
+ ```bash
14
+ npx sci-calc-pro "sin(45)"
15
+ ```
16
+
17
+ ## Usage
18
+
19
+ ### One-liner
20
+
21
+ ```bash
22
+ sci-calc "2^10"
23
+ sci-calc "sin(45) + cos(60)"
24
+ sci-calc "sqrt(144)"
25
+ sci-calc "fact(10)"
26
+ sci-calc "log(1000)"
27
+ sci-calc "C(10,3)"
28
+ ```
29
+
30
+ ### Interactive REPL
31
+
32
+ ```bash
33
+ sci-calc --repl
34
+ sci-calc
35
+ ```
36
+
37
+ ```
38
+ sci> 2 + 3
39
+ = 5
40
+ sci> sin(45)
41
+ = 0.707106781187
42
+ sci> set x = 10
43
+ sci> x^2
44
+ = 100
45
+ sci> help
46
+ sci> exit
47
+ ```
48
+
49
+ ## Functions
50
+
51
+ | Category | Functions |
52
+ | -------------- | --------------------------------------------------- |
53
+ | Basic | `sqrt`, `cbrt`, `abs`, `pow`, `mod` |
54
+ | Trigonometric | `sin`, `cos`, `tan`, `asin`, `acos`, `atan`, `atan2` |
55
+ | Hyperbolic | `sinh`, `cosh`, `tanh`, `asinh`, `acosh`, `atanh` |
56
+ | Logarithmic | `log` (base 10), `ln` (natural), `log2`, `exp`, `exp2`, `exp10` |
57
+ | Combinatorics | `fact`/`factorial`, `perm`/`P`, `comb`/`C` |
58
+ | Rounding | `round`, `floor`, `ceil`, `trunc` |
59
+ | Aggregation | `max`, `min` |
60
+
61
+ ## Constants
62
+
63
+ - `pi` - 3.14159265358979...
64
+ - `e` - 2.71828182845904...
65
+ - `phi` - Golden ratio (1.61803398874989...)
66
+
67
+ ## REPL Commands
68
+
69
+ | Command | Description |
70
+ | ----------------- | ------------------------------ |
71
+ | `help` | Show help |
72
+ | `list` | List all functions |
73
+ | `vars` | Show stored variables |
74
+ | `set x = expr` | Store a variable |
75
+ | `history` | Show calculation history |
76
+ | `unit deg`/`rad` | Set angle unit |
77
+ | `ms expr` | Store to memory |
78
+ | `mr` | Recall memory |
79
+ | `mc` | Clear memory |
80
+ | `clear` | Clear screen |
81
+ | `exit` | Quit |
82
+
83
+ ## Flags
84
+
85
+ ```
86
+ sci-calc <expression> Evaluate expression
87
+ sci-calc --repl Start interactive mode
88
+ sci-calc --help Show help
89
+ sci-calc --version Show version
90
+ sci-calc --list List functions
91
+ sci-calc --unit <deg|rad> Set angle unit for evaluation
92
+ ```
93
+
94
+ ## Examples
95
+
96
+ ```bash
97
+ # Trig in degrees
98
+ sci-calc "sin(30)"
99
+ sci-calc "atan2(1,1)"
100
+
101
+ # Factorials and combinations
102
+ sci-calc "fact(20)"
103
+ sci-calc "C(52,5)" # 5-card poker hands
104
+
105
+ # Complex expressions
106
+ sci-calc "sqrt(e^2 + pi^2)"
107
+ sci-calc "log(1000) + ln(e^5)"
108
+
109
+ # Powers and roots
110
+ sci-calc "2^128"
111
+ sci-calc "cbrt(27)"
112
+
113
+ # With angle unit
114
+ sci-calc --unit rad "sin(1.5708)"
115
+ ```
116
+
117
+ ## License
118
+
119
+ MIT
package/bin/cli.js ADDED
@@ -0,0 +1,240 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ const readline = require('readline');
5
+ const Parser = require('../lib/parser');
6
+ const SciCalculator = require('../lib/calculator');
7
+
8
+ const VERSION = '1.0.0';
9
+ const BANNER = `
10
+ ╔═══════════════════════════════════════════╗
11
+ ║ SCI-CALC PRO v${VERSION} ║
12
+ ║ Professional Scientific Calculator ║
13
+ ╚═══════════════════════════════════════════╝
14
+ `;
15
+
16
+ const HELP = `
17
+ USAGE:
18
+ sci-calc <expression> Evaluate an expression
19
+ sci-calc --repl Start interactive mode
20
+ sci-calc --help Show this help
21
+ sci-calc --version Show version
22
+ sci-calc --list List available functions
23
+ sci-calc --unit <deg|rad> Set angle unit
24
+
25
+ FUNCTIONS:
26
+ Trig: sin, cos, tan, asin, acos, atan, atan2
27
+ Hyperbolic: sinh, cosh, tanh, asinh, acosh, atanh
28
+ Log: log (base10), ln (natural), log2, exp, exp2, exp10
29
+ Root: sqrt, cbrt
30
+ Combin: fact (factorial), perm, comb
31
+ Round: round, floor, ceil, trunc
32
+ Other: abs, pow, mod, max, min
33
+
34
+ CONSTANTS:
35
+ pi, e, phi
36
+
37
+ EXAMPLES:
38
+ sci-calc "sin(45)"
39
+ sci-calc "2^10"
40
+ sci-calc "sqrt(144) + ln(100)"
41
+ sci-calc "fact(10)"
42
+ sci-calc "C(10,3)"
43
+ sci-calc --repl
44
+ `;
45
+
46
+ function printResult(value, calc) {
47
+ console.log(` = ${calc.format(value)}`);
48
+ }
49
+
50
+ function printError(msg) {
51
+ console.error(` \x1b[31mError: ${msg}\x1b[0m`);
52
+ }
53
+
54
+ // ── Single expression mode ──
55
+ function evaluateExpression(expr, parser) {
56
+ try {
57
+ const result = parser.parse(expr);
58
+ printResult(result, parser.calc);
59
+ } catch (err) {
60
+ printError(err.message);
61
+ process.exit(1);
62
+ }
63
+ }
64
+
65
+ // ── REPL mode ──
66
+ function startREPL(angleUnit) {
67
+ console.log(BANNER);
68
+ console.log(' Type "help" for commands, "exit" to quit.\n');
69
+
70
+ const parser = new Parser();
71
+ parser.setAngleUnit(angleUnit);
72
+
73
+ const rl = readline.createInterface({
74
+ input: process.stdin,
75
+ output: process.stdout,
76
+ prompt: `\x1b[36msci>\x1b[0m `,
77
+ });
78
+
79
+ rl.prompt();
80
+
81
+ rl.on('line', (line) => {
82
+ const input = line.trim();
83
+ if (!input) { rl.prompt(); return; }
84
+
85
+ const lower = input.toLowerCase();
86
+
87
+ if (lower === 'exit' || lower === 'quit' || lower === 'q') {
88
+ console.log(' Goodbye!');
89
+ rl.close();
90
+ return;
91
+ }
92
+
93
+ if (lower === 'help' || lower === 'h' || lower === '?') {
94
+ console.log(HELP);
95
+ rl.prompt();
96
+ return;
97
+ }
98
+
99
+ if (lower === 'list') {
100
+ console.log(HELP);
101
+ rl.prompt();
102
+ return;
103
+ }
104
+
105
+ if (lower === 'clear' || lower === 'cls') {
106
+ console.clear();
107
+ rl.prompt();
108
+ return;
109
+ }
110
+
111
+ if (lower === 'vars') {
112
+ console.log(' Variables:', JSON.stringify(parser.vars, null, 2));
113
+ rl.prompt();
114
+ return;
115
+ }
116
+
117
+ if (lower.startsWith('set ')) {
118
+ const parts = input.slice(4).split('=');
119
+ if (parts.length === 2) {
120
+ const varName = parts[0].trim();
121
+ const val = parser.parse(parts[1].trim());
122
+ parser.vars[varName] = val;
123
+ console.log(` ${varName} = ${parser.calc.format(val)}`);
124
+ } else {
125
+ printError('Usage: set varname = expression');
126
+ }
127
+ rl.prompt();
128
+ return;
129
+ }
130
+
131
+ if (lower.startsWith('unit ')) {
132
+ const unit = lower.slice(5).trim();
133
+ try {
134
+ parser.setAngleUnit(unit);
135
+ console.log(` Angle unit set to: ${unit}`);
136
+ } catch (e) {
137
+ printError(e.message);
138
+ }
139
+ rl.prompt();
140
+ return;
141
+ }
142
+
143
+ if (lower === 'history') {
144
+ const hist = parser.calc.history;
145
+ if (hist.length === 0) {
146
+ console.log(' No history yet.');
147
+ } else {
148
+ hist.slice(-20).forEach((h, i) => {
149
+ console.log(` ${String(i + 1).padStart(3)}. ${h.expr} = ${parser.calc.format(h.result)}`);
150
+ });
151
+ }
152
+ rl.prompt();
153
+ return;
154
+ }
155
+
156
+ if (lower === 'mem' || lower === 'memory') {
157
+ console.log(` Memory = ${parser.calc.format(parser.calc.memory)}`);
158
+ rl.prompt();
159
+ return;
160
+ }
161
+
162
+ if (lower.startsWith('ms ')) {
163
+ try {
164
+ const val = parser.parse(input.slice(3));
165
+ parser.calc.memStore(val);
166
+ console.log(` Memory = ${parser.calc.format(val)}`);
167
+ } catch (e) { printError(e.message); }
168
+ rl.prompt();
169
+ return;
170
+ }
171
+
172
+ if (lower === 'mr') {
173
+ console.log(` ${parser.calc.format(parser.calc.memRecall())}`);
174
+ rl.prompt();
175
+ return;
176
+ }
177
+
178
+ if (lower === 'mc') {
179
+ parser.calc.memClear();
180
+ console.log(' Memory cleared.');
181
+ rl.prompt();
182
+ return;
183
+ }
184
+
185
+ evaluateExpression(input, parser);
186
+ rl.prompt();
187
+ });
188
+
189
+ rl.on('close', () => process.exit(0));
190
+ }
191
+
192
+ // ── CLI entry ──
193
+ function main() {
194
+ const args = process.argv.slice(2);
195
+
196
+ if (args.length === 0) {
197
+ startREPL('deg');
198
+ return;
199
+ }
200
+
201
+ const flag = args[0].toLowerCase();
202
+
203
+ if (flag === '--help' || flag === '-h') {
204
+ console.log(HELP);
205
+ return;
206
+ }
207
+
208
+ if (flag === '--version' || flag === '-v') {
209
+ console.log(`sci-calc-pro v${VERSION}`);
210
+ return;
211
+ }
212
+
213
+ if (flag === '--list') {
214
+ console.log(HELP);
215
+ return;
216
+ }
217
+
218
+ if (flag === '--repl' || flag === '-r') {
219
+ const unit = args.includes('--rad') ? 'rad' : 'deg';
220
+ startREPL(unit);
221
+ return;
222
+ }
223
+
224
+ if (flag === '--unit') {
225
+ const unit = (args[1] || 'deg').toLowerCase();
226
+ const expression = args.slice(2).join(' ');
227
+ const parser = new Parser();
228
+ parser.setAngleUnit(unit);
229
+ if (!expression) { startREPL(unit); return; }
230
+ evaluateExpression(expression, parser);
231
+ return;
232
+ }
233
+
234
+ // Direct evaluation
235
+ const expression = args.join(' ');
236
+ const parser = new Parser();
237
+ evaluateExpression(expression, parser);
238
+ }
239
+
240
+ main();
@@ -0,0 +1,135 @@
1
+ 'use strict';
2
+
3
+ const DEG_TO_RAD = Math.PI / 180;
4
+ const RAD_TO_DEG = 180 / Math.PI;
5
+
6
+ class SciCalculator {
7
+ constructor(angleUnit = 'deg') {
8
+ this.angleUnit = angleUnit;
9
+ this.memory = 0;
10
+ this.history = [];
11
+ }
12
+
13
+ _toRad(angle) {
14
+ return this.angleUnit === 'deg' ? angle * DEG_TO_RAD : angle;
15
+ }
16
+
17
+ _fromRad(rad) {
18
+ return this.angleUnit === 'deg' ? rad * RAD_TO_DEG : rad;
19
+ }
20
+
21
+ _record(expr, result) {
22
+ this.history.push({ expr, result, time: new Date() });
23
+ if (this.history.length > 100) this.history.shift();
24
+ return result;
25
+ }
26
+
27
+ // Basic arithmetic
28
+ add(a, b) { return this._record(`${a} + ${b}`, a + b); }
29
+ sub(a, b) { return this._record(`${a} - ${b}`, a - b); }
30
+ mul(a, b) { return this._record(`${a} × ${b}`, a * b); }
31
+ div(a, b) {
32
+ if (b === 0) throw new Error('Division by zero');
33
+ return this._record(`${a} ÷ ${b}`, a / b);
34
+ }
35
+ mod(a, b) { return this._record(`${a} mod ${b}`, a % b); }
36
+ pow(a, b) { return this._record(`${a} ^ ${b}`, Math.pow(a, b)); }
37
+ sqrt(a) {
38
+ if (a < 0) throw new Error('Square root of negative number');
39
+ return this._record(`√(${a})`, Math.sqrt(a));
40
+ }
41
+ cbrt(a) { return this._record(`∛(${a})`, Math.cbrt(a)); }
42
+ abs(a) { return this._record(`|${a}|`, Math.abs(a)); }
43
+ factorial(a) {
44
+ if (a < 0 || !Number.isInteger(a)) throw new Error('Factorial requires a non-negative integer');
45
+ if (a > 170) throw new Error('Factorial overflow (max 170)');
46
+ let result = 1;
47
+ for (let i = 2; i <= a; i++) result *= i;
48
+ return this._record(`${a}!`, result);
49
+ }
50
+
51
+ // Trigonometric functions
52
+ sin(a) { return this._record(`sin(${a})`, Math.sin(this._toRad(a))); }
53
+ cos(a) { return this._record(`cos(${a})`, Math.cos(this._toRad(a))); }
54
+ tan(a) {
55
+ const rad = this._toRad(a);
56
+ if (Math.abs(Math.cos(rad)) < 1e-15) throw new Error('tan: undefined (asymptote)');
57
+ return this._record(`tan(${a})`, Math.tan(rad));
58
+ }
59
+ asin(a) {
60
+ if (a < -1 || a > 1) throw new Error('asin: domain error [-1, 1]');
61
+ return this._record(`asin(${a})`, this._fromRad(Math.asin(a)));
62
+ }
63
+ acos(a) {
64
+ if (a < -1 || a > 1) throw new Error('acos: domain error [-1, 1]');
65
+ return this._record(`acos(${a})`, this._fromRad(Math.acos(a)));
66
+ }
67
+ atan(a) { return this._record(`atan(${a})`, this._fromRad(Math.atan(a))); }
68
+ atan2(y, x) { return this._record(`atan2(${y}, ${x})`, this._fromRad(Math.atan2(y, x))); }
69
+
70
+ // Hyperbolic functions
71
+ sinh(a) { return this._record(`sinh(${a})`, Math.sinh(this._toRad(a))); }
72
+ cosh(a) { return this._record(`cosh(${a})`, Math.cosh(this._toRad(a))); }
73
+ tanh(a) { return this._record(`tanh(${a})`, Math.tanh(this._toRad(a))); }
74
+ asinh(a) { return this._record(`asinh(${a})`, this._fromRad(Math.asinh(a))); }
75
+ acosh(a) {
76
+ if (a < 1) throw new Error('acosh: domain error [1, ∞)');
77
+ return this._record(`acosh(${a})`, this._fromRad(Math.acosh(a)));
78
+ }
79
+ atanh(a) {
80
+ if (a <= -1 || a >= 1) throw new Error('atanh: domain error (-1, 1)');
81
+ return this._record(`atanh(${a})`, this._fromRad(Math.atanh(a)));
82
+ }
83
+
84
+ // Logarithmic & exponential
85
+ log(a) { return this._record(`log(${a})`, Math.log10(a)); }
86
+ ln(a) { return this._record(`ln(${a})`, Math.log(a)); }
87
+ log2(a) { return this._record(`log2(${a})`, Math.log2(a)); }
88
+ exp(a) { return this._record(`exp(${a})`, Math.exp(a)); }
89
+ exp2(a) { return this._record(`2^${a}`, Math.pow(2, a)); }
90
+ exp10(a) { return this._record(`10^${a}`, Math.pow(10, a)); }
91
+
92
+ // Combinatorics
93
+ perm(n, r) {
94
+ if (n < 0 || r < 0 || r > n) throw new Error('P(n,r): invalid arguments');
95
+ let result = 1;
96
+ for (let i = 0; i < r; i++) result *= (n - i);
97
+ return this._record(`P(${n},${r})`, result);
98
+ }
99
+ comb(n, r) {
100
+ if (n < 0 || r < 0 || r > n) throw new Error('C(n,r): invalid arguments');
101
+ let result = 1;
102
+ for (let i = 0; i < r; i++) result = result * (n - i) / (i + 1);
103
+ return this._record(`C(${n},${r})`, Math.round(result));
104
+ }
105
+
106
+ // Constants
107
+ pi() { return Math.PI; }
108
+ e() { return Math.E; }
109
+ phi() { return (1 + Math.sqrt(5)) / 2; }
110
+
111
+ // Memory
112
+ memStore(v) { this.memory = v; return this.memory; }
113
+ memRecall() { return this.memory; }
114
+ memAdd(v) { this.memory += v; return this.memory; }
115
+ memSub(v) { this.memory -= v; return this.memory; }
116
+ memClear() { this.memory = 0; return 0; }
117
+
118
+ // Rounding
119
+ round(a, d = 0) { return this._record(`round(${a},${d})`, Number(a.toFixed(d))); }
120
+ floor(a) { return this._record(`floor(${a})`, Math.floor(a)); }
121
+ ceil(a) { return this._record(`ceil(${a})`, Math.ceil(a)); }
122
+ truncate(a) { return this._record(`trunc(${a})`, Math.trunc(a)); }
123
+
124
+ // Format result
125
+ format(value) {
126
+ if (typeof value !== 'number' || isNaN(value)) return 'NaN';
127
+ if (!isFinite(value)) return value > 0 ? 'Infinity' : '-Infinity';
128
+ if (Number.isInteger(value) && Math.abs(value) < 1e15) return value.toString();
129
+ if (Math.abs(value) < 1e-10 || Math.abs(value) >= 1e15) return value.toExponential(10);
130
+ const str = value.toPrecision(12);
131
+ return parseFloat(str).toString();
132
+ }
133
+ }
134
+
135
+ module.exports = SciCalculator;
package/lib/parser.js ADDED
@@ -0,0 +1,251 @@
1
+ 'use strict';
2
+
3
+ const SciCalculator = require('./calculator');
4
+
5
+ class Parser {
6
+ constructor() {
7
+ this.calc = new SciCalculator();
8
+ this.vars = { pi: Math.PI, e: Math.E, phi: (1 + Math.sqrt(5)) / 2 };
9
+ }
10
+
11
+ setAngleUnit(unit) {
12
+ if (unit !== 'deg' && unit !== 'rad') throw new Error('Angle unit must be "deg" or "rad"');
13
+ this.calc.angleUnit = unit;
14
+ }
15
+
16
+ parse(input) {
17
+ const cleaned = input
18
+ .trim()
19
+ .replace(/×/g, '*')
20
+ .replace(/÷/g, '/')
21
+ .replace(/\^/g, '**')
22
+ .replace(/\s+/g, ' ')
23
+ .replace(/√\(/g, 'sqrt(')
24
+ .replace(/∛\(/g, 'cbrt(');
25
+
26
+ const result = this._eval(cleaned, { pos: 0 });
27
+ return result;
28
+ }
29
+
30
+ _eval(input, state) {
31
+ let result = this._parseAddSub(input, state);
32
+ return result;
33
+ }
34
+
35
+ _parseAddSub(input, state) {
36
+ let left = this._parseMulDiv(input, state);
37
+ while (state.pos < input.length) {
38
+ this._skipSpaces(input, state);
39
+ const ch = input[state.pos];
40
+ if (ch === '+') {
41
+ state.pos++;
42
+ const right = this._parseMulDiv(input, state);
43
+ left = left + right;
44
+ } else if (ch === '-' && (state.pos === 0 || '+-*/(^,'.includes(input[state.pos - 1]))) {
45
+ state.pos++;
46
+ const right = this._parseMulDiv(input, state);
47
+ left = left - right;
48
+ } else {
49
+ break;
50
+ }
51
+ }
52
+ return left;
53
+ }
54
+
55
+ _parseMulDiv(input, state) {
56
+ let left = this._parsePower(input, state);
57
+ while (state.pos < input.length) {
58
+ this._skipSpaces(input, state);
59
+ const ch = input[state.pos];
60
+ if (ch === '*') {
61
+ state.pos++;
62
+ if (input[state.pos] === '*') { state.pos++; }
63
+ const right = this._parsePower(input, state);
64
+ left = left * right;
65
+ } else if (ch === '/') {
66
+ state.pos++;
67
+ const right = this._parsePower(input, state);
68
+ left = left / right;
69
+ } else if (ch === '%') {
70
+ state.pos++;
71
+ const right = this._parsePower(input, state);
72
+ left = left % right;
73
+ } else {
74
+ break;
75
+ }
76
+ }
77
+ return left;
78
+ }
79
+
80
+ _parsePower(input, state) {
81
+ let base = this._parseUnary(input, state);
82
+ this._skipSpaces(input, state);
83
+ if (state.pos < input.length && input[state.pos] === '*') {
84
+ if (input[state.pos + 1] === '*') {
85
+ state.pos += 2;
86
+ const exp = this._parseUnary(input, state);
87
+ return Math.pow(base, exp);
88
+ }
89
+ }
90
+ return base;
91
+ }
92
+
93
+ _parseUnary(input, state) {
94
+ this._skipSpaces(input, state);
95
+ if (state.pos < input.length && input[state.pos] === '-') {
96
+ state.pos++;
97
+ return -this._parseAtom(input, state);
98
+ }
99
+ if (state.pos < input.length && input[state.pos] === '+') {
100
+ state.pos++;
101
+ }
102
+ return this._parseAtom(input, state);
103
+ }
104
+
105
+ _parseAtom(input, state) {
106
+ this._skipSpaces(input, state);
107
+
108
+ if (state.pos >= input.length) throw new Error('Unexpected end of expression');
109
+
110
+ // Parentheses
111
+ if (input[state.pos] === '(') {
112
+ state.pos++;
113
+ const result = this._eval(input, state);
114
+ this._skipSpaces(input, state);
115
+ if (state.pos < input.length && input[state.pos] === ')') state.pos++;
116
+ return result;
117
+ }
118
+
119
+ // Number
120
+ if (this._isDigit(input[state.pos]) || input[state.pos] === '.') {
121
+ return this._parseNumber(input, state);
122
+ }
123
+
124
+ // Function or variable
125
+ if (this._isAlpha(input[state.pos])) {
126
+ return this._parseFunctionOrVar(input, state);
127
+ }
128
+
129
+ throw new Error(`Unexpected character: '${input[state.pos]}' at position ${state.pos}`);
130
+ }
131
+
132
+ _parseNumber(input, state) {
133
+ const start = state.pos;
134
+ while (state.pos < input.length && (this._isDigit(input[state.pos]) || input[state.pos] === '.')) {
135
+ state.pos++;
136
+ }
137
+ // Scientific notation: 1e10, 1E-5
138
+ if (state.pos < input.length && (input[state.pos] === 'e' || input[state.pos] === 'E')) {
139
+ if (state.pos + 1 < input.length && (input[state.pos + 1] === '-' || input[state.pos + 1] === '+')) {
140
+ state.pos += 2;
141
+ while (state.pos < input.length && this._isDigit(input[state.pos])) state.pos++;
142
+ } else if (state.pos + 1 < input.length && this._isDigit(input[state.pos + 1])) {
143
+ state.pos += 2;
144
+ while (state.pos < input.length && this._isDigit(input[state.pos])) state.pos++;
145
+ }
146
+ }
147
+ const numStr = input.slice(start, state.pos);
148
+ const num = parseFloat(numStr);
149
+ if (isNaN(num)) throw new Error(`Invalid number: ${numStr}`);
150
+ return num;
151
+ }
152
+
153
+ _parseFunctionOrVar(input, state) {
154
+ const start = state.pos;
155
+ while (state.pos < input.length && this._isAlphaNum(input[state.pos])) state.pos++;
156
+ const name = input.slice(start, state.pos);
157
+
158
+ this._skipSpaces(input, state);
159
+
160
+ // Function call
161
+ if (state.pos < input.length && input[state.pos] === '(') {
162
+ state.pos++;
163
+ const args = this._parseArgs(input, state);
164
+ this._skipSpaces(input, state);
165
+ if (state.pos < input.length && input[state.pos] === ')') state.pos++;
166
+ return this._callFunction(name, args);
167
+ }
168
+
169
+ // Variable
170
+ if (name in this.vars) return this.vars[name];
171
+ throw new Error(`Unknown variable: ${name}`);
172
+ }
173
+
174
+ _parseArgs(input, state) {
175
+ const args = [];
176
+ this._skipSpaces(input, state);
177
+ if (state.pos < input.length && input[state.pos] === ')') return args;
178
+
179
+ args.push(this._eval(input, state));
180
+ while (state.pos < input.length && input[state.pos] === ',') {
181
+ state.pos++;
182
+ args.push(this._eval(input, state));
183
+ }
184
+ return args;
185
+ }
186
+
187
+ _callFunction(name, args) {
188
+ const c = this.calc;
189
+ switch (name.toLowerCase()) {
190
+ // Basic
191
+ case 'sqrt': return c.sqrt(args[0]);
192
+ case 'cbrt': return c.cbrt(args[0]);
193
+ case 'abs': return c.abs(args[0]);
194
+ case 'fact': case 'factorial': return c.factorial(args[0]);
195
+ case 'mod': return c.mod(args[0], args[1]);
196
+
197
+ // Trig
198
+ case 'sin': return c.sin(args[0]);
199
+ case 'cos': return c.cos(args[0]);
200
+ case 'tan': return c.tan(args[0]);
201
+ case 'asin': return c.asin(args[0]);
202
+ case 'acos': return c.acos(args[0]);
203
+ case 'atan': return c.atan(args[0]);
204
+ case 'atan2': return c.atan2(args[0], args[1]);
205
+
206
+ // Hyperbolic
207
+ case 'sinh': return c.sinh(args[0]);
208
+ case 'cosh': return c.cosh(args[0]);
209
+ case 'tanh': return c.tanh(args[0]);
210
+ case 'asinh': return c.asinh(args[0]);
211
+ case 'acosh': return c.acosh(args[0]);
212
+ case 'atanh': return c.atanh(args[0]);
213
+
214
+ // Log
215
+ case 'log': case 'log10': return c.log(args[0]);
216
+ case 'ln': return c.ln(args[0]);
217
+ case 'log2': return c.log2(args[0]);
218
+ case 'exp': return c.exp(args[0]);
219
+ case 'exp2': return c.exp2(args[0]);
220
+ case 'exp10': return c.exp10(args[0]);
221
+
222
+ // Combinatorics
223
+ case 'perm': case 'permutation': case 'p': return c.perm(args[0], args[1]);
224
+ case 'comb': case 'combination': case 'choose': case 'c': return c.comb(args[0], args[1]);
225
+
226
+ // Rounding
227
+ case 'round': return c.round(args[0], args[1] || 0);
228
+ case 'floor': return c.floor(args[0]);
229
+ case 'ceil': return c.ceil(args[0]);
230
+ case 'trunc': return c.truncate(args[0]);
231
+
232
+ // Misc
233
+ case 'pow': return c.pow(args[0], args[1]);
234
+ case 'max': return Math.max(...args);
235
+ case 'min': return Math.min(...args);
236
+
237
+ default:
238
+ throw new Error(`Unknown function: ${name}`);
239
+ }
240
+ }
241
+
242
+ _skipSpaces(input, state) {
243
+ while (state.pos < input.length && input[state.pos] === ' ') state.pos++;
244
+ }
245
+
246
+ _isDigit(ch) { return ch >= '0' && ch <= '9'; }
247
+ _isAlpha(ch) { return (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || ch === '_'; }
248
+ _isAlphaNum(ch) { return this._isAlpha(ch) || this._isDigit(ch); }
249
+ }
250
+
251
+ module.exports = Parser;
package/package.json ADDED
@@ -0,0 +1,28 @@
1
+ {
2
+ "name": "sci-calc-pro",
3
+ "version": "1.0.0",
4
+ "description": "Professional scientific calculator CLI for Node.js",
5
+ "main": "lib/calculator.js",
6
+ "bin": {
7
+ "sci-calc": "bin/cli.js",
8
+ "sci-calc-pro": "bin/cli.js"
9
+ },
10
+ "scripts": {
11
+ "start": "node bin/cli.js --repl",
12
+ "calc": "node bin/cli.js"
13
+ },
14
+ "keywords": [
15
+ "calculator",
16
+ "scientific",
17
+ "cli",
18
+ "math",
19
+ "trigonometry",
20
+ "logarithm",
21
+ "algebra"
22
+ ],
23
+ "author": "IIEE",
24
+ "license": "MIT",
25
+ "engines": {
26
+ "node": ">=14.0.0"
27
+ }
28
+ }