Monday, October 20, 2008

Updated javascript.plt

I've just updated my implementation of JavaScript in PLT Scheme. The latest version fixes some of the subtle ways I'd deviated from the spec with respect to variable binding. Back in the day, I was wooed into thinking that JavaScript was so similar to Scheme that I could pretty easily just compile JavaScript variable bindings directly to similar Scheme variable bindings. For the most part, I got variable hoisting (i.e., the scope of var-declarations being hoisted to the top of their nearest enclosing function body) right. But the real subtlety comes in with the with (dynamically add a computed object value to the runtime environment as a new environment frame) and eval constructs.

I almost had with right: as soon as I detect that the programmer is using with, I enter a "stupid" mode, where I synthesize an explicit runtime environment and populate it with whatever's currently in the static environment. Within the body of the with, variable lookup is performed dynamically in this runtime environment. So outside of a with, you get normal, efficient lexical scope; inside a with, you get stupid, slow, dynamic scope. My only mistake there was forgetting to make the runtime environment entries aliases to the existing variables so that mutations persist after the with-block returns.

But eval is hard: Scheme's eval doesn't have access to the lexical environment where it is called, whereas JavaScript's does. And JavaScript's eval also inherits the ambient "variable object" (ECMA-262 10.1.3), which means that eval code can create variable bindings in the caller's scope. Getting all of this right means simulating the variable object similarly to the way I simulate the runtime environment, and switching into "stupid" mode whenever I detect a potential application of eval. (The standard allows you to refuse to run eval in situations other than a "direct" call to a variable reference spelled e-v-a-l, so you don't have to treat every single function call as a potential call to eval. It's messy, but it's better than forcing you to implement a dynamic environment everywhere.)

4 comments:

Adrian said...

Eval...eval... with possible creation of bidings in the caller's context...
Can't you just use a C/EPS (continuation/environment passing style) transformation of the code ?

(define (evaluate expr env bin-cont)
(if (number? expr)
(bin-cont env expr)
...)))

By doing this, EVAL should be able to return the environment to use in the continuation, shouldn't? (Or was your point about not seeing this feature on your first read of the specs?)

P!

Dave Herman said...

There are lots of ways I could implement `eval', but I wanted to write my compiler "shallow-embedding" style.

In truth, I hadn't paid much attention to `eval' on my first reading. So I did overlook this feature. But since I had already implemented the environment-passing mode for `with', implementing `eval' was possible. Just ugly.

MarkM said...

How much cleaner do you think an implementation of ES3.1-strict would be in PLT Scheme than full ES3.1? The answer may be a good test of how well ES3.1-strict has met its goals. Thoughts?

Dave Herman said...

The answer may be a good test of how well ES3.1-strict has met its goals. Thoughts?

Well, any real implementation of ES will have to implement the whole language, not just 3.1-strict, so it wouldn't necessarily tell us anything about implementability. As far as semantic cleanliness, it would mostly just tell us how much closer the language is to the semantics of PLT Scheme. That might be an improvement but it is certainly subjective. I'd be more likely to try to judge the language directly rather than through a benchmark like how easy it is to compile to Scheme. Seems a little gimmicky to me.

OTOH, it might be at least a sanity check to help us find any potential gotchas. So maybe it's worth trying as an attempt to falsify rather than to verify.