TomFrost / Jexl

Javascript Expression Language: Powerful context-based expression parser and evaluator
MIT License
573 stars 93 forks source link

Minus token is confused for binaryOp in function arguments #137

Open fdb opened 9 months ago

fdb commented 9 months ago

I wanted to use a function with negative values. In the first argument position this works. However in the second argument position Jexl is confused because it expects a binaryOp and this is a unaryOp.

import jexl from "jexl";

function add(a, b) {
  return a + b;
}
jexl.addFunction("add", add);

// This works
const result1 = jexl.evalSync("add(-5, 3)");
console.log(result1);

// This does not:
// Error: Token - (binaryOp) unexpected in expression: add(5, -
const result2 = jexl.evalSync("add(5, -3)");
console.log(result2);

Here's the stack trace:

/home/runner/JexlBinaryOpConfusion/node_modules/jexl/dist/parser/Parser.js:99
        throw new Error("Token ".concat(token.raw, " (").concat(token.type, ") unexpected in expression: ").concat(this._exprStr));
              ^

Error: Token - (binaryOp) unexpected in expression: add(5, -
    at Parser.addToken (/home/runner/JexlBinaryOpConfusion/node_modules/jexl/dist/parser/Parser.js:99:15)
    at Parser.addToken (/home/runner/JexlBinaryOpConfusion/node_modules/jexl/dist/parser/Parser.js:73:41)
    at Array.forEach (<anonymous>)
    at Parser.addTokens (/home/runner/JexlBinaryOpConfusion/node_modules/jexl/dist/parser/Parser.js:114:14)
    at Expression.compile (/home/runner/JexlBinaryOpConfusion/node_modules/jexl/dist/Expression.js:42:14)
    at Expression._getAst (/home/runner/JexlBinaryOpConfusion/node_modules/jexl/dist/Expression.js:92:28)
    at /home/runner/JexlBinaryOpConfusion/node_modules/jexl/dist/Expression.js:83:25
    at PromiseSync.then (/home/runner/JexlBinaryOpConfusion/node_modules/jexl/dist/PromiseSync.js:37:25)
    at Expression._eval (/home/runner/JexlBinaryOpConfusion/node_modules/jexl/dist/Expression.js:82:32)
    at Expression.evalSync (/home/runner/JexlBinaryOpConfusion/node_modules/jexl/dist/Expression.js:72:22)

Here's a demo on Replit: https://replit.com/@fdb/JexlBinaryOpConfusion

ablsc commented 8 months ago

If memory serves correctly this issue is raised in another ticket, likely to not be obvious at first but when digging in to the issue it's all the same (again if memory serves).

The work-around is to make your negative value an 'expression' ie. "add(5, 0 - 3)" instead of "add(5, -3)"

fdb commented 8 months ago

Thanks! Stupid but it works :-) I now have the following code:

// Jexl can't deal with unary "-" operators. This function replaces this with a binary operator.
// It's ridiculous, but it works.
// So an expression like this will fail: add(1, -1)
// We replace it with: add(1, 0-1)
// We don't do full tokenization, we just use regular expressions to fix common cases.
function fixupExpression(expr) {
  return expr.replace(/,\s*-/g, ', 0-');
}

export function evalExpression(expr) {
  expr = fixupExpression(expr);
  return jexl.evalSync(expr, context);
}