// This test cannot use imports because it needs to run in non-strict mode.

function assert(actual, expected, message) {
    if (arguments.length == 1)
        expected = true;

    if (actual === expected)
        return;

    if (typeof actual == 'number' && isNaN(actual)
    &&  typeof expected == 'number' && isNaN(expected))
        return;

    if (actual !== null && expected !== null
    &&  typeof actual == 'object' && typeof expected == 'object'
    &&  actual.toString() === expected.toString())
        return;

    var msg = message ? " (" + message + ")" : "";
    throw Error("assertion failed: got |" + actual + "|" +
                ", expected |" + expected + "|" + msg);
}

function assert_throws(expected_error, func, message)
{
    var err = false;
    var msg = message ? " (" + message + ")" : "";
    try {
        switch (typeof func) {
        case 'string':
            eval(func);
            break;
        case 'function':
            func();
            break;
        }
    } catch(e) {
        err = true;
        if (!(e instanceof expected_error)) {
            throw Error(`expected ${expected_error.name}, got ${e.name}${msg}`);
        }
    }
    if (!err) {
        throw Error(`expected ${expected_error.name}${msg}`);
    }
}

/*----------------*/

function test_op1()
{
    var r, a;
    r = 1 + 2;
    assert(r, 3, "1 + 2 === 3");

    r = 1 - 2;
    assert(r, -1, "1 - 2 === -1");

    r = -1;
    assert(r, -1, "-1 === -1");

    r = +2;
    assert(r, 2, "+2 === 2");

    r = 2 * 3;
    assert(r, 6, "2 * 3 === 6");

    r = 4 / 2;
    assert(r, 2, "4 / 2 === 2");

    r = 4 % 3;
    assert(r, 1, "4 % 3 === 3");

    r = 4 << 2;
    assert(r, 16, "4 << 2 === 16");

    r = 1 << 0;
    assert(r, 1, "1 << 0 === 1");

    r = 1 << 31;
    assert(r, -2147483648, "1 << 31 === -2147483648");

    r = 1 << 32;
    assert(r, 1, "1 << 32 === 1");

    r = (1 << 31) < 0;
    assert(r, true, "(1 << 31) < 0 === true");

    r = -4 >> 1;
    assert(r, -2, "-4 >> 1 === -2");

    r = -4 >>> 1;
    assert(r, 0x7ffffffe, "-4 >>> 1 === 0x7ffffffe");

    r = 1 & 1;
    assert(r, 1, "1 & 1 === 1");

    r = 0 | 1;
    assert(r, 1, "0 | 1 === 1");

    r = 1 ^ 1;
    assert(r, 0, "1 ^ 1 === 0");

    r = ~1;
    assert(r, -2, "~1 === -2");

    r = !1;
    assert(r, false, "!1 === false");

    assert((1 < 2), true, "(1 < 2) === true");

    assert((2 > 1), true, "(2 > 1) === true");

    assert(('b' > 'a'), true, "('b' > 'a') === true");

    assert(2 ** 8, 256, "2 ** 8 === 256");
}

function test_cvt()
{
    assert((NaN | 0), 0);
    assert((Infinity | 0), 0);
    assert(((-Infinity) | 0), 0);
    assert(("12345" | 0), 12345);
    assert(("0x12345" | 0), 0x12345);
    assert(((4294967296 * 3 - 4) | 0), -4);

    assert(("12345" >>> 0), 12345);
    assert(("0x12345" >>> 0), 0x12345);
    assert((NaN >>> 0), 0);
    assert((Infinity >>> 0), 0);
    assert(((-Infinity) >>> 0), 0);
    assert(((4294967296 * 3 - 4) >>> 0), (4294967296 - 4));
    assert((19686109595169230000).toString(), "19686109595169230000");
}

function test_eq()
{
    assert(null == undefined);
    assert(undefined == null);
    assert(true == 1);
    assert(0 == false);
    assert("" == 0);
    assert("123" == 123);
    assert("122" != 123);
    assert((new Number(1)) == 1);
    assert(2 == (new Number(2)));
    assert((new String("abc")) == "abc");
    assert({} != "abc");
}

function test_inc_dec()
{
    var a, r;

    a = 1;
    r = a++;
    assert(r === 1 && a === 2, true, "++");

    a = 1;
    r = ++a;
    assert(r === 2 && a === 2, true, "++");

    a = 1;
    r = a--;
    assert(r === 1 && a === 0, true, "--");

    a = 1;
    r = --a;
    assert(r === 0 && a === 0, true, "--");

    a = {x:true};
    a.x++;
    assert(a.x, 2, "++");

    a = {x:true};
    a.x--;
    assert(a.x, 0, "--");

    a = [true];
    a[0]++;
    assert(a[0], 2, "++");

    a = {x:true};
    r = a.x++;
    assert(r === 1 && a.x === 2, true, "++");

    a = {x:true};
    r = a.x--;
    assert(r === 1 && a.x === 0, true, "--");

    a = [true];
    r = a[0]++;
    assert(r === 1 && a[0] === 2, true, "++");

    a = [true];
    r = a[0]--;
    assert(r === 1 && a[0] === 0, true, "--");
}

function F(x)
{
    this.x = x;
}

function test_op2()
{
    var a, b;
    a = new Object;
    a.x = 1;
    assert(a.x, 1, "new");
    b = new F(2);
    assert(b.x, 2, "new");

    a = {x : 2};
    assert(("x" in a), true, "in");
    assert(("y" in a), false, "in");

    a = {};
    assert((a instanceof Object), true, "instanceof");
    assert((a instanceof String), false, "instanceof");

    assert((typeof 1), "number", "typeof");
    assert((typeof Object), "function", "typeof");
    assert((typeof null), "object", "typeof");
    assert((typeof unknown_var), "undefined", "typeof");

    a = {x: 1, if: 2, async: 3};
    assert(a.if === 2);
    assert(a.async === 3);
}

function test_delete()
{
    var a, err;

    a = {x: 1, y: 1};
    assert((delete a.x), true, "delete");
    assert(("x" in a), false, "delete");

    /* the following are not tested by test262 */
    assert(delete "abc"[100], true);

    err = false;
    try {
        delete null.a;
    } catch(e) {
        err = (e instanceof TypeError);
    }
    assert(err, true, "delete");

    err = false;
    try {
        a = { f() { delete super.a; } };
        a.f();
    } catch(e) {
        err = (e instanceof ReferenceError);
    }
    assert(err, true, "delete");
}

function test_prototype()
{
    var f = function f() { };
    assert(f.prototype.constructor, f, "prototype");

    var g = function g() { };
    /* QuickJS bug */
    Object.defineProperty(g, "prototype", { writable: false });
    assert(g.prototype.constructor, g, "prototype");
}

function test_arguments()
{
    function f2() {
        assert(arguments.length, 2, "arguments");
        assert(arguments[0], 1, "arguments");
        assert(arguments[1], 3, "arguments");
    }
    f2(1, 3);
}

function test_class()
{
    var o;
    class C {
        constructor() {
            this.x = 10;
        }
        f() {
            return 1;
        }
        static F() {
            return -1;
        }
        get y() {
            return 12;
        }
    };
    class D extends C {
        constructor() {
            super();
            this.z = 20;
        }
        g() {
            return 2;
        }
        static G() {
            return -2;
        }
        h() {
            return super.f();
        }
        static H() {
            return super["F"]();
        }
    }

    assert(C.F(), -1);
    assert(Object.getOwnPropertyDescriptor(C.prototype, "y").get.name === "get y");

    o = new C();
    assert(o.f(), 1);
    assert(o.x, 10);

    assert(D.F(), -1);
    assert(D.G(), -2);
    assert(D.H(), -1);

    o = new D();
    assert(o.f(), 1);
    assert(o.g(), 2);
    assert(o.x, 10);
    assert(o.z, 20);
    assert(o.h(), 1);

    /* test class name scope */
    var E1 = class E { static F() { return E; } };
    assert(E1, E1.F());

    class S {
        static x = 42;
        static y = S.x;
        static z = this.x;
    }
    assert(S.x, 42);
    assert(S.y, 42);
    assert(S.z, 42);
    
    class P {
        get;
        set;
        async;
        get = () => "123";
        set = () => "456";
        async = () => "789";
        static() { return 42; }
    }
    assert(new P().get(), "123");
    assert(new P().set(), "456");
    assert(new P().async(), "789");
    assert(new P().static(), 42);
};

function test_template()
{
    var a, b;
    b = 123;
    a = `abc${b}d`;
    assert(a, "abc123d");

    a = String.raw `abc${b}d`;
    assert(a, "abc123d");

    a = "aaa";
    b = "bbb";
    assert(`aaa${a, b}ccc`, "aaabbbccc");
}

function test_template_skip()
{
    var a = "Bar";
    var { b = `${a + `a${a}` }baz` } = {};
    assert(b, "BaraBarbaz");
}

function test_object_literal()
{
    var x = 0, get = 1, set = 2; async = 3;
    a = { get: 2, set: 3, async: 4, get a(){ return this.get} };
    assert(JSON.stringify(a), '{"get":2,"set":3,"async":4,"a":2}');
    assert(a.a, 2);

    a = { x, get, set, async };
    assert(JSON.stringify(a), '{"x":0,"get":1,"set":2,"async":3}');
}

function test_regexp_skip()
{
    var a, b;
    [a, b = /abc\(/] = [1];
    assert(a, 1);

    [a, b =/abc\(/] = [2];
    assert(a, 2);
}

function test_labels()
{
    do x: { break x; } while(0);
    if (1)
        x: { break x; }
    else
        x: { break x; }
    with ({}) x: { break x; };
    while (0) x: { break x; };
}

function test_destructuring()
{
    function * g () { return 0; };
    var [x] = g();
    assert(x, void 0);
}

function test_spread()
{
    var x;
    x = [1, 2, ...[3, 4]];
    assert(x.toString(), "1,2,3,4");

    x = [ ...[ , ] ];
    assert(Object.getOwnPropertyNames(x).toString(), "0,length");
}

function test_function_length()
{
    assert( ((a, b = 1, c) => {}).length, 1);
    assert( (([a,b]) => {}).length, 1);
    assert( (({a,b}) => {}).length, 1);
    assert( ((c, [a,b] = 1, d) => {}).length, 1);
}

function test_argument_scope()
{
    var f;
    var c = "global";

    f = function(a = eval("var arguments")) {};
    // for some reason v8 does not throw an exception here
    if (typeof require === 'undefined')
        assert_throws(SyntaxError, f);

    f = function(a = eval("1"), b = arguments[0]) { return b; };
    assert(f(12), 12);

    f = function(a, b = arguments[0]) { return b; };
    assert(f(12), 12);

    f = function(a, b = () => arguments) { return b; };
    assert(f(12)()[0], 12);

    f = function(a = eval("1"), b = () => arguments) { return b; };
    assert(f(12)()[0], 12);

    (function() {
        "use strict";
        f = function(a = this) { return a; };
        assert(f.call(123), 123);

        f = function f(a = f) { return a; };
        assert(f(), f);

        f = function f(a = eval("f")) { return a; };
        assert(f(), f);
    })();

    f = (a = eval("var c = 1"), probe = () => c) => {
        var c = 2;
        assert(c, 2);
        assert(probe(), 1);
    }
    f();

    f = (a = eval("var arguments = 1"), probe = () => arguments) => {
        var arguments = 2;
        assert(arguments, 2);
        assert(probe(), 1);
    }
    f();

    f = function f(a = eval("var c = 1"), b = c, probe = () => c) {
        assert(b, 1);
        assert(c, 1);
        assert(probe(), 1)
    }
    f();

    assert(c, "global");
    f = function f(a, b = c, probe = () => c) {
        eval("var c = 1");
        assert(c, 1);
        assert(b, "global");
        assert(probe(), "global")
    }
    f();
    assert(c, "global");

    f = function f(a = eval("var c = 1"), probe = (d = eval("c")) => d) {
        assert(probe(), 1)
    }
    f();
}

function test_function_expr_name()
{
    var f;

    /* non strict mode test : assignment to the function name silently
       fails */

    f = function myfunc() {
        myfunc = 1;
        return myfunc;
    };
    assert(f(), f);

    f = function myfunc() {
        myfunc = 1;
        (() => {
            myfunc = 1;
        })();
        return myfunc;
    };
    assert(f(), f);

    f = function myfunc() {
        eval("myfunc = 1");
        return myfunc;
    };
    assert(f(), f);

    /* strict mode test : assignment to the function name raises a
       TypeError exception */

    f = function myfunc() {
        "use strict";
        myfunc = 1;
    };
    assert_throws(TypeError, f);

    f = function myfunc() {
        "use strict";
        (() => {
            myfunc = 1;
        })();
    };
    assert_throws(TypeError, f);

    f = function myfunc() {
        "use strict";
        eval("myfunc = 1");
    };
    assert_throws(TypeError, f);
}

function test_expr(expr, err) {
    if (err)
        assert_throws(err, expr, `for ${expr}`);
    else
        assert(1, eval(expr), `for ${expr}`);
}

function test_name(name, err)
{
    test_expr(`(function() { return typeof ${name} ? 1 : 1; })()`);
    test_expr(`(function() { var ${name}; ${name} = 1; return ${name}; })()`);
    test_expr(`(function() { let ${name}; ${name} = 1; return ${name}; })()`, name == 'let' ? SyntaxError : undefined);
    test_expr(`(function() { const ${name} = 1; return ${name}; })()`, name == 'let' ? SyntaxError : undefined);
    test_expr(`(function(${name}) { ${name} = 1; return ${name}; })()`);
    test_expr(`(function({${name}}) { ${name} = 1; return ${name}; })({})`);
    test_expr(`(function ${name}() { return ${name} ? 1 : 0; })()`);
    test_expr(`"use strict"; (function() { return typeof ${name} ? 1 : 1; })()`, err);
    test_expr(`"use strict"; (function() { if (0) ${name} = 1; return 1; })()`, err);
    test_expr(`"use strict"; (function() { var x; if (0) x = ${name}; return 1; })()`, err);
    test_expr(`"use strict"; (function() { var ${name}; return 1; })()`, err);
    test_expr(`"use strict"; (function() { let ${name}; return 1; })()`, err);
    test_expr(`"use strict"; (function() { const ${name} = 1; return 1; })()`, err);
    test_expr(`"use strict"; (function() { var ${name}; ${name} = 1; return 1; })()`, err);
    test_expr(`"use strict"; (function() { var ${name}; ${name} = 1; return ${name}; })()`, err);
    test_expr(`"use strict"; (function(${name}) { return 1; })()`, err);
    test_expr(`"use strict"; (function({${name}}) { return 1; })({})`, err);
    test_expr(`"use strict"; (function ${name}() { return 1; })()`, err);
    test_expr(`(function() { "use strict"; return typeof ${name} ? 1 : 1; })()`, err);
    test_expr(`(function() { "use strict"; if (0) ${name} = 1; return 1; })()`, err);
    test_expr(`(function() { "use strict"; var x; if (0) x = ${name}; return 1; })()`, err);
    test_expr(`(function() { "use strict"; var ${name}; return 1; })()`, err);
    test_expr(`(function() { "use strict"; let ${name}; return 1; })()`, err);
    test_expr(`(function() { "use strict"; const ${name} = 1; return 1; })()`, err);
    test_expr(`(function() { "use strict"; var ${name}; ${name} = 1; return 1; })()`, err);
    test_expr(`(function() { "use strict"; var ${name}; ${name} = 1; return ${name}; })()`, err);
    test_expr(`(function(${name}) { "use strict"; return 1; })()`, err);
    test_expr(`(function({${name}}) { "use strict"; return 1; })({})`, SyntaxError);
    test_expr(`(function ${name}() { "use strict"; return 1; })()`, err);
}

function test_reserved_names()
{
    test_name('await');
    test_name('yield', SyntaxError);
    test_name('implements', SyntaxError);
    test_name('interface', SyntaxError);
    test_name('let', SyntaxError);
    test_name('package', SyntaxError);
    test_name('private', SyntaxError);
    test_name('protected', SyntaxError);
    test_name('public', SyntaxError);
    test_name('static', SyntaxError);
}

function test_number_literals()
{
    assert(0.1.a, undefined);
    assert(0x1.a, undefined);
    assert(0b1.a, undefined);
    assert(01.a, undefined);
    assert(0o1.a, undefined);
    test_expr('0.a', SyntaxError);
    assert(parseInt("0_1"), 0);
    assert(parseInt("1_0"), 1);
    assert(parseInt("0_1", 8), 0);
    assert(parseInt("1_0", 8), 1);
    assert(parseFloat("0_1"), 0);
    assert(parseFloat("1_0"), 1);
    assert(1_0, 10);
    assert(parseInt("Infinity"), NaN);
    assert(parseFloat("Infinity"), Infinity);
    assert(parseFloat("Infinity1"), Infinity);
    assert(parseFloat("Infinity_"), Infinity);
    assert(parseFloat("Infinity."), Infinity);
    test_expr('0_0', SyntaxError);
    test_expr('00_0', SyntaxError);
    test_expr('01_0', SyntaxError);
    test_expr('08_0', SyntaxError);
    test_expr('09_0', SyntaxError);
}

function test_syntax()
{
    assert_throws(SyntaxError, "do");
    assert_throws(SyntaxError, "do;");
    assert_throws(SyntaxError, "do{}");
    assert_throws(SyntaxError, "if");
    assert_throws(SyntaxError, "if\n");
    assert_throws(SyntaxError, "if 1");
    assert_throws(SyntaxError, "if \0");
    assert_throws(SyntaxError, "if ;");
    assert_throws(SyntaxError, "if 'abc'");
    assert_throws(SyntaxError, "if `abc`");
    assert_throws(SyntaxError, "if /abc/");
    assert_throws(SyntaxError, "if abc");
    assert_throws(SyntaxError, "if abc\u0064");
    assert_throws(SyntaxError, "if abc\\u0064");
    assert_throws(SyntaxError, "if \u0123");
    assert_throws(SyntaxError, "if \\u0123");
}

/* optional chaining tests not present in test262 */
function test_optional_chaining()
{
    var a, z;
    z = null;
    a = { b: { c: 2 } };
    assert(delete z?.b.c, true);
    assert(delete a?.b.c, true);
    assert(JSON.stringify(a), '{"b":{}}', "optional chaining delete");

    a = { b: { c: 2 } };
    assert(delete z?.b["c"], true);
    assert(delete a?.b["c"], true);
    assert(JSON.stringify(a), '{"b":{}}');

    a = {
        b() { return this._b; },
        _b: { c: 42 }
    };

    assert((a?.b)().c, 42);

    assert((a?.["b"])().c, 42);
}

function test_parse_semicolon()
{
    /* 'yield' or 'await' may not be considered as a token if the
       previous ';' is missing */
    function *f()
    {
        function func() {
        }
        yield 1;
        var h = x => x + 1
        yield 2;
    }
    async function g()
    {
        function func() {
        }
        await 1;
        var h = x => x + 1
        await 2;
    }
}

test_op1();
test_cvt();
test_eq();
test_inc_dec();
test_op2();
test_delete();
test_prototype();
test_arguments();
test_class();
test_template();
test_template_skip();
test_object_literal();
test_regexp_skip();
test_labels();
test_destructuring();
test_spread();
test_function_length();
test_argument_scope();
test_function_expr_name();
test_reserved_names();
test_number_literals();
test_syntax();
test_optional_chaining();
test_parse_semicolon();
