exprify 1.0.0 → 1.0.1

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.
@@ -1,70 +1,140 @@
1
- import { tokenize } from "../parser/tokenizer.js";
2
- import { infixToPostfix } from "../parser/infixToPostfix.js";
3
- import { evaluatePostfix } from "../parser/evaluator.js";
4
-
5
- import { mathOperations } from "../math/operations.js";
6
-
7
- import { internalFunctions } from "../functions/internalFunctions.js";
8
- import { externalFunctions } from "../functions/externalFunctions.js";
9
-
10
- import { variablesDB } from "../variables/variables.js";
11
-
12
- import { stringToJS } from "../utils/typeConverter.js";
13
-
14
- class ViewPoint {
15
-
16
- constructor() {
17
- // Shared state
18
- this.variablesDB = variablesDB;
19
- this.func_DB_intrnl = internalFunctions;
20
- this.func_DB_extrnl = externalFunctions;
21
-
22
- this.operator_precedence = mathOperations.operator_precedence;
23
- }
24
-
25
- setVariable(name, value) {
26
- this.variablesDB[name] = value;
27
- }
28
-
29
- addFunction(name, fn) {
30
- this.func_DB_extrnl[name] = fn;
31
- }
32
-
33
- stringToJS(str) {
34
- return stringToJS.call(this, str, this.variablesDB);
35
- }
36
-
37
- evaluate(expr) {
38
-
39
- if (typeof expr !== "string") {
40
- throw new Error("Expression must be a string");
41
- }
42
-
43
- const context = {
44
- variablesDB: this.variablesDB,
45
- func_DB_intrnl: this.func_DB_intrnl,
46
- func_DB_extrnl: this.func_DB_extrnl,
47
- stringToJS: this.stringToJS.bind(this),
48
- evaluate: this.evaluate.bind(this)
49
- };
50
-
51
- // Step 1: Tokenize
52
- const tokens = tokenize(expr, context);
53
-
54
- // Step 2: Infix → Postfix
55
- const postfix = infixToPostfix(
56
- tokens,
57
- this.operator_precedence
58
- );
59
-
60
- // Step 3: Evaluate Postfix
61
- const result = evaluatePostfix(
62
- postfix,
63
- mathOperations
64
- );
65
-
66
- return result;
67
- }
68
- }
69
-
70
- export default ViewPoint;
1
+ import { tokenize } from "../parser/tokenizer.js";
2
+ // import { infixToPostfix } from "../parser/infixToPostfix.js";
3
+ import { evaluateAST } from "../parser/evaluator.js";
4
+ import { createContext } from "./context.js";
5
+ import { mathOperations } from "../math/operations.js";
6
+
7
+ import { createUnitsStore } from "../utils/store.js";
8
+ import { globalUnits } from "../utils/globalUnits.js";
9
+
10
+ import { createVarStore } from "../variables/store.js";
11
+ import { createFunctionRegistry } from "../function/registry.js";
12
+ import { internalFunctions } from "../function/internal.js";
13
+
14
+ import { buildAST } from "../parser/astBuild.js";
15
+
16
+
17
+ //
18
+
19
+ const isComplex = (value) =>
20
+ value && typeof value === "object" && "re" in value && "im" in value;
21
+
22
+ const isUnitValue = (value) =>
23
+ value && typeof value === "object" && "value" in value && "unit" in value;
24
+
25
+ const isMatrix = (value) =>
26
+ Array.isArray(value) && value.length > 0 && value.every(Array.isArray);
27
+
28
+ const formatComplex = (value) => {
29
+ if (!isComplex(value)) return value;
30
+
31
+ const real = value.re;
32
+ const imaginary = Math.abs(value.im);
33
+ const sign = value.im < 0 ? "-" : "+";
34
+
35
+ if (real === 0) {
36
+ if (value.im === 1) return "i";
37
+ if (value.im === -1) return "-i";
38
+ return `${value.im}i`;
39
+ }
40
+
41
+ const imagPart = imaginary === 1 ? "i" : `${imaginary}i`;
42
+ return `${real} ${sign} ${imagPart}`;
43
+ };
44
+
45
+ const formatResult = (value) => {
46
+ if (isComplex(value)) {
47
+ return formatComplex(value);
48
+ }
49
+
50
+ if (isUnitValue(value)) {
51
+ return `${value.value} ${value.unit}`;
52
+ }
53
+
54
+ if (isMatrix(value)) {
55
+ return value.map((row) => row.join("\t")).join("\n");
56
+ }
57
+
58
+ if (Array.isArray(value)) {
59
+ return value.join("\n");
60
+ }
61
+
62
+ return value;
63
+ };
64
+
65
+ class exprify {
66
+ constructor() {
67
+ // Shared state
68
+ this.math = mathOperations;
69
+ this.units = createUnitsStore(globalUnits);
70
+ this.functions = createFunctionRegistry(internalFunctions);
71
+ this.variables = createVarStore();
72
+ this._cache = new Map();
73
+ }
74
+
75
+ setVariable(name, value) {
76
+ this.variables.set(name, value);
77
+ }
78
+
79
+ getVariable(name) {
80
+ return this.variables.get(name);
81
+ }
82
+
83
+ addFunction(name, fn) {
84
+ this.functions.register(name, fn);
85
+ }
86
+
87
+ _createContext() {
88
+ return createContext({
89
+ functions: this.functions,
90
+ variables: this.variables,
91
+ units: this.units,
92
+ evaluate: this.evaluate.bind(this)
93
+ });
94
+ }
95
+
96
+ tokenize(expr) {
97
+ if (typeof expr !== "string") {
98
+ throw new Error("Expression must be a string");
99
+ }
100
+ return tokenize(expr, this._createContext());
101
+ }
102
+
103
+ parse(expr) {
104
+ const tokens = this.tokenize(expr);
105
+ const ast = buildAST(tokens);
106
+ return { tokens, ast };
107
+ }
108
+
109
+ evaluate(expr) {
110
+ const { ast } = this.parse(expr);
111
+ return formatResult(evaluateAST(
112
+ ast,
113
+ this._createContext()
114
+ ));
115
+ }
116
+
117
+ compile(expr) {
118
+ if (this._cache.has(expr)) {
119
+ return this._cache.get(expr);
120
+ }
121
+
122
+ const { ast } = this.parse(expr);
123
+
124
+ const compiledFn = (scope = {}) => {
125
+ const baseContext = this._createContext();
126
+ const scopedContext = baseContext.withScope(scope);
127
+ return formatResult(evaluateAST(ast, scopedContext));
128
+ };
129
+
130
+ this._cache.set(expr, compiledFn);
131
+ return compiledFn;
132
+ }
133
+
134
+ clearCache() {
135
+ this._cache.clear();
136
+ }
137
+
138
+ }
139
+
140
+ export default exprify;
@@ -0,0 +1,30 @@
1
+ export function createContext({ variables, functions, units, evaluate}) {
2
+ if (!variables) throw new Error("Variable store missing");
3
+ if (!functions) throw new Error("Function registry missing");
4
+ if (!units) throw new Error("Units list missing");
5
+ if (!evaluate) throw new Error("evaluate function missing");
6
+
7
+ return {
8
+ variables: variables,
9
+ functions: functions,
10
+ units: units,
11
+ evaluate,
12
+ withScope(scope = {}) {
13
+ const tempVars = {
14
+ ...variables.all?.(),
15
+ ...scope
16
+ };
17
+ return createContext({
18
+ functions: functions,
19
+ evaluate,
20
+ units,
21
+ variables: {
22
+ get: (k) => tempVars[k],
23
+ set: (k, v) => (tempVars[k] = v),
24
+ all: () => tempVars
25
+ }
26
+ });
27
+
28
+ }
29
+ };
30
+ }
@@ -0,0 +1,64 @@
1
+ export function createFunctionExecutor(fnRegistry, options = {}) {
2
+ if (!fnRegistry) {
3
+ throw new Error("Function registry is required");
4
+ }
5
+
6
+ const config = {
7
+ strict: options.strict ?? true
8
+ };
9
+
10
+ /* ================= EXECUTE ================= */
11
+
12
+ function execute(name, args = [], context) {
13
+ const fn = fnRegistry.get(name);
14
+
15
+ /* ----- NOT FOUND ----- */
16
+ if (!fn) {
17
+ if (config.strict) {
18
+ throw new Error(`Unknown function: ${name}`);
19
+ }
20
+ return undefined;
21
+ }
22
+
23
+ /* ----- VALIDATE ARGS ----- */
24
+ if (!Array.isArray(args)) {
25
+ throw new Error(`Arguments for "${name}" must be an array`);
26
+ }
27
+
28
+ /* ----- EXECUTE ----- */
29
+ try {
30
+ return fn(...args);
31
+ } catch (err) {
32
+ throw new Error(
33
+ `Error in function "${name}": ${err.message}`
34
+ );
35
+ }
36
+ }
37
+
38
+ /* ================= SAFE EXECUTE ================= */
39
+
40
+ function safeExecute(name, args = [], context) {
41
+ try {
42
+ return execute(name, args, context);
43
+ } catch (err) {
44
+ return {
45
+ error: true,
46
+ message: err.message
47
+ };
48
+ }
49
+ }
50
+
51
+ /* ================= EXISTS ================= */
52
+
53
+ function exists(name) {
54
+ return fnRegistry.has(name);
55
+ }
56
+
57
+ /* ================= API ================= */
58
+
59
+ return {
60
+ execute,
61
+ safeExecute,
62
+ exists
63
+ };
64
+ }
@@ -0,0 +1,270 @@
1
+ function validateSquareMatrix(matrix) {
2
+ if (!Array.isArray(matrix) || matrix.length === 0) {
3
+ throw new Error("det() expects a non-empty matrix");
4
+ }
5
+
6
+ if (!matrix.every(Array.isArray)) {
7
+ throw new Error("det() expects a 2D matrix");
8
+ }
9
+
10
+ const size = matrix.length;
11
+ if (!matrix.every((row) => row.length === size)) {
12
+ throw new Error("det() expects a square matrix");
13
+ }
14
+
15
+ for (const row of matrix) {
16
+ for (const value of row) {
17
+ if (typeof value !== "number" && typeof value !== "bigint") {
18
+ throw new Error("det() matrix values must be numeric");
19
+ }
20
+ }
21
+ }
22
+ }
23
+
24
+ function determinant(matrix) {
25
+ validateSquareMatrix(matrix);
26
+
27
+ if (matrix.length === 1) {
28
+ return matrix[0][0];
29
+ }
30
+
31
+ if (matrix.length === 2) {
32
+ return (matrix[0][0] * matrix[1][1]) - (matrix[0][1] * matrix[1][0]);
33
+ }
34
+
35
+ return matrix[0].reduce((sum, value, columnIndex) => {
36
+ const minor = matrix.slice(1).map((row) =>
37
+ row.filter((_, index) => index !== columnIndex)
38
+ );
39
+ const cofactor = columnIndex % 2 === 0 ? value : -value;
40
+ return sum + (cofactor * determinant(minor));
41
+ }, 0);
42
+ }
43
+
44
+ function splitTerms(expression) {
45
+ const normalized = expression.replace(/\s+/g, "");
46
+ if (!normalized) {
47
+ return [];
48
+ }
49
+
50
+ return normalized
51
+ .replace(/-/g, "+-")
52
+ .split("+")
53
+ .filter(Boolean);
54
+ }
55
+
56
+ function parsePolynomial(expression, variable) {
57
+ const terms = splitTerms(expression);
58
+ const coefficients = new Map();
59
+
60
+ for (const term of terms) {
61
+ if (term.includes(variable)) {
62
+ const [rawCoeff, rawPower] = term.split(variable);
63
+ let coefficient;
64
+
65
+ if (rawCoeff === "" || rawCoeff === "+") coefficient = 1;
66
+ else if (rawCoeff === "-") coefficient = -1;
67
+ else {
68
+ const cleaned = rawCoeff.endsWith("*") ? rawCoeff.slice(0, -1) : rawCoeff;
69
+ coefficient = Number(cleaned);
70
+ }
71
+
72
+ if (!Number.isFinite(coefficient)) {
73
+ throw new Error("Unsupported algebra term");
74
+ }
75
+
76
+ let power = 1;
77
+ if (rawPower) {
78
+ if (!rawPower.startsWith("^")) {
79
+ throw new Error("Unsupported algebra term");
80
+ }
81
+
82
+ power = Number(rawPower.slice(1));
83
+ }
84
+
85
+ if (!Number.isInteger(power) || power < 0) {
86
+ throw new Error("Only non-negative integer powers are supported");
87
+ }
88
+
89
+ coefficients.set(power, (coefficients.get(power) || 0) + coefficient);
90
+ } else {
91
+ const constant = Number(term);
92
+ if (!Number.isFinite(constant)) {
93
+ throw new Error("Unsupported algebra term");
94
+ }
95
+ coefficients.set(0, (coefficients.get(0) || 0) + constant);
96
+ }
97
+ }
98
+
99
+ return coefficients;
100
+ }
101
+
102
+ function formatPolynomial(coefficients, variable) {
103
+ const ordered = [...coefficients.entries()]
104
+ .filter(([, coefficient]) => coefficient !== 0)
105
+ .sort((a, b) => b[0] - a[0]);
106
+
107
+ if (!ordered.length) {
108
+ return "0";
109
+ }
110
+
111
+ return ordered.map(([power, coefficient], index) => {
112
+ const negative = coefficient < 0;
113
+ const absCoeff = Math.abs(coefficient);
114
+ let body;
115
+
116
+ if (power === 0) {
117
+ body = `${absCoeff}`;
118
+ } else if (power === 1) {
119
+ body = absCoeff === 1 ? variable : `${absCoeff} * ${variable}`;
120
+ } else {
121
+ body = absCoeff === 1
122
+ ? `${variable}^${power}`
123
+ : `${absCoeff} * ${variable}^${power}`;
124
+ }
125
+
126
+ if (index === 0) {
127
+ return negative ? `-${body}` : body;
128
+ }
129
+
130
+ return negative ? `- ${body}` : `+ ${body}`;
131
+ }).join(" ");
132
+ }
133
+
134
+ function simplifyExpression(expression) {
135
+ const compact = expression.replace(/\s+/g, "");
136
+ const variableMatch = compact.match(/[a-zA-Z]+/);
137
+ const variable = variableMatch?.[0] || "x";
138
+ const coefficients = parsePolynomial(expression, variable);
139
+ return formatPolynomial(coefficients, variable);
140
+ }
141
+
142
+ function derivativeExpression(expression, variable) {
143
+ const coefficients = parsePolynomial(expression, variable);
144
+ const derived = new Map();
145
+
146
+ for (const [power, coefficient] of coefficients.entries()) {
147
+ if (power === 0) continue;
148
+ derived.set(power - 1, (derived.get(power - 1) || 0) + (coefficient * power));
149
+ }
150
+
151
+ return formatPolynomial(derived, variable);
152
+ }
153
+
154
+ export const internalFunctions = {
155
+ max: (...args) => {
156
+ if (!args.length) throw new Error("max() requires arguments");
157
+ return Math.max(...args);
158
+ },
159
+
160
+ min: (...args) => {
161
+ if (!args.length) throw new Error("min() requires arguments");
162
+ return Math.min(...args);
163
+ },
164
+
165
+ abs: (x) => Math.abs(x),
166
+
167
+ round: (x) => Math.round(x),
168
+
169
+ floor: (x) => Math.floor(x),
170
+
171
+ ceil: (x) => Math.ceil(x),
172
+
173
+ sqrt: (x) => {
174
+ if (x < 0) throw new Error("sqrt() domain error");
175
+ return Math.sqrt(x);
176
+ },
177
+
178
+ pow: (a, b) => a ** b,
179
+ det: (matrix) => determinant(matrix),
180
+ simplify: (expression) => {
181
+ if (typeof expression !== "string") {
182
+ throw new Error("simplify() expects an expression string");
183
+ }
184
+ return simplifyExpression(expression);
185
+ },
186
+ derivative: (expression, variable = "x") => {
187
+ if (typeof expression !== "string" || typeof variable !== "string") {
188
+ throw new Error("derivative() expects expression and variable strings");
189
+ }
190
+ return derivativeExpression(expression, variable);
191
+ },
192
+
193
+ /* ================= TRIGONOMETRY ================= */
194
+
195
+ sin: (x) => Math.sin(x),
196
+ cos: (x) => Math.cos(x),
197
+ tan: (x) => Math.tan(x),
198
+
199
+ asin: (x) => Math.asin(x),
200
+ acos: (x) => Math.acos(x),
201
+ atan: (x) => Math.atan(x),
202
+
203
+ /* ================= LOG / EXP ================= */
204
+
205
+ log: (x) => {
206
+ if (x <= 0) throw new Error("log() domain error");
207
+ return Math.log(x);
208
+ },
209
+
210
+ log10: (x) => {
211
+ if (x <= 0) throw new Error("log10() domain error");
212
+ return Math.log10(x);
213
+ },
214
+
215
+ exp: (x) => Math.exp(x),
216
+
217
+ /* ================= RANDOM ================= */
218
+
219
+ random: () => Math.random(),
220
+
221
+ /* ================= BOOLEAN / LOGIC ================= */
222
+
223
+ and: (a, b) => Boolean(a && b),
224
+
225
+ or: (a, b) => Boolean(a || b),
226
+
227
+ not: (a) => !a,
228
+ "!": (a) => !a,
229
+
230
+ /* ================= COMPARISON ================= */
231
+
232
+ eq: (a, b) => a === b,
233
+
234
+ neq: (a, b) => a !== b,
235
+ "notEqual": (a, b) => a !== b,
236
+
237
+ gt: (a, b) => a > b,
238
+ "greaterThan": (a, b) => a > b,
239
+
240
+ lt: (a, b) => a < b,
241
+ "lessThan": (a, b) => a < b,
242
+
243
+ gte: (a, b) => a >= b,
244
+ "greaterThanOrEqual": (a, b) => a >= b,
245
+
246
+ lte: (a, b) => a <= b,
247
+ "lessThanOrEqual": (a, b) => a <= b,
248
+
249
+ /* ================= UTILITY ================= */
250
+
251
+ clamp: (x, min, max) => {
252
+ if (min > max) throw new Error("clamp(): min > max");
253
+ return Math.min(Math.max(x, min), max);
254
+ },
255
+
256
+ if: (condition, a, b) => (condition ? a : b),
257
+
258
+ /* ================= TYPE ================= */
259
+
260
+ typeof: (x) => typeof x,
261
+
262
+ /* ================= STRING ================= */
263
+
264
+ length: (x) => {
265
+ if (typeof x === "string" || Array.isArray(x)) {
266
+ return x.length;
267
+ }
268
+ throw new Error("length() expects string or array");
269
+ }
270
+ };
@@ -0,0 +1,68 @@
1
+ export function createFunctionRegistry(initial = {}) {
2
+ const store = Object.create(null);
3
+
4
+ for (const key in initial) {
5
+ if (typeof initial[key] === "function") {
6
+ store[key] = initial[key];
7
+ }
8
+ }
9
+
10
+ return {
11
+ getAllFunctionsName() {
12
+ return Object.keys(store);
13
+ },
14
+ // register new formula
15
+ register(name, fn) {
16
+ if (typeof name !== "string" || !name) {
17
+ throw new Error("Formula name must be a non-empty string");
18
+ }
19
+
20
+ if (typeof fn !== "function") {
21
+ throw new Error(`Formula "${name}" must be callable`);
22
+ }
23
+
24
+ store[name] = fn;
25
+ },
26
+
27
+ // get formula
28
+ get(name) {
29
+ return store[name];
30
+ },
31
+
32
+ // check existence
33
+ has(name) {
34
+ return Object.prototype.hasOwnProperty.call(store, name);
35
+ },
36
+
37
+ // remove formula
38
+ remove(name) {
39
+ delete store[name];
40
+ },
41
+
42
+ // list all
43
+ all() {
44
+ return { ...store };
45
+ },
46
+
47
+ // clear registry
48
+ clear() {
49
+ for (const key in store) {
50
+ delete store[key];
51
+ }
52
+ },
53
+
54
+ // extend multiple
55
+ extend(formulas = {}) {
56
+ for (const name in formulas) {
57
+ if (typeof formulas[name] === "function") {
58
+ store[name] = formulas[name];
59
+ }
60
+ }
61
+ },
62
+
63
+ // clone (for scoped instances)
64
+ clone() {
65
+ return createFormulaRegistry(store);
66
+ }
67
+ };
68
+ }
package/src/index.js CHANGED
@@ -1,38 +1,2 @@
1
- /**
2
- * Exprify — Math Expression Parser & Evaluator
3
- *
4
- * A lightweight JavaScript library for parsing and evaluating mathematical expressions
5
- * with runtime data-type checking.
6
- *
7
- * Author: Nirmal Paul (N Paul)
8
- * GitHub: https://github.com/nirmalpaul383
9
- *
10
- * License: GNU General Public License v3.0 (GPLv3)
11
- *
12
- * Note:
13
- * This project is licensed under GPLv3. While not legally required,
14
- * attribution is highly appreciated. If you use, modify, or distribute
15
- * this project, please give credit to the original author.
16
- *
17
- * This library has been carefully designed with clean, structured, and
18
- * maintainable code. Acknowledgment of the original work is encouraged.
19
- *
20
- * Resources:
21
- * GitHub Repository: https://github.com/nirmalpaul383/ViewPoint
22
- * YouTube Channel: https://www.youtube.com/channel/UCY6JY8bTlR7hZEvhy6Pldxg/
23
- * Facebook Page: https://www.facebook.com/a.New.Way.Technical/
24
- */
25
-
26
- import Exprify from "./core/Exprify.js";
27
- import { mathOperations } from "./math/operations.js";
28
- import { internalFunctions } from "./functions/internalFunctions.js";
29
- import { externalFunctions } from "./functions/externalFunctions.js";
30
- import { variablesDB } from "./variables/variables.js";
31
-
32
- export {
33
- Exprify,
34
- mathOperations,
35
- internalFunctions,
36
- externalFunctions,
37
- variablesDB
38
- };
1
+ import Exprify from "./core/Exprify.js";
2
+ export default Exprify;