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

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

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

%exec-class ParseDirectiveTest

public class ParseDirectiveTest inherits QUnit::Test {

    constructor() : Test("ParseDirectiveTest", "1.0") {
        addTestCase("strict-types test", \strictTypesTest());
        addTestCase("returns test", \returnsTest());
        addTestCase("missing arg to %append-include-path", \missingArgAppendIncludePath());
        addTestCase("missing arg to %append-module-path", \missingArgAppendModulePath());
        addTestCase("missing arg to %set-time-zone", \missingArgSetTimeZone());
        addTestCase("missing arg to %enable-warning", \missingArgEnableWarning());
        addTestCase("missing arg to %try-module", \missingArgTryModule());
        addTestCase("missing arg to %requires", \missingArgRequires());
        addTestCase("missing arg to %requires (with space)", \missingArgRequiresWithSpace());
        addTestCase("tab in arg", \tabInArg());
        addTestCase("space after %exec-class", \spaceAfterExecClass());
        addTestCase("invalid id in %try-module", \invalidIdInTryModule());
        addTestCase("ignored text before '(' in %try-module", \ignoredTextTryModule());
        addTestCase("correct %try-module", \correctTryModule());
        addTestCase("correct %requires", \correctRequires());
        addTestCase("%exec-class tests", \execClass());
        addTestCase("%exec-class crlf", \execClassCrlf());
        addTestCase("%strong-encapsulation", \strongEncapsulation());

        # Return for compatibility with test harness that checks return value.
        set_return_value(main());
    }

    strictTypesTest() {
        {
            Program p(PO_NEW_STYLE | PO_STRICT_TYPES);
            assertThrows("INVALID-OPERATION", \p.parse(), ("auto sub t() { return { (0,): 1, }; }", ""));
        }

        {
            Program p(PO_NEW_STYLE | PO_STRICT_TYPES);
            p.parse("auto sub t() { int i = 1; delete i; return i; }", "");
            assertEq(0, p.callFunction("t"));
        }

        {
            Program p(PO_NEW_STYLE | PO_STRICT_TYPES);
            p.parse("auto sub t() { int i = 1; remove i; return i; }", "");
            assertEq(0, p.callFunction("t"));
        }

        {
            Program p(PO_NEW_STYLE | PO_STRICT_TYPES);
            assertThrows("PARSE-TYPE-ERROR", \p.parse(), ("*int v0 = 0; int v1 = v0;", ""));
        }

        {
            Program p(PO_NEW_STYLE | PO_STRICT_TYPES);
            assertThrows("PARSE-TYPE-ERROR", \p.parse(), ("*float v0 = 0.0; float v1 = v0;", ""));
        }

        {
            Program p(PO_NEW_STYLE | PO_STRICT_TYPES);
            assertThrows("PARSE-TYPE-ERROR", \p.parse(), ("*number v0 = 0n; number v1 = v0;", ""));
        }

        {
            Program p(PO_NEW_STYLE | PO_STRICT_TYPES);
            assertThrows("PARSE-TYPE-ERROR", \p.parse(), ("*string v0 = ''; string v1 = v0;", ""));
        }

        {
            Program p(PO_NEW_STYLE | PO_STRICT_TYPES);
            assertThrows("PARSE-TYPE-ERROR", \p.parse(), ("*binary v0 = <00>; binary v1 = v0;", ""));
        }

        {
            Program p(PO_NEW_STYLE | PO_STRICT_TYPES);
            assertThrows("PARSE-TYPE-ERROR", \p.parse(), ("*date v0 = 1970-01-01Z; date v1 = v0;", ""));
        }

        {
            Program p(PO_NEW_STYLE | PO_STRICT_TYPES);
            assertThrows("PARSE-TYPE-ERROR", \p.parse(), ("*hash<auto> v0 = {}; hash<auto> v1 = v0;", ""));
        }

        {
            Program p(PO_NEW_STYLE | PO_STRICT_TYPES);
            assertThrows("PARSE-TYPE-ERROR", \p.parse(), ("*list<auto> v0 = (); list<auto> v1 = v0;", ""));
        }

        {
            Program p(PO_NEW_STYLE | PO_STRICT_TYPES);
            assertThrows("PARSE-TYPE-ERROR", \p.parse(), ("int sub t() { *int v0 = 0; return v0; }", ""));
        }

        {
            Program p(PO_NEW_STYLE | PO_STRICT_TYPES);
            assertThrows("PARSE-TYPE-ERROR", \p.parse(), ("float sub t() { *float v0 = 0.0; return v0; }", ""));
        }

        {
            Program p(PO_NEW_STYLE | PO_STRICT_TYPES);
            assertThrows("PARSE-TYPE-ERROR", \p.parse(), ("number sub t() { *number v0 = 0n; return v0; }", ""));
        }

        {
            Program p(PO_NEW_STYLE | PO_STRICT_TYPES);
            assertThrows("PARSE-TYPE-ERROR", \p.parse(), ("string sub t() { *string v0 = ''; return v0; }", ""));
        }

        {
            Program p(PO_NEW_STYLE | PO_STRICT_TYPES);
            assertThrows("PARSE-TYPE-ERROR", \p.parse(), ("binary sub t() { *binary v0 = <00>; return v0; }", ""));
        }

        {
            Program p(PO_NEW_STYLE | PO_STRICT_TYPES);
            assertThrows("PARSE-TYPE-ERROR", \p.parse(), ("date sub t() { *date v0 = 1970-01-01Z; return v0; }", ""));
        }

        {
            Program p(PO_NEW_STYLE | PO_STRICT_TYPES);
            assertThrows("PARSE-TYPE-ERROR", \p.parse(), ("hash<auto> sub t() { *hash<auto> v0 = {}; return v0; }", ""));
        }

        {
            Program p(PO_NEW_STYLE | PO_STRICT_TYPES);
            assertThrows("PARSE-TYPE-ERROR", \p.parse(), ("list<auto> sub t() { *list<auto> v0 = (); return v0; }", ""));
        }

        {
            Program p(PO_NEW_STYLE | PO_STRICT_TYPES);
            p.parse("auto sub t() { bool b; return b; }", "");
            assertEq(False, p.callFunction("t"));
        }

        {
            Program p(PO_NEW_STYLE | PO_STRICT_TYPES);
            p.parse("auto sub t() { int i; return i; }", "");
            assertEq(0, p.callFunction("t"));
        }

        {
            Program p(PO_NEW_STYLE | PO_STRICT_TYPES);
            p.parse("auto sub t() { float f; return f; }", "");
            assertEq(0.0, p.callFunction("t"));
        }

        {
            Program p(PO_NEW_STYLE | PO_STRICT_TYPES);
            p.parse("auto sub t() { number n; return n; }", "");
            assertEq(0n, p.callFunction("t"));
        }

        {
            Program p(PO_NEW_STYLE | PO_STRICT_TYPES);
            p.parse("auto sub t() { string str; return str; }", "");
            assertEq("", p.callFunction("t"));
        }

        {
            Program p(PO_NEW_STYLE | PO_STRICT_TYPES);
            p.parse("auto sub t() { binary b; return b; }", "");
            assertEq(binary(), p.callFunction("t"));
        }

        {
            Program p(PO_NEW_STYLE | PO_STRICT_TYPES);
            p.parse("auto sub t() { date d; return d; }", "");
            assertEq(1970-01-01Z, p.callFunction("t"));
        }

        {
            Program p(PO_NEW_STYLE | PO_STRICT_TYPES);
            p.parse("auto sub t() { hash<auto> h; return h; }", "");
            assertEq("hash<auto>", p.callFunction("t").fullType());
        }

        {
            Program p(PO_NEW_STYLE | PO_STRICT_TYPES);
            p.parse("auto sub t() { hash h; return h; }", "");
            assertEq("hash", p.callFunction("t").fullType());
        }

        {
            Program p(PO_NEW_STYLE | PO_STRICT_TYPES);
            p.parse("auto sub t() { list<auto> l; return l; }", "");
            assertEq("list<auto>", p.callFunction("t").fullType());
        }

        {
            Program p(PO_NEW_STYLE | PO_STRICT_TYPES);
            p.parse("auto sub t() { list l; return l; }", "");
            assertEq("list", p.callFunction("t").fullType());
        }

        {
            Program p(PO_NEW_STYLE | PO_STRICT_TYPES);
            p.parse("auto sub t() { object o; return o; }", "");
            assertNothing(p.callFunction("t"));
        }

        {
            Program p(PO_NEW_STYLE | PO_STRICT_TYPES);
            p.parse("auto sub t() { Mutex m; return m; }", "");
            assertNothing(p.callFunction("t"));
        }

        # negative tests
        {
            Program p(PO_NEW_STYLE);
            p.parse("auto sub t() { return { (0,): 1, }; }", "");
            assertEq("hash<string, int>", p.callFunction("t").fullType());
        }

        {
            Program p(PO_NEW_STYLE);
            p.parse("int sub t() { *int v0 = 0; return v0; }", "");
            assertEq(0, p.callFunction("t"));
        }

        {
            Program p(PO_NEW_STYLE);
            p.parse("float sub t() { *float v0 = 0.0; return v0; }", "");
            assertEq(0.0, p.callFunction("t"));
        }

        {
            Program p(PO_NEW_STYLE);
            p.parse("number sub t() { *number v0 = 0n; return v0; }", "");
            assertEq(0n, p.callFunction("t"));
        }

        {
            Program p(PO_NEW_STYLE);
            p.parse("string sub t() { *string v0 = ''; return v0; }", "");
            assertEq("", p.callFunction("t"));
        }

        {
            Program p(PO_NEW_STYLE);
            p.parse("binary sub t() { *binary v0 = <00>; return v0; }", "");
            assertEq(<00>, p.callFunction("t"));
        }

        {
            Program p(PO_NEW_STYLE);
            p.parse("date sub t() { *date v0 = 1970-01-01Z; return v0; }", "");
            assertEq(1970-01-01Z, p.callFunction("t"));
        }

        {
            Program p(PO_NEW_STYLE);
            p.parse("hash<auto> sub t() { *hash<auto> v0 = {}; return v0; }", "");
            assertEq("hash<auto>", p.callFunction("t").fullType());
        }

        {
            Program p(PO_NEW_STYLE);
            p.parse("list<auto> sub t() { *list<auto> v0 = (); return v0; }", "");
            assertEq("list<auto>", p.callFunction("t").fullType());
        }

        {
            Program p(PO_NEW_STYLE);
            p.parse("auto sub t() { bool b; return b; }", "");
            assertNothing(p.callFunction("t"));
        }

        {
            Program p(PO_NEW_STYLE);
            p.parse("auto sub t() { int i; return i; }", "");
            assertNothing(p.callFunction("t"));
        }

        {
            Program p(PO_NEW_STYLE);
            p.parse("auto sub t() { float f; return f; }", "");
            assertNothing(p.callFunction("t"));
        }

        {
            Program p(PO_NEW_STYLE);
            p.parse("auto sub t() { number n; return n; }", "");
            assertNothing(p.callFunction("t"));
        }

        {
            Program p(PO_NEW_STYLE);
            p.parse("auto sub t() { string str; return str; }", "");
            assertNothing(p.callFunction("t"));
        }

        {
            Program p(PO_NEW_STYLE);
            p.parse("auto sub t() { binary b; return b; }", "");
            assertNothing(p.callFunction("t"));
        }

        {
            Program p(PO_NEW_STYLE);
            p.parse("auto sub t() { date d; return d; }", "");
            assertNothing(p.callFunction("t"));
        }

        {
            Program p(PO_NEW_STYLE);
            p.parse("auto sub t() { hash<auto> h; return h; }", "");
            assertNothing(p.callFunction("t"));
        }

        {
            Program p(PO_NEW_STYLE);
            p.parse("auto sub t() { hash h; return h; }", "");
            assertNothing(p.callFunction("t"));
        }

        {
            Program p(PO_NEW_STYLE);
            p.parse("auto sub t() { list<auto> l; return l; }", "");
            assertNothing(p.callFunction("t"));
        }

        {
            Program p(PO_NEW_STYLE);
            p.parse("auto sub t() { list l; return l; }", "");
            assertNothing(p.callFunction("t"));
        }

        {
            Program p(PO_NEW_STYLE);
            p.parse("auto sub t() { object o; return o; }", "");
            assertNothing(p.callFunction("t"));
        }

        {
            Program p(PO_NEW_STYLE);
            p.parse("auto sub t() { Mutex m; return m; }", "");
            assertNothing(p.callFunction("t"));
        }
    }

    returnsTest() {
        {
            Program p();
            assertThrows("PARSE-EXCEPTION", \p.parse(), ("sub t() returns int { return 1; }", ""));
        }

        {
            Program p(PO_ALLOW_RETURNS);
            p.parse("sub t() returns int { return 1; }", "");
            assertEq(1, p.callFunction("t"));
        }
    }

    missingArgAppendIncludePath() {
        Program p();
        p.replaceParseOptions(PO_DEFAULT);
        assertThrows("PARSE-EXCEPTION", "missing argument to %append-include-path",
            \p.parse(), ("%append-include-path\nprint();", ""));
    }

    missingArgAppendModulePath() {
        Program p();
        p.replaceParseOptions(PO_DEFAULT);
        assertThrows("PARSE-EXCEPTION", "missing argument to %append-module-path",
            \p.parse(), ("%append-module-path   \nprint();", ""));
    }

    missingArgSetTimeZone() {
        Program p();
        p.replaceParseOptions(PO_DEFAULT);
        assertThrows("PARSE-EXCEPTION", "missing argument to %set-time-zone",
            \p.parse(), ("%set-time-zone\t   \nprint();", ""));
    }

    missingArgEnableWarning() {
        Program p();
        p.replaceParseOptions(PO_DEFAULT);
        assertThrows("PARSE-EXCEPTION", "missing argument to %enable-warning",
            \p.parse(), ("%enable-warning\nprint();", ""));
    }

    missingArgTryModule() {
        Program p();
        p.replaceParseOptions(PO_DEFAULT);
        assertThrows("PARSE-EXCEPTION", "missing argument to %try-module",
            \p.parse(), ("%try-module\n($x) xml;\nprint();\n%endtry\n", ""));
    }

    missingArgRequires() {
        Program p();
        p.replaceParseOptions(PO_DEFAULT);
        assertThrows("PARSE-EXCEPTION", "missing argument to %requires",
            \p.parse(), ("%requires\nprint();\n", ""));
    }

    missingArgRequiresWithSpace() {
        Program p();
        p.replaceParseOptions(PO_DEFAULT);
        assertThrows("PARSE-EXCEPTION", "missing argument to %requires",
            \p.parse(), ("%requires \nprint();\n", ""));
    }

    tabInArg() {
        Program p();
        p.replaceParseOptions(PO_DEFAULT);
        p.parse("
our $x = 0;
our $y = 0;
%append-include-path xxx\t++$x;
++$y;
%append-module-path xxx\t++$x;
++$y;
%enable-warning deprecated\t++$x;
++$y;
%disable-warning deprecated\t++$x;
++$y;
%ifdef xxx\t++$x;
$y = 9;
%endif
%ifndef yyy\t++$x;
++$y;
%endif
", "");
        p.run();
        assertEq(5, p.getGlobalVariable("y"));
        assertEq(0, p.getGlobalVariable("x"));
    }

    spaceAfterExecClass() {
        Program p();
        p.replaceParseOptions(PO_DEFAULT);
        assertThrows("PARSE-EXCEPTION", \p.parse(), ("%exec-classMyClass\nclass MyClass {}", ""));
    }

    invalidIdInTryModule() {
        Program p();
        p.replaceParseOptions(PO_DEFAULT);
        assertThrows("PARSE-EXCEPTION", "try-module not in format.*invalid exception variable name.*",
            \p.parse(), ("%try-module (\"2%) Util\nprint();\n%endtry\nprint();\n", ""));
    }

    ignoredTextTryModule() {
        Program p();
        p.replaceParseOptions(PO_DEFAULT);
        assertThrows("PARSE-EXCEPTION", "try-module not in format.*extra text before.*",
            \p.parse(), ("%try-module whatever \"$@ except a parenthesis ($x) Util\nprint();\n%endtry\nprint();\n", ""));
    }

    correctTryModule() {
        Program p();
        p.replaceParseOptions(PO_DEFAULT);
        p.parse("%try-module\t         (\t     $x\t     \t) Util\nprint();\n%endtry\nprint();\n", "");
    }

    correctRequires() {
        Program p();
        p.replaceParseOptions(PO_DEFAULT);
        p.parse("%requires \t Util  \t   \nprint();\n", "");
    }

    execClass() {
        Program p(PO_NEW_STYLE);
        p.parse("%exec-class T\nclass T {abstract t();}", "exec-class");
        assertThrows("ABSTRACT-CLASS-ERROR", \p.run());
    }

    execClassCrlf() {
        Program p(PO_NEW_STYLE);
        p.parse("%exec-class T\r\nclass T {}\r\n", "exec-class-clrf");
    }

    strongEncapsulation() {
        testEncapsulation("namespace N; const N::c = 1;", "namespace constant");
        testEncapsulation("namespace N; class N::C;", "namespace class");
        testEncapsulation("namespace N; our int N::i = 0;", "namespace global var");
        testEncapsulation("namespace N; sub N::t() {}", "namespace function");
        testEncapsulation("namespace N { namespace N1; const N1::c = 0; }", "nested namespace constant");
        testEncapsulation("namespace N { namespace N1; sub N1::f() {} }", "nested namespace function");
        testEncapsulation("namespace N { namespace N1; class N1::C {} }", "nested namespace class");
        testEncapsulation("class C; static C::i() {}", "class method");
    }

    testEncapsulation(string code, string label) {
        {
            Program p(PO_NEW_STYLE);
            assertEq(NOTHING, p.parse(code, ""), "positive: " + label);
        }

        {
            Program p(PO_NEW_STYLE | PO_STRONG_ENCAPSULATION);
            assertThrows("PARSE-EXCEPTION", "PO_STRONG_ENCAPSULATION", \p.parse(), (code, ""), "negative: " + label);
        }

        {
            Program p(PO_NEW_STYLE);
            assertThrows("PARSE-EXCEPTION", "PO_STRONG_ENCAPSULATION", \p.parse(), ("%strong-encapsulation\n" + code, ""), "negative po: " + label);
        }
    }
}
