// 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 CallFrame = function(label, proc) { this.label = label; this.proc = proc; }; var PromptFrame = function(label, tag) { this.label = label; this.tag = tag; }; // 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; }; var ContinuationPromptTag = function(name) { this.name = name; }; var DEFAULT_CONTINUATION_PROMPT_TAG = new ContinuationPromptTag("default-continuation-prompt-tag"); // 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 captureControl = function(MACHINE, skip, tag) { var i; for (i = MACHINE.control.length - 1 - skip; i >= 0; i--) { if (MACHINE.control[i].tag === tag) { return MACHINE.control.slice(i + 1, MACHINE.control.length - skip); } } raise(new Error("captureControl: unable to find tag " + tag)); }; var restoreControl = function(MACHINE, tag) { var i; for (i = MACHINE.control.length - 1; i >= 0; i--) { if (MACHINE.control[i].tag === tag) { MACHINE.control = MACHINE.control.slice(0, i+1).concat( MACHINE.env[MACHINE.env.length - 1]); return; } } raise(new Error("restoreControl: unable to find tag " + tag)); } 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); };