Sample CPS transformations
The samples below will parse, transform to CPS and generate JavaScript. You can see how our λanguage compiles to proper JavaScript in continuation-passing style. Indentation of the output is done with UglifyJS (if you look at the page source you'll find that these samples are actually generated by your browser). The code to do this looks like this:
var ast = parse(TokenStream(InputStream(code)));
var cps = to_cps(ast, function(x){ return x });
var out = make_js(cps);
// get beautified output:
out = UglifyJS.parse(out).print_to_string({ beautify: true });
The “toplevel continuation” that we pass to to_cps
just
returns whatever AST node it gets.
You'll notice in the samples below that the return
keyword is
meaningless, just as it was in our CPS evaluator. Functions don't
return
, instead they call the continuation with the result.
Function calls
You can see how each function call injects as first argument a continuation that wraps the rest of the program.
a = foo(10);
|
→ |
a = foo(bar(10));
Might be counter-intuitive the fact that |
→ |
a = foo(10) + bar(20);
|
→ |
Sequences (prog)
a = foo();
b = bar();
c = baz();
Remember, the whole program is parsed into a |
→ |
foo();
bar();
baz();
However, this one pointlessly repeats the |
→ |
Conditionals
Notice how the following two if
samples compile exactly the same.
This is interesting and it signals a problem.
if foo() then a = 1 else a = 2;
|
→ |
a = if foo() then 1 else 2;
|
→ |
Here's what we get for consecutive if
-s:
a = if foo() then 1 else 2;
b = if bar() then 3 else 4;
c = if baz() then 5 else 6;
The output is technically correct, but the continuation of each
|
→ |
Functions (lambda)
add = λ(a, b) a + b;
|
→ |
dup = λ(x) add(x, x);
You can notice here the lack of tail call optimization. The last
(well, only, in our sample) thing that |
→ |
Still, the resulted code passes to add
a full continuation
whose only purpose is to call the original continuation
that dup
had with the result — β_K(β_R)
. That
whole function could be just replaced with β_K
:
dup = function(β_K_1, x) {
return add(β_K_1, x, x);
};
This is an important and simple optimization that we should do.
Let blocks
let (a = foo(), b = bar(a)) {
print(a + b);
}
We can notice that the way we currently handle |
→ |
The fib
function
Just to show a sample of a little more meaningful program:
fib = λ(n) {
if n < 2 then n
else
fib(n - 1) +
fib(n - 2);
};
print(fib(20));
|
→ |
Conclusions
So we still have a few issues to fix in order to get a decent language.
On top of that, we're back to square one in terms of recursion limit — but
the fix is quite easy and we'll use the same infrastructure we built for
the CPS evaluator: Execute
, Continuation
and
GUARD
. We'll fix a bunch of the issues in the next section directly in the CPS
transformer and JS code generator.