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.
- package/.gitattributes +2 -0
- package/.github/workflows/ci.yml +40 -0
- package/.github/workflows/npm-publish.yml +38 -0
- package/.github/workflows/security-audit.yml +34 -0
- package/CHANGELOG.md +11 -0
- package/LICENSE +673 -673
- package/README.md +203 -135
- package/dist/exprify.cjs.js +2320 -503
- package/dist/exprify.cjs.js.map +1 -1
- package/dist/exprify.esm.js +2320 -497
- package/dist/exprify.esm.js.map +1 -1
- package/dist/exprify.js +2340 -523
- package/dist/exprify.js.map +1 -1
- package/dist/exprify.min.js +2 -2
- package/dist/exprify.min.js.map +1 -1
- package/doc/tokenType.txt +48 -0
- package/package.json +7 -3
- package/rollup.config.js +80 -0
- package/src/assets/capture.jpg +0 -0
- package/src/core/Exprify.js +140 -70
- package/src/core/context.js +30 -0
- package/src/function/executor.js +64 -0
- package/src/function/internal.js +270 -0
- package/src/function/registry.js +68 -0
- package/src/index.js +2 -38
- package/src/math/operations.js +37 -47
- package/src/parser/astBuild.js +508 -0
- package/src/parser/evaluator.js +430 -57
- package/src/parser/tokenizer.js +399 -145
- package/src/utils/globalUnits.js +217 -0
- package/src/utils/store.js +178 -0
- package/src/variables/store.js +75 -0
- package/test/browser.html +23 -0
- package/test/exprify.test.js +140 -0
- package/src/functions/externalFunctions.js +0 -19
- package/src/functions/internalFunctions.js +0 -53
- package/src/parser/infixToPostfix.js +0 -78
- package/src/utils/typeConverter.js +0 -63
- package/src/variables/variables.js +0 -28
package/src/core/Exprify.js
CHANGED
|
@@ -1,70 +1,140 @@
|
|
|
1
|
-
import { tokenize } from "../parser/tokenizer.js";
|
|
2
|
-
import { infixToPostfix } from "../parser/infixToPostfix.js";
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
import { mathOperations } from "../math/operations.js";
|
|
6
|
-
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
|
|
10
|
-
import {
|
|
11
|
-
|
|
12
|
-
import {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
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
|
-
|
|
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;
|