#!/usr/bin/env qore
# -*- mode: qore; indent-tabs-mode: nil -*-

%new-style
%enable-all-warnings
%require-types
%strict-args
%no-child-restrictions

%requires ../../../../qlib/Util.qm
%requires ../../../../qlib/QUnit.qm

%exec-class Test

%disable-warning invalid-operation

class DataTest {
    public {
        hash b;
    }

    private {
        int x;
    }

    constructor() {
        x = 1;
    }

    hash doPlusFirst(hash h) {
        return h + self;
    }

    hash doPlusEqualsFirst(hash h) {
        h += self;
        return h;
    }
}

class Test inherits QUnit::Test {
    constructor() : QUnit::Test("operators", "1.0", \ARGV) {
        addTestCase("minor hash test", \minusHashTest());
        addTestCase("push unshift tests", \pushUnshiftTests());
        addTestCase("basic operator tests", \basicTests());
        addTestCase("date tests", \dateTests());
        addTestCase("lvalue tests", \lvalueTests());
        addTestCase("const modulo", \constModulo());
        addTestCase("post/pre inc/dec", \postPreIncDec());
        addTestCase("identity assignment", \idAssignment());
        addTestCase("assignment type restriction test", \assignmentTypeRestrictionTest());
        addTestCase("delete/remove test", \deleteRemoveTest());
        addTestCase("square brackets test", \squareBracketsTest());
        addTestCase("binary splice test", \binarySpliceTest());
        addTestCase("binary extract test", \binaryExtractTest());
        addTestCase("map test", \mapTest());
        addTestCase("select test", \selectTest());
        addTestCase("fold test", \foldTest());
        addTestCase("keys test", \keysTest());
        addTestCase("binary not", \binNot());
        addTestCase("transliteration", \transliteration());
        addTestCase("comparison", \comparison());
        addTestCase("absolute", \absolute());
        addTestCase("plus", \plus());
        addTestCase("minus", \minus());
        addTestCase("range test", \rangeAndSlice());
        addTestCase("push & += tests", \pushPlusEqualsTests());

        set_return_value(main());
    }

    minusHashTest() {
        hash<string, hash<StatInfo>> h0 = {
            "a": <StatInfo>{},
            "b": <StatInfo>{},
        };
        hash<string, hash<StatInfo>> h1 = h0 - 'a';
        assertEq("hash<string, hash<StatInfo>>", h1.fullType());
    }

    pushUnshiftTests() {
        # issue #3317: ensure typed and untyped lists are handled equally with push and unshift
        {
            list l;
            push l, 1;
            assertEq((1,), l);
            remove l;
            unshift l, 1;
            assertEq((1,), l);
        }
        {
            list<int> l;
            push l, 1;
            assertEq((1,), l);
            remove l;
            unshift l, 1;
            assertEq((1,), l);
        }
        {
            *list l;
            push l, 1;
            assertEq((1,), l);
            remove l;
            unshift l, 1;
            assertEq((1,), l);
        }
        {
            *list<int> l;
            push l, 1;
            assertEq((1,), l);
            remove l;
            unshift l, 1;
            assertEq((1,), l);
        }
    }

    pushPlusEqualsTests() {
        {
            list l0;
            list l1;
            l0 += 1;
            push l1, 1;
            assertEq((1,), l0);
            assertEq((1,), l1);
        }
        {
            softlist l0;
            softlist l1;
            l0 += 1;
            push l1, 1;
            assertEq((1,), l0);
            assertEq((1,), l1);
        }
        {
            *list l0;
            *list l1;
            assertEq((1,), l0 += 1);
            # issue #3317: push always operates on lists, so if the lvalue is not assigned and can accept a list, then
            # a list is created
            push l1, 1;
            assertEq((1,), l1);
        }
        {
            *list l1;
            # issue #3317: unshift always operates on lists, so if the lvalue is not assigned and can accept a list,
            # then a list is created
            unshift l1, 1;
            assertEq((1,), l1);
        }
        {
            *softlist l0;
            *softlist l1;
            l0 += 1;
            assertEq((1,), l0);
            # issue #3317: push always operates on lists, so if the lvalue is not assigned and can accept a list, then
            # a list is created
            push l1, 1;
            assertEq((1,), l1);
        }
        {
            *softlist l1;
            # issue #3317: unshift always operates on lists, so if the lvalue is not assigned and can accept a list,
            # then a list is created
            unshift l1, 1;
            assertEq((1,), l1);
        }
        {
            auto l0;
            auto l1;
            l0 += 1;
            assertEq(1, l0);
            # issue #3317: push always operates on lists, so if the lvalue is not assigned and can accept a list, then
            # a list is created
            push l1, 1;
            assertEq((1,), l1);
        }
        {
            auto l1;
            # issue #3317: unshift always operates on lists, so if the lvalue is not assigned and can accept a list,
            # then a list is created
            unshift l1, 1;
            assertEq((1,), l1);
        }
        {
            int l0;
            l0 += 1;
            assertEq(1, l0);
            Program p(PO_NEW_STYLE);
            assertThrows("PARSE-TYPE-ERROR", \p.parse(), ("int l1; push l1, 1;", ""));
        }
        {
            float l0;
            l0 += 1;
            assertEq(1.0, l0);
            Program p(PO_NEW_STYLE);
            assertThrows("PARSE-TYPE-ERROR", \p.parse(), ("float l1; push l1, 1;", ""));
        }
        {
            number l0;
            l0 += 1;
            assertEq(1n, l0);
            Program p(PO_NEW_STYLE);
            assertThrows("PARSE-TYPE-ERROR", \p.parse(), ("number l1; push l1, 1;", ""));
        }
        {
            # tests issue #2591
            date l0;
            l0 += 1;
            assertEq(1970-01-01T00:00:01Z, l0);
            Program p(PO_NEW_STYLE);
            assertThrows("PARSE-TYPE-ERROR", \p.parse(), ("date l1; push l1, 1;", ""));
        }
    }

    basicTests() {
        {
            Program p(PO_NEW_STYLE);
            p.parse("class Issue2634 {add(hash h) {self += h;}} int sub test() {Issue2634 i();i.add((\"a\": 1));return i.a;}", "issue 2634");
            assertEq(1, p.callFunction("test"));
        }
        int a = 1;
        testAssertionValue("variable assignment", a, 1);
        a += 3;
        testAssertionValue("integer += operator", a, 4);
        a -= 2;
        testAssertionValue("integer -= operator", a, 2);
        a |= 1;
        testAssertionValue("|= operator", a, 3);
        a &= 1;
        testAssertionValue("&= operator", a, 1);
        a *= 10;
        testAssertionValue("integer *= operator", a, 10);
        float f = a;
        f *= 2.2;
        testAssertionValue("first float *= operator", f, 22.0);
        f *= 2;
        testAssertionValue("second float *= operator", f, 44.0);
        f /= 4.4;
        testAssertionValue("float /= operator", f, 10.0);
        a = 10;
        a /= 2;
        testAssertionValue("integer /= operator", a, 5);
        testAssertionValue("first / operator", 4 / 2, 2);
        a = 0xfdb4902a;
        a ^= 0xbf40e848;
        testAssertionValue("^= xor equals operator", a, 0x42f47862);
        a <<= 2;
        testAssertionValue("<<= shift-left-equals operator", a, 0x10bd1e188);
        a >>= 3;
        testAssertionValue(">>= shift-right-equals operator", a, 0x217a3c31);

        string astr = "hello" + " there";
        testAssertionValue("string concatenation", astr, "hello there");
        astr += " gee";
        testAssertionValue("string plus equals", astr, "hello there gee");

        f = 1.0;
        f += 1.2;
        testAssertionValue("float += operator", f, 2.2);
        f -= 1.1;
        testAssertionValue("float -= operator", f, 1.1);
        f = 5.5 * 2.0;
        testAssertionValue("float * operator", f, 11.0);

        any ni += 3.2;
        testAssertionValue("float +=, lhs NOTHING", ni, 3.2);
        delete ni;
        ni += "hello";
        testAssertionValue("string +=, lhs NOTHING", ni, "hello");
        delete ni;
        ni -= 4.5;
        testAssertionValue("float -=, lhs NOTHING", ni, -4.5);
        delete ni;
        ni -= 4;
        testAssertionValue("integer -=, lhs NOTHING", ni, -4);
        # some array and hash tests in separate functions

        # get function closure with bound local variable (multiply by 2)
        code c = mapClosure(2);

        # map function to list
        testAssertionValue("map operator using closure", (map c($1), (1, 2, 3)), (2, 4, 6));

        # map immediate expression to list
        testAssertionValue("map operator using expression", (map $1 * 2, (1, 2, 3)), (2, 4, 6));

        # map function to list with optional select code as expression
        testAssertionValue("map operator using closure with optional select expression", (map c($1), (1, 2, 3), $1 > 1), (4, 6));

        # select all elements from list greater than 1 with expression
        testAssertionValue("select operator with expression", (select (1, 2, 3), $1 > 1), (2, 3));

        # create a sinple closure to subtract the second argument from the first
        c = any sub(auto x, auto y) { return x - y; };

        # left fold function on list using closure
        testAssertionValue("foldl operator with closure", (foldl c($1, $2), (2, 3, 4)), -5);

        # left fold function on list using expression
        testAssertionValue("foldl operator with expression", (foldl $1 - $2, (2, 3, 4)), -5);

        # right fold function on list using immediate closure
        testAssertionValue("foldr operator with closure", (foldr c($1, $2), (2, 3, 4)), -1);

        # right fold function on list using expression and implicit arguments
        testAssertionValue("foldr operator with expression", (foldr $1 - $2, (2, 3, 4)), -1);

        # right fold function on list using an iterator and implicit arguments
        testAssertionValue("foldr operator with iterator", (foldr $1 - $2, (2, 3, 4).iterator()), -1);

        hash h = ("test" : 1, "two" : 2.0, "three" : "three", "four" : False );
        testAssertionValue("first remove operator", remove h.two, 2.0);

        testAssertion("int div by zero", any sub () {int i = 0; return 1 / i;}, NOTHING, new TestResultExceptionType("DIVISION-BY-ZERO"));
        testAssertion("float div by zero", any sub () {float n = 0; return 1 / n;}, NOTHING, new TestResultExceptionType("DIVISION-BY-ZERO"));
        testAssertion("numeric div by zero", any sub () {number n = 0n; return 1 / n;}, NOTHING, new TestResultExceptionType("DIVISION-BY-ZERO"));
        testAssertion("div by zero", any sub () {any n; return 1 / n;}, NOTHING, new TestResultExceptionType("DIVISION-BY-ZERO"));

        assertEq(32, 1 << 5);
        assertEq(2, 32 >> 4);
        {
            int i = 32;
            int l = 1;
            int r = 5;
            assertEq(i, l << r);
            i = 2;
            l = 32;
            r = 4;
            assertEq(i, l >> r);
        }

        assertEq(True, exists 1);
        assertEq(True, exists NULL);
        assertEq(False, exists NOTHING);
        {
            int i = 1;
            assertEq(True, exists i);
            delete i;
            assertEq(False, exists i);
        }

        assertEq(3, elements "str");
        assertEq(6, elements "straße");
        assertEq(2, elements <bead>);
        assertEq(2, elements (1, 2));
        assertEq(2, elements ("a": 1, "b": 2));
        assertEq(0, elements NOTHING);
        {
            string str1 = "str";
            string str2 = "straße";
            binary bin = <bead>;
            list l = (1, 2);
            hash th = ("a": 1, "b": 2);
            assertEq(3, elements str1);
            assertEq(6, elements str2);
            assertEq(2, elements bin);
            assertEq(2, elements l);
            assertEq(2, elements th);
            delete str1;
            assertEq(0, elements str1);
        }

        assertEq(True, stdout instanceof File);
        assertEq(False, stdout instanceof Mutex);
        assertEq(False, 1 instanceof File);
        {
            auto x = stdout;
            assertEq(True, x instanceof File);
            assertEq(False, x instanceof Mutex);
            x = 1;
            assertEq(False, x instanceof File);
        }

        {
            hash<string, int> x = new hash<string, softint>();
            assertEq("hash<string, int>", x.fullType());
            assertEq(True, x instanceof hash<string, int>);
        }

        Program p(PO_NEW_STYLE);
        testAssertion("trim operator parse-time return value", \p.parse(), ("string sub test() {string str = \"a string \\n\"; string astr = trim str; return astr;}", "trim operator test"), new TestResultValue(NOTHING));
        testAssertionValue("trim operator", p.callFunction("test"), "a string");
    }

    dateTests() {
        assertTrue(now() > (now() - 1D));
        assertTrue(now() >= (now() - 1h));
        assertTrue((now() - 1m) < now());
        assertTrue((now() - 1M) <= now());

        date bt = date at = now();
        assertTrue(at == bt);
        at = 2004-02-28-12:00:00;
        at += 1D;
        assertEq(2004-02-29-12:00:00, at);
        at -= (3h + 5m);
        assertEq(2004-02-29-08:55:00, at);

        # relative date arithmetic tests
        assertEq(3s, 5s - 2s);
        assertEq(-1s, 2s - 3s);
        # check for normalization between hours & minutes
        assertEq(30m, 1h - 30m);
        # rel date - abs date is performed by using the seconds + us offset from the epoch in the absolute date
        assertEq(15s, 30s - 1970-01-01T00:00:15Z);

        # relative date comparison tests
        assertEq(True, 72h > 2D);
        assertEq(True, 72h == 3D);
        assertEq(True, 72h < 4D);
        assertEq(True, 7201s > 2h);
        assertEq(True, 7200s == 2h);
        assertEq(True, 7199s < 2h);
        assertEq(True, 86401s > 1D);
        assertEq(True, 86400s == 1D);
        assertEq(True, 86399s < 1D);
        assertEq(True, 32D > 1M);
        assertEq(True, 31D == 1M);
        assertEq(True, 30D < 1M);
        assertEq(True, 2678399s < 1M);
        assertEq(True, 2678400s == 1M);
        assertEq(True, 2678401s > 1M);
        assertEq(True, P1M32D > 2M);
        assertEq(True, P1M31D == 2M);
        assertEq(True, P1M30D < 2M);
        assertEq(True, 13M > 1Y);
        assertEq(True, 12M == 1Y);
        assertEq(True, 11M < 1Y);
        assertEq(True, 366D > 1Y);
        assertEq(True, 365D == 1Y);
        assertEq(True, 364D < 1Y);
        assertEq(True, 86400s + 1us > 1D);
        assertEq(True, 86400s + 1000ms == 1D + 1s);
        assertEq(True, 86400s + 20ms < 1D + 1s);
    }

    lvalueTests() {
        # bug 88: lvalue reference-handling bug
        hash h.a = 1;
        h.b = h;
        testAssertionValue("hash assignment", sprintf("%y", h), "{a: 1, b: {a: 1}}");
        string str = "a";
        h = (str: 1); h.b += h;
        testAssertionValue("hash +=", sprintf("%y", h), "{a: 1, b: {a: 1}}");
    }

    constModulo() {
        assertEq(2, 12 % 10);
    }

    static code mapClosure(any v) {
        return any sub(any v1) { return v * v1; };
    }

    postPreIncDec() {
        int i = 1;
        assertEq(1, i++);
        assertEq(2, i);
        assertEq(2, i--);
        assertEq(1, i);
        assertEq(2, ++i);
        assertEq(2, i);
        assertEq(1, --i);
        assertEq(1, i);

        float f = 1.8;
        assertFloatEq(1.8, f++);
        assertFloatEq(2.8, f);
        assertFloatEq(2.8, f--);
        assertFloatEq(1.8, f);
        assertFloatEq(2.8, ++f);
        assertFloatEq(2.8, f);
        assertFloatEq(1.8, --f);
        assertFloatEq(1.8, f);

        number n = 123.456n;
        assertNumberEq(123.456, n++);
        assertNumberEq(124.456, n);
        assertNumberEq(124.456, n--);
        assertNumberEq(123.456, n);
        assertNumberEq(124.456, ++n);
        assertNumberEq(124.456, n);
        assertNumberEq(123.456, --n);
        assertNumberEq(123.456, n);

        auto x = 1;
        assertEq(1, x++);
        assertEq(2, x);
        assertEq(2, x--);
        assertEq(1, x);
        assertEq(2, ++x);
        assertEq(2, x);
        assertEq(1, --x);
        assertEq(1, x);
    }

    idAssignment() {
        {
            Program p(PO_NEW_STYLE);
            assertThrows("PARSE-EXCEPTION", "illegal assignment of variable \"a\" to itself", \p.parse(), ("any a = a;", ""));
        }
        {
            Program p(PO_NEW_STYLE);
            assertThrows("PARSE-EXCEPTION", "illegal assignment of variable \"a\" to itself", \p.parse(), ("any a; a = a;", ""));
        }
    }

    assignmentTypeRestrictionTest() {
        bool b;
        binary bn;
        code c;
        date d;
        float f;
        hash h;
        int i;
        list lst;
        number n;
        string s;
        timeout t;
        softbool sb;
        softdate sd;
        softfloat sf;
        softint si;
        softnumber sn;
        softstring ss;

        auto x = {};
        assertThrows("RUNTIME-TYPE-ERROR", sub () {b = x;});
        assertThrows("RUNTIME-TYPE-ERROR", sub () {bn = x;});
        assertThrows("RUNTIME-TYPE-ERROR", sub () {c = x;});
        assertThrows("RUNTIME-TYPE-ERROR", sub () {d = x;});
        assertThrows("RUNTIME-TYPE-ERROR", sub () {f = x;});
        assertThrows("RUNTIME-TYPE-ERROR", sub () {i = x;});
        assertThrows("RUNTIME-TYPE-ERROR", sub () {lst = x;});
        assertThrows("RUNTIME-TYPE-ERROR", sub () {n = x;});
        assertThrows("RUNTIME-TYPE-ERROR", sub () {s = x;});
        assertThrows("RUNTIME-TYPE-ERROR", sub () {t = x;});
        assertThrows("RUNTIME-TYPE-ERROR", sub () {sb = x;});
        assertThrows("RUNTIME-TYPE-ERROR", sub () {sd = x;});
        assertThrows("RUNTIME-TYPE-ERROR", sub () {sf = x;});
        assertThrows("RUNTIME-TYPE-ERROR", sub () {si = x;});
        assertThrows("RUNTIME-TYPE-ERROR", sub () {sn = x;});
        assertThrows("RUNTIME-TYPE-ERROR", sub () {ss = x;});

        x = list();
        assertThrows("RUNTIME-TYPE-ERROR", sub () {b = x;});
        assertThrows("RUNTIME-TYPE-ERROR", sub () {bn = x;});
        assertThrows("RUNTIME-TYPE-ERROR", sub () {c = x;});
        assertThrows("RUNTIME-TYPE-ERROR", sub () {d = x;});
        assertThrows("RUNTIME-TYPE-ERROR", sub () {f = x;});
        assertThrows("RUNTIME-TYPE-ERROR", sub () {h = x;});
        assertThrows("RUNTIME-TYPE-ERROR", sub () {i = x;});
        assertThrows("RUNTIME-TYPE-ERROR", sub () {n = x;});
        assertThrows("RUNTIME-TYPE-ERROR", sub () {s = x;});
        assertThrows("RUNTIME-TYPE-ERROR", sub () {t = x;});
        assertThrows("RUNTIME-TYPE-ERROR", sub () {sb = x;});
        assertThrows("RUNTIME-TYPE-ERROR", sub () {sd = x;});
        assertThrows("RUNTIME-TYPE-ERROR", sub () {sf = x;});
        assertThrows("RUNTIME-TYPE-ERROR", sub () {si = x;});
        assertThrows("RUNTIME-TYPE-ERROR", sub () {sn = x;});
        assertThrows("RUNTIME-TYPE-ERROR", sub () {ss = x;});

        {
            Program p(PO_NEW_STYLE);
            # no parse exception is possible here (until we support implicit types)
            p.parse("Mutex m; m.a = 1;", "");
            try {
                p.run();
                fail("should have thrown a runtime exception");
            }
            catch (hash ex) {
                assertEq("RUNTIME-TYPE-ERROR", ex.err);
                assertEq(True, ex.desc =~ /Mutex.*hash/);
                assertEq(NOTHING, ex.next);
            }
        }
        {
            Program p(PO_NEW_STYLE);
            p.parse("Mutex m(); reference r = \\m; r[0] = 1;", "");
            try {
                p.run();
                fail("should have thrown a runtime exception");
            }
            catch (hash ex) {
                assertEq("RUNTIME-TYPE-ERROR", ex.err);
                assertEq(True, ex.desc =~ /Mutex.*list/);
                assertEq(NOTHING, ex.next);
            }
        }
    }

    deleteRemoveTest() {
        {
            hash a.b.c = 1;
            # should have no effect
            delete a.a.c;
            assertEq(("b": ("c": 1)), a);
        }
        {
            hash a.b.c = 1;
            # should have no effect
            remove a.a.c;
            assertEq(("b": ("c": 1)), a);
        }
        {
            DataTest a();
            a.b.c.d = 1;
            # should have no effect
            delete a.b.c.c;
            assertEq(("c": ("d": 1)), a.b);
        }
        {
            DataTest a();
            a.b.c.d = 1;
            # should have no effect
            remove a.b.c.c;
            assertEq(("c": ("d": 1)), a.b);
        }
        {
            hash<auto> h = {
                "a": 1,
                "b": 2,
            };
            assertEq({"b": 2}, h{"b","c"});
            # issue #4122
            assertEq({"b": 2}, remove h{"b","c"});
        }
        {
            Program p(PO_NEW_STYLE);
            p.parse("class HashTest; HashTest sub get() { HashTest ht(); ht.a = 1; ht.b = 2; return ht; }", "");
            object o = p.callFunction("get");
            assertEq({"b": 2}, o{"b","c"});
            # issue #4122
            assertEq({"b": 2}, remove o{"b","c"});
        }
    }

    squareBracketsTest() {
        # issue 1566
        binary b = <00010203>;
        assertEq(Type::Int, b[1].type());
        assertEq(1, b[1]);
        splice b, 2, 1, chr(b[3]);
        assertEq(<00010303>, b);
        b = <00010203040506070809>;
        assertEq(<00010203>, extract b, 0, 4, <b1>);
        assertEq(<b1040506070809>, b);
        b = <00010203040506070809>;
        splice b, 1, 3, <ab>;
        assertEq(<00ab040506070809>, b);

    }

    binarySpliceTest() {
        binary b = binary();
        splice b, 0, 0, <01>;
        assertEq(<01>, b);
    }

    binaryExtractTest() {
        binary b = binary();
        assertEq(binary(), extract b, 0, 0, <01>);
        assertEq(<01>, b);
    }

    mapTest() {
        {
            list<hash<StatInfo>> l = map (auto sub () { return <StatInfo>{"size": 1};})(), new SingleValueIterator(1);
            assertEq("hash<StatInfo>", l[0].fullType());

            l = map (auto sub () { return <StatInfo>{"size": 1};})(), new SingleValueIterator(1), True;
            assertEq("hash<StatInfo>", l[0].fullType());

            hash<auto> h = map (auto sub () { return <StatInfo>{"size": 1};})(), 1;
            assertEq("hash<StatInfo>", h.fullType());

            h = map (auto sub () { return <StatInfo>{"size": 1};})(), 1, True;
            assertEq("hash<StatInfo>", h.fullType());
        }

        {
            code f = *hash<auto> sub (string a) {
                return {a: True};
            };

            hash<auto> h = {"a": 1};
            auto x = select (map f($1), keys h), $1;
            assertEq("list<hash<string, bool>>", x.fullType());

            x = select (map f($1), keys h, True), $1;
            assertEq("list<hash<string, bool>>", x.fullType());
        }

        assertEq(NOTHING, (map $1, NOTHING));
        assertEq(1, (map $1, 1));
        assertEq((1,), (map $1, 1.iterator()));
        assertEq((1,), (map $1, (1,)));

        assertEq(NOTHING, (map $1, (map $1, NOTHING)));
        assertEq(1, (map $1, (map $1, 1)));
        assertEq((1,), (map $1, (map $1, 1.iterator())));
        assertEq((1,), (map $1, (map $1, (1,))));

        assertEq(NOTHING, (map $1, (map $1, (map $1, NOTHING))));
        assertEq(1, (map $1, (map $1, (map $1, 1))));
        assertEq((1,), (map $1, (map $1, (map $1, 1.iterator()))));
        assertEq((1,), (map $1, (map $1, (map $1, (1,)))));

        assertEq(NOTHING, (map $1, NOTHING, True));
        assertEq(1, (map $1, 1, True));
        assertEq((1,), (map $1, 1.iterator(), True));
        assertEq((1,), (map $1, (1,), True));

        assertEq(NOTHING, (map $1, (map $1, NOTHING, True), True));
        assertEq(1, (map $1, (map $1, 1, True), True));
        assertEq((1,), (map $1, (map $1, 1.iterator(), True), True));
        assertEq((1,), (map $1, (map $1, (1,), True), True));

        assertEq(NOTHING, (map $1, (map $1, (map $1, NOTHING, True))));
        assertEq(1, (map $1, (map $1, (map $1, 1, True))));
        assertEq((1,), (map $1, (map $1, (map $1, 1.iterator(), True))));
        assertEq((1,), (map $1, (map $1, (map $1, (1,), True))));

        assertEq((1, 3, 5), (map $1, (map $1, (0, 1, 2, 3, 4, 5), ($1 % 2))));
        assertEq((1, 3, 5), (map $1, (map $#, (0, 1, 2, 3, 4, 5), ($1 % 2))));
    }

    selectTest() {
        assertEq(NOTHING, (select NOTHING, True));
        assertEq(1, (select 1, True));
        assertEq((1,), (select 1.iterator(), True));
        assertEq((1,), (select (1,), True));

        assertEq(NOTHING, (select (select NOTHING, True), True));
        assertEq(1, (select (select 1, True), True));
        assertEq((1,), (select (select 1.iterator(), True), True));
        assertEq((1,), (select (select (1,), True), True));

        assertEq(NOTHING, (select (map $1, NOTHING), True));
        assertEq(1, (select (map $1, 1), True));
        assertEq((1,), (select (map $1, 1.iterator()), True));
        assertEq((1,), (select (map $1, (1,)), True));
    }

    foldTest() {
        assertEq(NOTHING, (foldl $1 + "," + $2, NOTHING));
        assertEq(1, (foldl $1 + "," + $2, 1));
        assertEq(1, (foldl $1 + "," + $2, 1.iterator()));
        assertEq(1, (foldl $1 + "," + $2, (1,)));
        assertEq("1,2", (foldl $1 + "," + $2, (1,2)));

        assertEq(NOTHING, (foldl $1 + "," + $2, (map $1, NOTHING)));
        assertEq(1, (foldl $1 + "," + $2, (map $1, 1)));
        assertEq(1, (foldl $1 + "," + $2, (map $1, 1.iterator())));
        assertEq(1, (foldl $1 + "," + $2, (map $1, (1,))));
        assertEq("1,2", (foldl $1 + "," + $2, (map $1, (1,2))));

        assertEq(NOTHING, (foldr $1 + "," + $2, NOTHING));
        assertEq(1, (foldr $1 + "," + $2, 1));
        assertEq(1, (foldr $1 + "," + $2, (1,).iterator()));
        assertEq(1, (foldr $1 + "," + $2, (1,)));
        assertEq("2,1", (foldr $1 + "," + $2, (1,2)));

        assertEq(NOTHING, (foldr $1 + "," + $2, (map $1, NOTHING)));
        assertEq(1, (foldr $1 + "," + $2, (map $1, 1)));
        assertEq(1, (foldr $1 + "," + $2, (map $1, (1,).iterator())));
        assertEq(1, (foldr $1 + "," + $2, (map $1, (1,))));
        assertEq("2,1", (foldr $1 + "," + $2, (map $1, (1,2))));
    }

    keysTest() {
        hash h = ("a": 1);

        assertEq(NOTHING, keys NOTHING);
        assertEq(NOTHING, keys 1);
        assertEq(("a",), keys ("a": 1));
        assertEq(("a",), keys h);

        assertEq(NOTHING, map $1, (keys NOTHING));
        assertEq(NOTHING, map $1, (keys 1));
        assertEq(("a",), map $1, (keys ("a": 1)));
        assertEq(("a",), map $1, (keys h));

        DataTest d();
        d.b.a = 1;

        assertEq(("b",), keys d);
        assertEq(("b",), map $1, (keys d));

        # check for copy-on-write violations
        hash q = map {$1: ($1 % 2) ? NULL : True}, xrange(22);
        map delete q.$1, keys q, q.$1 === NULL;
        assertEq((0: True, 2: True, 4: True, 6: True, 8: True, 10: True, 12: True, 14: True, 16: True, 18: True, 20: True), q);
    }

    binNot() {
        assertEq(0, ~(~1 | 1));
        assertEq(-1, ~1 | 1);
        assertEq(-4294967297, ~(1 << 32));
    }

    transliteration() {
        string str = "this is a test";
        assertEq("ThiS iS a TeST", str =~ tr/j-z/J-Z/);
        assertEq("T78S 8S 0 T4ST", str =~ tr/a-z/0-9/);
        assertEq("T78S 8S 0 T4ST", str =~ tr/A-A/a-a/);
        assertEq("t78s 8s 0 t4st", str =~ tr/A-Z/a-z/);

        str = "01234567890";
        assertEq("ABCDEFGHIJA", str =~ tr/0-9/A-J/);

        # target range shorter than the source range
        assertEq("01234444440", str =~ tr/A-J/0-4/);

        {
            int i = 1;
            assertEq(NOTHING, i =~ tr/a-z/A-Z/);
            assertEq(1, i);
        }

        {
            any i = 1;
            assertEq(NOTHING, i =~ tr/0-9/A-J/);
            assertEq(1, i);
        }
    }

    comparison() {
        assertEq(-1, "str1" <=> "str2");
        assertEq(-1, "str1" <=> convert_encoding("str2", "latin1"));
        assertEq(-1, "1" <=> 2);
        assertEq(0, "" <=> "");
        assertEq(0, "str1" <=> "str1");
        assertEq(0, "str1" <=> convert_encoding("str1", "latin1"));
        assertEq(0, "1" <=> 1);
        assertEq(1, "str2" <=> "str1");
        assertEq(1, "str2" <=> convert_encoding("str1", "latin1"));
        assertEq(1, "2" <=> 1);
        assertEq(-1, "a" <=> "ab");
        assertEq(1, "ab" <=> "a");

        assertEq(-1, 1n <=> 2n);
        assertEq(-1, 1n <=> 2.0);
        assertEq(-1, 1n <=> 2);
        assertEq(-1, -1 <=> 1n);
        assertEq(-1, -1.0 <=> 1n);
        assertEq(0, 0n <=> 0n);
        assertEq(0, 1n <=> 1n);
        assertEq(0, 1n <=> 1.0);
        assertEq(0, 1n <=> 1);
        assertEq(0, -1 <=> -1n);
        assertEq(0, -1.0 <=> -1n);
        assertEq(1, 2n <=> 1n);
        assertEq(1, 2n <=> 1.0);
        assertEq(1, 2n <=> 1);
        assertEq(1, 1 <=> -1n);
        assertEq(1, 1.0 <=> -1n);

        assertEq(-1, 1.0 <=> 2);
        assertEq(-1, () <=> 1.0);
        assertEq(0, 0.0 <=> 0);
        assertEq(0, 0.0 <=> 0.0);
        assertEq(0, 1.0 <=> 1);
        assertEq(0, () <=> 0.0);
        assertEq(1, 2 <=> 1.0);
        assertEq(1, 1.0 <=> ());

        assertEq(-1, 1 <=> 2016-08-09);
        assertEq(-1, 1970-01-01 <=> 1.0);
        assertEq(0, 0.0 <=> 1970-01-01);
        assertEq(0, 1970-01-01 <=> 0.0);
        assertEq(1, 2016-08-09 <=> 1);
        assertEq(1, 1 <=> 1970-01-01);

        {
            Program p(PO_NEW_STYLE | PO_REQUIRE_TYPES | PO_STRICT_ARGS);
            assertThrows("NAN-COMPARE-ERROR", \p.parse(), ("int i = @nan@ <=> 1.0;", ""));
        }
        {
            Program p(PO_NEW_STYLE | PO_REQUIRE_TYPES | PO_STRICT_ARGS);
            assertThrows("NAN-COMPARE-ERROR", \p.parse(), ("int i = 1.0 <=> @nan@;", ""));
        }
        {
            Program p(PO_NEW_STYLE | PO_REQUIRE_TYPES | PO_STRICT_ARGS);
            assertThrows("NAN-COMPARE-ERROR", \p.parse(), ("int i = @nan@n <=> 1n;", ""));
        }
        {
            Program p(PO_NEW_STYLE | PO_REQUIRE_TYPES | PO_STRICT_ARGS);
            assertThrows("NAN-COMPARE-ERROR", \p.parse(), ("int i = 1n <=> @nan@n;", ""));
        }
        {
            Program p(PO_NEW_STYLE | PO_REQUIRE_TYPES | PO_STRICT_ARGS);
            p.parse("auto x = @nan@; int i = x <=> 1.0;", "");
            assertThrows("NAN-COMPARE-ERROR", \p.run());
        }
        {
            Program p(PO_NEW_STYLE | PO_REQUIRE_TYPES | PO_STRICT_ARGS);
            p.parse("auto x = @nan@; int i = 1.0 <=> x;", "");
            assertThrows("NAN-COMPARE-ERROR", \p.run());
        }
        {
            Program p(PO_NEW_STYLE | PO_REQUIRE_TYPES | PO_STRICT_ARGS);
            p.parse("auto x = @nan@n; int i = x <=> 1n;", "");
            assertThrows("NAN-COMPARE-ERROR", \p.run());
        }
        {
            Program p(PO_NEW_STYLE | PO_REQUIRE_TYPES | PO_STRICT_ARGS);
            p.parse("auto x = @nan@n; int i = 1n <=> x;", "");
            assertThrows("NAN-COMPARE-ERROR", \p.run());
        }
    }

    absolute() {
        assertEq(True, 1 === 1);
        assertEq(False, 1 === "1");
        assertEq(True, NOTHING === NOTHING);
        assertEq(False, NOTHING === NULL);
        assertEq(True, "1" === "1");
        assertEq(False, "1" === 1);
        assertEq(True, "1" === convert_encoding("1", "latin1"));
        assertEq(False, "1" === convert_encoding("2", "latin1"));

        assertEq(False, 1 !== 1);
        assertEq(True, 1 !== "1");
        assertEq(False, NOTHING !== NOTHING);
        assertEq(True, NOTHING !== NULL);
        assertEq(False, "1" !== "1");
        assertEq(True, "1" !== 1);
        assertEq(False, "1" !== convert_encoding("1", "latin1"));
        assertEq(True, "1" !== convert_encoding("2", "latin1"));

        int i = 1;
        string str = "1";
        any nt;
        any nl = NULL;
        assertEq(True, i === 1);
        assertEq(False, i === str);
        assertEq(True, nt === NOTHING);
        assertEq(False, NOTHING === nl);
        assertEq(True, "1" === str);
        assertEq(False, str === i);
        assertEq(True, str === convert_encoding("1", "latin1"));
        assertEq(False, str === convert_encoding("2", "latin1"));

        assertEq(False, i !== 1);
        assertEq(True, i !== str);
        assertEq(False, nt !== NOTHING);
        assertEq(True, nt !== nl);
        assertEq(False, str !== str);
        assertEq(True, str !== i);
        assertEq(False, str !== convert_encoding(str, "latin1"));
        assertEq(True, str !== convert_encoding("2", "latin1"));
    }

    plus() {
        assertEq((1, 2), (1,) + (2,));
        assertEq((1, 2), (1,) + 2);
        assertEq((1, 2), 1 + (2,));
        assertEq("12", "1" + "2");
        assertEq("12", "1" + 2);
        assertEq("12", 1 + "2");
        assertEq(PT1M1S, 1m + 1s);
        assertEq(PT1M1S, 1m + 1);
        assertEq(PT1M1S, 60 + 1s);
        assertEq(3n, 1n + 2n);
        assertEq(3n, 1n + 2);
        assertEq(3n, 1 + 2n);
        assertEq(3.0, 1.0 + 2.0);
        assertEq(3.0, 1.0 + 2);
        assertEq(3.0, 1 + 2.0);
        assertEq(3, 1 + 2);
        assertEq(1, 1 + {});
        assertEq(2, {} + 2);
        assertEq(("a": 1, "b": 2), ("a": 1) + ("b": 2));
        DataTest b();
        assertEq(("a": 1, "b": NOTHING), ("a": 1) + b);
        assertEq(("a": 1, "b": NOTHING, "x": 1), b.doPlusFirst(("a": 1)));
        assertEq(("a": 1, "b": NOTHING, "x": 1), b.doPlusEqualsFirst(("a": 1)));
        assertEq(("b": NOTHING, "a": 1), b + ("a": 1));
        assertEq(("a": 1), ("a": 1) + <abcd>);
        assertEq(("a": 1), <abcd> + ("a": 1));
        assertEq(b, b + <abcd>);
        assertEq(b, <abcd> + b);
        assertEq(<abcd>, <ab> + <cd>);
        assertEq(<ab>, <ab> + NULL);
        assertEq(<cd>, NULL + <cd>);
        assertEq(NOTHING, NULL + NOTHING);
        assertEq(NOTHING, NOTHING + NULL);

        list ll = (1, 2);
        list l1 = (1,);
        list l2 = (2,);
        int ii = 3;
        int i1 = 1;
        int i2 = 2;
        string ss = "12";
        string s1 = "1";
        string s2 = "2";
        date dd = PT1M1S;
        date d1 = 1m;
        date d2 = 1s;
        number nn = 3n;
        number n1 = 1n;
        number n2 = 2n;
        float ff = 3.0;
        float f1 = 1.0;
        float f2 = 2.0;
        hash hh = ("a": 1, "b": 2);
        hash h1 = ("a": 1);
        hash h2 = ("b": 2);
        binary bb = <abcd>;
        binary b1 = <ab>;
        binary b2 = <cd>;

        assertEq(ll, l1 + l2);
        assertEq(ll, l1 + i2);
        assertEq(ll, i1 + l2);
        assertEq(ss, s1 + s2);
        assertEq(ss, s1 + i2);
        assertEq(ss, i1 + s2);
        assertEq(dd, d1 + d2);
        assertEq(dd, d1 + i1);
        assertEq(dd, 60 + d2);
        assertEq(nn, n1 + n2);
        assertEq(nn, n1 + i2);
        assertEq(nn, i1 + n2);
        assertEq(ff, f1 + f2);
        assertEq(ff, f1 + i2);
        assertEq(ff, i1 + f2);
        assertEq(ii, i1 + i2);
        assertEq(i1, i1 + {});
        assertEq(i2, {} + i2);
        assertEq(hh, h1 + h2);
        assertEq(("a": 1, "b": NOTHING), h1 + b);
        assertEq(("b": NOTHING, "a": 1), b + h1);
        assertEq(h1, h1 + bb);
        assertEq(h1, bb + h1);
        assertEq(b, b + bb);
        assertEq(b, bb + b);
        assertEq(bb, b1 + b2);
        assertEq(b1, b1 + NULL);
        assertEq(b2, NULL + b2);
        assertEq(NOTHING, NULL + NOTHING);
        assertEq(NOTHING, NOTHING + NULL);
    }

    minus() {
        assertEq(2016-08-09, 2016-08-10 - 1D);
        assertEq(1970-01-01T00:00:01, 1970-01-01T00:00:02 - 1);
        assertEq(1s, 2 - 1s);
        assertEq(1969-12-31T23:59:59, ("0": 1) - 1s);
        assertEq(0s, True - 1s);

        assertEq(1n, 2n - 1n);
        assertEq(1n, 2n - 1);
        assertEq(1n, 2 - 1n);

        assertEq(1.0, 2.0 - 1.0);
        assertEq(1.0, 2.0 - 1);
        assertEq(1.0, 2 - 1.0);

        assertEq(1, 2 - 1);
        assertEq(1, 2 - 1);
        assertEq(1, 2 - 1);

        assertEq(("a": 1), ("a": 1, "b": 2) - "b");
        assertEq({}, ("a": 1, "b": 2) - ("a", "b"));
        assertEq({}, ("0": 1, "1": 2) - (0, 1));

        assertEq(NOTHING, ("0": 1, "1": 2) - True);
        assertEq(NOTHING, True - False);
        assertEq(NOTHING, NULL - {});
        assertEq(NOTHING, <bead> - True);
        assertEq(NOTHING, True - NOTHING);
        assertEq(NOTHING, False - NULL);
        assertEq(NOTHING, NULL - NOTHING);
    }

    multiplication() {
        assertEq(4n, 2n * 2n);
        assertEq(4n, 2n * 2);
        assertEq(4n, 2 * 2n);
        assertEq(2n, 2n * True);
        assertEq(0n, 2n * NULL);
        assertEq(0n, 2n * {});
        assertEq(0n, 2n * (1,));

        assertEq(4.0, 2.0 * 2.0);
        assertEq(4.0, 2.0 * 2);
        assertEq(4.0, 2 * 2.0);
        assertEq(2.0, 2.0 * True);
        assertEq(0.0, 2.0 * NULL);
        assertEq(0.0, 2.0 * {});
        assertEq(0.0, 2.0 * (1,));

        assertEq(4, 2 * 2);
        assertEq(4, 2 * "2");
        assertEq(4, "2" * 2);
        assertEq(2, 2 * True);
        assertEq(0, 2 * NULL);
        assertEq(0, 2 * {});
        assertEq(0, 2 * (1,));

        assertEq(NOTHING, True * <ab>);
        assertEq(NOTHING, NULL * False);
    }

    rangeAndSlice() {
        assertEq((3,4,5,6,7), 3..7);
        assertEq((-2,-1,0,1,2,3,4,5,6), (1-3)..(2*3));
        assertEq((6,5,4,3,2), 6..2);

        {
            Program p(PO_NEW_STYLE | PO_BROKEN_RANGE);
            p.parse("list<int> sub t(int start, int end) { return start..end; }", "test");
            assertEq((3,4,5,6,7), p.callFunction("t", 3, 7));
            assertEq((-2,-1,0,1,2,3,4,5,6), p.callFunction("t", (1-3), (2*3)));
            assertEq((6,5,4,3,2), p.callFunction("t", 6, 2));
        }

        list l = (7,3,5,1,8,7,2);
        assertEq((1,3,8), l[3,1,4]);
        assertEq((5,1,8,7), l[2..5]);
        assertEq((7,8,1,5), l[5..2]);
        assertEq((8,7,2), l[4..]);
        assertEq((8,7,2,NOTHING,NOTHING),          l[4..8]);
        assertEq((8,7,2,NOTHING,NOTHING), map  $1, l[4..8]);  # lazy evaluation
        assertEq((8,7,2,      0,      0), map +$1, l[4..8]);  # lazy evaluation
        assertEq((NOTHING,NOTHING,2,7,8), l[8..4]);
        assertEq((7,3,5,1), l[0..3]);
        assertEq((7,3,5,1), l[..3]);
        assertEq((16,2,10,6), map 2*$1, l[4..1]);    # lazy evaluation
        assertEq(23, foldl $1 + 2*$2, l[..2]);       # lazy evaluation
        assertEq(25, foldl $1 + 2*$2, l[2..0]);      # lazy evaluation
        assertEq(23, foldl $1 + 2*$2, l[(0..2)]);    # lazy evaluation
        assertEq(NOTHING, l[8]);
        assertEq((NOTHING,), l[8,]);
        assertEq((NOTHING,NOTHING,NOTHING,NOTHING,), l[8..11]);

        assertEq(   ((8,3,4), "asdf", 5, {"f":3, "g":2}),
                 (4, (8,3,4), "asdf", 5, {"f":3, "g":2}, 1) [1 .. 4]);

        assertEq("xsf", "asdfzxcv"[5,1,3]);
        assertEq("xcv", "asdfzxcv"[5..]);
        assertEq("asdf", "asdfzxcv"[-2..3]);
        assertEq("asdfz", "asdfzxcv"[..4]);
        assertEq("cxzf", "asdfzxcv"[6..3]);
        assertEq("v", "asdfzxcv"[16..7]);
        assertEq("cxzf*", map $1 + "*", "asdfzxcv"[6..3]);           # map does not create a list because its second operand is a single element
        assertEq("cxzf", foldl $1 + "|" + $2, "asdfzxcv"[6..3]);     # foldl does not do anything because its second operand is a single element
        assertEq(NOTHING, "asdfzxcv"[12]);
        assertEq("", "asdfzxcv"[12,]);

        binary b = <5e2cad96f1bbd473>;
        assertEq(<f1adbb>, b[4,2,5]);
        assertEq(<2cad96>, b[1..3]);
        assertEq(<96ad2c>, b[3..1]);
        assertEq(<5e2cad96f1>, b[..4]);
        assertEq(<f1bbd473>, b[4..]);
        assertEq(NOTHING, b[14]);
        assertEq(<5e2cad96>, b[..3]);
        assertEq(binary(), b[..-3]);
        assertEq(binary(), b[20..25]);
        assertEq(binary(), b[14..]);
        assertEq(<96f1bb>, b[2..7][1..3]);
        assertEq(187, b[2..7][1..3][2]);
        assertEq(<ad96f100>, map $1+<00>, b[2..4]);      # map does not create a list because its second operand is a single element
        assertEq(<bbd473>, foldl $1 + $2, b[5..]);       # foldl does not do anything because its second operand is a single element
        assertEq(<bbd473>, foldl $1 + $2, b[(5..7)]);    #        -"-
        assertEq(<bbd473>, b[5..]);
        assertEq("list<int>", (1,2)[0..1].fullType());
        assertEq("list<int>", (1,2)[1..0].fullType());
        assertEq("list<*int>", (NOTHING,2)[0..1].fullType());
        assertEq("list<*int>", (NOTHING,2)[1..0].fullType());
        assertEq("list<*int>", (0,2)[1,3].fullType());
        assertEq("list<*int>", (0,2)[3,1].fullType());

        {
            binary bin = <beadface>;
            assertEq(<bead>, bin[0..1]);
            assertEq(<bead>, bin[0,1]);
            assertEq(<bead>, bin[..1]);
            assertEq(<cefa>, bin[3..2]);
            assertEq(<cefa>, bin[3,2]);
        }

        {
            string str = "hello";
            assertEq("he", str[0..1]);
            assertEq("he", str[0,1]);
            assertEq("he", str[..1]);
            assertEq("ol", str[4..3]);
            assertEq("ol", str[4,3]);
        }

        {
            string str = "abcdef";
            assertEq("adef", str[0,45,3..4,5]);
            {
                any x = (0,);
                assertEq("adeaf", str[0,45,3..4,x,5]);
                string str0 = str;
                assertEq("bdeaf", remove str0[1,45,3..4,x,5]);
                assertEq("c", str0);
                str0 = str;
                assertEq("ef", str0[4..7]);
                assertEq("ef", remove str0[4..7]);
                assertEq("abcd", str0);
            }
            string str0 = remove str[1,45,3,5,3];
            assertEq("bdfd", str0);
            assertEq("ace", str);
            str0 = remove str[1..2];
            assertEq("ce", str0);
            assertEq("a", str);
            str = "abcdef";
            str0 = remove str[2..1];
            assertEq("cb", str0);
            assertEq("adef", str);
            str0 = remove str[2..];
            assertEq("ef", str0);
            assertEq("ad", str);
            str = "abcdef";
            assertEq("", str[45,56]);
            assertEq("", str[45..56]);
            assertEq("", str[45..56,57]);
            assertEq("", remove str[45,56]);
            assertEq("", remove str[45..56]);
            assertEq("", remove str[45..56,57]);
        }

        {
            binary bin = <abcdef0123457890>;
            assertEq(<ab012345>, bin[0,45,3..4,5]);
            {
                any x = (0,);
                assertEq(<ab0123ab45>, bin[0,45,3..4,x,5]);
                binary bin0 = bin;
                assertEq(<ab0123ab45>, remove bin0[0,45,3..4,x,5]);
                assertEq(<cdef7890>, bin0);
                bin0 = bin;
                assertEq(<7890>, bin0[6..9]);
                assertEq(<7890>, remove bin0[6..9]);
                assertEq(<abcdef012345>, bin0);
            }
            binary bin0 = remove bin[1,45,3,5,3];
            assertEq(<cd014501>, bin0);
            assertEq(<abef237890>, bin);
            bin0 = remove bin[1..2];
            assertEq(<ef23>, bin0);
            assertEq(<ab7890>, bin);
            bin = <abcdef0123457890>;
            bin0 = remove bin[2..1];
            assertEq(<efcd>, bin0);
            assertEq(<ab0123457890>, bin);
            bin0 = remove bin[2..];
            assertEq(<23457890>, bin0);
            assertEq(<ab01>, bin);
            bin = <abcdef0123457890>;
            assertEq(binary(), bin[45,56]);
            assertEq(binary(), bin[45..56]);
            assertEq(binary(), bin[45..56,57]);
            assertEq(binary(), remove bin[45,56]);
            assertEq(binary(), remove bin[45..56]);
            assertEq(binary(), remove bin[45..56,57]);
        }

        {
            list l0 = (1,2,3,4,5,6,7);
            assertEq((2,NOTHING,4,5,6), l0[1,45,3..4,5]);
            {
                any x = (0,);
                assertEq((1,NOTHING,4,5,1,6), l0[0,45,3..4,x,5]);
                list<auto> l1 = l0;
                assertEq((1,NOTHING,4,5,1,6), remove l1[0,45,3..4,x,5]);
                assertEq((2,3,7), l1);
                l1 = l0;
                assertEq((NOTHING,NOTHING), l1[7..8]);
                assertEq((NOTHING,NOTHING), remove l1[7..8]);
                assertEq(l0, l1);
                l1 = l0;
                assertEq((7,NOTHING,NOTHING), l1[6..8]);
                assertEq((7,NOTHING,NOTHING), remove l1[6..8]);
                assertEq((1,2,3,4,5,6), l1);
                l1 = l0;
                assertEq((6,7,NOTHING,NOTHING), l1[5..8]);
                assertEq((6,7,NOTHING,NOTHING), remove l1[5..8]);
                assertEq((1,2,3,4,5), l1);
                l1 = l0;
                assertEq((NOTHING,NOTHING,7,6), l1[8..5]);
                assertEq((NOTHING,NOTHING,7,6), remove l1[8..5]);
                assertEq((1,2,3,4,5), l1);
                l1 = l0;
                assertEq((NOTHING,NOTHING,7), l1[8..6]);
                assertEq((NOTHING,NOTHING,7), remove l1[8..6]);
                assertEq((1,2,3,4,5,6), l1);
                l1 = l0;
                assertEq((NOTHING,NOTHING), l1[8..7]);
                assertEq((NOTHING,NOTHING), remove l1[8..7]);
            }
            list l1 = remove l0[1,45,3,5,3];
            assertEq((2,NOTHING,4,6,4), l1);
            assertEq((1,3,5,7), l0);
            l1 = remove l0[1..2];
            assertEq((3,5), l1);
            assertEq((1,7), l0);
            l0 = (1,2,3,4,5,6,7);
            l1 = remove l0[2..1];
            assertEq((3,2), l1);
            assertEq((1,4,5,6,7), l0);
            l1 = remove l0[2..];
            assertEq((5,6,7), l1);
            assertEq((1,4), l0);
            l0 = (1,2,3,4,5,6,7);
            assertEq((NOTHING, NOTHING), l0[45,46]);
            assertEq((NOTHING, NOTHING), l0[45..46]);
            assertEq((NOTHING, NOTHING, NOTHING), l0[45..46,47]);
            assertEq((NOTHING, NOTHING), map $1, l0[45,46]);
            assertEq((NOTHING, NOTHING), map $1, l0[45..46]);
            assertEq((NOTHING, NOTHING, NOTHING), map $1, l0[45..46,47]);
            assertEq((NOTHING, NOTHING), remove l0[45,46]);
            assertEq((NOTHING, NOTHING), remove l0[45..46]);
            assertEq((NOTHING, NOTHING, NOTHING), remove l0[45..46,47]);
        }

        {
            list l0 = ((0, 1),);
            foreach int i in (l0[0])
                assertEq(Type::Int, i.type());
        }

        {
            list<auto> l0 = (1, 2, 3, 4);
            list<auto> l1 = l0;
            assertEq((1, 2), remove l1[0..1]);
            l1 = l0;
            assertEq("list<int>", l1.fullType());
            assertEq("list<int>", (remove l1[0..1]).fullType());
            assertEq((3, 4), l1);
            l1 = l0;
            assertEq((1, 2), remove l1[0,1]);
            assertEq((3, 4), l1);
            l1 = l0;
            assertEq("list<int>", (remove l1[0,1]).fullType());
            l1 = l0;
            assertEq((1, 2, 3), remove l1[0,1..2]);
            assertEq((4,), l1);
            l1 = l0;
            assertEq("list<int>", (remove l1[0,1..2]).fullType());

            assertEq((1, 2), l0[0..1]);
            assertEq((1, 2), l0[0,1]);
            assertEq((1, 2, 3), l0[0,1..2]);
            assertEq((1, 2), map $1, l0[0..1]);
            assertEq((1, 2), map $1, l0[0,1]);
            assertEq((1, 2, 3), map $1, l0[0,1..2]);
            {
                # ensure consistent behavior between functional and non-function complex [] dereferences
                auto x = (0,1);
                assertEq((1, 2, 3, 1), l0[0,1..2,x]);
                assertEq((1, 2, 3, 1), map $1, l0[0,1..2,x]);
            }

            string s0 = "1234";
            string s1 = s0;
            assertEq("12", remove s1[0..1]);
            assertEq("34", s1);
            s1 = s0;
            assertEq("12", remove s1[0,1]);
            assertEq("34", s1);
            s1 = s0;
            assertEq("123", remove s1[0,1..2]);
            assertEq("4", s1);

            assertEq("12", s0[0..1]);
            assertEq("12", s0[0,1]);
            assertEq("123", s0[0,1..2]);
            assertEq("12", map $1, s0[0..1]);
            assertEq("12", map $1, s0[0,1]);
            assertEq("123", map $1, s0[0,1..2]);
            {
                # ensure consistent behavior between functional and non-function complex [] dereferences
                auto x = (0,1);
                assertEq("1231", s0[0,1..2,x]);
                assertEq("1231", map $1, s0[0,1..2,x]);
            }

            binary b0 = <01020304>;
            binary b1 = b0;
            assertEq(<0102>, remove b1[0..1]);
            assertEq(<0304>, b1);
            b1 = b0;
            assertEq(<0102>, remove b1[0,1]);
            assertEq(<0304>, b1);
            b1 = b0;
            assertEq(<010203>, remove b1[0,1..2]);
            assertEq(<04>, b1);

            assertEq(<0102>, b0[0..1]);
            assertEq(<0102>, b0[0,1]);
            assertEq(<010203>, b0[0,1..2]);
            assertEq(<0102>, map $1, b0[0..1]);
            assertEq(<0102>, map $1, b0[0,1]);
            assertEq(<010203>, map $1, b0[0,1..2]);
            {
                # ensure consistent behavior between functional and non-function complex [] dereferences
                auto x = (0,1);
                assertEq(<01020301>, b0[0,1..2,x]);
                assertEq(<01020301>, map $1, b0[0,1..2,x]);
            }
        }

        # check range operator precedence
        assertEq((0,1,2), (0,1,2,3,4)[0..3-1]);

        {
            Program p(PO_NEW_STYLE);
            assertThrows("PARSE-TYPE-ERROR", \p.parse(), ("sub t() { list l = (()..()); delete l; }", ""));
        }

        {
            Program p(PO_NEW_STYLE);
            assertThrows("PARSE-TYPE-ERROR", \p.parse(), ("sub t() { list l = (0..()); delete l; }", ""));
        }

        {
            Program p(PO_NEW_STYLE);
            assertThrows("PARSE-TYPE-ERROR", \p.parse(), ("sub t() { list l = (()..5); delete l; }", ""));
        }

        {
            Program p(PO_NEW_STYLE);
            assertThrows("PARSE-TYPE-ERROR", \p.parse(), ("sub t() { auto v = {}[0..1]; delete v; }", ""));
        }

        {
            Program p(PO_NEW_STYLE);
            assertThrows("PARSE-EXCEPTION", \p.parse(), ("sub t() { list l = ()[..]; delete l; }", ""));
        }

        {
            Program p(PO_NEW_STYLE);
            assertThrows("PARSE-EXCEPTION", \p.parse(), ("sub t() { auto v = <abcd>[..]; delete v; }", ""));
        }

        {
            Program p(PO_NEW_STYLE);
            assertThrows("PARSE-EXCEPTION", \p.parse(), ("sub t() { auto v = ''[..]; delete v; }", ""));
        }

        {
            Program p(PO_NEW_STYLE);
            assertThrows("PARSE-EXCEPTION", \p.parse(), ("sub t() { list l = (0, 1); l[0,1] = (2,3); }", ""));
        }

        {
            Program p(PO_NEW_STYLE);
            assertThrows("PARSE-EXCEPTION", \p.parse(), ("sub t() { list l = (0, 1); l[0,1][1] = 2; }", ""));
        }

        {
            Program p(PO_NEW_STYLE);
            assertThrows("PARSE-EXCEPTION", \p.parse(), ("sub t() { list l = (0, 1); l[0..1] = (2,3); }", ""));
        }

        {
            # test invalid types in complex [] operations
            Program p(PO_NEW_STYLE);
            assertThrows("PARSE-TYPE-ERROR", "slice", \p.parse(), ("sub t() { list l = (0, 1); auto v = l[0..1,(0,1)]; }", ""));
            assertThrows("PARSE-TYPE-ERROR", "range", \p.parse(), ("sub t() { list l = (0, 1); auto v = l[0..(0,1)]; }", ""));
            assertThrows("PARSE-TYPE-ERROR", "slice", \p.parse(), ("sub t() { string str = '01'; auto v = str[0..1,(0,1)]; }", ""));
            assertThrows("PARSE-TYPE-ERROR", "range", \p.parse(), ("sub t() { string str = '01'; auto v = str[0..(0,1)]; }", ""));
            assertThrows("PARSE-TYPE-ERROR", "slice", \p.parse(), ("sub t() { binary bin = <0102>; auto v = bin[0..1,(0,1)]; }", ""));
            assertThrows("PARSE-TYPE-ERROR", "range", \p.parse(), ("sub t() { binary bin = <0102>; auto v = bin[0..(0,1)]; }", ""));
        }
    }
}
