Things look a little less promising after running a simple test. This input JavaScript:
while (i--) {
twait {
fs.readFile("one");
fs.readFile("two");
}
}
Gets compiled into this resulting "tamed" JavaScript:
var tame = require('tamejs').runtime;
var __tame_fn_0 = function (__tame_k) {
var __tame_k_implicit = {};
var __tame_fn_1 = function (__tame_k) {
if (i --) {
var __tame_fn_2 = function (__tame_k) {
var __tame_ev = new tame.Event (__tame_k);
var __tame_fn_3 = function (__tame_k) {
fs .readFile ( "one" ) ;
fs .readFile ( "two" ) ;
tame.callChain([__tame_k]);
};
__tame_fn_3(tame.end);
__tame_ev.trigger();
};
tame.callChain([__tame_fn_2, __tame_fn_1, __tame_k]);
} else {
tame.callChain([__tame_k]);
}
};
__tame_k_implicit.k_break = __tame_k;
__tame_k_implicit.k_continue = function() { __tame_fn_1(__tame_k); };
tame.callChain([__tame_fn_1, __tame_k]);
};
__tame_fn_0 (tame.end);
... not so nice to work with or debug. The general conclusion of that series of tickets was that the code generation required to make this CPS transformation work with all edge cases is a bit too hairy to be worth it on balance. Depending on how much sequential async you're doing, YMMV.
It's on the list of todos to preserve the input line-numbering in the output code. This would mean some changes to the code emitter, and also to the code emitted to preserve the ordering of statements.
In tame/C++, debugging either compiler or runtime errors is straight-ahead, as the developer doesn't need to examine the mangled code. Now if only JavaScript had the equivalent of cpp's #line directive....
Progress was made but looks like it's stalled atm. Since Tame has an endpoint maybe easier to just suck it in?
Edit:
That compiled script looks a wee scary. I need to be able to fully dive into a debugger with clarity and can't imagine if there were tens or hundreds of lines of this.
As jjm says, with Tame as a concrete, proven solution, it'd sure be great to have CS adopt something like it. Turning the problem "inside out" like this may be the best approach.
I.e., it has well-defined semantics and "only" involves JS rewriting, which is CS's forte.
The branch which attempted the defer keyword also had working code. The input was clean and the edge cases of what to do with return, try/catch, etc had been thought through.
The stopping issue was twofold: Coffeescript cares about the readability of both the compiler input and output and… Coffeescript cares about not inserting dynamic lookups and calls into the program which can add undefined performance impact.
Perhaps a fork of coffeescript that is targeted for writers of async NodeJS code would be able to safely trade those off. I think jashkenas believes that Javascript will benefit from many little languages springing up to solve specific problems.
https://github.com/jashkenas/coffee-script/issues/241
https://github.com/jashkenas/coffee-script/issues/287
https://github.com/jashkenas/coffee-script/issues/350
Edit:
Things look a little less promising after running a simple test. This input JavaScript:
Gets compiled into this resulting "tamed" JavaScript: ... not so nice to work with or debug. The general conclusion of that series of tickets was that the code generation required to make this CPS transformation work with all edge cases is a bit too hairy to be worth it on balance. Depending on how much sequential async you're doing, YMMV.