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 +49 -0
- package/README.md +113 -160
- package/SECURITY.md +18 -0
- package/bin/cli.mjs +234 -0
- package/dist/exprify.cjs.cjs +5341 -0
- package/dist/exprify.cjs.cjs.map +1 -0
- package/dist/exprify.esm.js +3558 -1220
- package/dist/exprify.esm.js.map +1 -1
- package/dist/exprify.js +3560 -1222
- package/dist/exprify.js.map +1 -1
- package/dist/exprify.min.js +2 -2
- package/dist/exprify.min.js.map +1 -1
- package/package.json +55 -19
- package/src/core/context.js +35 -27
- package/src/core/exprify.js +880 -0
- package/src/function/executor.js +29 -20
- package/src/function/internal.js +1150 -153
- package/src/function/registry.js +23 -16
- package/src/index.js +1 -1
- package/src/math/bignumber.js +31 -0
- package/src/math/fraction.js +112 -0
- package/src/math/operations.js +38 -24
- package/src/parser/astBuild.js +276 -214
- package/src/parser/evaluator.js +431 -171
- package/src/parser/tokenizer.js +179 -146
- package/src/utils/decimal.js +264 -0
- package/src/utils/globalUnits.js +43 -35
- package/src/utils/matrix.js +14 -14
- package/src/utils/store.js +69 -47
- package/src/variables/store.js +18 -15
- package/dist/exprify.cjs.js +0 -3003
- package/dist/exprify.cjs.js.map +0 -1
- package/src/assets/capture.jpg +0 -0
- package/src/core/Exprify.js +0 -369
package/src/function/registry.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
export function createFunctionRegistry(initial = {}) {
|
|
2
|
+
// Object.create(null) avoids prototype pollution (no inherited properties)
|
|
2
3
|
const store = Object.create(null);
|
|
3
4
|
|
|
4
5
|
for (const key in initial) {
|
|
5
|
-
if (typeof initial[key] ===
|
|
6
|
+
if (typeof initial[key] === 'function') {
|
|
6
7
|
store[key] = initial[key];
|
|
7
8
|
}
|
|
8
9
|
}
|
|
@@ -11,58 +12,64 @@ export function createFunctionRegistry(initial = {}) {
|
|
|
11
12
|
getAllFunctionsName() {
|
|
12
13
|
return Object.keys(store);
|
|
13
14
|
},
|
|
14
|
-
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* @param {string} name
|
|
18
|
+
* @param {any} fn
|
|
19
|
+
*/
|
|
15
20
|
register(name, fn) {
|
|
16
|
-
if (typeof name !==
|
|
17
|
-
throw new Error(
|
|
21
|
+
if (typeof name !== 'string' || !name) {
|
|
22
|
+
throw new Error('Formula name must be a non-empty string');
|
|
18
23
|
}
|
|
19
24
|
|
|
20
|
-
if (typeof fn !==
|
|
25
|
+
if (typeof fn !== 'function') {
|
|
21
26
|
throw new Error(`Formula "${name}" must be callable`);
|
|
22
27
|
}
|
|
23
28
|
|
|
24
29
|
store[name] = fn;
|
|
25
30
|
},
|
|
26
31
|
|
|
27
|
-
|
|
32
|
+
/**
|
|
33
|
+
* @param {string} name
|
|
34
|
+
*/
|
|
28
35
|
get(name) {
|
|
29
36
|
return store[name];
|
|
30
37
|
},
|
|
31
38
|
|
|
32
|
-
|
|
39
|
+
/**
|
|
40
|
+
* @param {any} name
|
|
41
|
+
*/
|
|
33
42
|
has(name) {
|
|
34
43
|
return Object.prototype.hasOwnProperty.call(store, name);
|
|
35
44
|
},
|
|
36
45
|
|
|
37
|
-
|
|
46
|
+
/**
|
|
47
|
+
* @param {string | number} name
|
|
48
|
+
*/
|
|
38
49
|
remove(name) {
|
|
39
50
|
delete store[name];
|
|
40
51
|
},
|
|
41
52
|
|
|
42
|
-
// list all
|
|
43
53
|
all() {
|
|
44
54
|
return { ...store };
|
|
45
55
|
},
|
|
46
56
|
|
|
47
|
-
// clear registry
|
|
48
57
|
clear() {
|
|
49
58
|
for (const key in store) {
|
|
50
59
|
delete store[key];
|
|
51
60
|
}
|
|
52
61
|
},
|
|
53
62
|
|
|
54
|
-
// extend multiple
|
|
55
63
|
extend(formulas = {}) {
|
|
56
64
|
for (const name in formulas) {
|
|
57
|
-
if (typeof formulas[name] ===
|
|
65
|
+
if (typeof formulas[name] === 'function') {
|
|
58
66
|
store[name] = formulas[name];
|
|
59
67
|
}
|
|
60
68
|
}
|
|
61
69
|
},
|
|
62
70
|
|
|
63
|
-
// clone (for scoped instances)
|
|
64
71
|
clone() {
|
|
65
|
-
return
|
|
66
|
-
}
|
|
72
|
+
return createFunctionRegistry(store);
|
|
73
|
+
},
|
|
67
74
|
};
|
|
68
|
-
}
|
|
75
|
+
}
|
package/src/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import Exprify from
|
|
1
|
+
import Exprify from './core/exprify.js';
|
|
2
2
|
export default Exprify;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { ExprDecimal } from '../utils/decimal.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @param {any} value
|
|
5
|
+
*/
|
|
6
|
+
export function bigNumber(value) {
|
|
7
|
+
if (ExprDecimal.isDecimal(value)) {
|
|
8
|
+
return value;
|
|
9
|
+
}
|
|
10
|
+
if (typeof value === 'number' || typeof value === 'string' || typeof value === 'bigint') {
|
|
11
|
+
return new ExprDecimal(value);
|
|
12
|
+
}
|
|
13
|
+
throw new Error('bignumber() expects a number, string, or bigint');
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* @param {unknown} v
|
|
18
|
+
*/
|
|
19
|
+
export function isBigNumber(v) {
|
|
20
|
+
return ExprDecimal.isDecimal(v);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* @param {ExprDecimal} v
|
|
25
|
+
*/
|
|
26
|
+
export function formatBigNumber(v) {
|
|
27
|
+
if (!ExprDecimal.isDecimal(v)) {
|
|
28
|
+
return String(v);
|
|
29
|
+
}
|
|
30
|
+
return v.toString();
|
|
31
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
const gcd = (/** @type {number} */ a, /** @type {number} */ b) => {
|
|
2
|
+
a = Math.abs(a);
|
|
3
|
+
b = Math.abs(b);
|
|
4
|
+
while (b) {
|
|
5
|
+
[a, b] = [b, a % b];
|
|
6
|
+
}
|
|
7
|
+
return a;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* @param {any} n
|
|
12
|
+
*/
|
|
13
|
+
export function fraction(n, d = 1) {
|
|
14
|
+
if (typeof n !== 'number' || typeof d !== 'number') {
|
|
15
|
+
throw new Error('Fraction requires numeric arguments');
|
|
16
|
+
}
|
|
17
|
+
if (!Number.isInteger(n) || !Number.isInteger(d)) {
|
|
18
|
+
throw new Error('Fraction requires integer arguments');
|
|
19
|
+
}
|
|
20
|
+
if (d === 0) {
|
|
21
|
+
throw new Error('Fraction denominator cannot be zero');
|
|
22
|
+
}
|
|
23
|
+
if (d < 0) {
|
|
24
|
+
n = -n;
|
|
25
|
+
d = -d;
|
|
26
|
+
}
|
|
27
|
+
const g = gcd(n, d);
|
|
28
|
+
return { n: n / g, d: d / g };
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* @param {any} v
|
|
33
|
+
*/
|
|
34
|
+
export function isFraction(v) {
|
|
35
|
+
return v && typeof v === 'object' && 'n' in v && 'd' in v && !('re' in v) && !('unit' in v);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* @param {{ n: number; d: number; }} a
|
|
40
|
+
* @param {{ d: number; n: number; }} b
|
|
41
|
+
*/
|
|
42
|
+
export function addFrac(a, b) {
|
|
43
|
+
return fraction(a.n * b.d + b.n * a.d, a.d * b.d);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* @param {{ n: number; d: number; }} a
|
|
48
|
+
* @param {{ d: number; n: number; }} b
|
|
49
|
+
*/
|
|
50
|
+
export function subFrac(a, b) {
|
|
51
|
+
return fraction(a.n * b.d - b.n * a.d, a.d * b.d);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* @param {{ n: number; d: number; }} a
|
|
56
|
+
* @param {{ n: number; d: number; }} b
|
|
57
|
+
*/
|
|
58
|
+
export function mulFrac(a, b) {
|
|
59
|
+
return fraction(a.n * b.n, a.d * b.d);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* @param {{ n: number; d: number; }} a
|
|
64
|
+
* @param {{ d: number; n: number; }} b
|
|
65
|
+
*/
|
|
66
|
+
export function divFrac(a, b) {
|
|
67
|
+
return fraction(a.n * b.d, a.d * b.n);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* @param {{ n: number; d: number; }} a
|
|
72
|
+
* @param {any} exp
|
|
73
|
+
*/
|
|
74
|
+
export function powFrac(a, exp) {
|
|
75
|
+
if (!Number.isInteger(exp) || exp < 0) {
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
return fraction(a.n ** exp, a.d ** exp);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* @param {{ n: any; }} v
|
|
83
|
+
*/
|
|
84
|
+
export function numer(v) {
|
|
85
|
+
if (!isFraction(v)) {
|
|
86
|
+
throw new Error('numer() expects a fraction');
|
|
87
|
+
}
|
|
88
|
+
return v.n;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* @param {{ d: any; }} v
|
|
93
|
+
*/
|
|
94
|
+
export function denom(v) {
|
|
95
|
+
if (!isFraction(v)) {
|
|
96
|
+
throw new Error('denom() expects a fraction');
|
|
97
|
+
}
|
|
98
|
+
return v.d;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* @param {{ d: number; n: any; }} v
|
|
103
|
+
*/
|
|
104
|
+
export function formatFraction(v) {
|
|
105
|
+
if (!isFraction(v)) {
|
|
106
|
+
return String(v);
|
|
107
|
+
}
|
|
108
|
+
if (v.d === 1) {
|
|
109
|
+
return String(v.n);
|
|
110
|
+
}
|
|
111
|
+
return `${v.n}/${v.d}`;
|
|
112
|
+
}
|
package/src/math/operations.js
CHANGED
|
@@ -1,38 +1,52 @@
|
|
|
1
|
-
const isValidNumberPair = (a, b) =>
|
|
2
|
-
|
|
3
|
-
(typeof a === 'number' || typeof a === 'bigint');
|
|
1
|
+
const isValidNumberPair = (/** @type {any} */ a, /** @type {any} */ b) =>
|
|
2
|
+
typeof a === typeof b && (typeof a === 'number' || typeof a === 'bigint');
|
|
4
3
|
|
|
5
4
|
export const mathOperations = Object.freeze({
|
|
6
|
-
power: function(a, b) {
|
|
7
|
-
if (isValidNumberPair(a, b))
|
|
8
|
-
|
|
5
|
+
power: function (/** @type {number} */ a, /** @type {number} */ b) {
|
|
6
|
+
if (isValidNumberPair(a, b)) {
|
|
7
|
+
return a ** b;
|
|
8
|
+
}
|
|
9
|
+
throw new Error('Invalid types for ^');
|
|
9
10
|
},
|
|
10
11
|
|
|
11
|
-
multiply: function(a, b) {
|
|
12
|
-
if (isValidNumberPair(a, b))
|
|
13
|
-
|
|
12
|
+
multiply: function (/** @type {number} */ a, /** @type {number} */ b) {
|
|
13
|
+
if (isValidNumberPair(a, b)) {
|
|
14
|
+
return a * b;
|
|
15
|
+
}
|
|
16
|
+
throw new Error('Invalid types for *');
|
|
14
17
|
},
|
|
15
18
|
|
|
16
|
-
divide: function(a, b) {
|
|
19
|
+
divide: function (/** @type {number} */ a, /** @type {number} */ b) {
|
|
17
20
|
if (isValidNumberPair(a, b)) {
|
|
18
|
-
if (b === 0)
|
|
21
|
+
if (b === 0) {
|
|
22
|
+
throw new Error('Division by zero');
|
|
23
|
+
}
|
|
19
24
|
return a / b;
|
|
20
25
|
}
|
|
21
|
-
throw new Error(
|
|
26
|
+
throw new Error('Invalid types for /');
|
|
22
27
|
},
|
|
23
28
|
|
|
24
|
-
add: function(a, b) {
|
|
25
|
-
if (isValidNumberPair(a, b))
|
|
26
|
-
|
|
27
|
-
|
|
29
|
+
add: function (/** @type {string} */ a, /** @type {string} */ b) {
|
|
30
|
+
if (isValidNumberPair(a, b)) {
|
|
31
|
+
return a + b;
|
|
32
|
+
}
|
|
33
|
+
if (typeof a === 'string' && typeof b === 'string') {
|
|
34
|
+
return a + b;
|
|
35
|
+
}
|
|
36
|
+
throw new Error('Invalid types for +');
|
|
28
37
|
},
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
38
|
+
|
|
39
|
+
subtract: function (/** @type {number} */ a, /** @type {number} */ b) {
|
|
40
|
+
if (isValidNumberPair(a, b)) {
|
|
41
|
+
return a - b;
|
|
42
|
+
}
|
|
43
|
+
throw new Error('Invalid types for -');
|
|
32
44
|
},
|
|
33
45
|
|
|
34
|
-
modulus: function(a, b) {
|
|
35
|
-
if (isValidNumberPair(a, b))
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
46
|
+
modulus: function (/** @type {number} */ a, /** @type {number} */ b) {
|
|
47
|
+
if (isValidNumberPair(a, b)) {
|
|
48
|
+
return a % b;
|
|
49
|
+
}
|
|
50
|
+
throw new Error('Invalid types for %');
|
|
51
|
+
},
|
|
52
|
+
});
|