UNPKG

simple-eval

Version:

Simple JavaScript expression evaluator

230 lines (228 loc) 6.19 kB
// src/utils.ts function isConstructable(fn) { try { new new Proxy(fn, { construct: () => ({}) })(); return true; } catch { return false; } } function isCallable(value) { return typeof value === "function"; } function fnToString(fn) { return Function.toString.call(fn); } function toString(value) { const stringTag = value[Symbol.toStringTag]; if (typeof stringTag === "string") { return stringTag; } return Object.prototype.toString.call(value).slice(8, -1); } function printValue(value) { switch (typeof value) { case "string": case "number": case "boolean": case "symbol": case "undefined": case "bigint": return String(value); case "function": return fnToString(value); case "object": if (value === null) { return "null"; } return `#<${toString(value)}>`; } } // src/reduce.ts function reduce(node, ctx) { switch (node.type) { case "Program": return reduceProgram(node, ctx); case "ExpressionStatement": return reduce(node.expression, ctx); case "MemberExpression": return reduceMemExpr(node, ctx); case "LogicalExpression": return reduceLogExpr(node, ctx); case "ConditionalExpression": return reduceConExpr(node, ctx); case "BinaryExpression": return reduceBinExpr(node, ctx); case "UnaryExpression": return reduceUnExpr(node, ctx); case "CallExpression": return reduceCallExpr(node, ctx); case "NewExpression": return reduceNewExpr(node, ctx); case "ArrayExpression": return reduceArrExpr(node, ctx); case "ThisExpression": return ctx; case "Identifier": return resolveIdentifier(node.name, ctx); case "Literal": return node.value; default: throw SyntaxError("Unexpected node"); } } function reduceProgram(node, ctx) { if (node.body.length !== 1) { throw SyntaxError("Too complex expression"); } return reduce(node.body[0], ctx); } function reduceMemExpr(node, ctx) { const value = reduce(node.object, ctx); const key = node.property.type === "Identifier" ? node.property.name : reduce(node.property, ctx); if (value === null || value === void 0) { throw TypeError( `Cannot read properties of ${String(value)} (reading '${printValue(value)}')` ); } const accessedValue = value[key]; return isCallable(accessedValue) ? accessedValue.bind(value) : accessedValue; } function reduceCallExpr(node, ctx) { const maybeCallable = reduce(node.callee, ctx); if (!isCallable(maybeCallable)) { throw TypeError(`${maybeCallable} is not a function`); } return Reflect.apply( maybeCallable, null, node.arguments.map((arg) => reduce(arg, ctx)) ); } function reduceNewExpr(node, ctx) { const maybeConstructable = reduce(node.callee, ctx); if (!isConstructable(maybeConstructable)) { throw TypeError(`${printValue(maybeConstructable)} is not a constructor`); } return Reflect.construct( maybeConstructable, node.arguments.map((arg) => reduce(arg, ctx)) ); } function reduceUnExpr(node, ctx) { if (!node.prefix || node.argument.type === "UnaryExpression") { throw SyntaxError("Unexpected operator"); } switch (node.operator) { case "!": return !reduce(node.argument, ctx); case "-": return -reduce(node.argument, ctx); case "+": return +reduce(node.argument, ctx); case "~": return ~reduce(node.argument, ctx); case "typeof": return typeof reduce(node.argument, ctx); case "void": return void reduce(node.argument, ctx); case "delete": { if (node.argument.type !== "MemberExpression") { return true; } const obj = reduce(node.argument.object, ctx); if (typeof obj !== "object" || obj === null) { return true; } return Reflect.deleteProperty( obj, node.argument.property.type === "Identifier" ? node.argument.property.name : reduce(node.argument.property, ctx) ); } default: throw SyntaxError(`Unsupported unary operator: ${node.operator}`); } } function reduceConExpr(node, ctx) { return reduce(node.test, ctx) ? reduce(node.consequent, ctx) : reduce(node.alternate, ctx); } function reduceBinExpr(node, ctx) { return evalLhsRhs(node, ctx); } function reduceLogExpr(node, ctx) { return evalLhsRhs(node, ctx); } function reduceArrExpr(node, ctx) { return node.elements.map((el) => el === null ? null : reduce(el, ctx)); } function evalLhsRhs(node, ctx) { const left = reduce(node.left, ctx); const right = reduce(node.right, ctx); const isLogicalExpression = node.type === "LogicalExpression"; switch (node.operator) { case "-": return left - right; case "+": return left + right; case "==": return left == right; case "!=": return left != right; case "===": return left === right; case "!==": return left !== right; case "<": return left < right; case "<=": return left <= right; case ">": return left > right; case ">=": return left >= right; case "<<": return left << right; case ">>": return left >> right; case ">>>": return left >>> right; case "*": return left * right; case "/": return left / right; case "%": return left % right; case "**": return left ** right; case "|": return left | right; case "^": return left ^ right; case "&": return left & right; case "in": return left in right; case "instanceof": return left instanceof right; case "&&": return left && right; case "||": return left || right; case "??": return left ?? right; default: throw SyntaxError( `Unsupported ${isLogicalExpression ? "logical" : "binary"} operator: ${node["operator"]}` ); } } function resolveIdentifier(name, ctx) { if (ctx === void 0 || !(name in ctx)) { throw ReferenceError(`${name} is not defined`); } return Reflect.get(ctx, name, ctx); } export { reduce }; //# sourceMappingURL=chunk-7ZYRIKQM.js.map