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
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
export const globalUnits = {
|
|
2
|
+
// Length
|
|
3
|
+
length: {
|
|
4
|
+
m: { value: 1, unit: 'meter', symbol: 'm' },
|
|
5
|
+
cm: { value: 0.01, unit: 'centimeter', symbol: 'cm' },
|
|
6
|
+
mm: { value: 0.001, unit: 'millimeter', symbol: 'mm' },
|
|
7
|
+
km: { value: 1000, unit: 'kilometer', symbol: 'km' },
|
|
8
|
+
um: { value: 0.000001, unit: 'micrometer', symbol: 'um', note: 'also called micron' },
|
|
9
|
+
nm: { value: 0.000000001, unit: 'nanometer', symbol: 'nm' },
|
|
10
|
+
px: { value: 0.000264583, unit: 'pixel', symbol: 'px', note: '96dpi standard' },
|
|
11
|
+
em: { value: 0.000264583 * 16, unit: 'em', symbol: 'em', note: '1em = 16px by default' },
|
|
12
|
+
rem: { value: 0.000264583 * 16, unit: 'rem', symbol: 'rem', note: 'root em = 16px by default' },
|
|
13
|
+
pt: { value: 0.000352778, unit: 'point', symbol: 'pt', note: '1pt = 1/72 inch' },
|
|
14
|
+
pc: { value: 0.00423333, unit: 'pica', symbol: 'pc', note: '1pc = 12pt' },
|
|
15
|
+
inch: { value: 0.0254, unit: 'inch', symbol: 'in' },
|
|
16
|
+
ft: { value: 0.3048, unit: 'foot', symbol: 'ft' },
|
|
17
|
+
yd: { value: 0.9144, unit: 'yard', symbol: 'yd' },
|
|
18
|
+
mi: { value: 1609.344, unit: 'mile', symbol: 'mi' },
|
|
19
|
+
thou: { value: 0.0000254, unit: 'mil', symbol: 'thou', note: 'thousandth of an inch' },
|
|
20
|
+
furlong: { value: 201.168, unit: 'furlong', symbol: 'fur', note: '220 yards' },
|
|
21
|
+
nmi: { value: 1852, unit: 'nautical mile', symbol: 'nmi' },
|
|
22
|
+
fathom: { value: 1.8288, unit: 'fathom', symbol: 'fathom' },
|
|
23
|
+
au: { value: 1.496e11, unit: 'astronomical unit', symbol: 'AU' },
|
|
24
|
+
ly: { value: 9.4607e15, unit: 'light year', symbol: 'ly' },
|
|
25
|
+
pc: { value: 3.0857e16, unit: 'parsec', symbol: 'pc' }
|
|
26
|
+
},
|
|
27
|
+
|
|
28
|
+
// Weight / Mass
|
|
29
|
+
weight: {
|
|
30
|
+
mg: { value: 1e-6, unit: 'milligram', symbol: 'mg' },
|
|
31
|
+
g: { value: 0.001, unit: 'gram', symbol: 'g' },
|
|
32
|
+
kg: { value: 1, unit: 'kilogram', symbol: 'kg' },
|
|
33
|
+
t: { value: 1000, unit: 'tonne', symbol: 't', note: 'metric ton' },
|
|
34
|
+
lb: { value: 0.453592, unit: 'pound', symbol: 'lb' },
|
|
35
|
+
oz: { value: 0.0283495, unit: 'ounce', symbol: 'oz' },
|
|
36
|
+
stone: { value: 6.35029, unit: 'stone', symbol: 'st', note: '1 stone = 14 lb' }
|
|
37
|
+
},
|
|
38
|
+
|
|
39
|
+
// Time
|
|
40
|
+
time: {
|
|
41
|
+
s: { value: 1, unit: 'second', symbol: 's' },
|
|
42
|
+
min: { value: 60, unit: 'minute', symbol: 'min' },
|
|
43
|
+
h: { value: 3600, unit: 'hour', symbol: 'h' },
|
|
44
|
+
day: { value: 86400, unit: 'day', symbol: 'd' },
|
|
45
|
+
week: { value: 604800, unit: 'week', symbol: 'wk' },
|
|
46
|
+
month: { value: 2629800, unit: 'month', symbol: 'mo', note: 'average month = 30.44 days' },
|
|
47
|
+
year: { value: 31557600, unit: 'year', symbol: 'yr', note: 'average year = 365.25 days' }
|
|
48
|
+
},
|
|
49
|
+
|
|
50
|
+
// Voltage
|
|
51
|
+
voltage: {
|
|
52
|
+
V: { value: 1, unit: 'volt', symbol: 'V' },
|
|
53
|
+
mV: { value: 0.001, unit: 'millivolt', symbol: 'mV' },
|
|
54
|
+
kV: { value: 1000, unit: 'kilovolt', symbol: 'kV' },
|
|
55
|
+
MV: { value: 1e6, unit: 'megavolt', symbol: 'MV' },
|
|
56
|
+
GV: { value: 1e9, unit: 'gigavolt', symbol: 'GV' },
|
|
57
|
+
statV: { value: 299.792458, unit: 'statvolt', symbol: 'statV', note: 'CGS unit' },
|
|
58
|
+
abV: { value: 1e-8, unit: 'abvolt', symbol: 'abV', note: 'CGS electromagnetic unit' }
|
|
59
|
+
},
|
|
60
|
+
|
|
61
|
+
// Frequency
|
|
62
|
+
frequency: {
|
|
63
|
+
Hz: { value: 1, unit: 'hertz', symbol: 'Hz', note: '1 cycle per second' },
|
|
64
|
+
kHz: { value: 1e3, unit: 'kilohertz', symbol: 'kHz' },
|
|
65
|
+
MHz: { value: 1e6, unit: 'megahertz', symbol: 'MHz' },
|
|
66
|
+
GHz: { value: 1e9, unit: 'gigahertz', symbol: 'GHz' },
|
|
67
|
+
THz: { value: 1e12, unit: 'terahertz', symbol: 'THz' }
|
|
68
|
+
},
|
|
69
|
+
|
|
70
|
+
// Power
|
|
71
|
+
power: {
|
|
72
|
+
W: { value: 1, unit: 'watt', symbol: 'W', note: '1 joule per second' },
|
|
73
|
+
mW: { value: 0.001, unit: 'milliwatt', symbol: 'mW' },
|
|
74
|
+
kW: { value: 1000, unit: 'kilowatt', symbol: 'kW' },
|
|
75
|
+
MW: { value: 1e6, unit: 'megawatt', symbol: 'MW' },
|
|
76
|
+
GW: { value: 1e9, unit: 'gigawatt', symbol: 'GW' },
|
|
77
|
+
HP: { value: 745.7, unit: 'horsepower', symbol: 'HP', note: 'mechanical HP = 745.7 W' },
|
|
78
|
+
kcal_per_h: { value: 1.163, unit: 'kilocalorie per hour', symbol: 'kcal/h', note: '= 1.163 W' },
|
|
79
|
+
BTU_per_h: { value: 0.29307107, unit: 'BTU per hour', symbol: 'BTU/h', note: '= 0.293 W' }
|
|
80
|
+
},
|
|
81
|
+
|
|
82
|
+
// Sound
|
|
83
|
+
sound: {
|
|
84
|
+
dB: { value: 1, unit: 'decibel', symbol: 'dB', note: 'logarithmic unit of sound intensity' },
|
|
85
|
+
dBA: { value: 1, unit: 'A-weighted decibel', symbol: 'dBA', note: 'Adjusted for human hearing' },
|
|
86
|
+
dBC: { value: 1, unit: 'C-weighted decibel', symbol: 'dBC', note: 'Flat weighting for high-level sounds' }
|
|
87
|
+
},
|
|
88
|
+
|
|
89
|
+
// Temperature
|
|
90
|
+
temperature: {
|
|
91
|
+
K: { value: 1, unit: 'kelvin', symbol: 'K' },
|
|
92
|
+
C: { value: 1, unit: 'Celsius', symbol: '°C', note: '°C → K: add 273.15' },
|
|
93
|
+
F: { value: 1, unit: 'Fahrenheit', symbol: '°F', note: '°F → K: (°F - 32) * 5/9 + 273.15' }
|
|
94
|
+
},
|
|
95
|
+
|
|
96
|
+
// Pressure
|
|
97
|
+
pressure: {
|
|
98
|
+
Pa: { value: 1, unit: 'pascal', symbol: 'Pa' },
|
|
99
|
+
kPa: { value: 1000, unit: 'kilopascal', symbol: 'kPa' },
|
|
100
|
+
MPa: { value: 1e6, unit: 'megapascal', symbol: 'MPa' },
|
|
101
|
+
bar: { value: 1e5, unit: 'bar', symbol: 'bar' },
|
|
102
|
+
atm: { value: 101325, unit: 'atmosphere', symbol: 'atm' },
|
|
103
|
+
psi: { value: 6894.757, unit: 'pound per square inch', symbol: 'psi' },
|
|
104
|
+
mmHg:{ value: 133.322, unit: 'millimeter of mercury', symbol: 'mmHg' }
|
|
105
|
+
},
|
|
106
|
+
|
|
107
|
+
// Energy
|
|
108
|
+
energy: {
|
|
109
|
+
J: { value: 1, unit: 'joule', symbol: 'J' },
|
|
110
|
+
kJ: { value: 1000, unit: 'kilojoule', symbol: 'kJ' },
|
|
111
|
+
cal: { value: 4.184, unit: 'calorie', symbol: 'cal' },
|
|
112
|
+
kcal:{ value: 4184, unit: 'kilocalorie', symbol: 'kcal' },
|
|
113
|
+
eV: { value: 1.60218e-19, unit: 'electronvolt', symbol: 'eV' },
|
|
114
|
+
BTU: { value: 1055.06, unit: 'BTU', symbol: 'BTU' }
|
|
115
|
+
},
|
|
116
|
+
|
|
117
|
+
// Force
|
|
118
|
+
force: {
|
|
119
|
+
N: { value: 1, unit: 'newton', symbol: 'N' },
|
|
120
|
+
kN: { value: 1000, unit: 'kilonewton', symbol: 'kN' },
|
|
121
|
+
lbf: { value: 4.44822, unit: 'pound-force', symbol: 'lbf' },
|
|
122
|
+
kgf: { value: 9.80665, unit: 'kilogram-force', symbol: 'kgf' },
|
|
123
|
+
dyne:{ value: 1e-5, unit: 'dyne', symbol: 'dyn' }
|
|
124
|
+
},
|
|
125
|
+
|
|
126
|
+
// Area
|
|
127
|
+
area: {
|
|
128
|
+
m2: { value: 1, unit: 'square meter', symbol: 'm²' },
|
|
129
|
+
cm2: { value: 0.0001, unit: 'square centimeter', symbol: 'cm²' },
|
|
130
|
+
km2: { value: 1e6, unit: 'square kilometer', symbol: 'km²' },
|
|
131
|
+
acre: { value: 4046.856, unit: 'acre', symbol: 'acre' },
|
|
132
|
+
hectare:{ value: 10000, unit: 'hectare', symbol: 'ha' },
|
|
133
|
+
ft2: { value: 0.092903, unit: 'square foot', symbol: 'ft²' },
|
|
134
|
+
yd2: { value: 0.836127, unit: 'square yard', symbol: 'yd²' }
|
|
135
|
+
},
|
|
136
|
+
|
|
137
|
+
// Volume
|
|
138
|
+
volume: {
|
|
139
|
+
m3: { value: 1, unit: 'cubic meter', symbol: 'm³' },
|
|
140
|
+
L: { value: 0.001, unit: 'liter', symbol: 'L' },
|
|
141
|
+
mL: { value: 1e-6, unit: 'milliliter', symbol: 'mL' },
|
|
142
|
+
gallon:{ value: 0.00378541, unit: 'US gallon', symbol: 'gal' },
|
|
143
|
+
pint: { value: 0.000473176, unit: 'US pint', symbol: 'pt' },
|
|
144
|
+
floz: { value: 2.9574e-5, unit: 'US fluid ounce', symbol: 'fl oz' }
|
|
145
|
+
},
|
|
146
|
+
|
|
147
|
+
// Electrical Current
|
|
148
|
+
current: {
|
|
149
|
+
A: { value: 1, unit: 'ampere', symbol: 'A' },
|
|
150
|
+
mA: { value: 0.001, unit: 'milliampere', symbol: 'mA' },
|
|
151
|
+
uA: { value: 0.000001, unit: 'microampere', symbol: 'uA' },
|
|
152
|
+
kA: { value: 1000, unit: 'kiloampere', symbol: 'kA' }
|
|
153
|
+
},
|
|
154
|
+
|
|
155
|
+
// Resistance / Conductance
|
|
156
|
+
resistance: {
|
|
157
|
+
ohm: { value: 1, unit: 'ohm' },
|
|
158
|
+
kohm: { value: 1000, unit: 'kiloohm'},
|
|
159
|
+
megaohm: { value: 1e6, unit: 'megaohm'},
|
|
160
|
+
S: { value: 1, unit: 'siemens', symbol: 'S', note: 'conductance' }
|
|
161
|
+
},
|
|
162
|
+
|
|
163
|
+
// Capacitance / Inductance
|
|
164
|
+
capacitance: {
|
|
165
|
+
F: { value: 1, unit: 'farad', symbol: 'F' },
|
|
166
|
+
mF: { value: 0.001, unit: 'millifarad'},
|
|
167
|
+
uF: { value: 0.000001, unit: 'microfarad' }
|
|
168
|
+
},
|
|
169
|
+
inductance: {
|
|
170
|
+
H: { value: 1, unit: 'henry', symbol: 'H' },
|
|
171
|
+
mH: { value: 0.001, unit: 'millihenry', symbol: 'mH' },
|
|
172
|
+
uH: { value: 0.000001, unit: 'microhenry', symbol: 'uH' }
|
|
173
|
+
},
|
|
174
|
+
|
|
175
|
+
// Luminous Intensity / Illuminance
|
|
176
|
+
light: {
|
|
177
|
+
cd: { value: 1, unit: 'candela', symbol: 'cd' },
|
|
178
|
+
lm: { value: 1, unit: 'lumen', symbol: 'lm' },
|
|
179
|
+
lx: { value: 1, unit: 'lux', symbol: 'lx' }
|
|
180
|
+
},
|
|
181
|
+
|
|
182
|
+
// Data / Digital Storage
|
|
183
|
+
data: {
|
|
184
|
+
bit: { value: 1, unit: 'bit', symbol: 'bit' },
|
|
185
|
+
B: { value: 8, unit: 'byte', symbol: 'B' },
|
|
186
|
+
KB: { value: 8e3, unit: 'kilobyte', symbol: 'KB' },
|
|
187
|
+
MB: { value: 8e6, unit: 'megabyte', symbol: 'MB' },
|
|
188
|
+
GB: { value: 8e9, unit: 'gigabyte', symbol: 'GB' },
|
|
189
|
+
TB: { value: 8e12, unit: 'terabyte', symbol: 'TB' }
|
|
190
|
+
},
|
|
191
|
+
|
|
192
|
+
// Angle
|
|
193
|
+
angle: {
|
|
194
|
+
deg: { value: 1, unit: 'degree', symbol: '°' },
|
|
195
|
+
rad: { value: 57.2958, unit: 'radian', symbol: 'rad', note: '1 rad = 57.2958°' },
|
|
196
|
+
grad:{ value: 0.9, unit: 'grad', symbol: 'grad', note: '1 grad = 0.9°' }
|
|
197
|
+
},
|
|
198
|
+
radiation: {
|
|
199
|
+
// Absorbed Dose
|
|
200
|
+
Gy: { value: 1, unit: 'gray', symbol: 'Gy', note: 'Absorbed dose: 1 Gy = 1 J/kg' },
|
|
201
|
+
mGy: { value: 0.001, unit: 'milligray', symbol: 'mGy' },
|
|
202
|
+
rad: { value: 0.01, unit: 'rad', symbol: 'rad', note: '1 rad = 0.01 Gy' },
|
|
203
|
+
|
|
204
|
+
// Dose Equivalent
|
|
205
|
+
Sv: { value: 1, unit: 'sievert', symbol: 'Sv', note: 'Biological effect dose equivalent' },
|
|
206
|
+
mSv: { value: 0.001, unit: 'millisievert', symbol: 'mSv' },
|
|
207
|
+
rem: { value: 0.01, unit: 'rem', symbol: 'rem', note: '1 rem = 0.01 Sv' },
|
|
208
|
+
|
|
209
|
+
// Radioactivity
|
|
210
|
+
Bq: { value: 1, unit: 'becquerel', symbol: 'Bq', note: '1 decay per second' },
|
|
211
|
+
kBq: { value: 1e3, unit: 'kilobecquerel', symbol: 'kBq' },
|
|
212
|
+
MBq: { value: 1e6, unit: 'megabecquerel', symbol: 'MBq' },
|
|
213
|
+
GBq: { value: 1e9, unit: 'gigabecquerel', symbol: 'GBq' },
|
|
214
|
+
Ci: { value: 3.7e10, unit: 'curie', symbol: 'Ci', note: '1 Ci = 3.7 x 10¹⁰ decays per second' },
|
|
215
|
+
mCi: { value: 3.7e7, unit: 'millicurie', symbol: 'mCi' }
|
|
216
|
+
}
|
|
217
|
+
};
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
export function createUnitsStore(initial = {}) {
|
|
2
|
+
let units = { ...initial};
|
|
3
|
+
|
|
4
|
+
// ---------- Helpers ----------
|
|
5
|
+
|
|
6
|
+
function getAllUnitsFlat() {
|
|
7
|
+
const result = new Set();
|
|
8
|
+
|
|
9
|
+
for (const type in units) {
|
|
10
|
+
for (const key in units[type]) {
|
|
11
|
+
const u = units[type][key];
|
|
12
|
+
|
|
13
|
+
const keyLower = key.toLowerCase();
|
|
14
|
+
result.add(keyLower);
|
|
15
|
+
|
|
16
|
+
// Unit name
|
|
17
|
+
if (u.unit) {
|
|
18
|
+
const unitLower = u.unit.toLowerCase();
|
|
19
|
+
|
|
20
|
+
// Avoid duplicate like "m" vs "meter"
|
|
21
|
+
if (unitLower !== keyLower) {
|
|
22
|
+
// Optional: only single-word units
|
|
23
|
+
if (unitLower.split(/\s+/).length === 1) {
|
|
24
|
+
result.add(unitLower);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Symbol
|
|
30
|
+
if (u.symbol) {
|
|
31
|
+
const symbolLower = u.symbol.toLowerCase();
|
|
32
|
+
|
|
33
|
+
// Avoid duplicate with unit name
|
|
34
|
+
if (!u.unit || symbolLower !== u.unit.toLowerCase()) {
|
|
35
|
+
result.add(symbolLower);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return Array.from(result);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function findUnit(input) {
|
|
45
|
+
input = input.toLowerCase();
|
|
46
|
+
|
|
47
|
+
for (const type in units) {
|
|
48
|
+
for (const key in units[type]) {
|
|
49
|
+
const u = units[type][key];
|
|
50
|
+
|
|
51
|
+
if (
|
|
52
|
+
key.toLowerCase() === input ||
|
|
53
|
+
u.unit?.toLowerCase() === input ||
|
|
54
|
+
u.symbol?.toLowerCase() === input
|
|
55
|
+
) {
|
|
56
|
+
return { type, key , data: u};
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// ---------- Core Convert ----------
|
|
65
|
+
|
|
66
|
+
function convert(value, fromUnit, toUnit) {
|
|
67
|
+
const from = findUnit(fromUnit);
|
|
68
|
+
const to = findUnit(toUnit);
|
|
69
|
+
|
|
70
|
+
if (!from) throw new Error(`Unknown unit: ${fromUnit}`);
|
|
71
|
+
if (!to) throw new Error(`Unknown unit: ${toUnit}`);
|
|
72
|
+
|
|
73
|
+
if (from.type !== to.type) {
|
|
74
|
+
throw new Error(`Cannot convert ${fromUnit} to ${toUnit} (${to.data.unit || to.key}). ${from.data.unit || from.key} conversion units like ${Object.keys(units[from.type]).join(", ")}`);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const result = value * (from.data.value / to.data.value);
|
|
78
|
+
|
|
79
|
+
return { value: result, unit: to.key };
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// ---------- Public API ----------
|
|
83
|
+
|
|
84
|
+
return {
|
|
85
|
+
// Get all units
|
|
86
|
+
getUnits: () => units,
|
|
87
|
+
|
|
88
|
+
// Replace all units
|
|
89
|
+
setUnits: (newUnits) => {
|
|
90
|
+
units = { ...newUnits };
|
|
91
|
+
},
|
|
92
|
+
|
|
93
|
+
// Update single type
|
|
94
|
+
updateType: (type, data) => {
|
|
95
|
+
units[type] = { ...units[type], ...data };
|
|
96
|
+
},
|
|
97
|
+
|
|
98
|
+
// Add new unit
|
|
99
|
+
addUnit: (type, key, unitObj) => {
|
|
100
|
+
if (!units[type]) units[type] = {};
|
|
101
|
+
units[type][key] = unitObj;
|
|
102
|
+
},
|
|
103
|
+
compute(op, left, right) {
|
|
104
|
+
|
|
105
|
+
const isUnit = (v) =>
|
|
106
|
+
v && typeof v === "object" && "value" in v && "unit" in v;
|
|
107
|
+
|
|
108
|
+
const apply = (a, b) => {
|
|
109
|
+
switch (op) {
|
|
110
|
+
case "+": return a + b;
|
|
111
|
+
case "-": return a - b;
|
|
112
|
+
case "*": return a * b;
|
|
113
|
+
case "/": return a / b;
|
|
114
|
+
case "%": return a % b;
|
|
115
|
+
case "^": return Math.pow(a, b);
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
// BOTH UNIT
|
|
120
|
+
if (isUnit(left) && isUnit(right)) {
|
|
121
|
+
|
|
122
|
+
const from = this.findUnit(right.unit);
|
|
123
|
+
const to = this.findUnit(left.unit);
|
|
124
|
+
|
|
125
|
+
if (from.type !== to.type) {
|
|
126
|
+
throw new Error(`Cannot operate on different unit types`);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// convert right → left unit
|
|
130
|
+
const r = right.value * (from.data.value / to.data.value);
|
|
131
|
+
|
|
132
|
+
const result = apply(left.value, r);
|
|
133
|
+
|
|
134
|
+
// multiplication/division produce compound units
|
|
135
|
+
if (op === "*") {
|
|
136
|
+
return { value: result, unit: left.unit };
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (op === "/") {
|
|
140
|
+
return { value: result, unit: left.unit };
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (op === "^") {
|
|
144
|
+
return { value: result, unit: left.unit };
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return { value: result, unit: left.unit };
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// ================= LEFT UNIT =================
|
|
151
|
+
if (isUnit(left) && !isUnit(right)) {
|
|
152
|
+
const result = apply(left.value, right);
|
|
153
|
+
|
|
154
|
+
return { value: result, unit: left.unit };
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// ================= RIGHT UNIT =================
|
|
158
|
+
if (!isUnit(left) && isUnit(right)) {
|
|
159
|
+
const result = apply(left, right.value);
|
|
160
|
+
|
|
161
|
+
if (op === "/") {
|
|
162
|
+
return { value: result, unit: right.unit };
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
return { value: result, unit: right.unit };
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// ================= NORMAL =================
|
|
169
|
+
return apply(left, right);
|
|
170
|
+
},
|
|
171
|
+
// Convert
|
|
172
|
+
convert,
|
|
173
|
+
|
|
174
|
+
// Search helpers
|
|
175
|
+
getAllUnitsFlat,
|
|
176
|
+
findUnit
|
|
177
|
+
};
|
|
178
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
const validVarName = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/;
|
|
2
|
+
|
|
3
|
+
export function createVarStore(initial = {}) {
|
|
4
|
+
let store = Object.create(null);
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
for (const key in initial) {
|
|
8
|
+
store[key] = initial[key];
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
return {
|
|
12
|
+
set(name, value, { override = true } = {}) {
|
|
13
|
+
|
|
14
|
+
// Name validation
|
|
15
|
+
if (typeof name !== "string" || !name) {
|
|
16
|
+
throw new Error("Variable name must be a non-empty string");
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if (!validVarName.test(name)) {
|
|
20
|
+
throw new Error(`Variable Name Error: '${name}' is not a valid variable name`);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Value validation
|
|
24
|
+
if (value === undefined) {
|
|
25
|
+
throw new Error(`Variable Value Error: '${name}' cannot be undefined`);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Prevent overwrite (optional)
|
|
29
|
+
if (!override && name in variablesDB) {
|
|
30
|
+
throw new Error(`Variable '${name}' already exists`);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
store[name] = value;
|
|
34
|
+
},
|
|
35
|
+
|
|
36
|
+
//get variable
|
|
37
|
+
get(name) {
|
|
38
|
+
return store[name];
|
|
39
|
+
},
|
|
40
|
+
|
|
41
|
+
// check existence
|
|
42
|
+
has(name) {
|
|
43
|
+
return Object.prototype.hasOwnProperty.call(store, name);
|
|
44
|
+
},
|
|
45
|
+
|
|
46
|
+
// remove variable
|
|
47
|
+
remove(name) {
|
|
48
|
+
delete store[name];
|
|
49
|
+
},
|
|
50
|
+
|
|
51
|
+
// get all variables (snapshot)
|
|
52
|
+
all() {
|
|
53
|
+
return { ...store };
|
|
54
|
+
},
|
|
55
|
+
|
|
56
|
+
// clear all
|
|
57
|
+
clear() {
|
|
58
|
+
store = Object.create(null);
|
|
59
|
+
},
|
|
60
|
+
|
|
61
|
+
// merge multiple variables
|
|
62
|
+
merge(obj = {}) {
|
|
63
|
+
for (const key in obj) {
|
|
64
|
+
store[key] = obj[key];
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
|
|
68
|
+
// clone store (for scoped instances)
|
|
69
|
+
clone() {
|
|
70
|
+
return createVarStore(store);
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export default { createVarStore };
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
<script src="../dist/exprify.js"></script>
|
|
2
|
+
<script>
|
|
3
|
+
const expr = new Exprify(); // ✅ no namespace needed (usually)
|
|
4
|
+
// console.log(expr.parse("5 + 7 * 2")); // 19
|
|
5
|
+
// console.log(expr.evaluate("5 + 7 * 2")); // 19
|
|
6
|
+
console.log(expr.parse("max(5, 2)")); // 19
|
|
7
|
+
// console.log(expr.evaluate("max('5' + 7 * 2)")); // 19
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
// const exprify = new Exprify();
|
|
11
|
+
// const exprFn = exprify.compile("or(a, b)");
|
|
12
|
+
// console.log(exprFn({ a: true, b: false })); // 22
|
|
13
|
+
// console.log(exprFn({ a: 7, b: 3 })); // 17
|
|
14
|
+
|
|
15
|
+
console.log(expr.parse("5 px to em")); // 19
|
|
16
|
+
console.log(expr.evaluate("5 px to em")); // 19
|
|
17
|
+
|
|
18
|
+
// console.log(expr.parse("value |> double |> sqrt")); // 19
|
|
19
|
+
// console.log(expr.evaluate("value |> double |> sqrt")); // 19
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
</script>
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import Exprify from "../src/core/Exprify.js";
|
|
2
|
+
|
|
3
|
+
describe("Exprify Engine - Extended Tests", () => {
|
|
4
|
+
let expr;
|
|
5
|
+
|
|
6
|
+
beforeEach(() => {
|
|
7
|
+
expr = new Exprify();
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
/* ================= BASIC ================= */
|
|
11
|
+
test("addition", () => {
|
|
12
|
+
expect(expr.evaluate("2 + 3 + 5")).toBe(10);
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
test("operator precedence", () => {
|
|
16
|
+
expect(expr.evaluate("2 + 3 * 4")).toBe(14);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
test("parentheses override precedence", () => {
|
|
20
|
+
expect(expr.evaluate("(2 + 3) * 4")).toBe(20);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
test("mixed parentheses", () => {
|
|
24
|
+
expect(expr.evaluate("(1 + 2) * (3 + 4)")).toBe(21);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
/* ================= NESTED ================= */
|
|
28
|
+
test("nested parentheses", () => {
|
|
29
|
+
expect(expr.evaluate("((2 + 3) * (4 + 1))")).toBe(25);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test("deep nesting", () => {
|
|
33
|
+
expect(expr.evaluate("(((1 + 1) + 1) + 1)")).toBe(4);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
/* ================= UNARY ================= */
|
|
37
|
+
test("unary minus", () => {
|
|
38
|
+
expect(expr.evaluate("-5 + 10")).toBe(5);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test("double unary", () => {
|
|
42
|
+
expect(expr.evaluate("--5")).toBe(5);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
/* ================= POWER ================= */
|
|
46
|
+
test("power operator", () => {
|
|
47
|
+
expect(expr.evaluate("2 ^ 3")).toBe(8);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
test("power precedence", () => {
|
|
51
|
+
expect(expr.evaluate("2 + 2 ^ 3")).toBe(10);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
/* ================= LOGICAL ================= */
|
|
55
|
+
test("logical AND", () => {
|
|
56
|
+
expect(expr.evaluate("true && false")).toBe(false);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
test("logical OR", () => {
|
|
60
|
+
expect(expr.evaluate("true || false")).toBe(true);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
/* ================= FUNCTION ================= */
|
|
64
|
+
test("function call", () => {
|
|
65
|
+
expect(expr.evaluate("max(2, 5, 3)")).toBe(5);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
test("nested function", () => {
|
|
69
|
+
expect(expr.evaluate("max(2, min(5, 3))")).toBe(3);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
test("matrix determinant with semicolon rows", () => {
|
|
73
|
+
expect(expr.evaluate("det([-1, 2; 3, 1])")).toBe(-7);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
/* ================= STRING ================= */
|
|
77
|
+
test("string concat", () => {
|
|
78
|
+
expect(expr.evaluate('"Hello " + "World"')).toBe("Hello World");
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
/* ================= BIGINT ================= */
|
|
82
|
+
test("bigint power", () => {
|
|
83
|
+
expect(expr.evaluate("11n ^ 2n")).toBe(121n);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
/* ================= UNIT ================= */
|
|
87
|
+
test("unit conversion", () => {
|
|
88
|
+
expect(expr.evaluate("2 inch to cm")).toBe("5.08 cm");
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
test("unit addition", () => {
|
|
92
|
+
expect(expr.evaluate("5 cm + 2 inch")).toBe("10.08 cm");
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
/* ================= EDGE CASE ================= */
|
|
96
|
+
test("division", () => {
|
|
97
|
+
expect(expr.evaluate("10 / 2")).toBe(5);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
test("modulus", () => {
|
|
101
|
+
expect(expr.evaluate("10 % 3")).toBe(1);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
test("invalid expression", () => {
|
|
105
|
+
expect(() => expr.evaluate("(2 + 3")).toThrow();
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
test("set and use variable", () => {
|
|
110
|
+
expr.setVariable("x", 5);
|
|
111
|
+
expr.setVariable("y", 3);
|
|
112
|
+
expect(expr.evaluate("x + y")).toBe(8);
|
|
113
|
+
expect(expr.evaluate("x * y + 2")).toBe(17); // 5*3=15 +2=17
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
test("variable in parentheses", () => {
|
|
117
|
+
expr.setVariable("a", 2);
|
|
118
|
+
expr.setVariable("b", 4);
|
|
119
|
+
expect(expr.evaluate("(a + b) * 3")).toBe(18); // (2+4)*3=18
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
test("add and use external function", () => {
|
|
123
|
+
// Example: double(n) returns n*2
|
|
124
|
+
expr.addFunction("double", (n) => n * 2);
|
|
125
|
+
expect(expr.evaluate("double(4)")).toBe(8);
|
|
126
|
+
expect(expr.evaluate("2 + double(5)")).toBe(12); // 2+10=12
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
test("external function with multiple arguments", () => {
|
|
130
|
+
expr.addFunction("sumThree", (a, b, c) => a + b + c);
|
|
131
|
+
expect(expr.evaluate("sumThree(2, 3, 5)")).toBe(10);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
test("nested function calls", () => {
|
|
135
|
+
expr.addFunction("double", (n) => n * 2);
|
|
136
|
+
expr.addFunction("addTen", (n) => n + 10);
|
|
137
|
+
expect(expr.evaluate("addTen(double(5))")).toBe(20); // double(5)=10 → addTen(10)=20
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
});
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
export const externalFunctions = {};
|
|
2
|
-
|
|
3
|
-
// Add user-defined function
|
|
4
|
-
export function addFunction(name, fn) {
|
|
5
|
-
|
|
6
|
-
if (typeof name !== "string" || name.trim() === "") {
|
|
7
|
-
throw new Error("Function name must be a non-empty string");
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
if (typeof fn !== "function") {
|
|
11
|
-
throw new Error("Function must be a valid function");
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
if (name in externalFunctions) {
|
|
15
|
-
throw new Error(`Function '${name}' already exists`);
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
externalFunctions[name] = fn;
|
|
19
|
-
}
|