I struggle with this question from time to time.
Lots of tools compile to JS, not just compilers for other languages but also JS language extensions and instrumentation. In those cases you want to minimally perturb the semantics of JS. Direct eval
is the hard part I struggle with.
Let's assume for the moment you have some global variable $RT
you can access to get to the compiler's runtime. There are tricky bits to this, but let's just take it as given.
When you see an apparent direct eval
in the source program, of the form eval(EXPR)
, you might translate to something like:
($RT.tmpEval = eval, $RT.tmpArg = EXPR, $RT.directEval((eval) => eval($RT.tmpArg)))
where $RT.directEval
is defined like so:
$RT.directEval = function(thunk) {
if (this.tmpEval === this.ORIGINAL_EVAL) {
this.tmpArg = this.compile(this.tmpArg);
return thunk(this.ORIGINAL_EVAL);
}
return thunk(this.tmpEval);
};
This strategy is careful to get a bunch of things right:
- it evaluates the callee and argument in the right order
- it only evaluates the callee and argument once each
- it performs a direct eval
- it doesn't bind any temporary variables, which would become visible to direct eval
- it only compiles the dynamic argument if the
eval
turns out to be the realeval
function (actually this behavior is arguable, but you can do it either way) - it uses fat arrow to respect the binding of
this
andarguments
(with some extra work you can use old-fashioned functions, you just have to make sure to restore these bindings)
In particular, it means you can compile a source snippet like this:
var obj = {
eval: function() { console.log("side effect!"); return eval; },
x: "hello world"
};
with (obj) {
eval("console.log(x)")
}
and it should only print "side effect!"
once.
The problem is that strict mode doesn't allow you to rebind the variable name eval
as I've done above. What I haven't figured out is how to make this work in strict mode. I can't use with
(and wouldn't want to, for performance), and I can't escape strict mode using Function
because then I lose the local scope I need for direct eval
.
(Just to be clear I'm not interested in "deep" compilation techniques that emulate all object operations, etc. I'm looking for reasonably local transformations and strategies that compile objects as objects, so e.g. they can perfectly interoperate with native libraries like the DOM.)