This essay explains how the ES6 Realm API makes it possible to create robust language abstractions that allow hooking into the behavior of eval
, and how this can be used to implement different dialects of JavaScript.
Imagine we want to add Doug Crockford's ??
operator, which acts like a short-circuiting logical OR operator, except instead of checking truthiness, it returns the first argument's value if the first argument is any value other than undefined
.
Since it makes everything simpler and cleaner, I'm going to assume I can use do
-expressions for the implementation. (They're looking good for ES7!) So with that said, when we "crockpile" EXPR1 ?? EXPR2
we should get:
do {
let tmp = EXPR1;
(typeof tmp !== 'undefined') ? tmp : EXPR2
}
except that tmp
has to be a fresh variable every time an instance of ??
is crockpiled, to avoid accidental name collisions.
The challenge is that even if you choose a fresh variable that isn't in scope, you can't be sure that an instance of direct eval
won't accidentally discover the variable name you chose. For example, if a particular instance of ??
crockpiles to a generated variable name tmp$1
, a naive crockpilation would still allow this to be discovered with direct eval
. For example, this code:
undefined ?? eval("tmp$1")
would naively crockpile to:
do {
let tmp$1 = undefined;
(typeof tmp$1 !== 'undefined') ? tmp$1 : eval("tmp$1")
}
but the real program should actually produce a reference to a global variable called tmp$1
, since the user's program does not actually create a local variable called tmp$1
. So the correct crockpilation needs some way of communicating to the direct eval
that it should ignore references to tmp$1
and treat them as global references.
The solution is for the crockpiler to inject a representation of the crockpiler environment at all direct eval
sites, so that the direct eval
can use that environment for its own crockpilation. So the above example crockpiles instead to:
do {
let tmp$1 = undefined;
(typeof tmp$1 !== 'undefined') ? tmp$1 : eval([{"tmp$1":"GENSYM"}], "tmp$1")
}
Now when the direct eval
is performed, it has a reified representation of the crockpilation environment to start in, so it knows when it comes across a reference to tmp$1
to replace it with something like window.tmp$1
.
Does the realm API used here correspond to what's proposed in https://gist.github.com/caridy/311ad2c17a0cd875ae17ac11ffb597a9 ?