exprify 1.0.1 → 1.0.3
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 +100 -33
- package/dist/exprify.cjs.js +671 -4
- package/dist/exprify.cjs.js.map +1 -1
- package/dist/exprify.esm.js +671 -4
- package/dist/exprify.esm.js.map +1 -1
- package/dist/exprify.js +672 -5
- 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 +1 -1
- package/src/core/Exprify.js +231 -2
- package/src/function/internal.js +342 -0
- package/src/parser/astBuild.js +24 -1
- package/src/parser/evaluator.js +31 -1
- package/src/utils/matrix.js +53 -0
- package/.gitattributes +0 -2
- package/.github/workflows/ci.yml +0 -40
- package/.github/workflows/npm-publish.yml +0 -38
- package/.github/workflows/security-audit.yml +0 -34
- package/CHANGELOG.md +0 -11
- package/doc/tokenType.txt +0 -48
- package/rollup.config.js +0 -80
- package/test/browser.html +0 -23
- package/test/exprify.test.js +0 -140
package/src/parser/evaluator.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { unwrapDenseMatrix, wrapDenseMatrix } from "../utils/matrix.js";
|
|
2
|
+
|
|
1
3
|
export function evaluateAST(node, context = {}) {
|
|
2
4
|
|
|
3
5
|
const vars = context.variables;
|
|
@@ -30,6 +32,7 @@ export function evaluateAST(node, context = {}) {
|
|
|
30
32
|
};
|
|
31
33
|
|
|
32
34
|
const normalizeMatrix = (value) => {
|
|
35
|
+
value = unwrapDenseMatrix(value);
|
|
33
36
|
if (isMatrix(value)) return value.map((row) => [...row]);
|
|
34
37
|
if (Array.isArray(value)) return [value];
|
|
35
38
|
throw new Error("Expected matrix-compatible value");
|
|
@@ -202,6 +205,16 @@ export function evaluateAST(node, context = {}) {
|
|
|
202
205
|
const simplifyComplex = (value) =>
|
|
203
206
|
value.im === 0 ? value.re : value;
|
|
204
207
|
|
|
208
|
+
const createFunctionScope = (params, args) => {
|
|
209
|
+
const scopedValues = {};
|
|
210
|
+
|
|
211
|
+
params.forEach((param, index) => {
|
|
212
|
+
scopedValues[param] = args[index];
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
return scopedValues;
|
|
216
|
+
};
|
|
217
|
+
|
|
205
218
|
const evalComplexBinary = (operator, left, right) => {
|
|
206
219
|
const a = toComplex(left);
|
|
207
220
|
const b = toComplex(right);
|
|
@@ -257,6 +270,9 @@ export function evaluateAST(node, context = {}) {
|
|
|
257
270
|
|
|
258
271
|
if (node.left.type === "Identifier") {
|
|
259
272
|
vars.set(node.left.name, value);
|
|
273
|
+
if (node.right.type === "ArrayExpression") {
|
|
274
|
+
return wrapDenseMatrix(unwrapDenseMatrix(value));
|
|
275
|
+
}
|
|
260
276
|
return value;
|
|
261
277
|
}
|
|
262
278
|
|
|
@@ -270,6 +286,20 @@ export function evaluateAST(node, context = {}) {
|
|
|
270
286
|
throw new Error("Invalid assignment target");
|
|
271
287
|
}
|
|
272
288
|
|
|
289
|
+
case "FunctionAssignmentExpression": {
|
|
290
|
+
if (node.operator !== "=") {
|
|
291
|
+
throw new Error(`Operator ${node.operator} is not supported for function definitions`);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
const fn = (...args) => {
|
|
295
|
+
const scopedContext = context.withScope(createFunctionScope(node.params, args));
|
|
296
|
+
return evaluateAST(node.right, scopedContext);
|
|
297
|
+
};
|
|
298
|
+
|
|
299
|
+
fns.register(node.left.name, fn);
|
|
300
|
+
return fn;
|
|
301
|
+
}
|
|
302
|
+
|
|
273
303
|
/* ===== UNARY ===== */
|
|
274
304
|
case "UnaryExpression": {
|
|
275
305
|
const val = evaluateAST(node.argument, context);
|
|
@@ -290,7 +320,7 @@ export function evaluateAST(node, context = {}) {
|
|
|
290
320
|
let left = evaluateAST(node.left, context);
|
|
291
321
|
let right = evaluateAST(node.right, context);
|
|
292
322
|
|
|
293
|
-
//
|
|
323
|
+
// UNIT handling
|
|
294
324
|
if (isUnitObj(left) || isUnitObj(right)) {
|
|
295
325
|
|
|
296
326
|
if (!units) throw new Error("Unit system not available");
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
export const isDenseMatrixWrapper = (value) =>
|
|
2
|
+
value &&
|
|
3
|
+
typeof value === "object" &&
|
|
4
|
+
value.exprify === "DenseMatrix" &&
|
|
5
|
+
"data" in value &&
|
|
6
|
+
"size" in value;
|
|
7
|
+
|
|
8
|
+
export const cloneMatrixData = (value) => {
|
|
9
|
+
if (Array.isArray(value)) {
|
|
10
|
+
return value.map(cloneMatrixData);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
return value;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export const getMatrixSize = (data) => {
|
|
17
|
+
if (Array.isArray(data) && data.every(Array.isArray)) {
|
|
18
|
+
return [data.length, data[0]?.length || 0];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
if (Array.isArray(data)) {
|
|
22
|
+
return [data.length];
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
throw new Error("Matrix data must be an array");
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export const wrapDenseMatrix = (data) => ({
|
|
29
|
+
exprify: "DenseMatrix",
|
|
30
|
+
data: cloneMatrixData(data),
|
|
31
|
+
size: getMatrixSize(data)
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
export const unwrapDenseMatrix = (value) =>
|
|
35
|
+
isDenseMatrixWrapper(value) ? cloneMatrixData(value.data) : value;
|
|
36
|
+
|
|
37
|
+
export const serializeExprifyValue = (value) => {
|
|
38
|
+
if (isDenseMatrixWrapper(value)) {
|
|
39
|
+
return JSON.stringify(value);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (Array.isArray(value) || (value && typeof value === "object")) {
|
|
43
|
+
return JSON.stringify(value, (_, current) => {
|
|
44
|
+
if (isDenseMatrixWrapper(current)) {
|
|
45
|
+
return current;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return current;
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return value;
|
|
53
|
+
};
|
package/.gitattributes
DELETED
package/.github/workflows/ci.yml
DELETED
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
name: CI
|
|
2
|
-
|
|
3
|
-
on:
|
|
4
|
-
push:
|
|
5
|
-
branches:
|
|
6
|
-
- main
|
|
7
|
-
- master
|
|
8
|
-
pull_request:
|
|
9
|
-
branches:
|
|
10
|
-
- main
|
|
11
|
-
- master
|
|
12
|
-
|
|
13
|
-
jobs:
|
|
14
|
-
test:
|
|
15
|
-
runs-on: ubuntu-latest
|
|
16
|
-
|
|
17
|
-
strategy:
|
|
18
|
-
matrix:
|
|
19
|
-
node-version:
|
|
20
|
-
- 20
|
|
21
|
-
- 22
|
|
22
|
-
|
|
23
|
-
steps:
|
|
24
|
-
- name: Checkout repository
|
|
25
|
-
uses: actions/checkout@v4
|
|
26
|
-
|
|
27
|
-
- name: Setup Node.js
|
|
28
|
-
uses: actions/setup-node@v4
|
|
29
|
-
with:
|
|
30
|
-
node-version: ${{ matrix.node-version }}
|
|
31
|
-
cache: npm
|
|
32
|
-
|
|
33
|
-
- name: Install dependencies
|
|
34
|
-
run: npm ci
|
|
35
|
-
|
|
36
|
-
- name: Build package
|
|
37
|
-
run: npm run build
|
|
38
|
-
|
|
39
|
-
- name: Run tests
|
|
40
|
-
run: npm test
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
name: Publish Package
|
|
2
|
-
|
|
3
|
-
on:
|
|
4
|
-
release:
|
|
5
|
-
types:
|
|
6
|
-
- published
|
|
7
|
-
workflow_dispatch:
|
|
8
|
-
|
|
9
|
-
jobs:
|
|
10
|
-
publish:
|
|
11
|
-
runs-on: ubuntu-latest
|
|
12
|
-
permissions:
|
|
13
|
-
contents: read
|
|
14
|
-
|
|
15
|
-
steps:
|
|
16
|
-
- name: Checkout repository
|
|
17
|
-
uses: actions/checkout@v4
|
|
18
|
-
|
|
19
|
-
- name: Setup Node.js
|
|
20
|
-
uses: actions/setup-node@v4
|
|
21
|
-
with:
|
|
22
|
-
node-version: 22
|
|
23
|
-
registry-url: https://registry.npmjs.org/
|
|
24
|
-
cache: npm
|
|
25
|
-
|
|
26
|
-
- name: Install dependencies
|
|
27
|
-
run: npm ci
|
|
28
|
-
|
|
29
|
-
- name: Build package
|
|
30
|
-
run: npm run build
|
|
31
|
-
|
|
32
|
-
- name: Run tests
|
|
33
|
-
run: npm test
|
|
34
|
-
|
|
35
|
-
- name: Publish to npm
|
|
36
|
-
run: npm publish
|
|
37
|
-
env:
|
|
38
|
-
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
name: Security Audit
|
|
2
|
-
|
|
3
|
-
on:
|
|
4
|
-
push:
|
|
5
|
-
branches:
|
|
6
|
-
- main
|
|
7
|
-
- master
|
|
8
|
-
pull_request:
|
|
9
|
-
branches:
|
|
10
|
-
- main
|
|
11
|
-
- master
|
|
12
|
-
schedule:
|
|
13
|
-
- cron: "0 6 * * 1"
|
|
14
|
-
workflow_dispatch:
|
|
15
|
-
|
|
16
|
-
jobs:
|
|
17
|
-
audit:
|
|
18
|
-
runs-on: ubuntu-latest
|
|
19
|
-
|
|
20
|
-
steps:
|
|
21
|
-
- name: Checkout repository
|
|
22
|
-
uses: actions/checkout@v4
|
|
23
|
-
|
|
24
|
-
- name: Setup Node.js
|
|
25
|
-
uses: actions/setup-node@v4
|
|
26
|
-
with:
|
|
27
|
-
node-version: 22
|
|
28
|
-
cache: npm
|
|
29
|
-
|
|
30
|
-
- name: Install dependencies
|
|
31
|
-
run: npm ci
|
|
32
|
-
|
|
33
|
-
- name: Run npm audit
|
|
34
|
-
run: npm audit --audit-level=high
|
package/CHANGELOG.md
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
# Changelog
|
|
2
|
-
|
|
3
|
-
All notable changes to this project will be documented in this file.
|
|
4
|
-
|
|
5
|
-
The format is based on Keep a Changelog, and this project follows semantic versioning.
|
|
6
|
-
|
|
7
|
-
## [1.0.1] - 2026-04-05
|
|
8
|
-
|
|
9
|
-
### Fixed
|
|
10
|
-
- Corrected the import path in `src/index.js` to match the actual filename casing of `src/core/Exprify.js`.
|
|
11
|
-
- Resolved a cross-platform build issue where Rollup failed on Linux-based CI environments because of case-sensitive path resolution.
|
package/doc/tokenType.txt
DELETED
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
ALL TOKEN TYPES (FINAL DESIGN)
|
|
2
|
-
|
|
3
|
-
// LITERALS
|
|
4
|
-
{ type: "Number", value: 10 }
|
|
5
|
-
{ type: "BigInt", value: 10n }
|
|
6
|
-
{ type: "Boolean", value: true }
|
|
7
|
-
{ type: "String", value: "hello" }
|
|
8
|
-
|
|
9
|
-
{ type: "Unit", value: "cm" }
|
|
10
|
-
{ type: "NumberWithUnit", value: 12.7, unit: "cm" }
|
|
11
|
-
{ type: "UnknownUnit", value: 5, unit: "abc" }
|
|
12
|
-
|
|
13
|
-
// IDENTIFIERS
|
|
14
|
-
{ type: "Identifier", value: "x" }
|
|
15
|
-
|
|
16
|
-
// FUNCTIONS
|
|
17
|
-
{ type: "Function", value: "max" }
|
|
18
|
-
|
|
19
|
-
// OPERATORS
|
|
20
|
-
{ type: "Operator", value: "+" }
|
|
21
|
-
{ type: "Operator", value: ">=" }
|
|
22
|
-
{ type: "Operator", value: "&&" }
|
|
23
|
-
{ type: "Operator", value: "|>" }
|
|
24
|
-
|
|
25
|
-
// UNARY
|
|
26
|
-
{ type: "UnaryOperator", value: "-" }
|
|
27
|
-
{ type: "UnaryOperator", value: "!" }
|
|
28
|
-
|
|
29
|
-
// LOGIC / TERNARY
|
|
30
|
-
{ type: "Ternary", value: "?" }
|
|
31
|
-
{ type: "Ternary", value: ":" }
|
|
32
|
-
|
|
33
|
-
// STRUCTURE
|
|
34
|
-
{ type: "Parenthesis", value: "(" }
|
|
35
|
-
{ type: "Parenthesis", value: ")" }
|
|
36
|
-
{ type: "ArrayStart" }
|
|
37
|
-
{ type: "ArrayEnd" }
|
|
38
|
-
{ type: "BlockStart" }
|
|
39
|
-
{ type: "BlockEnd" }
|
|
40
|
-
{ type: "Colon" }
|
|
41
|
-
{ type: "Comma" }
|
|
42
|
-
{ type: "Dot" }
|
|
43
|
-
|
|
44
|
-
// KEYWORDS
|
|
45
|
-
{ type: "Keyword", value: "to" }
|
|
46
|
-
{ type: "Keyword", value: "if" }
|
|
47
|
-
|
|
48
|
-
|
package/rollup.config.js
DELETED
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
import fs from "fs";
|
|
2
|
-
import path from "path";
|
|
3
|
-
|
|
4
|
-
import resolve from "@rollup/plugin-node-resolve";
|
|
5
|
-
import commonjs from "@rollup/plugin-commonjs";
|
|
6
|
-
|
|
7
|
-
import terser from "@rollup/plugin-terser";
|
|
8
|
-
import pkg from "./package.json";
|
|
9
|
-
|
|
10
|
-
const short_banner = `/*! ${pkg.name} v${pkg.version} | * (c) ${new Date().getFullYear()} ${pkg.author} and contributors | ${pkg.license} License*/`;
|
|
11
|
-
|
|
12
|
-
const banner = `/*!
|
|
13
|
-
* ${pkg.name} v${pkg.version}
|
|
14
|
-
* (c) ${new Date().getFullYear()} ${pkg.author} and other contributors
|
|
15
|
-
*
|
|
16
|
-
* Released under the ${pkg.license} License
|
|
17
|
-
* Date: ${new Date().toISOString().split('T')[0]}
|
|
18
|
-
*/`;
|
|
19
|
-
|
|
20
|
-
function removeComment() {
|
|
21
|
-
return {
|
|
22
|
-
name: "remove-sourcemap-comment",
|
|
23
|
-
writeBundle(outputOptions, bundle) {
|
|
24
|
-
for (const fileName in bundle) {
|
|
25
|
-
if (fileName.endsWith(".js")) {
|
|
26
|
-
const filePath = path.join(
|
|
27
|
-
outputOptions.dir || path.dirname(outputOptions.file),
|
|
28
|
-
fileName
|
|
29
|
-
);
|
|
30
|
-
|
|
31
|
-
let code = fs.readFileSync(filePath, "utf-8");
|
|
32
|
-
|
|
33
|
-
code = code.replace(/\/\/# sourceMappingURL=.*$/gm, "");
|
|
34
|
-
|
|
35
|
-
fs.writeFileSync(filePath, code);
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
};
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
export default [
|
|
43
|
-
{
|
|
44
|
-
input: "src/index.js",
|
|
45
|
-
output: [
|
|
46
|
-
{
|
|
47
|
-
file: "dist/exprify.esm.js",
|
|
48
|
-
format: "esm",
|
|
49
|
-
sourcemap: true
|
|
50
|
-
},
|
|
51
|
-
{
|
|
52
|
-
file: "dist/exprify.cjs.js",
|
|
53
|
-
format: "cjs",
|
|
54
|
-
sourcemap: true,
|
|
55
|
-
exports: "default"
|
|
56
|
-
},
|
|
57
|
-
{
|
|
58
|
-
file: "dist/exprify.js",
|
|
59
|
-
format: "umd",
|
|
60
|
-
name: "Exprify",
|
|
61
|
-
banner: banner,
|
|
62
|
-
sourcemap: true,
|
|
63
|
-
indent: true,
|
|
64
|
-
exports: "default"
|
|
65
|
-
}
|
|
66
|
-
],
|
|
67
|
-
plugins: [resolve(), commonjs(), removeComment()]
|
|
68
|
-
},
|
|
69
|
-
{
|
|
70
|
-
input: "src/index.js",
|
|
71
|
-
output: {
|
|
72
|
-
file: "dist/exprify.min.js",
|
|
73
|
-
format: "umd",
|
|
74
|
-
name: "Exprify",
|
|
75
|
-
sourcemap: true,
|
|
76
|
-
banner: short_banner
|
|
77
|
-
},
|
|
78
|
-
plugins: [resolve(), commonjs(), terser(), removeComment()]
|
|
79
|
-
}
|
|
80
|
-
];
|
package/test/browser.html
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
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>
|
package/test/exprify.test.js
DELETED
|
@@ -1,140 +0,0 @@
|
|
|
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
|
-
});
|