diff --git a/relog-web/package-lock.json b/relog-web/package-lock.json index 8e075b9..118b5f4 100644 --- a/relog-web/package-lock.json +++ b/relog-web/package-lock.json @@ -16,6 +16,7 @@ "d3-array": "^2.12.1", "dagre": "^0.8.5", "idb": "^6.1.5", + "jsep": "^1.3.8", "leaflet": "^1.8.0", "react": "^17.0.2", "react-dom": "^17.0.2", @@ -11201,6 +11202,14 @@ } } }, + "node_modules/jsep": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/jsep/-/jsep-1.3.8.tgz", + "integrity": "sha512-qofGylTGgYj9gZFsHuyWAN4jr35eJ66qJCK4eKDnldohuUoQFbU3iZn2zjvEbd9wOAhP9Wx5DsAAduTyE1PSWQ==", + "engines": { + "node": ">= 10.16.0" + } + }, "node_modules/jsesc": { "version": "2.5.2", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", @@ -25021,6 +25030,11 @@ "xml-name-validator": "^3.0.0" } }, + "jsep": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/jsep/-/jsep-1.3.8.tgz", + "integrity": "sha512-qofGylTGgYj9gZFsHuyWAN4jr35eJ66qJCK4eKDnldohuUoQFbU3iZn2zjvEbd9wOAhP9Wx5DsAAduTyE1PSWQ==" + }, "jsesc": { "version": "2.5.2", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", diff --git a/relog-web/package.json b/relog-web/package.json index 67e313c..ce0553a 100644 --- a/relog-web/package.json +++ b/relog-web/package.json @@ -17,6 +17,7 @@ "d3-array": "^2.12.1", "dagre": "^0.8.5", "idb": "^6.1.5", + "jsep": "^1.3.8", "leaflet": "^1.8.0", "react": "^17.0.2", "react-dom": "^17.0.2", diff --git a/relog-web/src/casebuilder/expr.js b/relog-web/src/casebuilder/expr.js new file mode 100644 index 0000000..d871a34 --- /dev/null +++ b/relog-web/src/casebuilder/expr.js @@ -0,0 +1,50 @@ +import { Jsep } from "jsep"; +import { exportValue } from "./export"; + +export const evaluateExpr = (expr, data) => { + const node = Jsep.parse(expr); + return evaluateNode(node, data); +}; + +const evaluateNode = (node, data) => { + if (node.type == "BinaryExpression") { + return evaluateBinaryExprNode(node, data); + } else if (node.type == "UnaryExpression") { + return evaluateUnaryExprNode(node, data); + } else if (node.type == "Literal") { + return node.value; + } else if (node.type == "Identifier") { + return data[node.name]; + } else { + throw `Unknown type: ${node.type}`; + } +}; + +const evaluateBinaryExprNode = (node, data) => { + const leftVal = evaluateNode(node.left, data); + const rightVal = evaluateNode(node.right, data); + if (node.operator == "+") { + return leftVal + rightVal; + } else if (node.operator == "*") { + return leftVal * rightVal; + } else if (node.operator == "/") { + return leftVal / rightVal; + } else if (node.operator == "-") { + return leftVal - rightVal; + } else if (node.operator == "^") { + return Math.pow(leftVal, rightVal); + } else { + throw `Unknown operator: ${node.operator}`; + } +}; + +const evaluateUnaryExprNode = (node, data) => { + const arg = evaluateNode(node.argument, data); + if (node.operator == "+") { + return arg; + } else if (node.operator == "-") { + return -arg; + } else { + throw `Unknown operator: ${node.operator}`; + } +}; diff --git a/relog-web/src/casebuilder/expr.test.js b/relog-web/src/casebuilder/expr.test.js new file mode 100644 index 0000000..ddd293a --- /dev/null +++ b/relog-web/src/casebuilder/expr.test.js @@ -0,0 +1,19 @@ +import { evaluateExpr } from "./expr"; + +test("parse expression", () => { + // Basic expressions + expect(evaluateExpr("1 + 1")).toEqual(2); + expect(evaluateExpr("2 * 5")).toEqual(10); + expect(evaluateExpr("2 * (3 + 5)")).toEqual(16); + expect(evaluateExpr("14 / 2")).toEqual(7); + expect(evaluateExpr("10 - 3")).toEqual(7); + expect(evaluateExpr("-10")).toEqual(-10); + expect(evaluateExpr("+10")).toEqual(10); + expect(evaluateExpr("2^3")).toEqual(8); + expect(evaluateExpr("2^(3 + 1)")).toEqual(16); + + // With data + expect(evaluateExpr("x + 1", { x: 10 })).toEqual(11); + expect(evaluateExpr("2 ^ (3 + x)", { x: 1 })).toEqual(16); + expect(evaluateExpr("x + y", { x: 1, y: 2 })).toEqual(3); +});