Last active
June 23, 2016 12:43
-
-
Save obuchtala/9d53e426778868818c8a6fdbab2c183d to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /* | |
| 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