whalesong/runtime.js
2011-03-29 18:23:33 -04:00

466 lines
12 KiB
JavaScript

// Type representations:
//
// number are numbers
//
// cons pairs are [first, rest]
//
// function closures are Closures
// primitive procedures are regular functions.
// No error trapping at the moment.
var Frame = function(label, proc) {
this.label = label;
this.proc = proc;
};
// A closure consists of its free variables as well as a label
// into its text segment.
var Closure = function(label, arity, closedVals, displayName) {
this.label = label;
this.arity = arity;
this.closedVals = closedVals;
this.displayName = displayName;
};
// A primitive function is just a Javascript function.
// testArgument: (X -> boolean) X number string string -> boolean
// Produces true if val is true, and otherwise raises an error.
var testArgument = function(expectedTypeName,
predicate,
val,
position,
callerName) {
if (predicate(val)) {
return true;
}
else {
raise(new Error(callerName + ": expected " + expectedTypeName
+ " as argument #" + position
+ " but received " + val + " instead"));
}
};
var isNumber = function(x) { return typeof(x) === 'number'; };
var raise = function(e) { throw e; }
var Primitives = (function() {
var NULL = [];
return {
'display': function(MACHINE, arity) {
var firstArg = MACHINE.env[MACHINE.env.length-1];
MACHINE.params.currentDisplayer(firstArg);
},
'newline': function(MACHINE, arity) {
MACHINE.params.currentDisplayer("\n");
},
'displayln': function(MACHINE, arity){
var firstArg = MACHINE.env[MACHINE.env.length-1];
MACHINE.params.currentDisplayer(firstArg);
MACHINE.params.currentDisplayer("\n");
},
'pi' : Math.PI,
'e' : Math.E,
'=': function(MACHINE, arity) {
var firstArg = MACHINE.env[MACHINE.env.length-1];
var secondArg = MACHINE.env[MACHINE.env.length-2];
testArgument('number', isNumber, firstArg, 0, '=');
testArgument('number', isNumber, secondArg, 1, '=');
return firstArg === secondArg;
},
'<': function(MACHINE, arity) {
var firstArg = MACHINE.env[MACHINE.env.length-1];
var secondArg = MACHINE.env[MACHINE.env.length-2];
testArgument('number', isNumber, firstArg, 0, '<');
testArgument('number', isNumber, secondArg, 1, '<');
return firstArg < secondArg;
},
'>': function(MACHINE, arity) {
var firstArg = MACHINE.env[MACHINE.env.length-1];
var secondArg = MACHINE.env[MACHINE.env.length-2];
testArgument('number', isNumber, firstArg, 0, '>');
testArgument('number', isNumber, secondArg, 1, '>');
return firstArg > secondArg;
},
'<=': function(MACHINE, arity) {
var firstArg = MACHINE.env[MACHINE.env.length-1];
var secondArg = MACHINE.env[MACHINE.env.length-2];
testArgument('number', isNumber, firstArg, 0, '<=');
testArgument('number', isNumber, secondArg, 1, '<=');
return firstArg <= secondArg;
},
'>=': function(MACHINE, arity) {
var firstArg = MACHINE.env[MACHINE.env.length-1];
var secondArg = MACHINE.env[MACHINE.env.length-2];
testArgument('number', isNumber, firstArg, 0, '>=');
testArgument('number', isNumber, secondArg, 1, '>=');
return firstArg >= secondArg;
},
'+': function(MACHINE, arity) {
var result = 0;
var i = 0;
for (i=0; i < arity; i++) {
testArgument(
'number',
isNumber,
MACHINE.env[MACHINE.env.length - 1 - i],
i,
'+');
result += MACHINE.env[MACHINE.env.length - 1 - i];
};
return result;
},
'*': function(MACHINE, arity) {
var result = 1;
var i = 0;
for (i=0; i < arity; i++) {
testArgument(
'number',
isNumber,
MACHINE.env[MACHINE.env.length - 1 - i],
i,
'*');
result *= MACHINE.env[MACHINE.env.length - 1 - i];
}
return result;
},
'-': function(MACHINE, arity) {
if (arity === 0) { raise(new Error()); }
if (arity === 1) {
testArgument('number',
isNumber,
MACHINE.env[MACHINE.env.length-1],
0,
'-');
return -(MACHINE.env[MACHINE.env.length-1]);
}
var result = MACHINE.env[MACHINE.env.length - 1];
for (var i = 1; i < arity; i++) {
testArgument('number',
isNumber,
MACHINE.env[MACHINE.env.length-1-i],
i,
'-');
result -= MACHINE.env[MACHINE.env.length - 1 - i];
}
return result;
},
'/': function(MACHINE, arity) {
if (arity === 0) { raise(new Error()); }
testArgument('number',
isNumber,
MACHINE.env[MACHINE.env.length - 1],
0,
'/');
var result = MACHINE.env[MACHINE.env.length - 1];
for (var i = 1; i < arity; i++) {
result /= MACHINE.env[MACHINE.env.length - 1 - i];
}
return result;
},
'cons': function(MACHINE, arity) {
var firstArg = MACHINE.env[MACHINE.env.length-1];
var secondArg = MACHINE.env[MACHINE.env.length-2];
return [firstArg, secondArg];
},
'list': function(MACHINE, arity) {
var result = NULL;
for (var i = 0; i < arity; i++) {
result = [MACHINE.env[MACHINE.env.length - (arity - i)],
result];
}
return result;
},
'car': function(MACHINE, arity) {
var firstArg = MACHINE.env[MACHINE.env.length-1];
return firstArg[0];
},
'cdr': function(MACHINE, arity) {
var firstArg = MACHINE.env[MACHINE.env.length-1];
return firstArg[1];
},
'pair?': function(MACHINE, arity) {
var firstArg = MACHINE.env[MACHINE.env.length-1];
return (typeof(firstArg) == 'object' &&
firstArg.length === 2);
},
'set-car!': function(MACHINE, arity) {
var firstArg = MACHINE.env[MACHINE.env.length-1];
var secondArg = MACHINE.env[MACHINE.env.length-2];
firstArg[0] = secondArg;
},
'set-cdr!': function(MACHINE, arity) {
var firstArg = MACHINE.env[MACHINE.env.length-1];
var secondArg = MACHINE.env[MACHINE.env.length-2];
firstArg[1] = secondArg;
},
'not': function(MACHINE, arity) {
var firstArg = MACHINE.env[MACHINE.env.length-1];
return (!firstArg);
},
'null' : NULL,
'null?': function(MACHINE, arity) {
var firstArg = MACHINE.env[MACHINE.env.length-1];
return firstArg === NULL;
},
'add1': function(MACHINE, arity) {
var firstArg = MACHINE.env[MACHINE.env.length-1];
return firstArg + 1;
},
'sub1': function(MACHINE, arity) {
var firstArg = MACHINE.env[MACHINE.env.length-1];
return firstArg - 1;
},
'zero?': function(MACHINE, arity) {
var firstArg = MACHINE.env[MACHINE.env.length-1];
return firstArg === 0;
},
'vector': function(MACHINE, arity) {
var i;
var result = [];
for (i = 0; i < arity; i++) {
result.push(MACHINE.env[MACHINE.env.length-1-i]);
}
return result;
},
'vector->list': function(MACHINE, arity) {
var firstArg = MACHINE.env[MACHINE.env.length-1];
var i;
var result = NULL;
for (i = 0; i < firstArg.length; i++) {
result = [firstArg[firstArg.length - 1 - i], result];
}
return result;
},
'list->vector': function(MACHINE, arity) {
var firstArg = MACHINE.env[MACHINE.env.length-1];
var result = [];
while (firstArg !== NULL) {
result.push(firstArg[0]);
firstArg = firstArg[1];
}
return result;
},
'vector-ref': function(MACHINE, arity) {
var firstArg = MACHINE.env[MACHINE.env.length-1];
var secondArg = MACHINE.env[MACHINE.env.length-2];
return firstArg[secondArg];
},
'vector-set!': function(MACHINE, arity) {
var firstArg = MACHINE.env[MACHINE.env.length-1];
var secondArg = MACHINE.env[MACHINE.env.length-2];
var thirdArg = MACHINE.env[MACHINE.env.length-3];
firstArg[secondArg] = thirdArg;
return null;
},
'symbol?': function(MACHINE, arity) {
var firstArg = MACHINE.env[MACHINE.env.length-1];
return typeof(firstArg) === 'string';
},
'symbol->string': function(MACHINE, arity) {
var firstArg = MACHINE.env[MACHINE.env.length-1];
return firstArg;
},
'string-append': function(MACHINE, arity) {
var buffer = [];
var i;
for (i = 0; i < arity; i++) {
buffer.push(MACHINE.env[MACHINE.env.length - 1 - i]);
}
return buffer.join('');
},
'string-length': function(MACHINE, arity) {
var firstArg = MACHINE.env[MACHINE.env.length-1];
return firstArg.length;
},
'box': function(MACHINE, arity) {
var firstArg = MACHINE.env[MACHINE.env.length-1];
var result = [firstArg];
return result;
},
'unbox': function(MACHINE, arity) {
var firstArg = MACHINE.env[MACHINE.env.length-1];
return firstArg[0];
},
'set-box!': function(MACHINE, arity) {
var firstArg = MACHINE.env[MACHINE.env.length-1];
var secondArg = MACHINE.env[MACHINE.env.length-2];
firstArg[0] = secondArg;
return;
},
'void': function(MACHINE, arity) {
return;
},
'eq?': function(MACHINE, arity) {
var firstArg = MACHINE.env[MACHINE.env.length-1];
var secondArg = MACHINE.env[MACHINE.env.length-2];
return firstArg === secondArg;
},
'equal?': function(MACHINE, arity) {
var firstArg = MACHINE.env[MACHINE.env.length-1];
var secondArg = MACHINE.env[MACHINE.env.length-2];
var lset = [firstArg], rset = [secondArg];
while (lset.length !== 0 && rset.length !== 0) {
var lhs = lset.pop();
var rhs = rset.pop();
if (lhs === rhs) {
continue;
} else if (typeof(lhs) === 'object' &&
typeof(rhs) === 'object' &&
typeof(lhs.length) === 'number' &&
typeof(rhs.length) === 'number' &&
lhs.length === rhs.length) {
lset.push.apply(lset, lhs);
rset.push.apply(rset, rhs);
} else {
return false;
}
}
return true;
}
};
})();
var MACHINE = { callsBeforeTrampoline: 100,
val:undefined,
proc:undefined,
env: [],
control : [],
running : false,
params: { currentDisplayer: function(v) {},
currentSuccessHandler: function(MACHINE) {},
currentErrorHandler: function(MACHINE, exn) {},
currentNamespace: {},
// These parameters control how often
// control yields back to the browser
// for response. The implementation is a
// simple PID controller.
//
// To tune this, adjust desiredYieldsPerSecond.
// Do no touch numBouncesBeforeYield or
// maxNumBouncesBeforeYield, because those
// are adjusted automatically by the
// recomputeMaxNumBouncesBeforeYield
// procedure.
desiredYieldsPerSecond: 5,
numBouncesBeforeYield: 2000, // self-adjusting
maxNumBouncesBeforeYield: 2000 // self-adjusting
}
};
// recomputeGas: state number -> number
var recomputeMaxNumBouncesBeforeYield = function(observedDelay) {
// We'd like to see a delay of DESIRED_DELAY_BETWEEN_BOUNCES so
// that we get MACHINE.params.desiredYieldsPerSecond bounces per
// second.
var DESIRED_DELAY_BETWEEN_BOUNCES =
(1000 / MACHINE.params.desiredYieldsPerSecond);
var ALPHA = 256;
var delta = (ALPHA * ((DESIRED_DELAY_BETWEEN_BOUNCES -
observedDelay) /
DESIRED_DELAY_BETWEEN_BOUNCES));
MACHINE.params.maxNumBouncesBeforeYield =
Math.max(MACHINE.params.maxNumBouncesBeforeYield + delta,
1);
};
var trampoline = function(MACHINE, initialJump) {
var thunk = initialJump;
var startTime = (new Date()).valueOf();
MACHINE.callsBeforeTrampoline = 100;
MACHINE.params.numBouncesBeforeYield =
MACHINE.params.maxNumBouncesBeforeYield;
MACHINE.running = true;
while(thunk) {
try {
thunk(MACHINE);
break;
} catch (e) {
if (typeof(e) === 'function') {
thunk = e;
MACHINE.callsBeforeTrampoline = 100;
if (MACHINE.params.numBouncesBeforeYield-- < 0) {
recomputeMaxNumBouncesBeforeYield(
(new Date()).valueOf() - startTime);
setTimeout(
function() {
trampoline(MACHINE, thunk);
},
0);
return;
}
} else {
MACHINE.running = false;
return MACHINE.params.currentErrorHandler(MACHINE, e);
}
}
}
MACHINE.running = false;
return MACHINE.params.currentSuccessHandler(MACHINE);
};