function test(this_value, separator, arg_count) {
    var t;

    try {
        if (arg_count === 0) {
            t = Array.prototype.join.call(this_value);
        } else {
            t = Array.prototype.join.call(this_value, separator);
        }
        print(typeof t, t);
    } catch (e) {
        print(e.name);
    }
}

/*===
basic
string 
string 
string 
string 1
string 
string foo
string ,,true,false,123,foo,1,2,[object Object]
string foo:bar:quux
string 1,2,3
string 1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,2,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,3
string 1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,2,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,3
===*/

print('basic');

function basicTest() {
    var t;

    test([]);
    test({});  // object without 'length' -> ToUint32(undefined) = 0
    test({ '0': 'foo' });

    test([1]);
    test({ length: 1 });
    test({ '0': 'foo', length: 1 });

    test([ undefined, null, true, false, 123, 'foo', [1, 2], { foo: 1, bar: 2 } ]);

    test([ 'foo', 'bar', 'quux' ], ':');

    // dense array join

    t = [1,2,3];
    test(t);

    // sparse array join

    t = [1];
    t[100] = 3;
    t[50] = 2;
    test(t);

    // non-array join

    t = {
        '0': 1,
        '100': 3,
        '50': 2,
        length: 101
    };
    test(t);
}

try {
    basicTest();
} catch (e) {
    print(e);
}

/*===
coercion
TypeError
TypeError
TypeError
string 
string 
string 
string f,o,o
string 1,2
string 
string foo,bar,,quux,,,,,,
string foo,bar,quux
string foonullbarnullquux
string footruebartruequux
string foofalsebarfalsequux
string foo123bar123quux
string fooFOObarFOOquux
string foo1,2bar1,2quux
string foo[object Object]bar[object Object]quux
separator toString
length getter
length retvalue valueOf
0 getter
0 retvalue toString
1 getter
1 retvalue toString
2 getter
2 retvalue toString
string foo_bar_quux
string foo_bar_quux_baz
string foo_bar_quux
length valueOf
string foo_bar_quux
===*/

print('coercion');

function coercionTest() {
    var t;

    // this coercion

    test(undefined, undefined, 1);
    test(undefined);
    test(null);
    test(true);
    test(false);
    test(123);
    test('foo');
    test([1,2]);
    test({ foo: 1, bar: 2 });
    test({ '0': 'foo', '1': 'bar', '3': 'quux', length: 10 });

    // separator is tostring coerced

    test([ 'foo', 'bar', 'quux' ], undefined);
    test([ 'foo', 'bar', 'quux' ], null);
    test([ 'foo', 'bar', 'quux' ], true);
    test([ 'foo', 'bar', 'quux' ], false);
    test([ 'foo', 'bar', 'quux' ], 123);
    test([ 'foo', 'bar', 'quux' ], 'FOO');
    test([ 'foo', 'bar', 'quux' ], [1,2]);
    test([ 'foo', 'bar', 'quux' ], { foo: 1, bar: 2 });

    // values are ToString coerced (already tested above in basicTest)

    // coercion: ToObject(this) (no side effects here), [[Get]] "length",
    // ToUint32(length), ToString(separator), [[Get]] "0", ToString(0),
    // [[Get]] "1", ToString(1) ...

    Array.prototype.join.call({
        get length() {
            print('length getter');
            return {
                toString: function() { print('length retvalue toString'); return '4'; },
                valueOf: function() { print('length retvalue valueOf'); return 3; }
            };
        },
        set length() { print('length setter'); },

        get 0() {
            print('0 getter');
            return {
                toString: function() { print('0 retvalue toString'); return 'str0'; },
                valueOf: function() { print('0 retvalue toString'); return 'val0'; },
            };
        },
        set 0() { print('0 setter'); },

        get 1() {
            print('1 getter');
            return {
                toString: function() { print('1 retvalue toString'); return 'str1'; },
                valueOf: function() { print('1 retvalue toString'); return 'val1'; },
            };
        },
        set 1() { print('1 setter'); },

        get 2() {
            print('2 getter');
            return {
                toString: function() { print('2 retvalue toString'); return 'str2'; },
                valueOf: function() { print('2 retvalue toString'); return 'val2'; },
            };
        },
        set 2() { print('2 setter'); },
    }, {
        toString: function() { print('separator toString'); return ':' },
        valueOf: function() { print('separator valueOf'); return ';' }
    });

    // length coercion: fractions, string, etc.  Coercion is through ToUint32();
    // this is easiest to test with a non-array, because array 'length' has
    // special behavior (RangeError for invalid array length)

    t = { '0': 'foo', '1': 'bar', '2': 'quux', '3': 'baz', length: 3.9 };
    test(t, '_');

    t = { '0': 'foo', '1': 'bar', '2': 'quux', '3': 'baz', length: -256*256*256*256 + 3.9 };  // coerces to 4, rounding is towards zero before modulo
    test(t, '_');

    t = { '0': 'foo', '1': 'bar', '2': 'quux', '3': 'baz', length: '3.9' };
    test(t, '_');

    t = { '0': 'foo', '1': 'bar', '2': 'quux', '3': 'baz', length: {
        toString: function() { print('length toString'); return 4.9; },
        valueOf: function() { print('length valueOf'); return 3.9; }
    } };
    test(t, '_');
}

try {
    coercionTest();
} catch (e) {
    print(e);
}

