if(this['plt'] === undefined) { this['plt'] = {}; } // All of the values here are namespaced under "plt.runtime". (function() { this['plt']['runtime'] = {}; var exports = this['plt']['runtime']; // Type helpers // // Defines inheritance between prototypes. var heir = function(parentPrototype) { var f = function() {} f.prototype = parentPrototype; return new f(); }; // Consumes a class and creates a predicate that recognizes subclasses. var makeClassPredicate = function(aClass) { return function(x) { return x instanceof aClass; }; }; var isNumber = function(x) { return typeof(x) === 'number'; }; var isPair = function(x) { return (typeof(x) == 'object' && x.length === 2) }; var isList = function(x) { while (x !== NULL) { if (typeof(x) == 'object' && x.length === 2) { x = x[1]; } else { return false; } } return true; }; var isVector = function(x) { return (typeof(x) == 'object' && x.length !== undefined) }; var Machine = function() { this.callsBeforeTrampoline = 100; this.val = undefined; this.proc = undefined; this.argcount = undefined; this.env = []; this.control = []; // Arrayof (U CallFrame PromptFrame) this.running = false; this.params = { 'currentDisplayer': function(v) {}, 'currentOutputPort': new StandardOutputPort(), '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 }; this.primitives = Primitives; }; var Frame = function() {}; // Control stack elements: // A CallFrame represents a call stack frame. var CallFrame = function(label, proc) { this.label = label; this.proc = proc; }; CallFrame.prototype = heir(Frame.prototype); // PromptFrame represents a prompt frame. var PromptFrame = function(label, tag) { this.label = label; this.tag = tag; // ContinuationPromptTag }; PromptFrame.prototype = heir(Frame.prototype); var OutputPort = function() {}; var isOutputPort = makeClassPredicate(OutputPort); var StandardOutputPort = function() {}; StandardOutputPort.prototype = heir(OutputPort.prototype); StandardOutputPort.prototype.write = function(MACHINE, v) { MACHINE.params['currentDisplayer'](v); }; var OutputStringPort = function() { this.buf = []; }; OutputStringPort.prototype = heir(OutputPort.prototype); OutputStringPort.prototype.write = function(MACHINE, v) { this.buf.push(String(v)); }; OutputStringPort.prototype.getOutputString = function() { return this.buf.join(''); }; var isOutputStringPort = makeClassPredicate(OutputStringPort); // Function types: a function is either a Primitive or a Closure. // A Primitive is a function that's expected to return. It is not // allowed to call into Closures. Its caller is expected to pop off // its argument stack space. // // // A Closure is a function that takes on more responsibilities: it is // responsible for popping off stack space before it finishes, and it // is also explicitly responsible for continuing the computation by // popping off the control stack and doing the jump. Because of this, // closures can do pretty much anything to the machine. // 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; // (MACHINE -> void) this.arity = arity; // number this.closedVals = closedVals; // arrayof number this.displayName = displayName; // string }; // A continuation prompt tag labels a prompt frame. var ContinuationPromptTag = function(name) { this.name = name; }; // There is a single, distinguished default continuation prompt tag // that's used to wrap around toplevel prompts. var DEFAULT_CONTINUATION_PROMPT_TAG = new ContinuationPromptTag("default-continuation-prompt-tag"); var NULL = []; var raise = function(e) { throw e; } // 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 testArity = function(callerName, observed, minimum, maximum) { if (observed < minimum || observed > maximum) { raise(new Error(callerName + ": expected at least " + minimum + " arguments " + " but received " + observer)); } }; // captureControl implements the continuation-capturing part of // call/cc. It grabs the control frames up to (but not including) the // prompt tagged by the given tag. 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)); }; // restoreControl clears the control stack (up to, but not including the // prompt tagged by tag), and then appends the rest of the control frames. // At the moment, the rest of the control frames is assumed to be in the // top of the environment. 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)); }; // Splices the list argument in the environment. Adjusts MACHINE.argcount // appropriately. var spliceListIntoStack = function(MACHINE, depth) { var lst = MACHINE.env[MACHINE.env.length - 1 - depth]; var vals = []; while(lst !== NULL) { vals.push(lst[0]); lst = lst[1]; } vals.reverse(); MACHINE.env.splice.apply(MACHINE.env, [MACHINE.env.length - 1 - depth, 1].concat(vals)); MACHINE.argcount = MACHINE.argcount + vals.length - 1; }; // An arity is either a primitive number, an ArityAtLeast instance, // or a list of either primitive numbers or ArityAtLeast instances. var ArityAtLeast = function(n) { this.value = n; }; // isArityMatching: arity natural -> boolean // Produces true if n satisfies the arity. var isArityMatching = function(arity, n) { if (typeof(arity) === 'number') { return arity === n; } else if (arity instanceof ArityAtLeast) { return n >= arity.value; } else { while (arity !== NULL) { if (typeof(arity[0]) === 'number') { if (arity[0] === n) { return true; } } else if (arity instanceof ArityAtLeast) { if (n >= arity[0].value) { return true; } } arity = arity[1]; } return false; } } // Primitives are the set of primitive values. Not all primitives // are coded here; several of them (including call/cc) are injected by // the bootstrapping code. var Primitives = {}; Primitives['display'] = function(MACHINE, arity) { var firstArg = MACHINE.env[MACHINE.env.length-1]; var outputPort = MACHINE.params.currentOutputPort; if (arity === 2) { outputPort = MACHINE.env[MACHINE.env.length-2]; } outputPort.write(MACHINE, firstArg); }; Primitives['display'].arity = [1, [2, NULL]]; Primitives['display'].displayName = 'display'; Primitives['newline'] = function(MACHINE, arity) { var outputPort = MACHINE.params.currentOutputPort; if (arity === 1) { outputPort = MACHINE.env[MACHINE.env.length-1]; } outputPort.write(MACHINE, "\n"); }; Primitives['newline'].arity = [0, [1, NULL]]; Primitives['newline'].displayName = 'newline'; Primitives['displayln'] = function(MACHINE, arity){ var firstArg = MACHINE.env[MACHINE.env.length-1]; var outputPort = MACHINE.params.currentOutputPort; if (arity === 2) { outputPort = MACHINE.env[MACHINE.env.length-2]; } outputPort.write(MACHINE, firstArg); outputPort.write(MACHINE, "\n"); }; Primitives['displayln'].arity = [1, [2, NULL]]; Primitives['displayln'].displayName = 'displayln'; Primitives['pi'] = Math.PI; Primitives['e'] = Math.E; Primitives['='] = function(MACHINE, arity) { var firstArg = MACHINE.env[MACHINE.env.length-1]; testArgument('number', isNumber, firstArg, 0, '='); for (var i = 1; i < arity; i++) { testArgument('number', isNumber, MACHINE.env[MACHINE.env.length - 1 - i], i, '='); if (MACHINE.env[MACHINE.env.length - 1 - i] !== MACHINE.env[MACHINE.env.length - 1 - i - 1]) { return false; } } return true; }; Primitives['='].arity = new ArityAtLeast(2); Primitives['='].displayName = '='; Primitives['<'] = function(MACHINE, arity) { var firstArg = MACHINE.env[MACHINE.env.length-1]; testArgument('number', isNumber, firstArg, 0, '<'); for (var i = 1; i < arity; i++) { testArgument('number', isNumber, MACHINE.env[MACHINE.env.length - 1 - i], i, '<'); if (! (MACHINE.env[MACHINE.env.length - 1 - i] < MACHINE.env[MACHINE.env.length - 1 - i - 1])) { return false; } } return true; }; Primitives['<'].arity = new ArityAtLeast(2); Primitives['<'].displayName = '<'; Primitives['>'] = function(MACHINE, arity) { var firstArg = MACHINE.env[MACHINE.env.length-1]; testArgument('number', isNumber, firstArg, 0, '>'); for (var i = 1; i < arity; i++) { testArgument('number', isNumber, MACHINE.env[MACHINE.env.length - 1 - i], i, '>'); if (! (MACHINE.env[MACHINE.env.length - 1 - i] > MACHINE.env[MACHINE.env.length - 1 - i - 1])) { return false; } } return true; }; Primitives['>'].arity = new ArityAtLeast(2); Primitives['>'].displayName = '>'; Primitives['<='] = function(MACHINE, arity) { var firstArg = MACHINE.env[MACHINE.env.length-1]; testArgument('number', isNumber, firstArg, 0, '<='); for (var i = 1; i < arity; i++) { testArgument('number', isNumber, MACHINE.env[MACHINE.env.length - 1 - i], i, '<='); if (! (MACHINE.env[MACHINE.env.length - 1 - i] <= MACHINE.env[MACHINE.env.length - 1 - i - 1])) { return false; } } return true; }; Primitives['<='].arity = new ArityAtLeast(2); Primitives['<='].displayName = '<='; Primitives['>='] = function(MACHINE, arity) { var firstArg = MACHINE.env[MACHINE.env.length-1]; testArgument('number', isNumber, firstArg, 0, '>='); for (var i = 1; i < arity; i++) { testArgument('number', isNumber, MACHINE.env[MACHINE.env.length - 1 - i], i, '>='); if (! (MACHINE.env[MACHINE.env.length - 1 - i] >= MACHINE.env[MACHINE.env.length - 1 - i - 1])) { return false; } } return true; }; Primitives['>='].arity = new ArityAtLeast(2); Primitives['>='].displayName = '>='; Primitives['+'] = 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; }; Primitives['+'].arity = new ArityAtLeast(0); Primitives['+'].displayName = '+'; Primitives['*'] = 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; }; Primitives['*'].arity = new ArityAtLeast(0); Primitives['*'].displayName = '*'; Primitives['-'] = function(MACHINE, arity) { 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; }; Primitives['-'].arity = new ArityAtLeast(1); Primitives['-'].displayName = '-'; Primitives['/'] = function(MACHINE, arity) { 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; }; Primitives['/'].arity = new ArityAtLeast(1); Primitives['/'].displayName = '/'; Primitives['cons'] = function(MACHINE, arity) { var firstArg = MACHINE.env[MACHINE.env.length-1]; var secondArg = MACHINE.env[MACHINE.env.length-2]; return [firstArg, secondArg]; }; Primitives['cons'].arity = 2; Primitives['cons'].displayName = 'cons'; Primitives['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; }; Primitives['list'].arity = new ArityAtLeast(0); Primitives['list'].displayName = 'list'; Primitives['car'] = function(MACHINE, arity) { testArgument('pair', isPair, MACHINE.env[MACHINE.env.length - 1], 0, 'car'); var firstArg = MACHINE.env[MACHINE.env.length-1]; return firstArg[0]; }; Primitives['car'].arity = 1; Primitives['car'].displayName = 'car'; Primitives['cdr'] = function(MACHINE, arity) { testArgument('pair', isPair, MACHINE.env[MACHINE.env.length - 1], 0, 'cdr'); var firstArg = MACHINE.env[MACHINE.env.length-1]; return firstArg[1]; }; Primitives['cdr'].arity = 1; Primitives['cdr'].displayName = 'cdr'; Primitives['pair?'] = function(MACHINE, arity) { var firstArg = MACHINE.env[MACHINE.env.length-1]; return isPair(firstArg); }; Primitives['pair?'].arity = 1; Primitives['pair?'].displayName = 'pair?'; Primitives['set-car!'] = function(MACHINE, arity) { testArgument('pair', isPair, MACHINE.env[MACHINE.env.length - 1], 0, 'set-car!'); var firstArg = MACHINE.env[MACHINE.env.length-1]; var secondArg = MACHINE.env[MACHINE.env.length-2]; firstArg[0] = secondArg; }; Primitives['set-car!'].arity = 2; Primitives['set-car!'].displayName = 'set-car!'; Primitives['set-cdr!'] = function(MACHINE, arity) { testArgument('pair', isPair, MACHINE.env[MACHINE.env.length - 1], 0, 'set-cdr!'); var firstArg = MACHINE.env[MACHINE.env.length-1]; var secondArg = MACHINE.env[MACHINE.env.length-2]; firstArg[1] = secondArg; }; Primitives['set-cdr!'].arity = 2; Primitives['set-cdr!'].displayName = 'set-cdr!'; Primitives['not'] = function(MACHINE, arity) { var firstArg = MACHINE.env[MACHINE.env.length-1]; return (!firstArg); }; Primitives['not'].arity = 1; Primitives['not'].displayName = 'not'; Primitives['null'] = NULL; Primitives['null?'] = function(MACHINE, arity) { var firstArg = MACHINE.env[MACHINE.env.length-1]; return firstArg === NULL; }; Primitives['null?'].arity = 1; Primitives['null?'].displayName = 'null?'; Primitives['add1'] = function(MACHINE, arity) { testArgument('number', isNumber, MACHINE.env[MACHINE.env.length - 1], 0, 'add1'); var firstArg = MACHINE.env[MACHINE.env.length-1]; return firstArg + 1; }; Primitives['add1'].arity = 1; Primitives['add1'].displayName = 'add1'; Primitives['sub1'] = function(MACHINE, arity) { testArgument('number', isNumber, MACHINE.env[MACHINE.env.length - 1], 0, 'sub1'); var firstArg = MACHINE.env[MACHINE.env.length-1]; return firstArg - 1; }; Primitives['sub1'].arity = 1; Primitives['sub1'].displayName = 'sub1'; Primitives['zero?'] = function(MACHINE, arity) { var firstArg = MACHINE.env[MACHINE.env.length-1]; return firstArg === 0; }; Primitives['zero?'].arity = 1; Primitives['zero?'].displayName = 'zero?'; Primitives['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; }; Primitives['vector'].arity = new ArityAtLeast(0); Primitives['vector'].displayName = 'vector'; Primitives['vector->list'] = function(MACHINE, arity) { testArgument('vector', isVector, MACHINE.env[MACHINE.env.length - 1], 0, 'vector->list'); 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; }; Primitives['vector->list'].arity = 1; Primitives['vector->list'].displayName = 'vector->list'; Primitives['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; }; Primitives['list->vector'].arity = 1; Primitives['list->vector'].displayName = 'list->vector'; Primitives['vector-ref'] = function(MACHINE, arity) { testArgument('vector', isVector, MACHINE.env[MACHINE.env.length - 1], 0, 'vector-ref'); var firstArg = MACHINE.env[MACHINE.env.length-1]; var secondArg = MACHINE.env[MACHINE.env.length-2]; return firstArg[secondArg]; }; Primitives['vector-ref'].arity = 2; Primitives['vector-ref'].displayName = 'vector-ref'; Primitives['vector-set!'] = function(MACHINE, arity) { testArgument('vector', isVector, MACHINE.env[MACHINE.env.length - 1], 0, 'vector-set!'); 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; }; Primitives['vector-set!'].arity = 3; Primitives['vector-set!'].displayName = 'vector-set!'; Primitives['symbol?'] = function(MACHINE, arity) { var firstArg = MACHINE.env[MACHINE.env.length-1]; return typeof(firstArg) === 'string'; }; Primitives['symbol?'].arity = 1; Primitives['symbol?'].displayName = 'symbol?'; Primitives['symbol->string'] = function(MACHINE, arity) { var firstArg = MACHINE.env[MACHINE.env.length-1]; return firstArg; }; Primitives['symbol->string'].arity = 1; Primitives['symbol->string'].displayName = 'symbol->string'; Primitives['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(''); }; Primitives['string-append'].arity = new ArityAtLeast(0); Primitives['string-append'].displayName = 'string-append'; Primitives['string-length'] = function(MACHINE, arity) { var firstArg = MACHINE.env[MACHINE.env.length-1]; return firstArg.length; }; Primitives['string-length'].arity = 1; Primitives['string-length'].displayName = 'string-length'; Primitives['box'] = function(MACHINE, arity) { var firstArg = MACHINE.env[MACHINE.env.length-1]; var result = [firstArg]; return result; }; Primitives['box'].arity = 1; Primitives['box'].displayName = 'box'; Primitives['unbox'] = function(MACHINE, arity) { var firstArg = MACHINE.env[MACHINE.env.length-1]; return firstArg[0]; }; Primitives['unbox'].arity = 1; Primitives['unbox'].displayName = 'unbox'; Primitives['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; }; Primitives['set-box!'].arity = 2; Primitives['set-box!'].displayName = 'set-box!'; Primitives['void'] = function(MACHINE, arity) { return; }; Primitives['void'].arity = new ArityAtLeast(0); Primitives['void'].displayName = 'void'; Primitives['eq?'] = function(MACHINE, arity) { var firstArg = MACHINE.env[MACHINE.env.length-1]; var secondArg = MACHINE.env[MACHINE.env.length-2]; return firstArg === secondArg; }; Primitives['eq?'].arity = 2; Primitives['eq?'].displayName = 'eq?'; Primitives['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; }; Primitives['equal?'].arity = 2; Primitives['equal?'].displayName = 'equal?'; // recomputeGas: state number -> number var recomputeMaxNumBouncesBeforeYield = function(MACHINE, 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( MACHINE, (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); }; // Exports exports['Machine'] = Machine; exports['CallFrame'] = CallFrame; exports['PromptFrame'] = PromptFrame; exports['Closure'] = Closure; exports['ContinuationPromptTag'] = ContinuationPromptTag; exports['DEFAULT_CONTINUATION_PROMPT_TAG'] = DEFAULT_CONTINUATION_PROMPT_TAG; exports['NULL'] = NULL; exports['testArgument'] = testArgument; exports['testArity'] = testArity; exports['raise'] = raise; exports['captureControl'] = captureControl; exports['restoreControl'] = restoreControl; exports['trampoline'] = trampoline; exports['spliceListIntoStack'] = spliceListIntoStack; exports['isNumber'] = isNumber; exports['isPair'] = isPair; exports['isList'] = isList; exports['isVector'] = isVector; exports['isOutputPort'] = isOutputPort; exports['isOutputStringPort'] = isOutputStringPort; exports['ArityAtLeast'] = ArityAtLeast; exports['isArityMatching'] = isArityMatching; exports['heir'] = heir; exports['makeClassPredicate'] = makeClassPredicate; }).call(this);