silentmatt / expr-eval

Mathematical expression evaluator in JavaScript
http://silentmatt.com/javascript-expression-evaluator/
MIT License
1.18k stars 239 forks source link

Substitute() nested variables #248

Open acarbonellb opened 3 years ago

acarbonellb commented 3 years ago

Hello!

Just wondering if there is a way to use substitute to resolve object property references. For example:

// having:
const exp = parser.parse('variable1 + myObject.variable2 + variable3')

// what I want to do is:
const resolved = exp.substitute('myObject.variable2', 1234)
resolved.toString() // 'variable1 + 1234 + variable3'

However, seems like the substitute mechanism is not able to interpolate it. Hope you can help me on this. Thanks!

silentmatt commented 3 years ago

The substitute function doesn't support that, but you could use simplify, which will evaluate what it can, and leave the rest of the expression alone.

const exp = parser.parse('variable1 + myObject.variable2 + variable3')
const resolved = exp.simplify({ myObject: { variable2: 1234 } })
resolved.toString() // '((variable1 + 1234) + variable3)'

The substitute and simplify functions are similar, but substitute is primarily intended for replacing variables with expressions, where simplify is more like partial evaluation, which is necessary in order to evaluate the .variable2 sub-expression. Supporting that does seem like a good possible enhancement though, so I'll leave this open for now to consider adding it.

acarbonellb commented 3 years ago

Hey @silentmatt thanks for the reply. I know about simplify but it does not fit my use case. To give more context, what I'm trying to do is to fully "resolve" an expression, just before evaluating it, for debugging and historic purposes. Ideally, what I'm after is something that would behave like "expression.substituteAll(variables?: object)" – Conceptually, of course. I've made it up.

To achieve this behaviour what I'm doing right now is to iterate over the expression variables() and resolve one by one on top of each new Expression returned by substitute(). And when I'm done iterating I call toString() on the final Expression, which is fully resolved because each iteration was accumulative. Like this:

const context = { a: 1, b: 2, c: 3 };
const expression = parser.parse('a + b + c');

const resolved = exp
  .variables() // ['a', 'b', 'c']
  .reduce((prevExp, v) => prevExp.substitute(v, context[v]), expression)
  .toString();

console.log(resolved); // '1 + 2 + 3'

This works great unless we have expressions with object references, like I was mentioning in the first post. Is there any way to achieve the ideal substituteAll() behaviour I was talking about? Thank you for helping!