function script1() { return arguments.length; }
function script2(x) { return x; }
function script3(x) { var o = arguments; return o[0]; }
function genClosure() { var x = 3; eval("x = 4"); return function(y) { return x + y } };
var closed1 = genClosure();
var closed2 = genClosure();
var closed3 = genClosure();
var native1 = String.prototype.search;
var native2 = String.prototype.match;
var tricky1 = { call:function(x,y) { return y }, apply:function(x,y) { return y } };

test0();
test1();
test2();
test3();

function test0() {
    assertEq(script1.call(null), 0);
    assertEq(script1.call(null, 1), 1);
    assertEq(script1.call(null, 1,2), 2);
    assertEq(native1.call("aabc", /b/), 2);
    assertEq(native1.call("abc"), 0);
    assertEq(tricky1.call(null, 9), 9);
    assertEq(script1.apply(null), 0);
    assertEq(script1.apply(null, [1]), 1);
    assertEq(script1.apply(null, [1,2]), 2);
    assertEq(native1.apply("aabc", [/b/]), 2);
    assertEq(native1.apply("abc"), 0);
    assertEq(tricky1.apply(null, 1), 1);
}
test0();

function test1() {
    function f(arr) {
        for (var i = 0; i < 10; ++i) {
            for (var j = 0; j < arr.length; ++j) {
                arr[j].call('a');
                arr[j].apply('a', []);
                var arg0 = [];
                arr[j].apply('a', arg0);
                (function() { arr[j].apply('a', arguments); })();

                arr[j].call('a', 1);
                arr[j].apply('a', [1]);
                var arg0 = [1];
                arr[j].apply('a', arg0);
                (function() { arr[j].apply('a', arguments); })(1);

                arr[j].call('a', 1,'g');
                arr[j].apply('a', [1,'g']);
                var arg0 = [1,'g'];
                arr[j].apply('a', arg0);
                (function() { arr[j].apply('a', arguments); })(1,'g');

                arr[j].call('a', 1,'g',3,4,5,6,7,8,9);
                arr[j].apply('a', [1,'g',3,4,5,6,7,8,9]);
                var arg0 = [1,'g',3,4,5,6,7,8,9];
                arr[j].apply('a', arg0);
                (function() { arr[j].apply('a', arguments); })(1,'g',3,4,5,6,7,8,9);
            }
        }
    }

    f([script1, script1, script1, script1, script2, script2, script1, script2]);
    f([script1, script2, script3, script1, script2, script3, script3, script3]);
    f([script1, script2, script2, script2, script2, script3, script1, script2]);
    f([script1, script1, script1, native1, native1, native1, native1, script1]);
    f([native1, native1, native1, native2, native2, native2, native2, native1]);
    f([native1, native2, native1, native2, native1, native2, native1, native2]);
    f([native1, native1, native1, script1, script2, script2, native1, script3]);
    f([closed1, closed1, closed1, closed2, closed2, closed2, script3, script3]);
    f([closed1, closed2, closed1, closed2, closed1, closed2, closed1, closed2]);
    f([closed1, closed2, closed3, closed1, closed2, closed3, script1, script2]);
    f([closed1, closed1, closed1, closed2, closed2, closed2, native1, native2]);
    f([closed1, closed1, closed1, closed2, closed2, closed2, native1, native2]);
    f([native1, native1, native1, closed1, closed2, script1, script2, native2]);
}

// test things that break our speculation
function test2() {
    var threw = false;
    try {
        (3).call(null, 1,2);
    } catch (e) {
        threw = true;
    }
    assertEq(threw, true);

    var threw = false;
    try {
        (3).apply(null, [1,2]);
    } catch (e) {
        threw = true;
    }
    assertEq(threw, true);

    var threw = false;
    try {
        var arr = [1,2];
        (3).apply(null, arr);
    } catch (e) {
        threw = true;
    }
    assertEq(threw, true);

    function tryAndFail(o) {
        var threw = false;
        try {
            o.call(null, 1,2);
        } catch(e) {
            threw = true;
        }
        assertEq(threw, true);
        threw = false;
        try {
            o.apply(null, [1,2]);
        } catch(e) {
            threw = true;
        }
        assertEq(threw, true);
    }

    tryAndFail(1);
    tryAndFail({});
    tryAndFail({call:{}, apply:{}});
    tryAndFail({call:function() { throw "not js_fun_call"}, apply:function(){ throw "not js_fun_apply" }});
}

// hit the stubs::CompileFunction path
function test3() {
    function genFreshFunction(s) { return new Function(s, "return " + s); }

    function callIt(f) {
        assertEq(f.call(null, 1,2), 1);
    }
    callIt(script2); callIt(script2); callIt(script2); callIt(script2);
    callIt(genFreshFunction("x"));
    callIt(genFreshFunction("y"));
    callIt(genFreshFunction("z"));

    function applyIt(f) {
        var arr = [1,2];
        assertEq(f.apply(null, arr), 1);
    }
    applyIt(script2); applyIt(script2); applyIt(script2); applyIt(script2);
    applyIt(genFreshFunction("x"));
    applyIt(genFreshFunction("y"));
    applyIt(genFreshFunction("z"));

    function applyIt1(f) {
        function g() {
            assertEq(f.apply(null, arguments), 1);
        }
        g(1,2);
    }
    applyIt1(script2); applyIt1(script2); applyIt1(script2); applyIt1(script2);
    applyIt1(genFreshFunction("x"));
    applyIt1(genFreshFunction("y"));
    applyIt1(genFreshFunction("z"));

    function applyIt2(f) {
        assertEq(f.apply(null, [1,2]), 1);
    }
    applyIt2(script2); applyIt2(script2); applyIt2(script2); applyIt2(script2);
    applyIt2(genFreshFunction("x"));
    applyIt2(genFreshFunction("y"));
    applyIt2(genFreshFunction("z"));
}
