// 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;
};



var Primitives = (function() {
    var NULL = [];
    return {
	'display': function(arity, returnLabel) {
	    var firstArg = MACHINE.env[MACHINE.env.length-1];
            MACHINE.params.currentDisplayer(firstArg);
	},

	'newline': function(arity, returnLabel) {
            MACHINE.params.currentDisplayer("\n");
	},

	'displayln': function(arity, returnLabel){
	    var firstArg = MACHINE.env[MACHINE.env.length-1];
            MACHINE.params.currentDisplayer(firstArg);
            MACHINE.params.currentDisplayer("\n");
	},

	'pi' : Math.PI,

	'e' : Math.E,

	'=': function(arity, returnLabel) {
	    var firstArg = MACHINE.env[MACHINE.env.length-1];
	    var secondArg = MACHINE.env[MACHINE.env.length-2];
            return firstArg === secondArg;
	},

	'<': function(arity, returnLabel) {
	    var firstArg = MACHINE.env[MACHINE.env.length-1];
	    var secondArg = MACHINE.env[MACHINE.env.length-2];
	    return firstArg < secondArg;
	},

	'>': function(arity, returnLabel) {
	    var firstArg = MACHINE.env[MACHINE.env.length-1];
	    var secondArg = MACHINE.env[MACHINE.env.length-2];
	    return firstArg > secondArg;
	},

	'<=': function(arity, returnLabel) {
	    var firstArg = MACHINE.env[MACHINE.env.length-1];
	    var secondArg = MACHINE.env[MACHINE.env.length-2];
	    return firstArg <= secondArg;
	},

	'>=': function(arity, returnLabel) {
	    var firstArg = MACHINE.env[MACHINE.env.length-1];
	    var secondArg = MACHINE.env[MACHINE.env.length-2];
	    return firstArg >= secondArg;
	},
	
	'+': function(arity, returnLabel) {
	    var firstArg = MACHINE.env[MACHINE.env.length-1];
	    var secondArg = MACHINE.env[MACHINE.env.length-2];

            return firstArg + secondArg;
	},
	
	'*': function(arity, returnLabel) {
	    var firstArg = MACHINE.env[MACHINE.env.length-1];
	    var secondArg = MACHINE.env[MACHINE.env.length-2];
            return firstArg * secondArg;
	},
	
	'-': function(arity, returnLabel) {
	    var firstArg = MACHINE.env[MACHINE.env.length-1];
	    var secondArg = MACHINE.env[MACHINE.env.length-2];
            return firstArg - secondArg;
	},
	
	'/': function(arity, returnLabel) {
	    var firstArg = MACHINE.env[MACHINE.env.length-1];
	    var secondArg = MACHINE.env[MACHINE.env.length-2];
	    return firstArg / secondArg;
	},

	'cons': function(arity, returnLabel) {
	    var firstArg = MACHINE.env[MACHINE.env.length-1];
	    var secondArg = MACHINE.env[MACHINE.env.length-2];
	    return [firstArg, secondArg];
	},

	'list': function(arity, returnLabel) {
	    var result = NULL;
	    for (var i = 0; i < arity; i++) {
		result = [MACHINE.env[MACHINE.env.length - (arity - i)],
			  result];
	    }
	    return result;
	},

	'car': function(arity, returnLabel) {
	    var firstArg = MACHINE.env[MACHINE.env.length-1];
	    return firstArg[0];
	},

	'cdr': function(arity, returnLabel) {
	    var firstArg = MACHINE.env[MACHINE.env.length-1];
	    return firstArg[1];
	},
	
	'not': function(arity, returnLabel) {
	    var firstArg = MACHINE.env[MACHINE.env.length-1];
	    return (!firstArg);
	},

	'null' : NULL,

	'null?': function(arity, returnLabel) {
	    var firstArg = MACHINE.env[MACHINE.env.length-1];
	    return firstArg === NULL;
	},

	'add1': function(arity, returnLabel) {
	    var firstArg = MACHINE.env[MACHINE.env.length-1];
	    return firstArg + 1;
	},

	'sub1': function(arity, returnLabel) {
	    var firstArg = MACHINE.env[MACHINE.env.length-1];
	    return firstArg - 1;
	},

	'vector': function(arity, returnLabel) {
	    var i;
	    var result = [];
	    for (i = 0; i < arity; i++) {
		result.push(MACHINE.env[MACHINE.env.length-1-i]);
	    }
	    return result;
	},

	'vector-ref': function(arity, returnLabel) {
	    var firstArg = MACHINE.env[MACHINE.env.length-1];
	    var secondArg = MACHINE.env[MACHINE.env.length-2];
	    return firstArg[secondArg];
	},

	'vector-set!': function(arity, returnLabel) {
	    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(arity, returnLabel) {
	    var firstArg = MACHINE.env[MACHINE.env.length-1];
	    return typeof(firstArg) === 'string';
	},

 	'call/cc': new Closure(callCCEntry,
 			       1,
 			       [],
 			       "call/cc"),
	'call-with-current-continuation': new Closure(callCCEntry,
 						      1,
 						      [],
 						      "call-with-current-continuation")

    };
})();




// // adaptToJs: closure -> (array (X -> void) -> void)
// // Converts closures to functions that can be called from the
// // JavaScript toplevel.
// Closure.prototype.adaptToJs = function() {
//     var that = this;
//     return function(args, success, fail) {
//         var oldEnv = MACHINE.env;
// 	var oldCont = MACHINE.cont;
// 	var oldProc = MACHINE.proc;
// 	var oldArgl = MACHINE.argl;
// 	var oldVal = MACHINE.val;
// 	trampoline(
// 	    function() {
// 		var proc = that;
// 		MACHINE.proc = proc;
// 		MACHINE.argl = undefined;
// 		for(var i = args.length - 1; i >= 0; i--) {
// 		    MACHINE.argl = [args[i], MACHINE.argl];
// 		}
		
// 		MACHINE.cont = function() {
// 		    var result = MACHINE.val;
//                     MACHINE.env = oldEnv;
// 		    MACHINE.cont = oldCont;
// 		    MACHINE.proc = oldProc;
// 		    MACHINE.argl = oldArgl;
// 		    MACHINE.val = oldVal;
//                     success(result);
// 		};
		
// 		proc.label();
//             },
//             function() {
//             },
//             function(e) {
// 		return fail(e);
// 	    });
//     }
// };



var MACHINE={callsBeforeTrampoline: 100, 
             val:undefined,
             proc:undefined, 
             env: [],
	     control : [],
             params: { currentDisplayer: function(v) {},
		       currentErrorHandler: function(e) {},
		       currentNamespace: {}}};


var trampoline = function(initialJump, success, fail) {
    var thunk = initialJump;
    MACHINE.callsBeforeTrampoline = 100;
    while(thunk) {
        try {
            thunk();
	    break;
        } catch (e) {
            if (typeof(e) === 'function') {
                thunk = e;
                MACHINE.callsBeforeTrampoline = 100;
            } else {
	        return fail(e);
            }
        }
    }
    return success();
};