Skip to content

Instantly share code, notes, and snippets.

@obuchtala
Last active June 23, 2016 12:43
Show Gist options
  • Select an option

  • Save obuchtala/9d53e426778868818c8a6fdbab2c183d to your computer and use it in GitHub Desktop.

Select an option

Save obuchtala/9d53e426778868818c8a6fdbab2c183d to your computer and use it in GitHub Desktop.
/*
This example shows how JS expression can be executed with dynamic context.
Consider you want to use symbols in an expression, for example
```
sum(1,2,PI)+ln(100)-x
```
The problem is, that eval() does not provide a means to ingest a context, so that
`sum`, `PI`, `ln`, or `x` could be evaluated.
There is a pretty sophisticated solution however.
1. as opposed to eval(), using `new Function(param1, param2, ...., source)`
allows us to declare variables used in the source.
2. Assuming that we only consider simple expressions `source = "return "+expr`
3. Because the number of ingested variables and their names are dynamic,
we need to create a constructor with dynamic arguments using `bind`
```
var ExprFunction = Function.prototype.bind.apply(Function, [null].concat(vars).concat(source));
var exprFunc = new ExprFunction();
```
> Note: creating a custom Function constructor calling `Function.prototype.bind` on
the default `Function` constructor.
As we have a variable number of arguments we need to use `bind.apply` for that.
`apply` takes as first argument the `this` context to be bound, which is not
used by a constructor, so we provide `null`.
The remaining arguments of the signature of `Function` are `symbols` and
the javascript source forming the function body.
4. The generated function can be executed with dynamic arguments
extracted from the context.
```
exprFunc.apply(null, symbols.map(function(name) { return context[name]; }));
```
*/
function ContextExpression(expr, symbols) {
this.symbols = symbols;
var source = "return "+expr;
var ExprFunction = Function.prototype.bind.apply(Function, [null].concat(symbols).concat(source));
this.f = new ExprFunction();
}
ContextExpression.prototype.eval = function(context) {
var args = this.symbols.map(function(name) {
return context[name];
});
return this.f.apply(null, args);
};
var context = {
PI: Math.PI,
sum: function() {
return Array.prototype.reduce.call(arguments, function(a, b) {
return a + b;
}, 0);
},
ln: Math.log10,
x: 100
};
var src = "sum(1,2,PI)+ln(100)-x";
var expression = new ContextExpression(src, Object.keys(context));
console.log(expression.eval(context));
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment