- // The execution core needs to be able to store incomplete/partial
- // computations and data. When evaluation eventually happens, it
- // needs to store the result, so other references don't need to
- // recompute. So we need mutable data, that can be referred to.
- // The 3 candidates for doing this are:
- // - array [] (5.3sec)
- // - object {} (4.4sec)
- // - function that operates on a "free variable"/closure. (18sec)
- //
- // some simple benchmarks on v8 showed that arrays and objects perform
- // likewise (obj slightly faster, but uses more memory). However, for
- // storing things like partially applied functions
- // (function, arg1, arg2 .. argN), that have many things to store, an
- // array is a better fit. objects would have to use an array-field or
- // resort to generated field-names {arg1: 2, arg2: 4}.
- // functions perform worst. So we focus on arrays for now.
- // Every entity is an array, where its first argument is a number
- // indicating its evaluation status and expected outcome.
- /*
- lambda machine 'thunk' types
- # stuff that cannot be evaluated further
- 1 = prim (wrapped javascript primitive)
- 2 = cons ("real" constructor)
- 3 = part (partially applied function)
- # thunks waiting to be evaluated
- 10 = collapse (thunk only referring to other thunk)
- 11 = thunk (function, ready to be applied)
- 12 = app (unevaluated part with args -> thunk)
- */
- // - prim is native javascript value, boxed
- // - cons is a normal functional value (constructor + fields)
- // - part is a function and its arguments, having too few yet.
- // - collapse happens during tail recursion, when all that is left is
- // a new thunk to evaluate. As we cannot tell references to update their
- // pointers, we collapse in place (copy status/value from other thunk)
- // - thunk (should probably be renamed) is a function with enough (or
- // even too much) arguments. It can be evaluated, but because of
- // laziness, it hasn't yet.
- // - app (should get better name too) is when we have a partially
- // applied function, and some additional arguments.
- // evaluating turns it into 11 or 3.
- var slice = Array.prototype.slice,
- splice = Array.prototype.splice;
- // compiler should inline this
- function prim(obj) { // wrap a primitive
- if (typeof obj === 'object' || typeof obj === 'function') { // array/obj/date/new String
- return [1, obj];
- } else { // string/number/bool
- return obj;
- }
- }
- // compiler should inline this
- function cons(constructorName) { // constructorName, *fields
- var result = slice.call(arguments);
- result.unshift(2);
- return result;
- };
- // compiler should inline this and perform compile-time analysis
- function thunk(fn) { // fn, args
- var result = slice.call(arguments);
- switch(typeof fn) {
- case 'function':
- if (fn.length < arguments.length) { // thunk that can be applied
- result.unshift(11);
- } else {
- result.unshift(3);
- }
- break;
- case 'object':
- if (result.length === 1) {
- result.unshift(10);
- } else {
- result.unshift(12);
- }
- break;
- default:
- result.unshift(1);
- }
- return result;
- }
- function leval(expr) {
- if (typeof expr !== 'object') { // runtime primitive
- return expr;
- }
- var first, type, args, result;
- while (true) {
- type = expr[0];
- first = expr[1];
- switch(type) {
- case 1: // runtime primitive
- return first;
- case 2: // whnf
- return expr.slice(1);
- case 3: // partial function application
- return expr.slice(1);
- case 10: // collapse
- if(first[0] >= 10) { leval(first); } // make sure it's evaluated
- result = first;
- break;
- case 11: // thunk
- args = expr.slice(2, 2 + first.length);
- result = thunk.apply(null, [first.apply(null, args)].concat(expr.slice(2 + first.length)));
- break;
- case 12: // app
- if(first[0] >= 10) { leval(first); } // make sure it's evaluated
- if (first[0] !== 3) { throw 'can only apply partial functions'; }
- result = thunk.apply(null, first.slice(1).concat(expr.slice(2)))
- break;
- default:
- throw 'unknown state in lambda machine';
- }
- splice.apply(expr, [0, expr.length].concat(result)); // pop into here
- }
- };
- // basic interface (out -> in)
- exports.leval = exports.$ = leval;
- exports.thunk = exports.$$ = thunk;
- // optimized constructors
- exports.prim = prim;
- exports.cons = cons;