//////////////////////////////////////////////////////////////////////


var run = function(state) {
	while (!state.isStuck()) {
	    interpret.step(state);
	}
	return state.v;
}

var step = interpret.step;


//////////////////////////////////////////////////////////////////////

var EXIT_ON_FIRST_ERROR = true;


//////////////////////////////////////////////////////////////////////


var StateModule = state;


var makeStateWithConstant = function(c) {
    var s = new StateModule.State();
    s.v = c;
    return s;
};


var makePrefix = function(n) {
    var arr = [];    
    for (var i = 0; i < n; i++) {
	arr.push(false);
    }
    return new control.Prefix({numLifts: 0,
			       toplevels: arr });
};

var makeMod = function(prefix, body) {
    return new control.ModControl(prefix, [], body);
};

var makeConstant = function(c) {
    return new control.ConstantControl(c);
};

var makeBranch = function(x, y, z) { 
    return new control.BranchControl(x, y, z);
};

var makeSeq = function() {
    return new control.SeqControl(arguments);
};

var makeBeg0 = function() {
    return new control.Beg0Control(arguments);
};

var makeToplevel = function(depth, pos) {
    return new control.ToplevelControl(depth, pos);
};


var makeDefValues = function(ids, body) {
    return new control.DefValuesControl(ids, body);
};


var makeLam = function(arity, closureMap, body) {
    var aClosureMap = [];
    var aClosureTypes = [];
    var aParamTypes = [];
    for (var i = 0; i < closureMap.length; i++) {
	aClosureMap.push(closureMap[i]);
	aClosureTypes.push("val/ref");
    }
    for (var i = 0; i < arity; i++) {
	aParamTypes.push("val");
    }

    return new control.LamControl({'numParams': arity,
				   'paramTypes': aParamTypes,
				   'isRest': false,
				   'closureMap' : aClosureMap,
				   'closureTypes' : aClosureTypes,
				   'body': body});    
};


var makeLamWithRest = function(arity, closureMap, body) {
    var aClosureMap = [];
    var aClosureTypes = [];
    var aParamTypes = [];
    for (var i = 0; i < closureMap.length; i++) {
	aClosureMap.push(closureMap[i]);
	aClosureTypes.push("val/ref");
    }
    for (var i = 0; i < arity; i++) {
	aParamTypes.push("val");
    }

    return new control.LamControl({'numParams': arity,
				   'paramTypes': aParamTypes,
				   'isRest': true,
				   'closureMap' : aClosureMap,
				   'closureTypes' : aClosureTypes,
				   'body': body});    
};






var makePrimval = function(name) {
    return new control.PrimvalControl(name);
};


var makeApplication = function(rator, rands) {
    assert.ok(typeof(rands) === 'object' && rands.length !== undefined);
    return new control.ApplicationControl(rator, rands);
};


var makeLocalRef = function(n) {
    return new control.LocalrefControl(n);
};


var makeApplyValues = function(proc, argsExpr) {
    return new control.ApplyValuesControl(proc, argsExpr);
};


var makeLet1 = function(rhs, body) {
    return new control.LetOneControl(rhs, body);
};


var makeLetVoid = function(count, isBoxes, body) {
    return new control.LetVoidControl({count: count,
				       isBoxes : isBoxes,
				       body : body});
};

var makeBoxenv = function(pos, body) {
    return new control.BoxenvControl(pos, body);
};


var makeInstallValue = function(count, pos, isBoxes, rhs, body) {
    return new control.InstallValueControl({count: count,
					    pos: pos,
					    isBoxes: isBoxes,
					    rhs: rhs,
					    body: body});

};


var makeWithContMark = function(key, val, body) {
    return new control.WithContMarkControl(key, val, body);
};


var makeAssign = function(id, rhs, isUndefOk) {
    return new control.AssignControl({id: id,
				      rhs: rhs,
				      isUndefOk: isUndefOk});
};

  
var makeVarref = function(aToplevel) {
    return new control.VarrefControl(aToplevel);
};


var makeClosure = function(genId) {
    return new control.ClosureControl(genId);
};


var makeCaseLam = function(name, clauses) {
    assert.ok(typeof(clauses) === 'object' && clauses.length !== undefined);
    return new control.CaseLamControl(name, clauses);
};


var makeLetrec = function(procs, body) {
    return new control.LetRecControl(procs, body);
};


/////////////////////////////////////////////////////////////////////


var testPrim = function(funName, f, baseArgs, expectedValue) {
    var state = new StateModule.State();
    var args = [];
    for (var i = 0; i < baseArgs.length; i++) {
	args.push(makeConstant(f(baseArgs[i])));
    }
    state.pushControl(makeApplication(makePrimval(funName), args));
    assert.ok(types.isEqual(run(state), 
			    expectedValue));
};

var testPrimF = function(funName, f, baseArgs, expectedValue, transform) {
    var state = new StateModule.State();
    var args = [];
    for (var i = 0; i < baseArgs.length; i++) {
	args.push(makeConstant(f(baseArgs[i])));
    }
    state.pushControl(makeApplication(makePrimval(funName), args));
    assert.deepEqual(transform(run(state)),
		     expectedValue);
}

var listToStringArray = function(lst) {
	var ret = [];
	while ( !lst.isEmpty() ) {
		ret.push( lst.first().toString() );
		lst = lst.rest();
	}
	return ret;
}

var id = function(x) {return x;};



//////////////////////////////////////////////////////////////////////

var runTest = function(name, thunk) {
    sys.print("running " + name + "... ");
    try {
	thunk();
    } catch(e) {
	sys.print(" FAIL\n");
	sys.print(e);
	if (EXIT_ON_FIRST_ERROR) {
	    if (typeof(console) !== 'undefined' && console.log && e.stack) {
			console.log(e.stack);
		}
//		if (typeof(console) !== 'undefined' && console.log && e.stack) {
//			console.log(e.stack);
//		}
//		sys.print(sys.inspect(e) + '\n');
	    throw e;
	}
    }
    sys.print(" ok\n")
    
};

//////////////////////////////////////////////////////////////////////


sys.print("START TESTS\n\n");

runTest("simple empty state",
	// Simple running should just terminate, and always be at the "stuck" state.
	function() { 
	    var state = new StateModule.State();
	    assert.ok(state.isStuck());
	    run(state);
	    assert.ok(state.isStuck());
	});



// Numeric constants should just evaluate through.
runTest("Numeric constant", 
	function() {
	    var state = new StateModule.State();
	    state.pushControl(makeConstant(42));
	    var result = run(state);
	    assert.deepEqual(result, 
			     42);
	    
	    assert.deepEqual(state, makeStateWithConstant(42));
	});



// String constant.
runTest("String constant",
	function() {
	    var state = new StateModule.State();
	    state.pushControl(makeConstant("hello world"));
	    var result = run(state);
	    assert.deepEqual(result, 
			     "hello world");

	    assert.deepEqual(state, makeStateWithConstant("hello world"));
	});


// boolean constant.
runTest("Boolean constant", 
	function() {
	    var state = new StateModule.State();
	    state.pushControl(makeConstant(true));
	    var result = run(state);
	    assert.deepEqual(result, true);

	    assert.deepEqual(state, makeStateWithConstant(true));
	});



runTest("external call",
	function() {
	    var state = new StateModule.State();
	    interpret.call(state, 
			   primitive.getPrimitive("*"),
			   [2, 3],
			   function(v) { assert.equal(v, 6) });
	});



// Simple branch to true
runTest("Simple boolean branch to true",
	function() {
	    var state = new StateModule.State();
	    state.pushControl(makeBranch(makeConstant(true),
					 makeConstant(true),
					 makeConstant(false)));
	    var result = run(state);
	    assert.deepEqual(result, true);
	});


// Simple branch to false
runTest("Simple boolean branch to false",
	function() {
	    var state = new StateModule.State();
	    state.pushControl(makeBranch(makeConstant(false),
					 makeConstant(false),
					 makeConstant(true)));
	    var result = run(state);
	    assert.deepEqual(result, 
			     true);

	    assert.deepEqual(state, makeStateWithConstant(true));
	});



// (if (if true false true) "apple" "pie") --> "pie"
runTest("nested booleans",
	function() {
	    var state = new StateModule.State();
	    state.pushControl(makeBranch(makeBranch(makeConstant(true), makeConstant(false), makeConstant(true)),
					 makeConstant("apple"),
					 makeConstant("pie")));
	    var result = run(state);
	    assert.deepEqual(result, "pie");

	    assert.deepEqual(state, makeStateWithConstant("pie"));
	});



// Sequences
runTest("Sequences",
	function() {
	    var state1 = new StateModule.State();
	    state1.pushControl(makeSeq(makeConstant(3),
				       makeConstant(4),
				       makeConstant(5)));
	    step(state1);
	    step(state1);
	    assert.ok(!state1.isStuck());
	    assert.deepEqual(state1.v, 3);
	    step(state1);
	    assert.deepEqual(state1.v, 4);
	    var result = run(state1);
	    assert.deepEqual(result, 5);

	    assert.deepEqual(state1, makeStateWithConstant(5));    
	});



// Module prefix
runTest("module prefix",
	function() {
	    var state = new StateModule.State();
	    state.pushControl(makeMod(makePrefix(3),
				      []));
	    run(state);   
	    assert.equal(1, state.vstack.length);
	    assert.ok(state.vstack[0] instanceof types.PrefixValue);
	    assert.equal(state.vstack[0].length(), 3);
	});


runTest("toplevel lookup",
	// toplevel lookup
	function() {
	    var state = new StateModule.State();
	    state.pushControl(makeMod(makePrefix(3),
				      []));
	    run(state);   

	    state.vstack[0].set(0, "zero");
	    state.vstack[0].set(1, "one");
	    state.vstack[0].set(2, "two");

	    state.pushControl(makeToplevel(0, 0));
	    assert.equal(run(state), "zero");

	    state.pushControl(makeToplevel(0, 1));
	    assert.equal(run(state), "one");

	    state.pushControl(makeToplevel(0, 2));
	    assert.equal(run(state), "two");
	});



runTest("define-values",
	// define-values
	function() {
	    var state = new StateModule.State();
	    state.pushControl(makeMod(makePrefix(3), []));
	    run(state);   
	    state.pushControl(makeDefValues([makeToplevel(0, 0)],
					    makeConstant("try it")));
	    run(state);

	    var expectedState = new StateModule.State();
	    expectedState.pushControl(makeMod(makePrefix(3),
					      []));
	    run(expectedState);   
	    expectedState.v = "try it";
	    expectedState.vstack[0].set(0, "try it");
	    assert.deepEqual(state, expectedState);
	});


runTest("lambda",
	// lambda
	function() {
	    var state = new StateModule.State();
	    state.pushControl(makeMod(makePrefix(3), []));
	    run(state);   
	    state.pushControl(makeDefValues([makeToplevel(0, 0)],
					    makeConstant("Some toplevel value")));

	    run(state);
	    state.pushControl(makeLam(3, [0], makeConstant("I'm a body")));

	    var result = run(state);

	    // result should be a lambda.
	    assert.ok(result instanceof types.ClosureValue);
	    assert.equal(result.closureVals.length, 1);
	    assert.ok(result.closureVals[0] instanceof types.PrefixValue);
	    assert.deepEqual(result.body, makeConstant("I'm a body"));
	    assert.equal(result.numParams, 3);
	});



runTest("primval (current-print)",
	// primval
	function() {
	    var state = new StateModule.State();
	    state.pushControl(makePrimval("current-print"));
	    var result = run(state);
	    assert.ok(result instanceof types.PrimProc);
	});


runTest("primval on bad primitive should throw error",
	// primval on unknowns should throw error
	function() {
	    var state = new StateModule.State();
	    state.pushControl(makePrimval("foobar"));
	    assert.throws(function() { run(state); });
	});


runTest("Primval on *",
	// primval on *
	// primval
	function() {
	    var state = new StateModule.State();
	    state.pushControl(makePrimval("*"));
	    var result = run(state);
	    assert.ok(result instanceof types.PrimProc);
	});


runTest("My own list function",
	function() {
	    var state = new StateModule.State();
	    state.pushControl(makeApplication(makeLamWithRest(0, [], makeLocalRef(0)),
					      [makeConstant("one"),
					       makeConstant("two"),
					       makeConstant("three")]))
	    var result = run(state);
	    assert.deepEqual(result,
			     types.list(["one", "two", "three"]));
	});


runTest("primitive application",
	// primitive application.
	function() {
	    var state = new StateModule.State();
	    state.pushControl(makeApplication(makePrimval("*"),
					      [makeConstant(types.rational(3)),
					       makeConstant(types.rational(5))]));
	    var result = run(state);
	    assert.deepEqual(result, types.rational(15));
	    assert.equal(state.vstack.length, 0);
	});


runTest("primitive application, no arguments",
	// primitive application with no arguments.
	function() {
	    var state = new StateModule.State();
	    state.pushControl(makeApplication(makePrimval("*"),
					      []));
	    var result = run(state);
	    assert.deepEqual(result, types.rational(1));
	    assert.equal(state.vstack.length, 0);
	});


runTest("primitive application, nested application",
	// primitive application, with nesting
	function() {
	    var state = new StateModule.State();
	    state.pushControl(makeApplication(
		makePrimval("*"),
		[makeApplication(
		    makePrimval("*"),
		    [makeConstant(types.rational(3)),
		     makeConstant(types.rational(5))]),
		 makeConstant(types.rational(7))]));
	    var result = run(state);
	    assert.deepEqual(result, types.rational(105));
	    assert.equal(state.vstack.length, 0);
	});


runTest("primitive appliation, nesting, testing non-commutativity",
	// primitive application, with nesting, testing order
	function() {
	    var state = new StateModule.State();
	    state.pushControl(makeApplication(
		makePrimval("string-append"),
		[makeApplication(
		    makePrimval("string-append"),
		    [makeConstant(types.string("hello")),
		     makeConstant(types.string("world"))]),
		 makeConstant(types.string("testing"))]));
	    var result = run(state);
	    assert.deepEqual(result, types.string("helloworldtesting"));
	    assert.equal(state.vstack.length, 0);
	});

runTest("primitive application, subtraction",
	// subtraction
	function() {
	    var state = new StateModule.State();
	    state.pushControl(makeApplication(
		makePrimval("-"),
		[makeApplication(
		    makePrimval("-"),
		    [makeConstant(types.rational(3)),
		     makeConstant(types.rational(4))]),
		 makeConstant(types.rational(15))]));
	    var result = run(state);
	    assert.deepEqual(result, types.rational(-16));
	    assert.equal(state.vstack.length, 0);
	});

runTest("primitive application, unary subtraction (negation)", 
	// Checking negation.
	function() {
	    var state = new StateModule.State();
	    state.pushControl(makeApplication(
		makePrimval("-"),
		[makeConstant(types.rational(1024))]));
	    var result = run(state);
	    assert.deepEqual(result, types.rational(-1024));
	    assert.equal(state.vstack.length, 0);
	});


runTest("closure application",
	// Closure application
	// lambda will just return a constant value
	function() {
	    var state = new StateModule.State();
	    state.pushControl(makeMod(makePrefix(1), []));
	    run(state);   
	    assert.equal(state.vstack.length, 1);
	    
	    state.pushControl(makeDefValues([makeToplevel(0, 0)],
					    makeLam(1, [],
						    makeConstant("I'm a body"))));
	    run(state);
	    state.pushControl(makeApplication(makeToplevel(1, 0), [makeConstant("boo")]));
	    var result = run(state);
	    assert.equal(result, "I'm a body");

	    assert.equal(state.vstack.length, 1);
	});


runTest("closure application, defining square",
	// Closure application
	// lambda will square its argument
	function() {
	    var state = new StateModule.State();
	    state.pushControl(makeMod(makePrefix(1), []));
	    run(state);   
	    assert.equal(state.vstack.length, 1);
	    
	    state.pushControl(makeDefValues([makeToplevel(0, 0)],
					    makeLam(1, [],
						    makeApplication(makePrimval("*"),
								    [makeLocalRef(2),
								     makeLocalRef(2)]))));
	    run(state);
	    state.pushControl(makeApplication(makeToplevel(1, 0), 
					      [makeConstant(types.rational(4))]));
	    var result = run(state);
	    assert.deepEqual(result, types.rational(16));
	    assert.equal(state.vstack.length, 1);
	});



runTest("closure application, testing tail calls",
	// Checking tail calling behavior
	// The standard infinite loop should consume bounded control stack.
	// (define (f) (f)) (begin (f)) --> infinite loop, but with bounded control stack.
	function() {
	    var state = new StateModule.State();
	    state.pushControl(makeMod(makePrefix(1), []));
	    run(state);   
	    assert.equal(state.vstack.length, 1);
	    
	    state.pushControl(makeDefValues([makeToplevel(0, 0)],
					    makeLam(0, [0],
						    makeApplication(makeToplevel(0, 0),
								    []))));
	    run(state);
	    state.pushControl(makeApplication(makeToplevel(0, 0), []));
	    var MAXIMUM_BOUND = 5;
	    var ITERATIONS = 1000000;
	    for (var i = 0; i < ITERATIONS; i++) {
		step(state);
		assert.ok(state.cstack.length < MAXIMUM_BOUND);
	    }
	});



runTest("closure application, testing tail calls with even/odd",
	// Checking tail calling behavior
	// The standard infinite loop should consume bounded control stack.
	// (define (even? x) (if (zero? x) true (odd? (sub1 x))))
	// (define (odd? x) (if (zero? x) false (even? (sub1 x))))
	function() {
	    var state = new StateModule.State();
	    state.pushControl(makeMod(makePrefix(2), []));
	    run(state);   
	    assert.equal(state.vstack.length, 1);
	    state.pushControl(makeDefValues
			      ([makeToplevel(0, 0)],
			       makeLam(1, [0],
				       makeBranch(
					   makeApplication(makePrimval("zero?"),
							   [makeLocalRef(2)]),
					   makeConstant(true),
					   makeApplication(makeToplevel(1, 1),
							   [makeApplication(
							       makePrimval("sub1"),
							       [makeLocalRef(3)])])))));
	    state.pushControl(makeDefValues
			      ([makeToplevel(0, 1)],
			       makeLam(1, [0],
				       makeBranch(
					   makeApplication(makePrimval("zero?"),
							   [makeLocalRef(2)]),
					   makeConstant(false),
					   makeApplication(makeToplevel(1, 0),
							   [makeApplication(
							       makePrimval("sub1"),
							       [makeLocalRef(3)])])))));
	    
	    run(state);

	    var even = function(n) {
		state.pushControl(makeApplication(makeToplevel(1, 0),
						  [makeConstant(types.rational(n))]));
		var MAXIMUM_BOUND = 10;
		while (!state.isStuck()) {
		    step(state);
		    assert.ok(state.cstack.length < MAXIMUM_BOUND);
		    //sys.print(state.cstack.length + "\n");
		}
		return state.v;
	    }
	    assert.equal(even(0), true);
	    assert.equal(even(1), false);
	    assert.equal(even(50), true);
	    assert.equal(even(51), false);
	    assert.equal(even(501), false);
	    assert.equal(even(1001), false);
	    assert.equal(even(10000), true);
	    assert.equal(even(10001), false);
	});


runTest("factorial",
	function() {
	    var state = new StateModule.State();
	    state.pushControl(makeMod(makePrefix(1), []));
	    run(state);   
	    assert.equal(state.vstack.length, 1);
	    
	    state.pushControl(makeDefValues(
		[makeToplevel(0, 0)],
		makeLam(1, [0],
			makeBranch(
			    makeApplication(makePrimval("zero?"),
					    [makeLocalRef(2)]),
			    makeConstant(types.rational(1)),
			    makeApplication(makePrimval("*"),
					    [makeLocalRef(3),
					     makeApplication(
						 makeToplevel(3, 0),
						 [makeApplication(makePrimval("sub1"),
								  [makeLocalRef(5)])])])))));

	    run(state);

	    var fact = function(n) {
		state.pushControl(makeApplication(makeToplevel(1, 0),
						  [makeConstant(types.rational(n))]));
		return run(state);
	    }

 	    assert.equal(fact(0), 1);
 	    assert.equal(fact(1), 1);
 	    assert.equal(fact(2), 2);
 	    assert.equal(fact(3), 6);
 	    assert.equal(fact(4), 24);
	    assert.equal(fact(5), 120);
	    assert.equal(fact(6), 720);
	    assert.equal(fact(10), 3628800);
	    assert.equal(fact(11), 39916800);
	    assert.equal(fact(12), 479001600);
	});



runTest("apply on a primitive *",
	function() {
	    var state = new StateModule.State();
	    state.pushControl(makeApplication(
		makePrimval("apply"),
		[makePrimval("*"),
		 makeConstant(
		     types.list([types.rational(3),
				   types.rational(9)]))]));
	    assert.deepEqual(run(state),
			     27);
	    assert.equal(state.vstack.length, 0);
	});



runTest("apply on a primitive -",
	function() {
	    var state = new StateModule.State();
	    state.pushControl(makeApplication(
		makePrimval("apply"),
		[makePrimval("-"),
		 makeConstant(
		     types.list([types.rational(3),
				   types.rational(9)]))]));
	    assert.deepEqual(run(state),
			     -6);
	    assert.equal(state.vstack.length, 0);
	});

runTest("apply on a primitive -, three arguments",
	function() {
	    var state = new StateModule.State();
	    state.pushControl(makeApplication(
		makePrimval("apply"),
		[makePrimval("-"),
		 makeConstant(
		     types.list([types.rational(3),
				   types.rational(9),
				   types.rational(12)]))]));
	    assert.deepEqual(run(state),
			     -18);
	    assert.equal(state.vstack.length, 0);
	});


runTest("values",
	function() {
	    var state = new StateModule.State();
	    state.pushControl(makeApplication(
		makePrimval("values"),
		[makePrimval("*"),
		 makeConstant(
		     types.list([types.rational(3),
				   types.rational(9),
				   types.rational(12)]))]));
	    var result = run(state);
	    assert.equal(state.vstack.length, 0);
	    assert.ok(result instanceof types.ValuesWrapper);
	    assert.equal(result.elts.length, 2);
	});



runTest("values with no arguments",
	function() {
	    var state = new StateModule.State();
	    state.pushControl(makeApplication(
		makePrimval("values"),[]));
	    var result = run(state);
	    assert.equal(state.vstack.length, 0);
	    assert.ok(result instanceof types.ValuesWrapper);
	    assert.equal(result.elts.length, 0);
	});




runTest("current-inexact-milliseconds",
	function() {
	    var state = new StateModule.State();
	    for (var i = 0; i < 2000; i++) {
		state.pushControl(makeApplication(
		    makePrimval("current-inexact-milliseconds"),[]));
		var result1 = run(state);


		state.pushControl(makeApplication(
		    makePrimval("current-inexact-milliseconds"),[]));
		var result2 = run(state);
		assert.ok(jsnums.lessThanOrEqual(result1, result2));
	    }
	});




runTest("values with def-values",
	function() {
	    var state = new StateModule.State();
	    state.pushControl(makeMod(makePrefix(2), []));
	    run(state);   
	    assert.equal(state.vstack.length, 1);
	    
	    state.pushControl(makeDefValues(
		[makeToplevel(0, 0),
		 makeToplevel(0, 1)],
		makeApplication(makePrimval("values"),
				[makeConstant("hello"),
				 makeConstant("world")])));
	    run(state);
	    assert.equal(state.vstack.length, 1);
	    assert.ok(state.vstack[0] instanceof types.PrefixValue);
	    assert.equal(state.vstack[0].ref(0), "hello");
	    assert.equal(state.vstack[0].ref(1), "world");
	});



runTest("apply-values",
	function() {
	    var state = new StateModule.State();
	    state.pushControl(makeMod(makePrefix(2), []));
	    run(state);   
	    state.pushControl(makeDefValues(
		[makeToplevel(0, 0),
		 makeToplevel(0, 1)],
		makeApplication(makePrimval("values"),
				[makeConstant(types.string("hello")),
				 makeConstant(types.string("world"))])));
	    run(state);

	    state.pushControl(makeApplyValues(
		makeLam(2, [], makeApplication(makePrimval("string-append"),
					       [makeLocalRef(2),
						makeLocalRef(3)])),
		makeApplication(makePrimval("values"),
				[makeToplevel(2, 0),
				 makeToplevel(2, 1)])));
	    assert.deepEqual(run(state), types.string("helloworld"));
	});



runTest("apply-values, testing no stack usage",
	function() {
	    var state = new StateModule.State();
	    state.pushControl(makeMod(makePrefix(2), []));
	    run(state);   
	    state.pushControl(makeDefValues(
		[makeToplevel(0, 0),
		 makeToplevel(0, 1)],
		makeApplication(makePrimval("values"),
				[makePrimval("zero?"),
				 makeConstant(types.rational(0))])));
	    run(state);

	    state.pushControl(makeApplyValues(
		makeToplevel(0, 0),
		makeToplevel(0, 1)));
	    assert.equal(run(state), true);
	    assert.equal(state.vstack.length, 1);
	});

runTest("let-one, trivial",
	function() {
	    var state = new StateModule.State();
	    assert.equal(state.vstack.length, 0);
	    var body = makeLocalRef(0);
	    state.pushControl(makeLet1(makeConstant("someValue"),
				       body));
	    while (state.cstack[state.cstack.length - 1] !== body) {
		step(state);
	    }
	    assert.equal(state.vstack.length, 1);
	    assert.equal(state.vstack[0], "someValue");
	    var result = run(state);
	    assert.equal(state.vstack.length, 0);
	    assert.deepEqual(result, "someValue");
	});


runTest("let-one, different body",
	function() {
	    var state = new StateModule.State();
	    assert.equal(state.vstack.length, 0);
	    var body = makeConstant("something else");
	    state.pushControl(makeLet1(makeConstant("someValue"),
				       body));
	    while (state.cstack[state.cstack.length - 1] !== body) {
		step(state);
	    }
	    assert.equal(state.vstack.length, 1);
	    assert.equal(state.vstack[0], "someValue");
	    var result = run(state);
	    assert.equal(state.vstack.length, 0);
	    assert.deepEqual(result, "something else");
	});


runTest("let-void, no boxes",
	function() {
	    var state = new StateModule.State();
	    var body = makeConstant("blah");
	    state.pushControl(makeLetVoid(2, false, body));
	    while (state.cstack[state.cstack.length - 1] !== body) {
		step(state);
	    }
	    assert.equal(state.vstack.length, 2);
	    for(var i = 0; i < state.vstack.length; i++) {
		assert.ok(state.vstack[i] === types.UNDEFINED);
	    }
	    var result = run(state);
	    assert.equal(result, "blah");
	    assert.equal(state.vstack.length, 0);
	});


runTest("let-void, with boxes",
	function() {
	    var state = new StateModule.State();
	    var body = makeConstant("blah");
	    state.pushControl(makeLetVoid(2, true, body));
	    while (state.cstack[state.cstack.length - 1] !== body) {
		step(state);
	    }
	    assert.equal(state.vstack.length, 2);
	    for(var i = 0; i < state.vstack.length; i++) {
		assert.ok( types.isBox(state.vstack[i]) );
	    }
	    var result = run(state);
	    assert.equal(result, "blah");
	    assert.equal(state.vstack.length, 0);
	});


runTest("beg0 with just one argument should immediately reduce to its argument",
	function() {
	    var state = new StateModule.State();
	    state.pushControl(makeBeg0(makeConstant("first post")));
	    step(state);
	    assert.equal(state.cstack.length, 1);
	    assert.deepEqual(state.cstack[0], 
			     makeConstant("first post"));
	    var result = run(state);
	    assert.equal(result, "first post");
	});



runTest("beg0, more general",
	function() {
	    var state = new StateModule.State();
	    state.pushControl(makeBeg0(makeConstant("first post"),
				       makeConstant("second post"),
				       makeConstant("third post"),
				       makeConstant("fourth post")));
	    step(state);

	    // By this point, there should be two elements
	    // in the control stack, the evaluation of the first
	    // argument, and a control to continue the
	    // rest of the sequence evaluation.
	    assert.equal(state.cstack.length, 2); 
	    var result = run(state);
	    assert.equal(result, "first post");
	});



runTest("boxenv",
	function() {
	    var state = new StateModule.State();
	    state.pushControl(makeLet1(makeConstant("foo"),
				       makeBoxenv(0, 
						  makeLocalRef(0))));
	    var result = run(state);
	    assert.ok( types.isBox(result) );
	    assert.deepEqual(result, types.box("foo"));
	});


runTest("install-value, without boxes",
	function() {
	    var state = new StateModule.State();
	    var aBody = makeConstant("peep");
	    state.pushControl
		(makeLetVoid
		 (4,
		  false,
		  makeInstallValue
		  (3, 1, false,
		   makeApplication(makePrimval("values"),
				   [makeConstant("3"),
				    makeConstant("1"),
				    makeConstant("4")]),
		   aBody)));
	    while (state.cstack[state.cstack.length - 1] !== aBody) {
		step(state);
	    }
	    assert.equal(state.vstack.length, 4);
	    assert.equal(state.vstack[0], "4");
	    assert.equal(state.vstack[1], "1");
	    assert.equal(state.vstack[2], "3");
	    var result = run(state);
	    assert.equal(result, "peep");
	    assert.equal(state.vstack.length, 0);
	});



runTest("install-value, with boxes",
	function() {
	    var state = new StateModule.State();
	    var aBody = makeConstant("peep");
	    state.pushControl
		(makeLetVoid
		 (4,
		  true,
		  makeInstallValue
		  (3, 1, true,
		   makeApplication(makePrimval("values"),
				   [makeConstant("3"),
				    makeConstant("1"),
				    makeConstant("4")]),
		   aBody)));
	    while (state.cstack[state.cstack.length - 1] !== aBody) {
		step(state);
	    }
	    assert.equal(state.vstack.length, 4);
	    assert.deepEqual(state.vstack[0], types.box("4"));
	    assert.deepEqual(state.vstack[1], types.box("1"));
	    assert.deepEqual(state.vstack[2], types.box("3"));
	    var result = run(state);
	    assert.equal(result, "peep");
	    assert.equal(state.vstack.length, 0);
	});


runTest("assign",
	function() {
	    var state = new StateModule.State();
	    state.pushControl(makeMod(makePrefix(1), 
				    [makeAssign(makeToplevel(0, 0),
						makeConstant("some value"),
						true)]));
	    run(state);
	    assert.equal(state.vstack.length, 1);
	    assert.equal(state.vstack[0].ref(0), "some value");
	});


runTest("varref",
	function() {
	    var state = new StateModule.State();
	    state.pushControl(makeMod(makePrefix(2),
				      [makeSeq(makeAssign(makeToplevel(0, 0),
						makeConstant("a toplevel value"),
							  true),
					       makeAssign(
						   makeToplevel(0, 1),
						   makeVarref(makeToplevel(0, 0))))]));
	    var prefixValue = run(state);
	    // WARNING: breaking abstractions.
	    // Let's look directly at the representation structures
	    // and make sure we are dealing with a variable reference.
	    var result = prefixValue.slots[1];
	    assert.ok(result instanceof types.VariableReference);
	    assert.equal(result.ref(), "a toplevel value");
	    result.set("something else!");
	    assert.equal(state.vstack.length, 1);
	    assert.equal(state.vstack[0].ref(0), "something else!");
	});


runTest("closure",
	function() {
	    var state = new StateModule.State();
	    state.heap['some-closure'] = 42;
	    state.pushControl(makeClosure('some-closure'));
	    // The way we process closures in bytecode-compiler
	    // should make this a direct heap lookup.
	    assert.equal(run(state), 42);
	});


runTest("with-cont-mark", 
	function() {
	    var state = new StateModule.State();
	    var aBody = makeConstant("peep");
	    state.pushControl
		(makeWithContMark(makeConstant
				  (types.symbol("x")),
				  makeConstant("42"),
				  aBody));
	    while (state.cstack[state.cstack.length -1] !== aBody) {
		step(state);
	    }
	    assert.equal(state.cstack.length, 2);
	    assert.ok( types.isContMarkRecordControl(state.cstack[0]) );
	    assert.equal(state.cstack[0].dict.get(types.symbol('x')),
			 "42");
	    var result = run(state);
	    assert.equal(result, "peep");
	});




runTest("closure application, testing tail calls in the presence of continuation marks",
	// Checking tail calling behavior
	// The standard infinite loop should consume bounded control stack.
	// (define (f) (call-with-continuation-marks 'x 1 (f))) (begin (f)) --> infinite loop, but with bounded control stack.
	function() {
	    var state = new StateModule.State();
	    state.pushControl(makeMod(makePrefix(1), []));
	    run(state);   
	    assert.equal(state.vstack.length, 1);
	    
	    state.pushControl(makeDefValues([makeToplevel(0, 0)],
					    makeLam(0, [0],
						    (makeWithContMark
						     (makeConstant(types.symbol("x")),
						      makeConstant(types.rational(1)),
						      
						      makeApplication(makeToplevel(0, 0),
								      []))))));
	    run(state);
	    state.pushControl(makeApplication(makeToplevel(0, 0), []));
	    var MAXIMUM_BOUND = 6;
	    var ITERATIONS = 1000000;
	    for (var i = 0; i < ITERATIONS; i++) {
		step(state);
		assert.ok(state.cstack.length < MAXIMUM_BOUND);
	    }
	});


runTest("case-lambda, with a function that consumes one or two values",
	function() {
	    var state = new StateModule.State();
	    state.pushControl
		(makeMod(makePrefix(1), 
			 [makeDefValues
			  ([makeToplevel(0, 0)],
			   makeCaseLam(types.symbol("last"),
				       [makeLam(1, [], makeLocalRef(0)),
					makeLam(2, [], makeLocalRef(1))]))]));
	    run(state);
	    state.pushControl(makeApplication(makeToplevel(1, 0),
					      [makeConstant(types.rational(5))]));
	    var result = run(state);
	    assert.deepEqual(result, types.rational(5));

	    state.pushControl(makeApplication(makeToplevel(2, 0),
					      [makeConstant(types.rational(7)),
					       makeConstant(types.rational(42))]));
	    result = run(state);
	    assert.deepEqual(result, types.rational(42));
	});



// runTest("factorial again, testing the accumulation of continuation marks",
// 	//
// 	// (define marks #f)
// 	// (define (f x)
// 	//   (with-continuation-marks 'x x
// 	//     (if (= x 0)
// 	//         (begin (set! marks (current-continuation-marks))
// 	//                1)
// 	//         (* x (f (sub1 x))))))
// 	function() {

// 	});


runTest("let-rec",
	function() {
	    var state = new StateModule.State();
	    state.pushControl(makeLetVoid(2,
					  false,
					  makeLetrec([makeLam(1, [1],
							      makeBranch
							      (makeApplication(makePrimval("zero?"),
									       [makeLocalRef(2)]),
							       makeConstant(true),
							       makeApplication(makeLocalRef(1),
									       [makeApplication
										(makePrimval("sub1"),
										 [makeLocalRef(3)])]))),
						      makeLam(1, [0],
							      makeBranch
							      (makeApplication(makePrimval("zero?"),
									       [makeLocalRef(2)]),
							       makeConstant(false),
							       makeApplication(makeLocalRef(1),
									       [makeApplication
										(makePrimval("sub1"),
										 [makeLocalRef(3)])])))],
						     makeLocalRef(0))));
	    var evenValue = run(state);
	    var e = function(x) {
		state.pushControl(makeApplication(makeConstant(evenValue),
						  [makeConstant(types.rational(x))]));
		return run(state);
	    }
	    assert.equal(state.vstack.length, 0);

	    assert.equal(e(0), true);
	    assert.equal(e(1), false);
	    assert.equal(e(2), true);
	    assert.equal(e(3), false);
	    assert.equal(e(100), true);
	    assert.equal(e(101), false);
	    assert.equal(e(10000), true);
	    assert.equal(e(10001), false);
	});


/***************************************
 *** Primitive String Function Tests ***
 ***************************************/

runTest('symbol?',
	function() {
		testPrim('symbol?', types.symbol, ['hi'], true);
		testPrim('symbol?', types.rational, [1], false);
	});

runTest('symbol=?',
	function() {
		testPrim('symbol=?', types.symbol, ['abc', 'abd'], false);
		testPrim('symbol=?', types.symbol, ['cdf', 'cdf'], true);
	});

runTest('string->symbol',
	function() {
		testPrim('string->symbol', id, ['hello!'], types.symbol('hello!'));
		testPrim('string->symbol', types.string, [' world'], types.symbol(' world'));
	});


runTest('symbol->string',
	function() {
		testPrim('symbol->string', types.symbol, ['hello!'], types.string('hello!'));
	});


runTest('number->string',
	function() {
		testPrim('number->string', types.rational, [5], types.string('5'));
		testPrim('number->string', id, [types.complex(0, 2)], types.string('0+2i'));
		testPrim('number->string', id, [types.rational(5, 3)], types.string('5/3'));
	});


runTest('stinrg->number',
	function() {
		testPrim('string->number', types.string, ['abc'], false);
		testPrim('string->number', id, ['123'], 123);
		testPrim('string->number', types.string, ['0+3i'], types.complex(0, 3));
	});


runTest('string?',
	function() {
		testPrim('string?', id, [types.symbol('hello!')], false);
		testPrim('string?', id, ['string'], true);
		testPrim('string?', types.string, ['world'], true);
	});


runTest('make-string',
	function() {
		testPrim('make-string', id, [0, types.char('A')], types.string(""));
		testPrim('make-string', id, [types.rational(3), types.char('b')], types.string('bbb'));
	});


runTest('string',
	function() {
		testPrim('string', id, [], types.string(''));
		testPrim('string', types.char, ['a', 'b'], types.string('ab'));
	});

runTest('string-length',
	function() {
		testPrim('string-length', types.string, [''], 0);
		testPrim('string-length', id, ['5'], 1);
		testPrim('string-length', types.string, ['antidisestablishmentarianism'], 28);
	});

runTest('string-ref',
	function() {
		testPrim('string-ref', id, ['world', 3], types.char('l'));
		testPrim('string-ref', id, [types.string('abcd'), 1], types.char('b'));
		testPrim('string-ref', id, [types.string('asdfasdf'), 4], types.char('a'));
	});

runTest('string=?',
	function() {
		testPrim('string=?', id, ['asdf', 'Asdf'], false);
		testPrim('string=?', id, ['asdf', types.string('asdf')], true);
		testPrim('string=?', types.string, ['asdf', 'asdf', 'Asdf'], false);
		testPrim('string=?', types.string, ['far', 'fAr'], false);
		testPrim('string=?', id, ['', ''], true);
		testPrim('string=?', types.string, ['as', 'as', 'as'], true);
		testPrim('string=?', types.string, ['1', '1', '2'], false);
	});

runTest('string-ci=?',
	function() {
		testPrim('string-ci=?', id, ['asdf', 'Asdf'], true);
		testPrim('string-ci=?', id, ['asdf', types.string('asdf')], true);
		testPrim('string-ci=?', types.string, ['asdf', 'asdf', 'Asdf'], true);
		testPrim('string-ci=?', types.string, ['far', 'fAr'], true);
		testPrim('string-ci=?', id, ['', ''], true);
		testPrim('string-ci=?', types.string, ['as', 'as', 'as'], true);
		testPrim('string-ci=?', types.string, ['1', '1', '2'], false);
	});

runTest('string<?',
	function() {
		testPrim('string<?', id, ["", "a"], true);
		testPrim('string<?', types.string, ['abc', 'ab'], false);
		testPrim('string<?', id, [types.string('abc'), 'abc'], false);
		testPrim('string<?', types.string, ['abc', 'def', 'cde'], false);
		testPrim('string<?', id, ['A', types.string(']'), 'a'], true);
		testPrim('string<?', types.string, ['a', 'b', 'c', 'd', 'dd', 'e'], true);
	});

runTest('string>?',
	function() {
		testPrim('string>?', id, ["", "a"], false);
		testPrim('string>?', types.string, ['abc', 'ab'], true);
		testPrim('string>?', id, [types.string('abc'), 'abc'], false);
		testPrim('string>?', types.string, ['abc', 'def', 'cde'], false);
		testPrim('string>?', id, ['a', types.string(']'), 'A'], true);
		testPrim('string>?', types.string, ['e', 'd', 'cc', 'c', 'b', 'a'], true);
	});

runTest('string<=?',
	function() {
		testPrim('string<=?', id, ["", "a"], true);
		testPrim('string<=?', types.string, ['abc', 'ab'], false);
		testPrim('string<=?', id, [types.string('abc'), 'abc'], true);
		testPrim('string<=?', types.string, ['abc', 'aBc'], false);
		testPrim('string<=?', types.string, ['abc', 'def', 'cde'], false);
		testPrim('string<=?', id, ['A', types.string(']'), 'a'], true);
		testPrim('string<=?', types.string, ['a', 'b', 'b', 'd', 'dd', 'e'], true);
	});

runTest('string>=?',
	function() {
		testPrim('string>=?', id, ["", "a"], false);
		testPrim('string>=?', types.string, ['abc', 'ab'], true);
		testPrim('string>=?', id, [types.string('abc'), 'abc'], true);
		testPrim('string>=?', types.string, ['aBc', 'abc'], false);
		testPrim('string>=?', types.string, ['abc', 'def', 'cde'], false);
		testPrim('string>=?', id, ['a', types.string(']'), 'A'], true);
		testPrim('string>=?', types.string, ['e', 'e', 'cc', 'c', 'b', 'a'], true);
	});

runTest('string-ci<?',
	function() {
		testPrim('string-ci<?', id, ["", "a"], true);
		testPrim('string-ci<?', id, [types.string('Abc'), 'ab'], false);
		testPrim('string-ci<?', types.string, ['abc', 'abc'], false);
		testPrim('string-ci<?', types.string, ['abc', 'def', 'cde'], false);
		testPrim('string-ci<?', types.string, ['a', 'b', 'C', 'd', 'dd', 'e'], true);
	});

runTest('string-ci>?',
	function() {
		testPrim('string-ci>?', id, ["", "a"], false);
		testPrim('string-ci>?', id, [types.string('Abc'), 'ab'], true);
		testPrim('string-ci>?', types.string, ['abc', 'abc'], false);
		testPrim('string-ci>?', types.string, ['def', 'abc', 'cde'], false);
		testPrim('string-ci>?', types.string, ['e', 'D', 'cc', 'c', 'b', 'a'], true);
	});

runTest('string-ci<=?',
	function() {
		testPrim('string-ci<=?', id, ["", "a"], true);
		testPrim('string-ci<=?', types.string, ['Abc', 'ab'], false);
		testPrim('string-ci<=?', id, [types.string('abc'), 'abc'], true);
		testPrim('string-ci<=?', types.string, ['abc', 'aBc'], true);
		testPrim('string-ci<=?', types.string, ['abc', 'def', 'cde'], false);
		testPrim('string-ci<=?', types.string, ['a', 'b', 'b', 'D', 'dd', 'e'], true);
	});

runTest('string-ci>=?',
	function() {
		testPrim('string-ci>=?', id, ["", "a"], false);
		testPrim('string-ci>=?', types.string, ['Abc', 'ab'], true);
		testPrim('string-ci>=?', id, [types.string('abc'), 'abc'], true);
		testPrim('string-ci>=?', types.string, ['aBc', 'abc'], true);
		testPrim('string-ci>=?', types.string, ['def', 'abc', 'cde'], false);
		testPrim('string-ci>=?', types.string, ['e', 'e', 'cc', 'C', 'b', 'a'], true);
	});


runTest('substring',
	function() {
		testPrim('substring', id, ['abc', 1], types.string('bc'));
		testPrim('substring', id, [types.string('abc'), 0], types.string('abc'));
		testPrim('substring', id, ['abcdefgh', 2, 4], types.string('cd'));
		testPrim('substring', id, [types.string('abc'), 3], types.string(''));
		testPrim('substring', id, [types.string('abcd'), 2, 2], types.string(''));
	});


runTest('string-append',
	function() {
		testPrim('string-append', types.string, [], types.string(''));
		testPrim('string-append', id, ['a', types.string('b'), 'c'], types.string('abc'));
		testPrim('string-append', types.string, ['a', '', 'b', ' world'], types.string('ab world'));
	});


runTest('string->list',
	function() {
		testPrim('string->list', types.string, [''], types.EMPTY);
		testPrim('string->list', id, ['one'], types.list([types.char('o'), types.char('n'), types.char('e')]));
		testPrim('string->list', types.string, ['two'], types.list([types.char('t'),
										types.char('w'),
										types.char('o')]));
	});

runTest('list->string',
	function() {
		testPrim('list->string', id, [types.EMPTY], types.string(''));
		testPrim('list->string', id,
			 [types.list([types.char('H'),
					types.char('e'),
					types.char('l'),
					types.char('l'),
					types.char('o')])],
			 types.string('Hello'));
	});


runTest('string-copy',
	function() {
		testPrim('string-copy', types.string, [''], types.string(''));
		testPrim('string-copy', id, ['had'], types.string('had'));
		testPrim('string-copy', types.string, ['hello'], types.string('hello'));

		var state = new StateModule.State();
		var str = types.string('hello');
		state.pushControl(makeApplication(makePrimval('string-copy'), [makeConstant(str)]));
		var result = run(state);
		assert.deepEqual(result, str);
		assert.ok(result !== str);
	});


runTest('format',
	function() {
		testPrim('format', types.string, ['hello'], types.string('hello'));
		testPrim('format', id, ['hello~n'], types.string('hello\n'));
		testPrim('format', id, [types.string('Test: ~a~nTest2: ~A~%'),
					types.char('A'),
					types.list([1, 2, 3])],
			 types.string('Test: A\nTest2: (1 2 3)\n'));
		testPrim('format', id, ['~s ~S ~a',
					types.char('b'),
					types.complex(0, 2),
					types.char('b')],
			 types.string('#\\b 0+2i b'));

		testPrim('format', id, ['~s ~a', primitive.getPrimitive('+'), primitive.getPrimitive('format')],
			 types.string('#<procedure:+> #<procedure:format>'));
		
		var box1 = types.box('junk');
		var box2 = types.box(box1);
		box1.set(box2);
		testPrim('format', id, ['~s', box1], types.string('#&#&...'));
		
		var box3 = types.box('junk');
		box3.set(box3);
		testPrim('format', id, ['~a', box3], types.string('#&...'));
	});


runTest('explode',
	function() {
		testPrim('explode', id, [''], types.EMPTY);
		testPrim('explode', types.string, ['hello'], types.list([types.string('h'),
									     types.string('e'),
									     types.string('l'),
									     types.string('l'),
									     types.string('o')]));
	});


runTest('implode',
	function() {
		testPrim('implode', id, [types.EMPTY], types.string(''));
		testPrim('implode', types.list, [[types.string('h'),
						    types.string('e'),
						    types.string('l'),
						    types.string('l'),
						    types.string('o')]],
			 types.string('hello'));
	});


runTest('string->int',
	function() {
		testPrim('string->int', types.string, ['0'], 48);
		testPrim('string->int', types.string, ['\n'], 10);
	});


runTest('int->string',
	function() {
		testPrim('int->string', id, [50], types.string('2'));
		testPrim('int->string', id, [10], types.string('\n'));
	});


runTest('string-alphabetic?',
	function() {
		testPrim('string-alphabetic?', id, ['abcd'], true);
		testPrim('string-alphabetic?', types.string, ['AbCZ'], true);
		testPrim('string-alphabetic?', id, ['a b c'], false);
		testPrim('string-alphabetic?', types.string, ['1243!'], false);
	});


runTest('string-ith',
	function() {
		testPrim('string-ith', id, ['abcde', 2], types.string('c'));
		testPrim('string-ith', id, [types.string('12345'), 0], types.string('1'));
	});


runTest('string-lower-case?',
	function() {
		testPrim('string-lower-case?', types.string, ['abcd'], true);
		testPrim('string-lower-case?', id, ['abc1'], false);
		testPrim('string-lower-case?', types.string, ['Abc'], false);
	});


runTest('string-numeric?',
	function() {
		testPrim('string-numeric?', id, ['1234'], true);
		testPrim('string-numeric?', types.string, ['5432'], true);
		testPrim('string-numeric?', types.string, ['0+2i'], false);
		testPrim('string-numeric?', types.string, ['03()'], false);
	});


runTest('string-upper-case?',
	function() {
		testPrim('string-upper-case?', id, ['ABCD'], true);
		testPrim('string-upper-case?', types.string, ['ADF'], true);
		testPrim('string-upper-case?', types.string, ['AbZ'], false);
		testPrim('string-upper-case?', types.string, ['05AB'], false);
	});


runTest('string-whitespace?',
	function() {
		testPrim('string-whitespace?', types.string, ['a b c'], false);
		testPrim('string-whitespace?', id, [' \n '], true);
		testPrim('string-whitespace?', types.string, ['\t\r\n '], true);
	});


runTest('replicate',
	function() {
		testPrim('replicate', id, [3, types.string('ab')], types.string('ababab'))
		testPrim('replicate', id, [0, 'hi'], types.string(''));
		testPrim('replicate', id, [50, types.string('')], types.string(''));
	});


runTest('string->immutable-string',
	function() {
		testPrim('string->immutable-string', id, ['hello'], 'hello');
		testPrim('string->immutable-string', types.string, ['world'], 'world');
	});


runTest('string-set!',
	function() {
		var str1 = types.string('hello');
		testPrim('string-set!', id, [str1, 2, types.char('w')], types.VOID);
		assert.deepEqual(str1, types.string('hewlo'));

		var str2 = types.string('no');
		testPrim('string-set!', id, [str2, 1, types.char('!')], types.VOID);
		assert.deepEqual(str2, types.string('n!'));
	});


runTest('string-fill!',
	function() {
		var str1 = types.string('lawl');
		testPrim('string-fill!', id, [str1, types.char('q')], types.VOID);
		assert.deepEqual(str1, types.string('qqqq'));

		var str2 = types.string('');
		testPrim('string-fill!', id, [str2, types.char(' ')], types.VOID);
		assert.deepEqual(str2, types.string(''));
	});



/*************************************
 *** Primitive Math Function Tests ***
 *************************************/


runTest("zero?",
	function() {
		testPrim('zero?', types.rational, [0], true);
		testPrim('zero?', types.rational, [1], false);
		testPrim('zero?', id, [types.complex(0, 1)], false);
	});



runTest("sub1",
	function() {
		testPrim('sub1', types.rational, [25], types.rational(24));
		testPrim('sub1', id, [types.complex(3, 5)], types.complex(2, 5));
	});


runTest("add1",
	function() {
		testPrim('add1', types.rational, [25], types.rational(26));
		testPrim('add1', id, [types.complex(3, 5)], types.complex(4, 5));
	});


runTest("+",
	function() {
		testPrim('+', types.rational, [], types.rational(0));
		testPrim('+', types.rational, [2], types.rational(2));
		testPrim('+', types.rational, [1, 2], types.rational(3));
		testPrim('+', types.rational, [1, 2, 3, 4], types.rational(10));
	});


runTest("-",
	function() {
		testPrim('-', types.rational, [2], types.rational(-2));
		testPrim('-', types.rational, [1, 2], types.rational(-1));
		testPrim('-', types.rational, [1, 2, 3, 4], types.rational(-8));
	});


runTest("*",
	function() {
		testPrim('*', types.rational, [], types.rational(1));
		testPrim('*', types.rational, [2], types.rational(2));
		testPrim('*', types.rational, [1, 2], types.rational(2));
		testPrim('*', types.rational, [1, 2, 3, 4], types.rational(24));
	});


runTest("/",
	function() {
		testPrim('/', types.rational, [2], types.rational(1, 2));
		testPrim('/', types.rational, [1, 3], types.rational(1, 3));
		testPrim('/', types.rational, [18, 2, 3, 4], types.rational(3, 4));
	});


runTest('abs',
	function() {
		testPrim('abs', types.rational, [2], types.rational(2));
		testPrim('abs', types.rational, [0], types.rational(0));
		testPrim('abs', types.rational, [-2], types.rational(2));
	});


runTest('quotient',
	function() {
		testPrim('quotient', types.rational, [5, 3], types.rational(1));
	});


runTest('remainder',
	function() {
		testPrim('remainder', types.rational, [5, 3], types.rational(2));
	});


runTest('modulo',
	function() {
	    testPrim('modulo', types.rational, [-5, 3], types.rational(1));
	});


runTest('=',
	function() {
	    testPrim('=', types.rational, [2, 3], false);
	    testPrim('=', types.rational, [2, 2, 2, 2], true);
	    testPrim('=', types.rational, [2, 2, 3, 3], false);
	});


runTest('<',
	function() {
	    testPrim('<', types.rational, [1, 2], true);
	    testPrim('<', types.rational, [2, 2], false);
	    testPrim('<', types.rational, [3, 2], false);
	    testPrim('<', types.rational, [1, 2, 3, 4], true);
	    testPrim('<', types.rational, [1, 2, 2, 3], false);
	    testPrim('<', types.rational, [1, 3, 5, 4], false);
	});


runTest('>',
	function() {
	    testPrim('>', types.rational, [1, 2], false);
	    testPrim('>', types.rational, [2, 2], false);
	    testPrim('>', types.rational, [3, 2], true);
	    testPrim('>', types.rational, [4, 3, 2, 1], true);
	    testPrim('>', types.rational, [4, 3, 3, 2], false);
	    testPrim('>', types.rational, [4, 3, 5, 2], false);
	});


runTest('<=',
	function() {
	    testPrim('<=', types.rational, [1, 2], true);
	    testPrim('<=', types.rational, [2, 2], true);
	    testPrim('<=', types.rational, [3, 2], false);
	    testPrim('<=', types.rational, [1, 2, 3, 4], true);
	    testPrim('<=', types.rational, [2, 3, 3, 3], true);
	    testPrim('<=', types.rational, [1, 3, 5, 4], false);
	});


runTest('>=',
	function() {
	    testPrim('>=', types.rational, [1, 2], false);
	    testPrim('>=', types.rational, [2, 2], true);
	    testPrim('>=', types.rational, [3, 2], true);
	    testPrim('>=', types.rational, [4, 3, 2, 1], true);
	    testPrim('>=', types.rational, [4, 3, 3, 2], true);
	    testPrim('>=', types.rational, [5, 3, 5, 4], false);
	});


runTest('positive?',
	function() {
		testPrim('positive?', types.rational, [-1], false);
		testPrim('positive?', types.rational, [0], false);
		testPrim('positive?', types.rational, [1], true);
	});


runTest('negative?',
	function() {
		testPrim('negative?', types.rational, [-1], true);
		testPrim('negative?', types.rational, [0], false);
		testPrim('negative?', types.rational, [1], false);
	});


runTest('max',
	function() {
		testPrim('max', types.rational, [1], types.rational(1));
		testPrim('max', types.rational, [1, 2], types.rational(2));
		testPrim('max', types.rational, [2, 1, 4, 3, 6, 2], types.rational(6));
	});


runTest('min',
	function() {
		testPrim('min', types.rational, [1], types.rational(1));
		testPrim('min', types.rational, [1, 2], types.rational(1));
		testPrim('min', types.rational, [2, 1, 4, 3, 6, 2], types.rational(1));
	});


runTest('=~',
	function() {
		testPrim('=~', id, [1, 2, 2], true);
		testPrim('=~', id, [1, 2, types.float(0.5)], false);
		testPrim('=~', types.rational, [5, 3, 1], false);
		testPrim('=~', types.rational, [5, 3, 4], true);
	});


runTest('conjugate',
	function() {
		testPrim('conjugate', id, [1], 1);
		testPrim('conjugate', id, [types.complex(3, 3)], types.complex(3, -3));
	});


runTest('magnitude',
	function() {
		testPrim('magnitude', id, [4], 4);
		testPrim('magnitude', id, [types.complex(3, 4)], 5);
		testPrim('magnitude', id, [types.float(3.5)], types.float(3.5));
		testPrim('magnitude', id, [types.rational(3, 5)], types.rational(3, 5));
		testPrim('magnitude', id, [types.complex(12, 5)], 13);
	});


runTest('number?',
	function() {
		testPrim('number?', id, [5], true);
		testPrim('number?', types.rational, [10], true);
		testPrim('number?', id, [types.rational(10, 3)], true);
		testPrim('number?', types.float, [10.5], true);
		testPrim('number?', id, [types.complex(5, 3)], true);
		testPrim('number?', id, ['string'], false);
	});


runTest('complex?',
	function() {
		testPrim('complex?', id, [5], true);
		testPrim('complex?', types.rational, [10], true);
		testPrim('complex?', id, [types.rational(10, 3)], true);
		testPrim('complex?', types.float, [10.5], true);
		testPrim('complex?', id, [types.complex(5, 3)], true);
		testPrim('complex?', id, ['string'], false);
	});


runTest('real?',
	function() {
		testPrim('real?', id, [5], true);
		testPrim('real?', types.rational, [10], true);
		testPrim('real?', id, [types.rational(10, 3)], true);
		testPrim('real?', types.float, [10.5], true);
		testPrim('real?', id, [types.complex(5, 3)], false);
		testPrim('real?', id, ['string'], false);
	});


runTest('rational?',
	function() {
		testPrim('rational?', id, [5], true);
		testPrim('rational?', types.rational, [10], true);
		testPrim('rational?', id, [types.rational(10, 3)], true);
		testPrim('rational?', types.float, [10.5], true);
		testPrim('rational?', types.float, [Math.sqrt(2)], true);
		testPrim('rational?', id, [types.complex(5, 3)], false);
		testPrim('rational?', id, ['string'], false);
	});


runTest('integer?',
	function() {
		testPrim('integer?', id, [5], true);
		testPrim('integer?', types.rational, [10], true);
		testPrim('integer?', id, [types.complex(5, 0)], true);
		testPrim('integer?', id, [types.rational(10, 3)], false);
		testPrim('integer?', types.float, [10.5], false);
		testPrim('integer?', id, [types.complex(5, 3)], false);
		testPrim('integer?', id, ['string'], false);
	});


runTest('exact?',
	function() {
		testPrim('exact?', id, [5], true);
		testPrim('exact?', id, [types.rational(4, 3)], true);
		testPrim('exact?', types.float, [10.0], false);
		testPrim('exact?', id, [types.complex(5, 2)], true);
		testPrim('exact?', id, [types.complex(types.float(5.2), types.float(0.1))], false);
	});


runTest('inexact?',
	function() {
		testPrim('inexact?', id, [5], false);
		testPrim('inexact?', id, [types.rational(4, 3)], false);
		testPrim('inexact?', types.float, [10.0], true);
		testPrim('inexact?', id, [types.complex(5, 2)], false);
		testPrim('inexact?', id, [types.complex(types.float(5.2), types.float(0.1))], true);
	});


runTest('odd? and even?',
	function() {
		testPrim('odd?', id, [5], true);
		testPrim('odd?', types.float, [10.0], false);
		testPrim('even?', id, [15], false);
		testPrim('even?', types.float, [13.0], false);
	});


runTest('gcd and lcm',
	function() {
		testPrim('gcd', id, [1001, 98], 7);
		testPrim('gcd', id, [6, 10, 15], 1);
		testPrim('lcm', id, [91, 77], 1001);
		testPrim('lcm', id, [6, 10, 15], 30);
	});


runTest('floor, ceiling, and round',
	function() {
	    testPrim('floor', id, [14], 14);
	    testPrim('floor', types.float, [12.56], types.float(12));
	    testPrim('ceiling', id, [13], 13);
	    testPrim('ceiling', types.float, [12.23], types.float(13));
	    testPrim('ceiling', types.float, [12.00], types.float(12));
	    testPrim('round', id, [124], 124);
	    testPrim('round', types.float, [12.432], types.float(12));
	    testPrim('round', types.float, [12.543], types.float(13));
	});


runTest('numerator and denominator',
	function() {
		testPrim('numerator', id, [30], 30);
		testPrim('numerator', id, [types.rational(10, -2)], -5);
		testPrim('numerator', types.float, [10.5], types.float(21));
		testPrim('numerator', types.float, [-2.53], types.float(-253));
		testPrim('denominator', id, [43], 1);
		testPrim('denominator', id, [types.rational(12, 4)], 1);
		testPrim('denominator', id, [types.rational(23, -5)], 5);
		testPrim('denominator', types.float, [12.125], types.float(8));
		testPrim('denominator', types.float, [-2.53], types.float(100));
	});


runTest('exp and log',
	function() {
		testPrim('exp', id, [0], 1);
		testPrim('exp', types.float, [0], types.float(1));
		testPrim('exp', id, [3], types.float(Math.exp(3)));
		testPrim('log', id, [1], 0);
		testPrim('log', types.float, [1], types.float(0));
		testPrim('log', id, [primitive.getPrimitive('e')], types.float(1));
	});


runTest('sin, cos, tan, asin, acos, atan',
	function() {
	    testPrim('sin', id, [20], types.float(Math.sin(20)));
 	    testPrim('sin', id, [0], 0);
 	    testPrim('cos', id, [0], 1);
 	    testPrim('cos', types.float, [43], types.float(Math.cos(43)));
 	    testPrim('tan', types.float, [0], types.float(0));
 	    testPrim('tan', id, [-30], types.float(Math.tan(-30)));
	    
 	    testPrim('asin', types.float, [-0.5], types.float(Math.asin(-0.5)));
 	    testPrim('acos', types.float, [0.53], types.float(Math.acos(0.53)));
 	    testPrim('atan', types.float, [-543], types.float(Math.atan(-543)));
	});


runTest('sqrt, integer-sqrt, and expt',
	function() {
		testPrim('sqrt', id, [25], 5);
		testPrim('sqrt', types.float, [1.44], types.float(1.2));
		testPrim('sqrt', id, [-1], types.complex(0, 1));
		testPrim('sqrt', id, [types.complex(0, 2)], types.complex(1, 1));
		testPrim('sqrt', id, [types.complex(types.float(0), types.float(-2))],
			 types.complex(types.float(1), types.float(-1)));

		testPrim('integer-sqrt', id, [15], 3);
		testPrim('integer-sqrt', id, [88], 9);

		testPrim('expt', id, [2, 20], 1048576);
		testPrim('expt', id, [3, 3], 27);
		testPrim('expt', types.float, [12.4, 5.43], types.float(Math.pow(12.4, 5.43)));
	});


runTest('make-rectangular, make-polar, real-part, imag-part, angle',
	function() {
		testPrim('make-rectangular', id, [5, 3], types.complex(5, 3));
		testPrim('make-rectangular', id, [5, types.float(4)],
			 types.complex(types.float(5), types.float(4)));
		
		testPrim('make-polar', id, [1, 0], types.complex(1, 0));
		testPrimF('make-polar', types.float, [5, Math.PI/2], true,
			  function(res) {
			  	return (jsnums.isInexact(res) &&
					Math.abs(jsnums.toFixnum(jsnums.realPart(res))) < 0.000001 &&
					Math.abs(jsnums.toFixnum(jsnums.imaginaryPart(res)) - 5) < 0.0000001);
				});

		testPrim('real-part', id, [14], 14);
		testPrim('real-part', types.float, [4], types.float(4));
		testPrim('real-part', id, [types.complex(0, 1)], 0);
		testPrim('real-part', id, [types.complex(types.float(1.44), types.float(5))], types.float(1.44));

		testPrim('imag-part', id, [14], 0);
		testPrim('imag-part', types.float, [4], 0);
		testPrim('imag-part', id, [types.complex(0, 1)], 1);
		testPrim('imag-part', id, [types.complex(types.float(1.44), types.float(5))], types.float(5));

		testPrim('angle', id, [types.complex(3, 0)], 0);
		testPrim('angle', types.float, [4.46], 0);
		testPrim('angle', id, [-54], types.float(Math.PI));
		testPrimF('angle', id, [types.complex(1, 1)], true,
		          function(res) {
			  	return (jsnums.isInexact(res) &&
					Math.abs(jsnums.toFixnum(res) - Math.PI/4) < 0.0000001);
				});
	});


runTest('exact->inexact and inexact->exact',
	function() {
		testPrim('exact->inexact', id, [5], types.float(5));
		testPrim('exact->inexact', types.float, [5.2], types.float(5.2));
		testPrim('exact->inexact', id, [types.rational(2, 3)], types.float(2/3));
		testPrim('exact->inexact', id, [types.complex(3, 5)], types.complex(types.float(3), types.float(5)));

		testPrim('inexact->exact', types.float, [0], 0);
		testPrim('inexact->exact', types.float, [1.25], types.rational(5, 4));
		testPrim('inexact->exact', id, [5], 5);
		testPrim('inexact->exact', id, [types.complex(5, 3)], types.complex(5, 3));
		testPrim('inexact->exact', id, [types.complex(types.float(5.2), types.float(4))],
			 types.complex(types.rational(26, 5), 4));
	});


runTest('first, second, third, fourth, fifth, sixth, seventh, eighth',
	function() {
		var testList1 = types.list([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
		var testList2 = types.list([types.list([1, 2]),
					      types.list([3, 4]),
					      types.list([5, 6]),
					      types.list([7, 8]),
					      types.list([9, 10]),
					      types.list([11, 12]),
					      types.list([13, 14]),
					      types.list([15, 16]),
					      types.list([17, 18]),
					      types.list([19, 20])]);
		testPrim('first', id, [testList1], 1);
		testPrim('first', id, [testList2], types.list([1, 2]));
		
		testPrim('second', id, [testList1], 2);
		testPrim('second', id, [testList2], types.list([3, 4]));

		testPrim('third', id, [testList1], 3);
		testPrim('third', id, [testList2], types.list([5, 6]));

		testPrim('fourth', id, [testList1], 4);
		testPrim('fourth', id, [testList2], types.list([7, 8]));

		testPrim('fifth', id, [testList1], 5);
		testPrim('fifth', id, [testList2], types.list([9, 10]));

		testPrim('sixth', id, [testList1], 6);
		testPrim('sixth', id, [testList2], types.list([11, 12]));

		testPrim('seventh', id, [testList1], 7);
		testPrim('seventh', id, [testList2], types.list([13, 14]));

		testPrim('eighth', id, [testList1], 8);
		testPrim('eighth', id, [testList2], types.list([15, 16]));
	});




/*************************************
 *** Primitive List Function Tests ***
 *************************************/


runTest('cons, car, and cdr',
	function() {
		var state = new StateModule.State();
		state.pushControl(makeApplication(makePrimval('car'),
						  [makeApplication(makePrimval('cons'),
							 	   [makeConstant(types.rational(1)),
								    makeConstant(types.EMPTY)])]));
		assert.deepEqual(run(state), types.rational(1));

		state.pushControl(makeApplication(makePrimval('cdr'),
						  [makeApplication(makePrimval('cons'),
							  	   [makeConstant(types.rational(1)),
								    makeConstant(types.EMPTY)])]));
		assert.deepEqual(run(state), types.EMPTY);

		state.pushControl(makeApplication(makePrimval('cdr'),
						  [makeApplication(makePrimval('cons'),
							[makeConstant(types.rational(1)),
							 makeApplication(makePrimval('cons'),
								[makeConstant(types.rational(2)),
								 makeConstant(types.EMPTY)])])]));
		assert.deepEqual(run(state), types.pair(2, types.EMPTY));
	});


runTest('list?',
	function() {
		testPrim('list?', id, [types.EMPTY], true);
		testPrim('list?', id, [types.pair(1, types.EMPTY)], true);
		testPrim('list?', id, [types.list([1, 2, 0, 3, 2])], true);
		testPrim('list?', id, [types.pair(1, 4)], false);
		testPrim('list?', id, [types.complex(0, 2)], false);
	});


runTest('list',
	function() {
		testPrim('list', types.rational, [], types.EMPTY);
		testPrim('list', types.rational, [1], types.pair(types.rational(1), types.EMPTY));
		testPrim('list', types.rational, [1, 5, 3], types.list([types.rational(1),
									    types.rational(5),
									    types.rational(3)]));
	});


runTest('list*',
	function() {
		testPrim('list*', id, [types.EMPTY], types.EMPTY);
		testPrim('list*', id, [types.rational(1), types.pair(types.rational(2), types.EMPTY)],
			 types.list([types.rational(1), types.rational(2)]));
		testPrim('list*', id, [1, 2, 3, types.list([4, 5])], types.list([1, 2, 3, 4, 5]));
	});


runTest('length',
	function() {
		testPrim('length', id, [types.EMPTY], 0);
		testPrim('length', types.list, [[1]], 1);
		testPrim('length', types.list, [[1, 2, 3, 4]], 4);
	});


runTest('append',
	function() {
		testPrim('append', types.list, [], types.EMPTY);
		testPrim('append', types.list, [[1]], types.list([1]));
		testPrim('append', types.list, [[], [1, 2, 3], [1, 2]],
			 types.list([1, 2, 3, 1, 2]));
		testPrim('append', id, [types.list([1, 2]), types.list([3]), 4],
			 types.pair(1, types.pair(2, types.pair(3, 4))));
		testPrim('append', id, [5], 5);
		testPrim('append', id, [types.EMPTY, 3], 3);
	});


runTest('reverse',
	function() {
		testPrim('reverse', id, [types.EMPTY], types.EMPTY);
		testPrim('reverse', id, [types.list([1])], types.list([1]));
		testPrim('reverse', id, [types.list([1, 2, 3, 4, 5])], types.list([5, 4, 3, 2, 1]));
	});


runTest('list-ref',
	function() {
		var testList = types.list([types.rational(1),
					     types.rational(1),
					     types.rational(2),
					     types.rational(3),
					     types.rational(5),
					     types.rational(8),
					     types.rational(11)]);
		testPrim('list-ref', id, [testList, types.rational(0)], types.rational(1));
		testPrim('list-ref', id, [testList, types.rational(5)], types.rational(8));
	});


runTest('memq',
	function() {
		testPrim('memq', id, [0, types.list([1, 2, 3])], false);
		testPrim('memq', id, [2, types.list([1, 2, 3])], types.list([2, 3]));
		testPrim('memq', id, [types.complex(2, 2),
				      types.list([types.complex(1, 1),
						    types.complex(2, 2),
						    types.complex(3, 3)])],
			 false);
		testPrim('memq', id, [types.char('a'),
				      types.list([types.char('c'),
						    types.char('b'),
						    types.char('a')])],
			 types.list([types.char('a')]));
		testPrim('memq', id, [types.string('a'),
				      types.list([types.string('c'),
						    types.string('b'),
						    types.string('a')])],
			 false);

		var str = types.string('hi');
		testPrim('memq', id, [str, types.list([types.string('Yo'),
						         types.string(', '),
						         str])],
			 types.list([str]));
	});


runTest('memv',
	function() {
		testPrim('memv', id, [0, types.list([1, 2, 3])], false);
		testPrim('memv', id, [2, types.list([1, 2, 3])], types.list([2, 3]));
		testPrim('memv', id, [types.complex(2, 2),
				      types.list([types.complex(1, 1),
						    types.complex(2, 2),
						    types.complex(3, 3)])],
			 types.list([types.complex(2, 2), types.complex(3, 3)]));
		testPrim('memv', id, [types.char('a'),
				      types.list([types.char('c'),
						    types.char('b'),
						    types.char('a')])],
			 types.list([types.char('a')]));
		testPrim('memv', id, [types.string('a'),
				      types.list([types.string('c'),
						    types.string('b'),
						    types.string('a')])],
			 false);

		var str = types.string('hi');
		testPrim('memv', id, [str, types.list([types.string('Yo'),
						         types.string(', '),
						         str])],
			 types.list([str]));
	});


runTest('member',
	function() {
		testPrim('member', id, [0, types.list([1, 2, 3])], false);
 		testPrim('member', id, [2, types.list([1, 2, 3])], types.list([2, 3]));
 		testPrim('member', id, [types.complex(2, 2),
 				        types.list([types.complex(1, 1),
 						      types.complex(2, 2),
 						      types.complex(3, 3)])],
 			 types.list([types.complex(2, 2), types.complex(3, 3)]));
 		testPrimF('member', id, [types.char('b'),
 					 types.list([types.char('c'),
 						       types.char('b'),
 						       types.char('a')])],
 			  ['#\\b', '#\\a'], listToStringArray);
 		testPrimF('member', id, [types.string('a'),
 					 types.list([types.string('c'),
 						       types.string('b'),
 						       types.string('a')])],
 			  ['a'], listToStringArray);

 		var str = types.string('hi');
 		testPrim('member', id, [str, types.list([types.string('Yo'),
 							   types.string(', '),
 							   str])],
 			 types.list([str]));
	});


runTest('remove',
	function() {
		testPrim('remove', id, [3, types.list([1, 2, 3, 4, 5])], types.list([1, 2, 4, 5]));
		testPrim('remove', id, [1, types.list([1, 2, 1, 2])], types.list([2, 1, 2]));
		testPrim('remove', id, [10, types.list([1, 2, 3, 4])], types.list([1,2,3,4]));
		testPrimF('remove', id, [types.string('a'), types.list([types.string('b'),
									    types.string('a'),
									    types.string('c'),
									    types.string('a')])],
			  ['b', 'c', 'a'], listToStringArray);
		var state = new StateModule.State();
		state.pushControl(makeApplication(makePrimval('remove'),
						  [makeConstant(types.string('a')),
						   makeConstant(types.list([types.string('b'),
									      types.string('a'),
									      types.string('c'),
									      types.string('a')]))]));
		var res = run(state);
		assert.deepEqual(res.first().toString(), 'b');
		assert.deepEqual(res.rest().first().toString(), 'c');
		assert.deepEqual(res.rest().rest().first().toString(), 'a');
		assert.deepEqual(res.rest().rest().rest(), types.EMPTY);
	});



runTest('map',
	function() {
		var state = new StateModule.State();
		state.pushControl(makeApplication(makePrimval('map'),
						  [makePrimval('add1'),
						   makeConstant(types.list([1, 2, 3]))]));
		assert.deepEqual(run(state), types.list([2, 3, 4]));
	});

runTest('filter',
	function() {
		var state = new StateModule.State();
		state.pushControl(makeApplication(makePrimval('filter'),
						  [makePrimval('even?'),
						   makeConstant(types.list([1, 2, 3, 4, 5, 6]))]));
		assert.deepEqual(run(state), types.list([2, 4, 6]));

		state.pushControl(makeApplication(makePrimval('filter'),
						  [makeLam(1, [], makeConstant(false)),
						   makeConstant(types.list([1, 2, 3, 4]))]));
		assert.deepEqual(run(state), types.EMPTY);

		state.pushControl(makeApplication(makePrimval('filter'),
						  [makeLam(1, [], makeConstant(true)),
						   makeConstant(types.list([1, 2, 3, 4]))]));
		assert.deepEqual(run(state), types.list([1, 2, 3, 4]));
	});


runTest('foldl',
	function() {
		var state = new StateModule.State();
		state.pushControl(makeApplication(makePrimval('foldl'),
						  [makePrimval('-'),
						   makeConstant(2),
						   makeConstant(types.list([1, 2, 3, 4]))]));
		assert.deepEqual(run(state), 4);

		state.pushControl(makeApplication(makePrimval('foldl'),
						  [makePrimval('cons'),
						   makeConstant(types.list([1, 2])),
						   makeConstant(types.list([3, 4, 5, 6]))]));
		assert.deepEqual(run(state), types.list([6, 5, 4, 3, 1, 2]));
	});


runTest('foldr',
	function() {
		var state = new StateModule.State();
		state.pushControl(makeApplication(makePrimval('foldr'),
						  [makePrimval('-'),
						   makeConstant(2),
						   makeConstant(types.list([1, 2, 3, 4]))]));
		assert.deepEqual(run(state), 0);

		state.pushControl(makeApplication(makePrimval('foldr'),
						  [makePrimval('cons'),
						   makeConstant(types.list([1, 2])),
						   makeConstant(types.list([3, 4, 5, 6]))]));
		assert.deepEqual(run(state), types.list([3, 4, 5, 6, 1, 2]));
	});



runTest('build-list',
	function() {
		var state = new StateModule.State();
		state.pushControl(makeApplication(makePrimval('build-list'),
						  [makeConstant(5), makePrimval('add1')]));
		assert.deepEqual(run(state), types.list([1, 2, 3, 4, 5]));

		state.pushControl(makeApplication(makePrimval('build-list'),
						  [makeConstant(5), makePrimval('number->string')]));
		assert.deepEqual(run(state), types.list([types.string('0'),
							   types.string('1'),
							   types.string('2'),
							   types.string('3'),
							   types.string('4')]));
	});


runTest('argmax',
	function() {
		var state = new StateModule.State();
		state.pushControl(makeApplication(makePrimval('argmax'),
						  [makePrimval('car'),
						   makeConstant(types.list([types.pair(1, 2),
									      types.list([1, 2, 3]),
									      types.pair(3, 5),
									      types.pair(2, 13)]))]));
		assert.deepEqual(run(state), types.pair(3, 5));

		state.pushControl(makeApplication(makePrimval('argmax'),
						  [makePrimval('-'),
						   makeConstant(types.list([1, 3, 5, 2, 4]))]));
		assert.deepEqual(run(state), 1);
	});


runTest('argmin',
	function() {
		var state = new StateModule.State();
		state.pushControl(makeApplication(makePrimval('argmin'),
						  [makePrimval('car'),
						   makeConstant(types.list([types.pair(1, 2),
									      types.list([1, 2, 3]),
									      types.pair(3, 5),
									      types.pair(2, 13)]))]));
		assert.deepEqual(run(state), types.pair(1, 2));

		state.pushControl(makeApplication(makePrimval('argmin'),
						  [makePrimval('-'),
						   makeConstant(types.list([1, 3, 5, 2, 4]))]));
		assert.deepEqual(run(state), 5);
	});


runTest('quicksort',
	function() {
	    var state = new StateModule.State();
	    state.pushControl(makeApplication(makePrimval('quicksort'),
					      [makeConstant(types.list([4, 3, 6, 8, 2, 9])),
					       makePrimval('<')]));
	    var result = run(state);
	    assert.ok(types.isEqual(result, types.list([2, 3, 4, 6, 8, 9])));

	    state.pushControl(makeApplication(makePrimval('quicksort'),
					      [makeConstant(types.list([types.char('k'),
									types.char('o'),
									types.char('c'),
									types.char('g')])),
					       makePrimval('char>?')]));
	    assert.ok(types.isEqual(run(state), types.list([types.char('o'),
							    types.char('k'),
							    types.char('g'),
							    types.char('c')])));
	});


runTest('compose',
	function() {
		var state = new StateModule.State();
		state.pushControl(makeApplication(makeApplication(makePrimval('compose'),
								  [makePrimval('magnitude'),
								   makePrimval('+'),
								   makePrimval('values')]),
						  [makeConstant(2),
						   makeConstant(3),
						   makeConstant(2),
						   makeConstant(types.complex(-4, 4))]));
		assert.deepEqual(run(state), types.rational(5));

		var composed = makeApplication(makePrimval('compose'),
					       [makePrimval('even?'),
						makePrimval('*'),
						makePrimval('values')]);
		state.pushControl(makeApplication(composed, [makeConstant(3), makeConstant(5)]));
		assert.deepEqual(run(state), false);
		state.pushControl(makeApplication(composed, [makeConstant(2), makeConstant(4), makeConstant(15)]));
		assert.deepEqual(run(state), true);
	});


runTest('caar, cadr, cdar, cddr, etc.',
	function() {
		var deepArrayToList = function(a) {
			if ( !(a instanceof Array) ) {
				return a;
			}
			return types.list( helpers.map(deepArrayToList, a) );
		}

		testPrim('car', types.list, [[1, 2, 3]], 1);
		testPrim('caar', deepArrayToList, [[[1, 2], [3, 4], []]], 1);
		testPrim('caar', deepArrayToList, [[[[1, 2], [3, 4]], [[5, 6], [7, 8]]]], types.list([1, 2]));
		testPrim('caar', types.list, [[types.pair(1, types.pair(2, 3))]], 1);

		testPrim('cadr', types.list, [[1, 2, 3]], 2);
		testPrim('cadr', deepArrayToList, [[[1, 2], [3, 4]]], types.list([3, 4]));

		testPrim('cdar', deepArrayToList, [[[1, 2], [3, 4], []]], types.list([2]));
		testPrim('cdar', types.list, [[types.pair(1, 2)]], 2);

		testPrim('cddr', types.list, [[1, 2, 3, 4]], types.list([3, 4]));
		testPrim('cddr', deepArrayToList, [[[], [1], [1, 2], [1, 2, 3]]], deepArrayToList([[1, 2], [1, 2, 3]]));
		testPrim('cddr', id, [types.pair(1, types.pair(2, 3))], 3);

		testPrim('caaar', deepArrayToList, [[[[1, 2], [3, 4]], [[5, 6], [7, 8]]]], 1);
		testPrim('caaar', deepArrayToList, [[[types.pair(0, 1)]]], 0);

		testPrim('caadr', deepArrayToList, [[[1, 2], [3, 4], []]], 3);
		testPrim('caadr', deepArrayToList, [[[[1, 2], [3, 4]], [[5, 6], [7, 8]]]], types.list([5, 6]));

		testPrim('cadar', deepArrayToList, [[[1, 2], [3, 4], []]], 2);
		testPrim('cadar', deepArrayToList, [[[[1, 2], [3, 4]], [[5, 6], [7, 8]]]], types.list([3, 4]));

		testPrim('cdaar', deepArrayToList, [[[[1, 2], [3, 4]], [[5, 6], [7, 8]]]], types.list([2]));
		testPrim('cdaar', deepArrayToList, [[[types.pair(0, 1)]]], 1);

		testPrim('cdadr', deepArrayToList, [[[1, 2], [3, 4], []]], types.list([4]));
		testPrim('cdadr', deepArrayToList, [[[[1, 2], [3, 4]], [[5, 6], [7, 8]]]], deepArrayToList([[7, 8]]));
		testPrim('cdadr', deepArrayToList, [[types.pair(1, 2), types.pair(3, 4)]], 4);

		testPrim('cddar', deepArrayToList, [[[1, 2], [3, 4], []]], types.EMPTY);
		testPrim('cddar', deepArrayToList, [[types.pair(1, types.pair(2, 3))]], 3);

		testPrim('caddr', types.list, [[1, 2, 3, 4]], 3);
		testPrim('caddr', deepArrayToList, [[[1, 2], [3, 4], []]], types.EMPTY);

		testPrim('cdddr', types.list, [[1, 2, 3, 4]], types.list([4]));
		testPrim('cdddr', id, [types.pair(1, types.pair(2, types.pair(3, 4)))], 4);

		testPrim('cadddr', types.list, [[1, 2, 3, 4]], 4);
		testPrim('cadddr', deepArrayToList, [[[1, 2], [3, 4], [5, 6], [7, 8]]], types.list([7, 8]));
	});




/***************************
 *** Box Primitive Tests ***
 ***************************/


runTest('box',
	function() {
		testPrim('box', id, [1], types.box(1));
		testPrim('box', types.string, ['abc'], types.box(types.string('abc')));
	});


runTest('box?',
	function() {
		testPrim('box?', types.box, [1], true);
		testPrim('box?', types.char, ['a'], false);
		testPrim('box?', id, [15], false);
	});


runTest('unbox',
	function() {
		testPrim('unbox', types.box, [2], 2);
		testPrim('unbox', types.box, [types.char('a')], types.char('a'));
	});


runTest('set-box!',
	function() {
		var testBox1 = types.box(1);
		var testBox2 = types.box(types.string('hello'));
		testPrim('set-box!', id, [testBox1, 15], types.VOID);
		testPrim('set-box!', id, [testBox2, types.string('world')], types.VOID);

		assert.deepEqual(testBox1, types.box(15));
		assert.deepEqual(testBox2, types.box(types.string('world')));
	});




/****************************
 *** Hash Primitive Tests ***
 ****************************/


runTest('hash?',
	function() {
		testPrim('hash?', id, [1], false);
		testPrim('hash?', types.vector, [[1, 2, 3]], false);
		testPrim('hash?', types.hash, [types.EMPTY], true);
		testPrim('hash?', types.hashEq, [types.EMPTY], true);
		testPrim('hash?', types.hash, [types.list([types.pair(1, 2)])], true);
		testPrim('hash?', types.hashEq, [types.list([types.pair(1, 2)])], true);
	});


runTest('str',
	function() {
	    assert.equal(typeof(types.string('a')), 'object');
	});


runTest('make-hash',
	function() {
		var state = new StateModule.State();
		state.pushControl(makeApplication(makePrimval('make-hash'), []));
		var res = run(state);
		assert.ok(types.isHash(res));
		assert.ok(res.hash.isEmpty());


		state.pushControl(makeApplication(makePrimval('make-hash'),
						  [makeConstant(types.list([types.pair(1, 2),
									      types.pair(3, 4),
									      types.pair(5, 6)]))]));
		var res2 = run(state);
		assert.ok(types.isHash(res2));
		assert.ok( !res2.hash.isEmpty() );
		assert.ok(res2.hash.containsKey(1));
		assert.ok(res2.hash.containsKey(3));
		assert.ok(res2.hash.containsKey(5));
		assert.deepEqual(res2.hash.get(1), 2);
		assert.deepEqual(res2.hash.get(3), 4);
		assert.deepEqual(res2.hash.get(5), 6);

		state.pushControl(makeApplication(makePrimval('make-hash'),
						  [makeConstant(types.list(
								  [types.pair(types.string('a'),
									  	2)]))]));
		var res3 = run(state);
		assert.deepEqual(res3.hash.get(types.string('a')), 2);
	});


runTest('make-hasheq',
	function() {
		var state = new StateModule.State();
		state.pushControl(makeApplication(makePrimval('make-hasheq'), []));
		var res = run(state);
		assert.ok(types.isHash(res));
		assert.ok(res.hash.isEmpty());


		state.pushControl(makeApplication(makePrimval('make-hasheq'),
						  [makeConstant(types.list([types.pair(1, 2),
									      types.pair(3, 4),
									      types.pair(5, 6)]))]));
		var res2 = run(state);
		assert.ok(types.isHash(res2));
		assert.ok( !res2.hash.isEmpty() );
		assert.ok(res2.hash.containsKey(1));
		assert.ok(res2.hash.containsKey(3));
		assert.ok(res2.hash.containsKey(5));
		assert.deepEqual(res2.hash.get(1), 2);
		assert.deepEqual(res2.hash.get(3), 4);
		assert.deepEqual(res2.hash.get(5), 6);

		var str1 = types.string('a');
		var str2 = types.string('a');
		state.pushControl(makeApplication(makePrimval('make-hasheq'),
						  [makeConstant(types.list(
								  [types.pair(str1, 1),
								   types.pair(str2, 2)]))]));
		var res3 = run(state);
		assert.ok( !res3.hash.containsKey(types.string('a')) );
		assert.deepEqual(res3.hash.get(str1), 1);
		assert.deepEqual(res3.hash.get(str2), 2);
	});


runTest('hash-set!',
	function() {
		var testHash = types.hash(types.list([types.pair(1, 1), types.pair(2, 3)]));
		
//		sys.print('\ntestHash = ' + sys.inspect(testHash) + "\n");
//		sys.print('testHash.hash = ' + sys.inspect(testHash.hash) + '\n');

		assert.deepEqual(testHash.hash.get(1), 1);
		assert.deepEqual(testHash.hash.containsKey(5), false);

		var state = new StateModule.State();
		state.pushControl(makeApplication(makePrimval('hash-set!'),
						  [makeConstant(testHash), makeConstant(5), makeConstant(8)]));
		var result = run(state);
		assert.deepEqual(result, types.VOID);
		assert.deepEqual(testHash.hash.get(5), 8);

		state.pushControl(makeApplication(makePrimval('hash-set!'),
						  [makeConstant(testHash), makeConstant(1), makeConstant(0)]));
		assert.deepEqual(run(state), types.VOID);
		assert.deepEqual(testHash.hash.get(1), 0);
	});


runTest('hash-ref',
	function() {
		var hash1 = types.hash(types.list([types.pair(1, 2),
						       types.pair(types.string('hello'),
								    types.string('world')),
						       types.pair(types.string('hello'),
								    types.string('world2'))]));

		testPrim('hash-ref', id, [hash1, types.string('hello')], types.string('world2'));
		testPrim('hash-ref', id, [hash1, 1, false], 2);
		testPrim('hash-ref', id, [hash1, 2, false], false);

		var str1 = types.string('hello');
		var str2 = str1.copy();
		var hash2 = types.hashEq(types.list([types.pair(str1, types.string('world')),
							 types.pair(str2, types.string('world2')),
							 types.pair(1, 2),
							 types.pair(3, 4)]));
		testPrim('hash-ref', id, [hash2, types.string('hello'), false], false);
		testPrim('hash-ref', id, [hash2, str1], types.string('world'));
		testPrim('hash-ref', id, [hash2, types.string('a'), 2], 2);

		var state = new StateModule.State();
		state.pushControl(makeApplication(makePrimval('hash-ref'),
						  [makeConstant(hash1),
						   makeConstant(2),
						   makeLam(0, [], makeConstant(15))]));
		assert.deepEqual(run(state), 15);

		state.pushControl(makeApplication(makePrimval('hash-ref'),
						  [makeConstant(hash2),
						   makeConstant(types.string('hello')),
						   makeLam(0, [], makeConstant(true))]));
		assert.deepEqual(run(state), true);
	});


runTest('hash-remove!',
	function() {
		var hash1 = types.hash(types.list([types.pair(1, 2),
						       types.pair(2, 3),
						       types.pair(3, 4),
						       types.pair(4, 5)]));
		assert.ok(hash1.hash.containsKey(1));
		testPrim('hash-remove!', id, [hash1, 1], types.VOID);
		assert.ok( !hash1.hash.containsKey(1) );

		var str1 = types.string('a');
		var str2 = types.string('b');
		var hash2 = types.hashEq(types.list([types.pair(str1, 5),
							 types.pair(str2, 3)]));
		testPrim('hash-remove!', id, [hash2, types.string('a')], types.VOID);
		assert.ok(hash2.hash.containsKey(str1));
		testPrim('hash-remove!', id, [hash2, str2], types.VOID);
		assert.ok( !hash2.hash.containsKey(str2) );
	});


runTest('hash-map',
	function() {
		var str1 = types.string('hello');
		var str2 = str1.copy();
		var str3 = str1.copy();
		var hash1 = types.hash(types.list([types.pair(str1, types.string('a')),
						       types.pair(str2, types.string('b')),
						       types.pair(str3, types.string('c'))]));

		var state = new StateModule.State();
		state.pushControl(makeApplication(makePrimval('hash-map'),
						  [makeConstant(hash1), makePrimval('string-append')]));
		assert.ok( hash1.hash.containsKey(types.string('hello')) );
		assert.deepEqual(run(state), types.list([types.string('helloc')]));

		var hash2 = types.hashEq(types.list([types.pair(str1, types.string('a')),
							 types.pair(str2, types.string('b')),
							 types.pair(str3, types.string('c'))]));

		var state = new StateModule.State();
		state.pushControl(makeApplication(makePrimval('hash-map'),
						  [makeConstant(hash2), makePrimval('string-append')]));
		assert.deepEqual(run(state), types.list([types.string('helloc'),
							   types.string('hellob'),
							   types.string('helloa')]));
	});


runTest('hash-for-each',
	function() {
		var hash1 = types.hash(types.list([types.pair(1, 2),
						       types.pair(2, 3),
						       types.pair(3, 4),
						       types.pair(4, 5)]));
		var state = new StateModule.State();
		var ret = [];
		state.pushControl(makeApplication(makePrimval('hash-for-each'),
						  [makeConstant(hash1),
						   makeConstant(new types.PrimProc('', 2, false, false,
								function(key, val) {
								  	ret.push( helpers.format('~s - ~s!~n', [key, val]) );
								}))]));
		assert.deepEqual(run(state), types.VOID);
		assert.deepEqual(ret, ['1 - 2!\n', '2 - 3!\n', '3 - 4!\n', '4 - 5!\n']);
	});





/******************************
 *** Vector Primitive Tests ***
 ******************************/


runTest('vector?',
	function() {
		testPrim('vector?', id, [1], false);
		testPrim('vector?', types.list, [[1, 2, 3]], false);
		testPrim('vector?', types.vector, [[1, 2, 3]], true);
	});


runTest('make-vector',
	function() {
		testPrim('make-vector', id, [0, types.char('a')], types.vector([]));
		testPrim('make-vector', id, [3, 5], types.vector([5, 5, 5]));
	});


runTest('vector',
	function() {
		testPrim('vector', id, [1, 2, 3, 4], types.vector([1, 2, 3, 4]));
		testPrim('vector', id, [], types.vector([]));
	});


runTest('vector-length',
	function() {
		testPrim('vector-length', types.vector, [[]], 0);
		testPrim('vector-length', types.vector, [[1, 2, 3]], 3);
	});


runTest('vector-ref',
	function() {
		testPrim('vector-ref', id, [types.vector([1, 2]), 1], 2);
		testPrim('vector-ref', id, [types.vector([3, 2, 1]), 0], 3);
	});


runTest('vector-set!',
	function() {
		testPrim('vector-set!', id, [types.vector([1, 2, 3]), 0, types.char('a')], types.VOID);

		var testVec = types.vector([1, 2, 3, 4]);
		var state = new StateModule.State();
		state.pushControl(makeApplication(makePrimval('vector-set!'),
						  [makeConstant(testVec),
						   makeConstant(2),
						   makeConstant(5)]));
		var result = run(state);
		assert.deepEqual(result, types.VOID);
		assert.deepEqual(testVec, types.vector([1, 2, 5, 4]));

		var testVec2 = types.vector([types.char('a'),
					       types.char('b'),
					       types.char('c')]);
		state.pushControl(makeApplication(makePrimval('vector-set!'),
						  [makeConstant(testVec2),
						   makeConstant(1),
						   makeConstant(types.char('B'))]));
		run(state);
		assert.deepEqual(testVec2, types.vector([types.char('a'),
							   types.char('B'),
							   types.char('c')]));
	});


runTest('vector->list',
	function() {
		testPrim('vector->list', types.vector, [[]], types.EMPTY);
		testPrim('vector->list', types.vector, [[1, 2, 3]], types.list([1, 2, 3]));
	});



/****************************
 *** Char Primitive Tests ***
 ****************************/




runTest('char?',
	function() {
		testPrim('char?', id, [types.symbol('hello!')], false);
		testPrim('char?', types.string, ['string'], false);
		testPrim('char?', types.char, ['w'], true);
	});


runTest('char=?',
	function() {
		testPrim('char=?', types.char, ['a', 's', 'D'], false);
		testPrim('char=?', types.char, ['f', 'F'], false);
		testPrim('char=?', types.char, ['a', 'a', 'a'], true);
		testPrim('char=?', types.char, ['1', '1', '2'], false);
	});

runTest('char-ci=?',
	function() {
		testPrim('char-ci=?', types.char, ['a', 's', 'D'], false);
		testPrim('char-ci=?', types.char, ['f', 'F'], true);
		testPrim('char-ci=?', types.char, ['a', 'a', 'a'], true);
		testPrim('char-ci=?', types.char, ['1', '1', '2'], false);
	});

runTest('char<?',
	function() {
		testPrim('char<?', types.char, ['A', 'a'], true);
		testPrim('char<?', types.char, ['a', 'b'], true);
		testPrim('char<?', types.char, ['b', 'a'], false);
		testPrim('char<?', types.char, ['a', 'd', 'c'], false);
		testPrim('char<?', types.char, ['a', 'b', 'b', 'd'], false);
		testPrim('char<?', types.char, ['a', 'b', 'c', 'd', 'e'], true);
	});

runTest('char>?',
	function() {
		testPrim('char>?', types.char, ['A', 'a'], false);
		testPrim('char>?', types.char, ['a', 'b'], false);
		testPrim('char>?', types.char, ['b', 'a'], true);
		testPrim('char>?', types.char, ['f', 'd', 'e'], false);
		testPrim('char>?', types.char, ['e', 'd', 'c', 'c', 'a'], false);
		testPrim('char>?', types.char, ['e', 'd', 'c', 'b', 'a'], true);
	});

runTest('char<=?',
	function() {
		testPrim('char<=?', types.char, ['A', 'a'], true);
		testPrim('char<=?', types.char, ['a', 'b'], true);
		testPrim('char<=?', types.char, ['b', 'a'], false);
		testPrim('char<=?', types.char, ['a', 'd', 'c'], false);
		testPrim('char<=?', types.char, ['a', 'b', 'b', 'd'], true);
		testPrim('char<=?', types.char, ['a', 'b', 'c', 'd', 'e'], true);
	});

runTest('char>=?',
	function() {
		testPrim('char>=?', types.char, ['A', 'a'], false);
		testPrim('char>=?', types.char, ['a', 'b'], false);
		testPrim('char>=?', types.char, ['b', 'a'], true);
		testPrim('char>=?', types.char, ['f', 'd', 'e'], false);
		testPrim('char>=?', types.char, ['e', 'd', 'c', 'c', 'a'], true);
		testPrim('char>=?', types.char, ['e', 'd', 'c', 'b', 'a'], true);
	});

runTest('char-ci<?',
	function() {
		testPrim('char-ci<?', types.char, ['A', 'a'], false);
		testPrim('char-ci<?', types.char, ['a', 'b'], true);
		testPrim('char-ci<?', types.char, ['b', 'A'], false);
		testPrim('char-ci<?', types.char, ['a', 'd', 'c'], false);
		testPrim('char-ci<?', types.char, ['a', 'b', 'b', 'd'], false);
		testPrim('char-ci<?', types.char, ['a', 'B', 'c', 'd', 'e'], true);
	});

runTest('char-ci>?',
	function() {
		testPrim('char-ci>?', types.char, ['a', 'A'], false);
		testPrim('char-ci>?', types.char, ['a', 'b'], false);
		testPrim('char-ci>?', types.char, ['b', 'A'], true);
		testPrim('char-ci>?', types.char, ['f', 'd', 'e'], false);
		testPrim('char-ci>?', types.char, ['e', 'd', 'c', 'c', 'a'], false);
		testPrim('char-ci>?', types.char, ['e', 'd', 'C', 'b', 'a'], true);
	});

runTest('char-ci<=?',
	function() {
		testPrim('char-ci<=?', types.char, ['a', 'A'], true);
		testPrim('char-ci<=?', types.char, ['a', 'B'], true);
		testPrim('char-ci<=?', types.char, ['b', 'a'], false);
		testPrim('char-ci<=?', types.char, ['a', 'd', 'c'], false);
		testPrim('char-ci<=?', types.char, ['a', 'b', 'B', 'd'], true);
		testPrim('char-ci<=?', types.char, ['a', 'b', 'C', 'd', 'e'], true);
	});

runTest('char-ci>=?',
	function() {
		testPrim('char-ci>=?', types.char, ['A', 'a'], true);
		testPrim('char-ci>=?', types.char, ['a', 'b'], false);
		testPrim('char-ci>=?', types.char, ['B', 'a'], true);
		testPrim('char-ci>=?', types.char, ['f', 'd', 'e'], false);
		testPrim('char-ci>=?', types.char, ['e', 'd', 'C', 'c', 'a'], true);
		testPrim('char-ci>=?', types.char, ['e', 'd', 'c', 'B', 'a'], true);
	});


runTest('char-alphabetic?',
	function() {
		testPrim('char-alphabetic?', types.char, ['a'], true);
		testPrim('char-alphabetic?', types.char, ['Z'], true);
		testPrim('char-alphabetic?', types.char, ['3'], false);
		testPrim('char-alphabetic?', types.char, [' '], false);
		testPrim('char-alphabetic?', types.char, ['!'], false);
		testPrim('char-alphabetic?', types.char, ['\n'], false);
	});


runTest('char-numeric?',
	function() {
		testPrim('char-numeric?', types.char, ['a'], false);
		testPrim('char-numeric?', types.char, ['Z'], false);
		testPrim('char-numeric?', types.char, ['3'], true);
		testPrim('char-numeric?', types.char, [' '], false);
		testPrim('char-numeric?', types.char, ['!'], false);
		testPrim('char-numeric?', types.char, ['\n'], false);
	});


runTest('char-whitespace?',
	function() {
		testPrim('char-whitespace?', types.char, ['a'], false);
		testPrim('char-whitespace?', types.char, ['Z'], false);
		testPrim('char-whitespace?', types.char, ['3'], false);
		testPrim('char-whitespace?', types.char, [' '], true);
		testPrim('char-whitespace?', types.char, ['!'], false);
		testPrim('char-whitespace?', types.char, ['\n'], true);
		testPrim('char-whitespace?', types.char, ['\t'], true);
	});


runTest('char-upper-case?',
	function() {
		testPrim('char-upper-case?', types.char, ['a'], false);
		testPrim('char-upper-case?', types.char, ['Z'], true);
		testPrim('char-upper-case?', types.char, ['3'], false);
		testPrim('char-upper-case?', types.char, [' '], false);
		testPrim('char-upper-case?', types.char, ['!'], false);
		testPrim('char-upper-case?', types.char, ['\n'], false);
	});


runTest('char-lower-case?',
	function() {
		testPrim('char-lower-case?', types.char, ['a'], true);
		testPrim('char-lower-case?', types.char, ['Z'], false);
		testPrim('char-lower-case?', types.char, ['3'], false);
		testPrim('char-lower-case?', types.char, [' '], false);
		testPrim('char-lower-case?', types.char, ['!'], false);
		testPrim('char-lower-case?', types.char, ['\n'], false);
	});


runTest('char->integer',
	function() {
		testPrim('char->integer', types.char, ['0'], 48);
		testPrim('char->integer', types.char, ['\n'], 10);
	});


runTest('integer->char',
	function() {
		testPrim('integer->char', id, [48], types.char('0'));
		testPrim('integer->char', id, [65], types.char('A'));
	});


runTest('char-upcase',
	function() {
		testPrim('char-upcase', types.char, ['a'], types.char('A'));
		testPrim('char-upcase', types.char, ['B'], types.char('B'));
		testPrim('char-upcase', types.char, ['2'], types.char('2'));
		testPrim('char-upcase', types.char, ['~'], types.char('~'));
	});


runTest('char-downcase',
	function() {
		testPrim('char-downcase', types.char, ['a'], types.char('a'));
		testPrim('char-downcase', types.char, ['B'], types.char('b'));
		testPrim('char-downcase', types.char, ['2'], types.char('2'));
		testPrim('char-downcase', types.char, ['~'], types.char('~'));
	});


runTest('char print formatting',
	function() {
		testPrim('format', id, ['~s', types.char('\n')], types.string('#\\newline'));
		testPrim('format', id, ['~s', types.char('\0')], types.string('#\\nul'));
		testPrim('format', id, ['~a', types.char('b')], types.string('b'));
		testPrim('format', id, ['~s', types.char('b')], types.string('#\\b'));

		var state = new StateModule.State();
		state.pushControl(makeApplication(makePrimval('format'),
						  [makeConstant('~s'),
						   makeApplication(makePrimval('integer->char'),
								   [makeConstant(24)])]));
		assert.deepEqual(run(state), types.string('#\\u0018'));

		state.pushControl(makeApplication(makePrimval('format'),
						  [makeConstant('~s'),
						   makeApplication(makePrimval('integer->char'),
								   [makeConstant(127)])]));
		assert.deepEqual(run(state), types.string('#\\rubout'));

		state.pushControl(makeApplication(makePrimval('format'),
						  [makeConstant('~s'),
						   makeApplication(makePrimval('integer->char'),
								   [makeConstant(955)])]));
		assert.deepEqual(run(state), types.string('#\\u03BB'));
	});


///////////////////////////////////////////////////////////////////////


runTest('values',
	function() {
		testPrim('values', id, [], new types.ValuesWrapper([]));
		testPrim('values', id, [1, 2, 3, 4], new types.ValuesWrapper([1, 2, 3, 4]));
		testPrim('values', id, [1], 1);
	});

runTest('call-with-values',
	function() {
		var state = new StateModule.State();
		state.pushControl(makeApplication(makePrimval('call-with-values'),
						  [makePrimval('values'),
						   makePrimval('+')]));
		assert.deepEqual(run(state), 0);

		state.pushControl(makeApplication(makePrimval('call-with-values'),
						  [makeLam(0, [], makeConstant(1)),
						   makePrimval('+')]));
		assert.deepEqual(run(state), 1);

		state.pushControl(makeApplication(makePrimval('call-with-values'),
						  [makeLam(0, [], makeApplication(makePrimval('values'),
								  		  [makeConstant(1),
										   makeConstant(2),
										   makeConstant(3)])),
						   makePrimval('+')]));
		assert.deepEqual(run(state), 6);
	});


runTest('not',
	function() {
		testPrim('not', id, [false], true);
		testPrim('not', id, [0], false);
		testPrim('not', id, [1], false);
		testPrim('not', types.char, ['0'], false);
	});


runTest('boolean?',
	function() {
		testPrim('boolean?', id, [false], true);
		testPrim('boolean?', id, [true], true);
		testPrim('boolean?', types.string, ['false'], false);
		testPrim('boolean?', id, [0], false);
		testPrim('boolean?', id, [1], false);
	});


runTest('eq?',
	function() {
		var testStr = types.string('hello');
		var testChar = types.char('H');
		testPrim('eq?', id, [1, 1], true);
		testPrim('eq?', id, [1, 2], false);
		testPrim('eq?', id, [types.rational(1, 3), types.rational(1, 3)], false);
		testPrim('eq?', types.symbol, ['a', 'a'], true);
		testPrim('eq?', types.string, ['a', 'a'], false);
		testPrim('eq?', id, [testStr, testStr], true);
		testPrim('eq?', id, [testChar, testChar], true);
		testPrim('eq?', id, [testChar, types.char('H')], true);
	});


runTest('eqv?',
	function() {
		var testStr = types.string('hello');
		var testChar = types.char('H');
		testPrim('eqv?', id, [1, 1], true);
		testPrim('eqv?', id, [1, 2], false);
		testPrim('eqv?', id, [types.rational(1, 3), types.rational(1, 3)], true);
		testPrim('eqv?', types.symbol, ['a', 'a'], true);
		testPrim('eqv?', types.string, ['a', 'a'], false);
		testPrim('eqv?', id, [testStr, testStr], true);
		testPrim('eqv?', id, [testChar, testChar], true);
		testPrim('eqv?', id, [testChar, types.char('H')], true);
	});


runTest('equal?',
	function() {
		var testStr = types.string('hello');
		var testChar = types.char('H');
		testPrim('equal?', id, [1, 1], true);
		testPrim('equal?', id, [1, 2], false);
		testPrim('equal?', id, [types.rational(1, 3), types.rational(1, 3)], true);
		testPrim('equal?', types.symbol, ['a', 'a'], true);
		testPrim('equal?', types.string, ['a', 'a'], true);
		testPrim('equal?', id, [testStr, testStr], true);
		testPrim('equal?', id, [testChar, testChar], true);
		testPrim('equal?', id, [testChar, types.char('H')], true);
	});


runTest('equal~?',
	function() {
		testPrim('equal~?', id, [types.string('h'), types.string('h'), 5], true);
		testPrim('equal~?', id, [5, 4, 0], false);
		testPrim('equal~?', id, [types.char('a'), types.char('b'), 3], false);
		testPrim('equal~?', id, [5, 3, 3], true);
		testPrim('equal~?', types.float, [5.4, 4.9, 0.5], true);
	});


runTest('struct?',
	function() {
		testPrim('struct?', types.string, ['a'], false);
		testPrim('struct?', id, [1], false);
		testPrim('struct?', id, [types.EMPTY], false);
		testPrim('struct?', types.box, [2], false);

	    var PosnType = types.makeStructureType(
		'posn', false, 2, 0, false, false);
	    testPrim('struct?', id, [PosnType.constructor(2, 4)], true);
	});


runTest('procedure-arity',
	function() {
		var state = new StateModule.State();
		state.pushControl(makeApplication(makePrimval('procedure-arity'), [makePrimval('+')]));
		assert.deepEqual(run(state), types.arityAtLeast(0));

		state.pushControl(makeApplication(makePrimval('procedure-arity'), [makePrimval('-')]));
		assert.deepEqual(run(state), types.arityAtLeast(1));

		state.pushControl(makeApplication(makePrimval('procedure-arity'), [makePrimval('equal?')]));
		assert.deepEqual(run(state), 2);

		state.pushControl(makeApplication(makePrimval('procedure-arity'), [makePrimval('random')]));
		assert.deepEqual(run(state), types.list([0, 1]));

		state.pushControl(makeApplication(makePrimval('procedure-arity'), [makePrimval('hash-ref')]));
		assert.deepEqual(run(state), types.list([2, 3]));

		var testProc = new types.CaseLambdaValue('',
			[new types.PrimProc('', 1, false, false, function() {}),
			 new types.PrimProc('', 2, true, false, function() {})]);
		state.pushControl(makeApplication(makePrimval('procedure-arity'), [makeConstant(testProc)]));
		assert.deepEqual(run(state), types.list([1, types.arityAtLeast(2)]));

		var testProc2 = new types.CaseLambdaValue('',
			[new types.PrimProc('', 1, false, false, function() {}),
			 new types.PrimProc('', 0, true, false, function() {})]);
		state.pushControl(makeApplication(makePrimval('procedure-arity'), [makeConstant(testProc2)]));
		assert.deepEqual(run(state), types.arityAtLeast(0));

		var testProc3 = new types.CaseLambdaValue('',
			[new types.PrimProc('', 1, false, false, function() {}),
			 new types.PrimProc('', 4, true, false, function() {}),
			 new types.PrimProc('', 0, false, false, function() {}),
			 new types.PrimProc('', 3, true, false, function() {}),
			 new types.PrimProc('', 3, false, false, function() {})]);
		state.pushControl(makeApplication(makePrimval('procedure-arity'), [makeConstant(testProc3)]));
		assert.deepEqual(run(state), types.list([0, 1, types.arityAtLeast(3)]));
	});


runTest('identity',
	function() {
		testPrim('identity', id, [5], 5);
		testPrim('identity', types.string, ['hello'], types.string('hello'));
	});


// runTest('make-posn',
// 	function() {
// 		testPrim('make-posn', id, [4, 5], types.posn(4, 5));
// 		testPrim('make-posn', types.char, ['a', 'B'], types.posn(types.char('a'), types.char('B')));
// 	});

// runTest('posn?',
// 	function() {
// 		testPrim('posn?', id, [4], false);
// 		testPrim('posn?', types.box, [4], false);
// 		testPrim('posn?', id, [types.posn(5, 4)], true);
// 	});

// runTest('posn-x',
// 	function() {
// 		testPrim('posn-x', id, [types.posn(5, 4)], 5);
// 		testPrim('posn-x', id, [types.posn(types.char('a'), types.char('b'))], types.char('a'));
// 	});

// runTest('posn-y',
// 	function() {	
// 		testPrim('posn-y', id, [types.posn(5, 4)], 4);
// 		testPrim('posn-y', id, [types.posn(types.char('a'), types.char('b'))], types.char('b'));
// 	});


runTest('structure equality',
	function() {
		var ParentType = types.makeStructureType('parent', false, 2, 0, false, false);
		var makeParent = ParentType.constructor;
		var ChildType = types.makeStructureType('child', ParentType, 0, 0, false, false);
		var makeChild = ChildType.constructor;

		testPrim('equal?', id, [makeParent('a', 5), makeParent('a', 5)], true);
		testPrim('equal?', id, [makeParent('a', 5), makeParent('b', 5)], false);
		testPrim('equal?', id, [makeParent('a', 5), makeChild('a', 5)], false);
		testPrim('equal?', id, [makeChild('a', 5), makeParent('a', 5)], false);
		testPrim('equal?', id, [makeParent('a', 5), types.color(4, 3, 6)], false);
	});


/***************************
 *** FFI Primitive Tests ***
 ***************************/


/*
runTest('get-js-object',
	function() {
		testPrim('get-js-object', id, ['setInterval'], types.jsObject('setInterval', setInterval));
		testPrim('get-js-object', id, [types.jsObject('types', types), 'box'],
			 types.jsObject('types.box', types.box));
		testPrim('get-js-object', types.string, ['types', 'cons'], types.jsObject('types.cons', types.cons));
		testPrim('get-js-object', id, ['world', types.string('Kernel'), 'ellipseImage'],
			 types.jsObject('world.Kernel.ellipseImage', world.Kernel.ellipseImage));
		testPrim('get-js-object', id, [types.jsObject('world', world), 'Kernel', 'isColor'],
			 types.jsObject('world.Kernel.isColor', world.Kernel.isColor));
		testPrim('get-js-object', id, [types.jsObject('world.config', world.config), 'Kernel', 'getNoneEffect'],
			 types.jsObject('world.config.Kernel.getNoneEffect', world.config.Kernel.getNoneEffect));
		testPrim('get-js-object', id, ['junk'], types.jsObject('junk', undefined));

		try {
			testPrim('get-js-object', id, ['world', 'junk', 'something'], false);
		} catch(e) {
			assert.deepEqual(e, types.schemeError(
				types.exnFailContract('get-js-object: tried to access field something of world.junk, '
					+ 'but world.junk was undefined'),
				false));
		}
	});


runTest('js-call',
	function() {
		testPrim('js-call', id, [types.jsObject('jsnums.greaterThan', jsnums.greaterThan), 4, types.rational(3, 2)], true);
		testPrim('js-call', id, [types.jsObject('types.hash', types.hash), types.EMPTY], types.hash(types.EMPTY));

		var state = new StateModule.State();
		var results = [];
		state.pushControl(makeApplication(makePrimval('js-call'),
						  [makeConstant(types.jsObject('setInterval', setInterval)),
						   makeConstant(function() { results.push('tick'); }),
						   makeConstant(500)]));
		var watchId = run(state);
		setTimeout(function() {
			clearInterval(watchId);
			assert.deepEqual(results, ['tick', 'tick', 'tick', 'tick', 'tick']);
		}, 2600);
	});
*/
		





runTest("topsyntax",
	function() {
	    sys.print("!Not implemented yet!  ");
	});




runTest("Error structure hierarchy",
	function() {
	    assert.ok(types.isExnFail(types.exnFail("hello", types.continuationMarkSet())));
	    assert.ok(types.isExnFail(types.exnFailContract("hello", types.continuationMarkSet())));
	    assert.ok(types.isExnFail(types.exnFailContractDivisionByZero("hello", types.continuationMarkSet())));
	});










/**
This next test is special and should be last.  It'll run an infinite loop, and
schedule a break.

Only after the interpreter breaks do we print "END TESTS".
*/
runTest("closure application, testing break",
	// (define (f) (f)) (begin (f)) --> infinite loop, but with bounded control stack.
	function() {
	    var state = new StateModule.State();
	    state.pushControl(makeMod(makePrefix(1), []));
	    run(state);   
	    state.pushControl(makeApplication(makeToplevel(0, 0), []));
	    state.pushControl(makeDefValues([makeToplevel(0, 0)],
					    makeLam(0, [0],
						    makeApplication(makeToplevel(0, 0),
								    []))));
	    var isTerminated = false;
	    state.onFail = function(e) {
	    	assert.ok(types.isSchemeError(e));
		assert.ok(types.isExnBreak(e.val));
		isTerminated = true;
	    };
	    interpret.run(state);
	    var waitTillBreak = function() {
		if (isTerminated) {
		    sys.print("\nEND TESTS\n")
		    return;
		} else {
		    state.breakRequested = true;
		    setTimeout(waitTillBreak, 10);
		}
	    };
	    waitTillBreak();
	});