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.
@@ -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
- // 🔥 UNIT handling
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
@@ -1,2 +0,0 @@
1
- # Auto detect text files and perform LF normalization
2
- * text=auto
@@ -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>
@@ -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
- });