#!/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 ExceptionLocationTest

class ExceptionLocationTest inherits QUnit::Test {
    public {
        const Code1 = "namespace A {\nconst A = 1;\nclass B { public { const C = 1; static int d = 1; }}}\n";

        const Code2 = "namespace A {\nconst A = 1;\nclass B {}}\n";
    }

    constructor() : QUnit::Test("Exception location test", "1.0") {
        addTestCase("Exception location test", \testExceptionLocation());
        set_return_value(main());
    }

    testExceptionLocation() {
        *int line;
        try {
            hash<auto> h = do_loc1();
            delete h;
        } catch (hash<auto> ex) {
            line = ex.line;
        }

        assertEq(30, line);

        {
            Program p(PO_NEW_STYLE|PO_REQUIRE_TYPES|PO_STRICT_ARGS);
            hash<auto> ex = getEx(\p.parse(), Code1 + "namespace A {\nconst A = 2;\n}", "B");
            assertEq(5, ex.line);
            assertEq(5, ex.endline);
            assertEq("B", ex.file);

            ex = getEx(\p.parse(), Code1 + "const A::A\n= 2;\n", "C");
            assertEq(4, ex.line);
            assertEq(5, ex.endline);
            assertEq("C", ex.file);

            ex = getEx(\p.parse(), Code1 + "class A\n{}\n", "D");
            assertEq(4, ex.line);
            assertEq(5, ex.endline);
            assertEq("D", ex.file);

            ex = getEx(\p.parse(), Code1 + "class\n ::A {}", "E");
            assertEq(5, ex.line);
            assertEq(5, ex.endline);
            assertEq("E", ex.file);

            ex = getEx(\p.parse(), Code1 + "namespace A {\nclass B {\n}\n}", "G");
            assertEq(5, ex.line);
            assertEq(6, ex.endline);
            assertEq("G", ex.file);

            ex = getEx(\p.parse(), Code1 + "sub t() {code a = \\a.printf(1\n);\n}", "H");
            assertEq(4, ex.line);
            assertEq(5, ex.endline);
            assertEq("H", ex.file);

            ex = getEx(\p.parse(), Code1 + "int i\n = 1;\n", "I");
            assertEq(4, ex.line);
            assertEq(4, ex.endline);
            assertEq("I", ex.file);

            ex = getEx(\p.parse(), Code1 + "class B {\nconstructor() {\nX::a();}\n}", "J");
            assertEq(6, ex.line);
            assertEq(6, ex.endline);
            assertEq("J", ex.file);

            ex = getEx(\p.parse(), Code1 + "class B inherits\nB {}\n", "J");
            assertEq(4, ex.line);
            assertEq(5, ex.endline);
            assertEq("J", ex.file);

            ex = getEx(\p.parse(), Code1 + "class B1 inherits B {} class B inherits\nB1 {}\n", "K");
            assertEq(4, ex.line);
            assertEq(5, ex.endline);
            assertEq("K", ex.file);

            ex = getEx(\p.parse(), Code1 + "class B1 {\n} class B inherits\nB1, private B1 {}\n", "L");
            assertEq(5, ex.line);
            assertEq(6, ex.endline);
            assertEq("L", ex.file);

            ex = getEx(\p.parse(), Code1 + "class B1 {\n} class B inherits\nB1 {constructor() : B1(int i = \n2) {}}\n", "M");
            assertEq(6, ex.line);
            assertEq(7, ex.endline);
            assertEq("M", ex.file);

            ex = getEx(\p.parse(), Code1 + "class B1 {\nfinal x() {}\n}\nclass B inherits B1 {x() {\n}\n}", "N");
            assertEq(7, ex.line);
            assertEq(7, ex.endline);
            assertEq("N", ex.file);

            ex = getEx(\p.parse(), Code1 + "class B1 {\n}\nclass B inherits B1 {\nconstructor() : B2() {\n}\n}\n", "O");
            assertEq(7, ex.line);
            assertEq(7, ex.endline);
            assertEq("O", ex.file);

            ex = getEx(\p.parse(), Code1 + "class B {\nconstructor() : B2() {\n}\n}\n", "P");
            assertEq(5, ex.line);
            assertEq(5, ex.endline);
            assertEq("P", ex.file);

            ex = getEx(\p.parse(), Code1 + "class B {\ncopy(B b, B b2) {\n}\n}\n", "Q");
            assertEq(5, ex.line);
            assertEq(5, ex.endline);
            assertEq("Q", ex.file);

            ex = getEx(\p.parse(), Code1 + "class B {\ncopy(int i) {\n}\n}\n", "R");
            assertEq(5, ex.line);
            assertEq(5, ex.endline);
            assertEq("R", ex.file);

            ex = getEx(\p.parse(), Code1 + "class B {\npublic {\nint i =\n(int j = 2);\n}\n}\n", "S");
            assertEq(6, ex.line);
            assertEq(7, ex.endline);
            assertEq("S", ex.file);

            ex = getEx(\p.parse(), Code1 + "class B {\npublic {\nstatic int i =\n(int j = 2);\n}\n}\n", "T");
            assertEq(6, ex.line);
            assertEq(7, ex.endline);
            assertEq("T", ex.file);

            ex = getEx(\p.parse(), Code1 + "class B {\npublic {\nstatic int i =\n'hi';\n}\n}\n", "U");
            assertEq(6, ex.line);
            assertEq(7, ex.endline);
            assertEq("U", ex.file);

            ex = getEx(\p.parse(), Code1 + "final class B1 {\n}\nclass B inherits B1 {\n}\n", "V");
            assertEq(6, ex.line);
            assertEq(7, ex.endline);
            assertEq("V", ex.file);

            ex = getEx(\p.parse(), Code1 + "class B {\nmethodGate(int i) {\n}\n}\n", "W");
            assertEq(5, ex.line);
            assertEq(5, ex.endline);
            assertEq("W", ex.file);

            ex = getEx(\p.parse(), Code1 + "A::X::method() {\nx = 1;\n}\n", "X");
            assertEq(4, ex.line);
            assertEq(6, ex.endline);
            assertEq("X", ex.file);

            ex = getEx(\p.parse(), Code1 + "const A::X::P\n= 1;\n", "Y");
            assertEq(4, ex.line);
            assertEq(5, ex.endline);
            assertEq("Y", ex.file);

            ex = getEx(\p.parse(), Code1 + "const A = 1;\nsub t() {\nA{'hi'}\n= 2;\n}\n", "Z");
            assertEq(6, ex.line);
            assertEq(6, ex.endline);
            assertEq("Z", ex.file);

            ex = getEx(\p.parse(), Code1 + "sub t() {\nstring a = '' =~ tr/a-/;\n}\n", "A1");
            assertEq(5, ex.line);
            assertEq(5, ex.endline);
            assertEq("A1", ex.file);

            ex = getEx(\p.parse(), Code1 + "sub t() {\nswitch (1) {\ndefault:\ndefault:\n}\n}\n", "A2");
            assertEq(7, ex.line);
            assertEq(7, ex.endline);
            assertEq("A2", ex.file);

            ex = getEx(\p.parse(), Code1 + "sub t() {\nswitch (1) {\ncase = 2:\n}\n}\n", "A3");
            assertEq(6, ex.line);
            assertEq(6, ex.endline);
            assertEq("A3", ex.file);

            ex = getEx(\p.parse(), Code1 + "sub t() {\nswitch (1) {\ncase (int i = 3):\n}\n}\n", "A4");
            assertEq(6, ex.line);
            assertEq(6, ex.endline);
            assertEq("A4", ex.file);

            ex = getEx(\p.parse(), Code1 + "sub t() {\nswitch (1) {\ncase 2:\ncase 2:\n}\n}\n", "A4");
            assertEq(7, ex.line);
            assertEq(7, ex.endline);
            assertEq("A4", ex.file);

            ex = getEx(\p.parse(), Code1 + "const B = C;\nconst C = D;\nconst D = B;\n", "A5");
            # line numbers could change the with the implementation
            #assertEq(2, ex.line);
            #assertEq(2, ex.endline);
            assertEq("A5", ex.file);

            ex = getEx(\p.parse(), Code1 + "class B {\npublic {\ni = 2;\n}\n}\n", "A6");
            assertEq(6, ex.line);
            assertEq(6, ex.endline);
            assertEq("A6", ex.file);

            ex = getEx(\p.parse(), Code1 + "sub t(\nt()) {\n}\n", "A7");
            assertEq(5, ex.line);
            assertEq(5, ex.endline);
            assertEq("A7", ex.file);

            ex = getEx(\p.parse(), Code1 + "class B {\nprivate {\nint i = 2;\n}\n}\nsub test() {\nB b();\nb.i = 3;\n}\n", "A8");
            assertEq(11, ex.line);
            assertEq(11, ex.endline);
            assertEq("A8", ex.file);

            ex = getEx(\p.parse(), Code1 + "class B {\npublic {\nconst i = 2;\nstatic int i = 3;\n}\n}\n", "A9");
            assertEq(7, ex.line);
            assertEq(7, ex.endline);
            assertEq("A9", ex.file);

            ex = getEx(\p.parse(), Code1 + "sub t() {\nxxxx();\n}\n", "A10");
            assertEq(5, ex.line);
            assertEq(5, ex.endline);
            assertEq("A10", ex.file);

            ex = getEx(\p.parse(), Code1 + "sub t() {\n1 = 2;\n}\n", "A10");
            assertEq(5, ex.line);
            assertEq(5, ex.endline);
            assertEq("A10", ex.file);

            ex = getEx(\p.parse(), Code1 + "sub t() {\nlist l = ();\nbackground pop l;\n}\n", "A11");
            assertEq(6, ex.line);
            assertEq(6, ex.endline);
            assertEq("A11", ex.file);

            ex = getEx(\p.parse(), Code1 + "our int i = 1;\nour string i = '2';\n", "A13");
            # line numbers could change the with the implementation
            #assertEq(5, ex.line);
            #assertEq(5, ex.endline);
            assertEq("A13", ex.file);

            ex = getEx(\p.parse(), Code1 + "sub t() {\nnew xxxxx();\n}\n", "A14");
            assertEq(5, ex.line);
            assertEq(5, ex.endline);
            assertEq("A14", ex.file);

            ex = getEx(\p.parse(), Code1 + "sub t() {\nreference r = \1;\n}\n", "B");
            assertEq(5, ex.line);
            assertEq(5, ex.endline);
            assertEq("B", ex.file);

            ex = getEx(\p.parse(), Code1 + "class B {\nprivate constructor() {}\n}\nnew B();\n", "B");
            assertEq(7, ex.line);
            assertEq(7, ex.endline);
            assertEq("B", ex.file);

            ex = getEx(\p.parse(), Code1 + "class B {\nprivate:internal constructor() {}\n}\nnew B();\n", "B");
            assertEq(7, ex.line);
            assertEq(7, ex.endline);
            assertEq("B", ex.file);

            ex = getEx(\p.parse(), Code1 + "sub t() {\nB b() = new B();\n}\n", "B");
            assertEq(5, ex.line);
            assertEq(5, ex.endline);
            assertEq("B", ex.file);

            ex = getEx(\p.parse(), Code1 + "sub t() {\nint i();\n}\n", "B");
            assertEq(5, ex.line);
            assertEq(5, ex.endline);
            assertEq("B", ex.file);

            ex = getEx(\p.parse(), Code1 + "\n\nour i;\n", "B");
            assertEq(6, ex.line);
            assertEq(6, ex.endline);
            assertEq("B", ex.file);

            ex = getEx(\p.parse(), Code1 + "sub t() {\nint i = 0;\nint i = 2;\n}\n", "B");
            assertEq(6, ex.line);
            assertEq(6, ex.endline);
            assertEq("B", ex.file);

            ex = getEx(\p.parse(), Code1 + "\nsplice 1, 1;\n", "B");
            assertEq(5, ex.line);
            assertEq(5, ex.endline);
            assertEq("B", ex.file);

            ex = getEx(\p.parse(), Code1 + "\ncast<a>(new Mutex());\n", "B");
            assertEq(5, ex.line);
            assertEq(5, ex.endline);
            assertEq("B", ex.file);

            ex = getEx(\p.parse(), Code1 + "\ncast<Mutex>();\n", "B");
            assertEq(5, ex.line);
            assertEq(5, ex.endline);
            assertEq("B", ex.file);

            ex = getEx(\p.parse(), Code1 + "\nsub t(i) {\n}\n", "B");
            assertEq(5, ex.line);
            assertEq(5, ex.endline);
            assertEq("B", ex.file);

            ex = getEx(\p.parse(), Code1 + "\nsub t(int $i) {\n}\n", "B");
            assertEq(5, ex.line);
            assertEq(5, ex.endline);
            assertEq("B", ex.file);

            ex = getEx(\p.parse(), Code1 + "\nsub t(Mutex m()) {\n}\n", "B");
            assertEq(5, ex.line);
            assertEq(5, ex.endline);
            assertEq("B", ex.file);

            ex = getEx(\p.parse(), Code1 + "sub t() {\nint i = %i;\n}\n", "B");
            assertEq(5, ex.line);
            assertEq(5, ex.endline);
            assertEq("B", ex.file);

            ex = getEx(\p.parse(), Code1 + "sub t() {\nhash h = %%;\n}\n", "B");
            assertEq(5, ex.line);
            assertEq(5, ex.endline);
            assertEq("B", ex.file);

            ex = getEx(\p.parse(), Code1 + "sub t() {\ncontext({}) {\nint i = %%;\n}\n}\n", "B");
            assertEq(6, ex.line);
            assertEq(6, ex.endline);
            assertEq("B", ex.file);

            ex = getEx(\p.parse(), Code1 + "sub t() {\nstring str =\n\"sdfdfssf;\n}", "B");
            assertEq(7, ex.line);
            assertEq(7, ex.endline);
            assertEq("B", ex.file);

            ex = getEx(\p.parse(), Code1 + "sub t() {\nstring str = '';\nstr =~ /sdfdfssf;\n}", "B");
            assertEq(7, ex.line);
            assertEq(7, ex.endline);
            assertEq("B", ex.file);

            ex = getEx(\p.parse(), Code1 + "sub t() {\nstring str = '';\nstr =~ s/sdfdfssf/;\n}", "B");
            assertEq(7, ex.line);
            assertEq(7, ex.endline);
            assertEq("B", ex.file);

            ex = getEx(\p.parse(), Code1 + "sub t() {\nstring str = '';\nstr =~ x/sdfdfssf;\n}", "B");
            assertEq(7, ex.line);
            assertEq(7, ex.endline);
            assertEq("B", ex.file);

            ex = getEx(\p.parse(), Code1 + "sub t() {\nstring str = '';\nstr !~ /sdfdfssf;\n}", "B");
            assertEq(7, ex.line);
            assertEq(7, ex.endline);
            assertEq("B", ex.file);

            ex = getEx(\p.parse(), Code1 + "sub t() {\nstring str = '';\nstr =~ tr/sdfdfssf;\n}", "B");
            assertEq(7, ex.line);
            assertEq(7, ex.endline);
            assertEq("B", ex.file);

            ex = getEx(\p.parse(), Code1 + "sub t() {\nstring s = sprintf()\"error string for\n%s\", \"foo\"));\n}", "B");
            assertEq(5, ex.line);
            assertEq(6, ex.endline);
            assertEq("B", ex.file);

            #printf("%s\n", get_exception_string(ex));
        }

        # test errors only possible with %old-style
        {
            Program p();
            hash ex = getEx(\p.parse(), Code2 + "class B {\nconstructor() {\n$.call();\n}\n}\n", "B");
            assertEq(6, ex.line);
            assertEq(6, ex.endline);
            assertEq("B", ex.file);

            ex = getEx(\p.parse(), Code2 + "class B {\nconstructor() {\ncode $c = \$.call();\ndelete $c;}\n}\n", "B");
            assertEq(6, ex.line);
            assertEq(6, ex.endline);
            assertEq("B", ex.file);

            ex = getEx(\p.parse(), Code2 + "class B {\npublic {}\n}\nclass B1 inherits B {\nconstructor() {\n$.i = 1;\n}\n}\n", "B");
            assertEq(9, ex.line);
            assertEq(9, ex.endline);
            assertEq("B", ex.file);

            ex = getEx(\p.parse(), Code2 + "our int $i = 1;\nstring $i;\n", "B");
            assertEq(5, ex.line);
            assertEq(5, ex.endline);
            assertEq("B", ex.file);

            ex = getEx(\p.parse(), Code2 + "\n$.i = 2;\n", "B");
            assertEq(5, ex.line);
            assertEq(5, ex.endline);
            assertEq("B", ex.file);

            ex = getEx(\p.parse(), Code2 + "\nsub t(i) {\n}\n", "B");
            assertEq(5, ex.line);
            assertEq(5, ex.endline);
            assertEq("B", ex.file);

            ex = getEx(\p.parse(), Code2 + "\nsub t(int i) {\n}\n", "B");
            assertEq(5, ex.line);
            assertEq(5, ex.endline);
            assertEq("B", ex.file);

            #printf("%s\n", get_exception_string(ex));
        }
    }

    static hash<auto> getEx(code c) {
        try {
            call_function_args(c, argv);
        }
        catch (hash<auto> ex) {
            return ex;
        }
        throw "GETEX-ERROR", "no exception thrown";
    }

    static *hash do_loc1() {
        return ExceptionLocationTest::do_loc1_1();
    }

    static *hash do_loc1_1() {
    }
}
