Skip to content

Commit 20fe200

Browse files
committed
update
1 parent 1bdb36e commit 20fe200

14 files changed

Lines changed: 856 additions & 36 deletions

README.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,17 @@ console.log(expr.evaluate("double(5) + 3"));
9090
// 13
9191
```
9292

93+
### Inline function definitions
94+
95+
You can also define functions directly inside Exprify expressions.
96+
97+
```js
98+
expr.evaluate("hyp(a, b) = sqrt(a ^ 2 + b ^ 2)");
99+
100+
console.log(expr.evaluate("hyp(3, 4)"));
101+
// 5
102+
```
103+
93104
## Supported Features
94105

95106
### Arithmetic and precedence

dist/exprify.cjs.js

Lines changed: 49 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -592,6 +592,16 @@ function evaluateAST(node, context = {}) {
592592
const simplifyComplex = (value) =>
593593
value.im === 0 ? value.re : value;
594594

595+
const createFunctionScope = (params, args) => {
596+
const scopedValues = {};
597+
598+
params.forEach((param, index) => {
599+
scopedValues[param] = args[index];
600+
});
601+
602+
return scopedValues;
603+
};
604+
595605
const evalComplexBinary = (operator, left, right) => {
596606
const a = toComplex(left);
597607
const b = toComplex(right);
@@ -660,6 +670,20 @@ function evaluateAST(node, context = {}) {
660670
throw new Error("Invalid assignment target");
661671
}
662672

673+
case "FunctionAssignmentExpression": {
674+
if (node.operator !== "=") {
675+
throw new Error(`Operator ${node.operator} is not supported for function definitions`);
676+
}
677+
678+
const fn = (...args) => {
679+
const scopedContext = context.withScope(createFunctionScope(node.params, args));
680+
return evaluateAST(node.right, scopedContext);
681+
};
682+
683+
fns.register(node.left.name, fn);
684+
return fn;
685+
}
686+
663687
/* ===== UNARY ===== */
664688
case "UnaryExpression": {
665689
const val = evaluateAST(node.argument, context);
@@ -680,7 +704,7 @@ function evaluateAST(node, context = {}) {
680704
let left = evaluateAST(node.left, context);
681705
let right = evaluateAST(node.right, context);
682706

683-
// 🔥 UNIT handling
707+
// UNIT handling
684708
if (isUnitObj(left) || isUnitObj(right)) {
685709

686710
if (!units) throw new Error("Unit system not available");
@@ -1767,7 +1791,7 @@ function buildAST(tokens) {
17671791
case "Identifier":
17681792
return { type: "Identifier", name: token.name };
17691793

1770-
case "Function": // 🔥 ADD THIS
1794+
case "Function":
17711795
return {
17721796
type: "Identifier",
17731797
name: token.name
@@ -2172,6 +2196,29 @@ function buildAST(tokens) {
21722196
) {
21732197
const operator = tokens[current - 1].value;
21742198

2199+
if (left.type === "CallExpression") {
2200+
const isFunctionTarget =
2201+
left.callee?.type === "Identifier" &&
2202+
left.arguments.every((arg) => arg.type === "Identifier");
2203+
2204+
if (!isFunctionTarget) {
2205+
throw new Error("Invalid function definition");
2206+
}
2207+
2208+
const right = parseAssignment();
2209+
2210+
return {
2211+
type: "FunctionAssignmentExpression",
2212+
operator,
2213+
left: {
2214+
type: "Identifier",
2215+
name: left.callee.name
2216+
},
2217+
params: left.arguments.map((arg) => arg.name),
2218+
right
2219+
};
2220+
}
2221+
21752222
if (
21762223
left.type !== "Identifier" &&
21772224
left.type !== "MemberExpression" &&

dist/exprify.cjs.js.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/exprify.esm.js

Lines changed: 49 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -590,6 +590,16 @@ function evaluateAST(node, context = {}) {
590590
const simplifyComplex = (value) =>
591591
value.im === 0 ? value.re : value;
592592

593+
const createFunctionScope = (params, args) => {
594+
const scopedValues = {};
595+
596+
params.forEach((param, index) => {
597+
scopedValues[param] = args[index];
598+
});
599+
600+
return scopedValues;
601+
};
602+
593603
const evalComplexBinary = (operator, left, right) => {
594604
const a = toComplex(left);
595605
const b = toComplex(right);
@@ -658,6 +668,20 @@ function evaluateAST(node, context = {}) {
658668
throw new Error("Invalid assignment target");
659669
}
660670

671+
case "FunctionAssignmentExpression": {
672+
if (node.operator !== "=") {
673+
throw new Error(`Operator ${node.operator} is not supported for function definitions`);
674+
}
675+
676+
const fn = (...args) => {
677+
const scopedContext = context.withScope(createFunctionScope(node.params, args));
678+
return evaluateAST(node.right, scopedContext);
679+
};
680+
681+
fns.register(node.left.name, fn);
682+
return fn;
683+
}
684+
661685
/* ===== UNARY ===== */
662686
case "UnaryExpression": {
663687
const val = evaluateAST(node.argument, context);
@@ -678,7 +702,7 @@ function evaluateAST(node, context = {}) {
678702
let left = evaluateAST(node.left, context);
679703
let right = evaluateAST(node.right, context);
680704

681-
// 🔥 UNIT handling
705+
// UNIT handling
682706
if (isUnitObj(left) || isUnitObj(right)) {
683707

684708
if (!units) throw new Error("Unit system not available");
@@ -1765,7 +1789,7 @@ function buildAST(tokens) {
17651789
case "Identifier":
17661790
return { type: "Identifier", name: token.name };
17671791

1768-
case "Function": // 🔥 ADD THIS
1792+
case "Function":
17691793
return {
17701794
type: "Identifier",
17711795
name: token.name
@@ -2170,6 +2194,29 @@ function buildAST(tokens) {
21702194
) {
21712195
const operator = tokens[current - 1].value;
21722196

2197+
if (left.type === "CallExpression") {
2198+
const isFunctionTarget =
2199+
left.callee?.type === "Identifier" &&
2200+
left.arguments.every((arg) => arg.type === "Identifier");
2201+
2202+
if (!isFunctionTarget) {
2203+
throw new Error("Invalid function definition");
2204+
}
2205+
2206+
const right = parseAssignment();
2207+
2208+
return {
2209+
type: "FunctionAssignmentExpression",
2210+
operator,
2211+
left: {
2212+
type: "Identifier",
2213+
name: left.callee.name
2214+
},
2215+
params: left.arguments.map((arg) => arg.name),
2216+
right
2217+
};
2218+
}
2219+
21732220
if (
21742221
left.type !== "Identifier" &&
21752222
left.type !== "MemberExpression" &&

dist/exprify.esm.js.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/exprify.js

Lines changed: 49 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -603,6 +603,16 @@
603603
const simplifyComplex = (value) =>
604604
value.im === 0 ? value.re : value;
605605

606+
const createFunctionScope = (params, args) => {
607+
const scopedValues = {};
608+
609+
params.forEach((param, index) => {
610+
scopedValues[param] = args[index];
611+
});
612+
613+
return scopedValues;
614+
};
615+
606616
const evalComplexBinary = (operator, left, right) => {
607617
const a = toComplex(left);
608618
const b = toComplex(right);
@@ -671,6 +681,20 @@
671681
throw new Error("Invalid assignment target");
672682
}
673683

684+
case "FunctionAssignmentExpression": {
685+
if (node.operator !== "=") {
686+
throw new Error(`Operator ${node.operator} is not supported for function definitions`);
687+
}
688+
689+
const fn = (...args) => {
690+
const scopedContext = context.withScope(createFunctionScope(node.params, args));
691+
return evaluateAST(node.right, scopedContext);
692+
};
693+
694+
fns.register(node.left.name, fn);
695+
return fn;
696+
}
697+
674698
/* ===== UNARY ===== */
675699
case "UnaryExpression": {
676700
const val = evaluateAST(node.argument, context);
@@ -691,7 +715,7 @@
691715
let left = evaluateAST(node.left, context);
692716
let right = evaluateAST(node.right, context);
693717

694-
// 🔥 UNIT handling
718+
// UNIT handling
695719
if (isUnitObj(left) || isUnitObj(right)) {
696720

697721
if (!units) throw new Error("Unit system not available");
@@ -1778,7 +1802,7 @@
17781802
case "Identifier":
17791803
return { type: "Identifier", name: token.name };
17801804

1781-
case "Function": // 🔥 ADD THIS
1805+
case "Function":
17821806
return {
17831807
type: "Identifier",
17841808
name: token.name
@@ -2183,6 +2207,29 @@
21832207
) {
21842208
const operator = tokens[current - 1].value;
21852209

2210+
if (left.type === "CallExpression") {
2211+
const isFunctionTarget =
2212+
left.callee?.type === "Identifier" &&
2213+
left.arguments.every((arg) => arg.type === "Identifier");
2214+
2215+
if (!isFunctionTarget) {
2216+
throw new Error("Invalid function definition");
2217+
}
2218+
2219+
const right = parseAssignment();
2220+
2221+
return {
2222+
type: "FunctionAssignmentExpression",
2223+
operator,
2224+
left: {
2225+
type: "Identifier",
2226+
name: left.callee.name
2227+
},
2228+
params: left.arguments.map((arg) => arg.name),
2229+
right
2230+
};
2231+
}
2232+
21862233
if (
21872234
left.type !== "Identifier" &&
21882235
left.type !== "MemberExpression" &&

dist/exprify.js.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/exprify.min.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/exprify.min.js.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/parser/astBuild.js

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ export function buildAST(tokens) {
6565
case "Identifier":
6666
return { type: "Identifier", name: token.name };
6767

68-
case "Function": // 🔥 ADD THIS
68+
case "Function":
6969
return {
7070
type: "Identifier",
7171
name: token.name
@@ -470,6 +470,29 @@ export function buildAST(tokens) {
470470
) {
471471
const operator = tokens[current - 1].value;
472472

473+
if (left.type === "CallExpression") {
474+
const isFunctionTarget =
475+
left.callee?.type === "Identifier" &&
476+
left.arguments.every((arg) => arg.type === "Identifier");
477+
478+
if (!isFunctionTarget) {
479+
throw new Error("Invalid function definition");
480+
}
481+
482+
const right = parseAssignment();
483+
484+
return {
485+
type: "FunctionAssignmentExpression",
486+
operator,
487+
left: {
488+
type: "Identifier",
489+
name: left.callee.name
490+
},
491+
params: left.arguments.map((arg) => arg.name),
492+
right
493+
};
494+
}
495+
473496
if (
474497
left.type !== "Identifier" &&
475498
left.type !== "MemberExpression" &&

0 commit comments

Comments
 (0)