/***************************************************************************************************
 **
 **  Copyright (C) 2013 Ingo Berg
 **
 **  Permission is hereby granted, free of charge, to any person obtaining a copy of this
 **  software and associated documentation files (the "Software"), to deal in the Software
 **  without restriction, including without limitation the rights to use, copy, modify,
 **  merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
 **  permit persons to whom the Software is furnished to do so, subject to the following conditions:
 **
 **  The above copyright notice and this permission notice shall be included in all copies or
 **  substantial portions of the Software.
 **
 **  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
 **  NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 **  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
 **  DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 **  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 **
 ******************************************************************************************************/

#include "qmuparsertest.h"

#include <QChar>
#include <QCoreApplication>
#include <QMessageLogger>
#include <QString>
#include <QtDebug>
#include <exception>
#include <limits>
#include <map>
#include <memory>
#include <sstream>
#include <stdexcept>
#include <stdio.h>
#include <string>
#include <utility>

#include "../vmisc/vmath.h"
#include "qmudef.h"
#include "qmuparser.h"
#include "qmuparsererror.h"

/**
 * @file
 * @brief This file contains the implementation of parser test cases.
 */

namespace qmu::Test
{
int QmuParserTester::c_iCount = 0;

//---------------------------------------------------------------------------------------------------------------------
QmuParserTester::QmuParserTester(QObject *parent)
  : QObject(parent),
    m_vTestFun()
{
    AddTest(&QmuParserTester::TestNames);
    AddTest(&QmuParserTester::TestSyntax);
    AddTest(&QmuParserTester::TestPostFix);
    AddTest(&QmuParserTester::TestInfixOprt);
    AddTest(&QmuParserTester::TestVarConst);
    AddTest(&QmuParserTester::TestMultiArg);
    AddTest(&QmuParserTester::TestExpression);
    AddTest(&QmuParserTester::TestIfThenElse);
    AddTest(&QmuParserTester::TestInterface);
    AddTest(&QmuParserTester::TestBinOprt);
    AddTest(&QmuParserTester::TestException);
    AddTest(&QmuParserTester::TestStrArg);
    AddTest(&QmuParserTester::TestBulkMode);

    QmuParserTester::c_iCount = 0;
}

//---------------------------------------------------------------------------------------------------------------------
auto QmuParserTester::IsHexVal(const QString &a_szExpr, qmusizetype *a_iPos, qreal *a_fVal, const QLocale &locale,
                               bool cNumbers, const QChar &decimal, const QChar &thousand) -> int
{
    Q_UNUSED(locale)
    Q_UNUSED(decimal)
    Q_UNUSED(thousand)
    Q_UNUSED(cNumbers)

    if (a_szExpr.size() <= 2 || (a_szExpr.at(0) != '0' || a_szExpr.at(1) != 'x'))
    {
        return 0;
    }

    unsigned iVal(0);

    std::wstring const a_szExprStd = a_szExpr.mid(2).toStdWString();

    // New code based on streams for UNICODE compliance:
    stringstream_type::pos_type nPos(0);
    stringstream_type ss(a_szExprStd);
    ss >> std::hex >> iVal;
    nPos = ss.tellg();

    if (nPos == static_cast<stringstream_type::pos_type>(0))
    {
        return 1;
    }

    *a_iPos += static_cast<qmusizetype>(2 + nPos);
    *a_fVal = static_cast<qreal>(iVal);
    return 1;
}

//---------------------------------------------------------------------------------------------------------------------
auto QmuParserTester::TestInterface() -> int
{
    int iStat = 0;
    qWarning() << "testing member functions...";

    // Test RemoveVar
    qreal afVal[3] = {1, 2, 3};
    QmuParser p;

    try
    {
        p.DefineVar("a", &afVal[0]);
        p.DefineVar("b", &afVal[1]);
        p.DefineVar("c", &afVal[2]);
        p.SetExpr("a+b+c");
        p.Eval();
    }
    catch (...)
    {
        iStat += 1; // this is not supposed to happen
    }

    try
    {
        p.RemoveVar("c");
        p.Eval();
        iStat += 1; // not supposed to reach this, nonexisting variable "c" deleted...
    }
    catch (...)
    { //-V565
      // failure is expected...
    }

    if (iStat == 0)
    {
        qWarning() << "TestInterface passed";
    }
    else
    {
        qWarning() << "\n TestInterface failed with " << iStat << " errors";
    }

    return iStat;
}

//---------------------------------------------------------------------------------------------------------------------
auto QmuParserTester::TestStrArg() -> int
{
    int iStat = 0;
    qWarning() << "testing string arguments...";

    iStat += EqnTest("valueof(\"\")", 123, true); // empty string arguments caused a crash
    iStat += EqnTest("valueof(\"aaa\")+valueof(\"bbb\")  ", 246, true);
    iStat += EqnTest("2*(valueof(\"aaa\")-23)+valueof(\"bbb\")", 323, true);
    // use in expressions with variables
    iStat += EqnTest("a*(atof(\"10\")-b)", 8, true);
    iStat += EqnTest("a-(atof(\"10\")*b)", -19, true);
    // string + numeric arguments
    iStat += EqnTest("strfun1(\"100\")", 100, true);
    iStat += EqnTest("strfun2(\"100\";1)", 101, true);
    iStat += EqnTest("strfun3(\"99\";1;2)", 102, true);

    if (iStat == 0)
    {
        qWarning() << "TestStrArg passed";
    }
    else
    {
        qWarning() << "\n TestStrArg failed with " << iStat << " errors";
    }

    return iStat;
}

//---------------------------------------------------------------------------------------------------------------------
auto QmuParserTester::TestBulkMode() -> int
{
    int iStat = 0;
    qWarning() << "testing bulkmode...";

#define EQN_TEST_BULK(EXPR, R1, R2, R3, R4, PASS)                                                                      \
    {                                                                                                                  \
        double res[] = {R1, R2, R3, R4};                                                                               \
        iStat += EqnTestBulk(EXPR, res, (PASS));                                                                       \
    }

    // Bulk Variables for the test:
    // a: 1,2,3,4
    // b: 2,2,2,2
    // c: 3,3,3,3
    // d: 5,4,3,2
    EQN_TEST_BULK("a", 1, 1, 1, 1, false)
    EQN_TEST_BULK("a", 1, 2, 3, 4, true)
    EQN_TEST_BULK("b=a", 1, 2, 3, 4, true)
    EQN_TEST_BULK("b=a; b*10", 10, 20, 30, 40, true)
    EQN_TEST_BULK("b=a; b*10; a", 1, 2, 3, 4, true)
    EQN_TEST_BULK("a+b", 3, 4, 5, 6, true)
    EQN_TEST_BULK("c*(a+b)", 9, 12, 15, 18, true)
#undef EQN_TEST_BULK

    if (iStat == 0)
    {
        qWarning() << "passed";
    }
    else
    {
        qWarning() << "\n  failed with " << iStat << " errors";
    }

    return iStat;
}

//---------------------------------------------------------------------------------------------------------------------
auto QmuParserTester::TestBinOprt() -> int
{
    int iStat = 0;
    qWarning() << "testing binary operators...";

    // built in operators
    // xor operator

    iStat += EqnTest("a++b", 3, true);
    iStat += EqnTest("a ++ b", 3, true);
    iStat += EqnTest("1++2", 3, true);
    iStat += EqnTest("1 ++ 2", 3, true);
    iStat += EqnTest("a add b", 3, true);
    iStat += EqnTest("1 add 2", 3, true);
    iStat += EqnTest("a<b", 1, true);
    iStat += EqnTest("b>a", 1, true);
    iStat += EqnTest("a>a", 0, true);
    iStat += EqnTest("a<a", 0, true);
    iStat += EqnTest("a>a", 0, true);
    iStat += EqnTest("a<=a", 1, true);
    iStat += EqnTest("a<=b", 1, true);
    iStat += EqnTest("b<=a", 0, true);
    iStat += EqnTest("a>=a", 1, true);
    iStat += EqnTest("b>=a", 1, true);
    iStat += EqnTest("a>=b", 0, true);

    // Test logical operators, expecially if user defined "&" and the internal "&&" collide
    iStat += EqnTest("1 && 1", 1, true);
    iStat += EqnTest("1 && 0", 0, true);
    iStat += EqnTest("(a<b) && (b>a)", 1, true);
    iStat += EqnTest("(a<b) && (a>b)", 0, true);
    // iStat += EqnTest("12 and 255", 12, true);
    // iStat += EqnTest("12 and 0", 0, true);
    iStat += EqnTest("12 & 255", 12, true);
    iStat += EqnTest("12 & 0", 0, true);
    iStat += EqnTest("12&255", 12, true);
    iStat += EqnTest("12&0", 0, true);

    // Assignement operator
    iStat += EqnTest("a = b", 2, true);
    iStat += EqnTest("a = sin(b)", 0.909297, true);
    iStat += EqnTest("a = 1+sin(b)", 1.909297, true);
    iStat += EqnTest("(a=b)*2", 4, true);
    iStat += EqnTest("2*(a=b)", 4, true);
    iStat += EqnTest("2*(a=b+1)", 6, true);
    iStat += EqnTest("(a=b+1)*2", 6, true);
    iStat += EqnTest("a=c; a*10", 30, true);
    iStat += EqnTest("a = -", 0, false); // broken assigment

    iStat += EqnTest("2^2^3", 256, true);
    iStat += EqnTest("1/2/3", 1.0 / 6.0, true);

    // reference: http://www.wolframalpha.com/input/?i=3%2B4*2%2F%281-5%29^2^3
    iStat += EqnTest("3+4*2/(1-5)^2^3", 3.0001220703125, true);

    if (iStat == 0)
    {
        qWarning() << "TestBinOprt passed";
    }
    else
    {
        qWarning() << "\n TestBinOprt failed with " << iStat << " errors";
    }

    return iStat;
}

//---------------------------------------------------------------------------------------------------------------------
/** @brief Check muParser name restriction enforcement. */
auto QmuParserTester::TestNames() -> int
{
    int iStat = 0, iErr = 0;

    qWarning() << "testing name restriction enforcement...";

    QmuParser p;

#define PARSER_THROWCHECK(DOMAIN, FAIL, EXPR, ARG)                                                                     \
    iErr = 0;                                                                                                          \
    QmuParserTester::c_iCount++;                                                                                       \
    try                                                                                                                \
    {                                                                                                                  \
        p.Define##DOMAIN(EXPR, ARG);                                                                                   \
    }                                                                                                                  \
    catch (QmuParserError &)                                                                                           \
    {                                                                                                                  \
        iErr = static_cast<int>(FAIL);                                                                                 \
    }                                                                                                                  \
    iStat += iErr;

    // constant names
    PARSER_THROWCHECK(Const, false, "0a", 1)
    PARSER_THROWCHECK(Const, false, "9a", 1)
    PARSER_THROWCHECK(Const, false, "+a", 1)
    PARSER_THROWCHECK(Const, false, "-a", 1)
    PARSER_THROWCHECK(Const, false, "a-", 1)
    PARSER_THROWCHECK(Const, false, "a*", 1)
    PARSER_THROWCHECK(Const, false, "a?", 1)
    PARSER_THROWCHECK(Const, true, "a", 1)
    PARSER_THROWCHECK(Const, true, "a_min", 1)
    PARSER_THROWCHECK(Const, true, "a_min0", 1)
    PARSER_THROWCHECK(Const, true, "a_min9", 1)
    // variable names
    qreal a;
    p.ClearConst();
    PARSER_THROWCHECK(Var, false, "123abc", &a)
    PARSER_THROWCHECK(Var, false, "9a", &a)
    PARSER_THROWCHECK(Var, false, "0a", &a)
    PARSER_THROWCHECK(Var, false, "+a", &a)
    PARSER_THROWCHECK(Var, false, "-a", &a)
    PARSER_THROWCHECK(Var, false, "?a", &a)
    PARSER_THROWCHECK(Var, false, "!a", &a)
    PARSER_THROWCHECK(Var, false, "a+", &a)
    PARSER_THROWCHECK(Var, false, "a-", &a)
    PARSER_THROWCHECK(Var, false, "a*", &a)
    PARSER_THROWCHECK(Var, false, "a?", &a)
    PARSER_THROWCHECK(Var, true, "a", &a)
    PARSER_THROWCHECK(Var, true, "a_min", &a)
    PARSER_THROWCHECK(Var, true, "a_min0", &a)
    PARSER_THROWCHECK(Var, true, "a_min9", &a)
    PARSER_THROWCHECK(Var, false, "a_min9", nullptr)
    // Postfix operators
    // fail
    PARSER_THROWCHECK(PostfixOprt, false, "(k", f1of1)
    PARSER_THROWCHECK(PostfixOprt, false, "9+", f1of1)
    PARSER_THROWCHECK(PostfixOprt, false, "+", nullptr)
    // pass
    PARSER_THROWCHECK(PostfixOprt, true, "-a", f1of1)
    PARSER_THROWCHECK(PostfixOprt, true, "?a", f1of1)
    PARSER_THROWCHECK(PostfixOprt, true, "_", f1of1)
    PARSER_THROWCHECK(PostfixOprt, true, "#", f1of1)
    PARSER_THROWCHECK(PostfixOprt, true, "&&", f1of1)
    PARSER_THROWCHECK(PostfixOprt, true, "||", f1of1)
    PARSER_THROWCHECK(PostfixOprt, true, "&", f1of1)
    PARSER_THROWCHECK(PostfixOprt, true, "|", f1of1)
    PARSER_THROWCHECK(PostfixOprt, true, "++", f1of1)
    PARSER_THROWCHECK(PostfixOprt, true, "--", f1of1)
    PARSER_THROWCHECK(PostfixOprt, true, "?>", f1of1)
    PARSER_THROWCHECK(PostfixOprt, true, "?<", f1of1)
    PARSER_THROWCHECK(PostfixOprt, true, "**", f1of1)
    PARSER_THROWCHECK(PostfixOprt, true, "xor", f1of1)
    PARSER_THROWCHECK(PostfixOprt, true, "and", f1of1)
    PARSER_THROWCHECK(PostfixOprt, true, "or", f1of1)
    PARSER_THROWCHECK(PostfixOprt, true, "not", f1of1)
    PARSER_THROWCHECK(PostfixOprt, true, "!", f1of1)
    // Binary operator
    // The following must fail with builtin operators activated
    // p.EnableBuiltInOp(true); -> this is the default
    p.ClearPostfixOprt();
    PARSER_THROWCHECK(Oprt, false, "+", f1of2)
    PARSER_THROWCHECK(Oprt, false, "-", f1of2)
    PARSER_THROWCHECK(Oprt, false, "*", f1of2)
    PARSER_THROWCHECK(Oprt, false, "/", f1of2)
    PARSER_THROWCHECK(Oprt, false, "^", f1of2)
    PARSER_THROWCHECK(Oprt, false, "&&", f1of2)
    PARSER_THROWCHECK(Oprt, false, "||", f1of2)
    // without activated built in operators it should work
    p.EnableBuiltInOprt(false);
    PARSER_THROWCHECK(Oprt, true, "+", f1of2)
    PARSER_THROWCHECK(Oprt, true, "-", f1of2)
    PARSER_THROWCHECK(Oprt, true, "*", f1of2)
    PARSER_THROWCHECK(Oprt, true, "/", f1of2)
    PARSER_THROWCHECK(Oprt, true, "^", f1of2)
    PARSER_THROWCHECK(Oprt, true, "&&", f1of2)
    PARSER_THROWCHECK(Oprt, true, "||", f1of2)
#undef PARSER_THROWCHECK

    if (iStat == 0)
    {
        qWarning() << "TestNames passed";
    }
    else
    {
        qWarning() << "\n TestNames failed with " << iStat << " errors";
    }

    return iStat;
}

//---------------------------------------------------------------------------------------------------------------------
auto QmuParserTester::TestSyntax() -> int
{
    int iStat = 0;
    qWarning() << "testing syntax engine...";

    iStat += ThrowTest("1;", ecUNEXPECTED_EOF);        // incomplete hex definition
    iStat += ThrowTest("a;", ecUNEXPECTED_EOF);        // incomplete hex definition
    iStat += ThrowTest("sin(8);", ecUNEXPECTED_EOF);   // incomplete hex definition
    iStat += ThrowTest("(sin(8));", ecUNEXPECTED_EOF); // incomplete hex definition
    iStat += ThrowTest("a{m};", ecUNEXPECTED_EOF);     // incomplete hex definition

    iStat += EqnTest("(1+ 2*a)", 3, true);    // Spaces within formula
    iStat += EqnTest("sqrt((4))", 2, true);   // Multiple brackets
    iStat += EqnTest("sqrt((2)+2)", 2, true); // Multiple brackets
    iStat += EqnTest("sqrt(2+(2))", 2, true); // Multiple brackets
    iStat += EqnTest("sqrt(a+(3))", 2, true); // Multiple brackets
    iStat += EqnTest("sqrt((3)+a)", 2, true); // Multiple brackets
    iStat += EqnTest("order(1;2)", 1, true);  // May not cause name collision with operator "or"
    iStat += EqnTest("(2+", 0, false);        // missing closing bracket
    iStat += EqnTest("2++4", 0, false);       // unexpected operator
    iStat += EqnTest("2+-4", 0, false);       // unexpected operator
    iStat += EqnTest("(2+)", 0, false);       // unexpected closing bracket
    iStat += EqnTest("--2", 0, false);        // double sign
    iStat += EqnTest("ksdfj", 0, false);      // unknown token
    iStat += EqnTest("()", 0, false);         // empty bracket without a function
    iStat += EqnTest("5+()", 0, false);       // empty bracket without a function
    iStat += EqnTest("sin(cos)", 0, false);   // unexpected function
    iStat += EqnTest("5t6", 0, false);        // unknown token
    iStat += EqnTest("5 t 6", 0, false);      // unknown token
    iStat += EqnTest("8*", 0, false);         // unexpected end of formula
    iStat += EqnTest(";3", 0, false);         // unexpected semicolon
    iStat += EqnTest("3;5", 0, false);        // unexpected semicolon
    iStat += EqnTest("sin(8;8)", 0, false);   // too many function args
    iStat += EqnTest("(7,8)", 0, false);      // too many function args
    iStat += EqnTest("sin)", 0, false);       // unexpected closing bracket
    iStat += EqnTest("a)", 0, false);         // unexpected closing bracket
    iStat += EqnTest("pi)", 0, false);        // unexpected closing bracket
    iStat += EqnTest("sin(())", 0, false);    // unexpected closing bracket
    iStat += EqnTest("sin()", 0, false);      // unexpected closing bracket

    if (iStat == 0)
    {
        qWarning() << "TestSyntax passed";
    }
    else
    {
        qWarning() << "\n TestSyntax failed with " << iStat << " errors";
    }

    return iStat;
}

//---------------------------------------------------------------------------------------------------------------------
auto QmuParserTester::TestVarConst() -> int
{
    int iStat = 0;
    qWarning() << "testing variable/constant detection...";

    // Test if the result changes when a variable changes
    iStat += EqnTestWithVarChange("a", 1, 1, 2, 2);
    iStat += EqnTestWithVarChange("2*a", 2, 4, 3, 6);

    // distinguish constants with same basename
    iStat += EqnTest("const", 1, true);
    iStat += EqnTest("const1", 2, true);
    iStat += EqnTest("const2", 3, true);
    iStat += EqnTest("2*const", 2, true);
    iStat += EqnTest("2*const1", 4, true);
    iStat += EqnTest("2*const2", 6, true);
    iStat += EqnTest("2*const+1", 3, true);
    iStat += EqnTest("2*const1+1", 5, true);
    iStat += EqnTest("2*const2+1", 7, true);
    iStat += EqnTest("const", 0, false);
    iStat += EqnTest("const1", 0, false);
    iStat += EqnTest("const2", 0, false);

    // distinguish variables with same basename
    iStat += EqnTest("a", 1, true);
    iStat += EqnTest("aa", 2, true);
    iStat += EqnTest("2*a", 2, true);
    iStat += EqnTest("2*aa", 4, true);
    iStat += EqnTest("2*a-1", 1, true);
    iStat += EqnTest("2*aa-1", 3, true);

    // custom value recognition
    iStat += EqnTest("0xff", 255, true);
    iStat += EqnTest("0x97 + 0xff", 406, true);

    // Finally test querying of used variables
    try
    {
        int idx;
        qmu::QmuParser p;
        qreal vVarVal[] = {1, 2, 3, 4, 5};
        p.DefineVar("a", &vVarVal[0]);
        p.DefineVar("b", &vVarVal[1]);
        p.DefineVar("c", &vVarVal[2]);
        p.DefineVar("d", &vVarVal[3]);
        p.DefineVar("e", &vVarVal[4]);

        // Test lookup of defined variables
        // 4 used variables
        p.SetExpr("a+b+c+d");
        qmu::varmap_type UsedVar = p.GetUsedVar();
        auto iCount = static_cast<int>(UsedVar.size());
        if (iCount != 4)
        {
            throw false;
        }

        // the next check will fail if the parser
        // erroneousely creates new variables internally
        if (p.GetVar().size() != 5)
        {
            throw false;
        }

        auto item = UsedVar.begin();
        for (idx = 0; item != UsedVar.end(); ++item)
        {
            if (&vVarVal[idx++] != item->second)
            {
                throw false;
            }
        }

        // Test lookup of undefined variables
        p.SetExpr("undef1+undef2+undef3");
        UsedVar = p.GetUsedVar();
        iCount = static_cast<int>(UsedVar.size());
        if (iCount != 3)
        {
            throw false;
        }

        // the next check will fail if the parser
        // erroneousely creates new variables internally
        if (p.GetVar().size() != 5)
        {
            throw false;
        }

        for (item = UsedVar.begin(); item != UsedVar.end(); ++item)
        {
            if (item->second != nullptr)
            {
                throw false; // all pointers to undefined variables must be null
            }
        }

        // 1 used variables
        p.SetExpr("a+b");
        UsedVar = p.GetUsedVar();
        iCount = static_cast<int>(UsedVar.size());
        if (iCount != 2)
        {
            throw false;
        }
        item = UsedVar.begin();
        for (idx = 0; item != UsedVar.end(); ++item)
        {
            if (&vVarVal[idx++] != item->second)
            {
                throw false;
            }
        }
    }
    catch (...)
    {
        iStat += 1;
    }

    if (iStat == 0)
    {
        qWarning() << "TestVarConst passed";
    }
    else
    {
        qWarning() << "\n TestVarConst failed with " << iStat << " errors";
    }

    return iStat;
}

//---------------------------------------------------------------------------------------------------------------------
auto QmuParserTester::TestMultiArg() -> int
{
    int iStat = 0;
    qWarning() << "testing multiarg functions...";

    // Compound expressions
    iStat += EqnTest("1;2;3", 3, true);
    iStat += EqnTest("a;b;c", 3, true);
    iStat += EqnTest("a=10;b=20;c=a*b", 200, true);
    iStat += EqnTest("1;\n2;\n3", 3, true);
    iStat += EqnTest("a;\nb;\nc", 3, true);
    iStat += EqnTest("a=10;\nb=20;\nc=a*b", 200, true);
    iStat += EqnTest("1;\r\n2;\r\n3", 3, true);
    iStat += EqnTest("a;\r\nb;\r\nc", 3, true);
    iStat += EqnTest("a=10;\r\nb=20;\r\nc=a*b", 200, true);

    // picking the right argument
    iStat += EqnTest("f1of1(1)", 1, true);
    iStat += EqnTest("f1of2(1; 2)", 1, true);
    iStat += EqnTest("f2of2(1; 2)", 2, true);
    iStat += EqnTest("f1of3(1; 2; 3)", 1, true);
    iStat += EqnTest("f2of3(1; 2; 3)", 2, true);
    iStat += EqnTest("f3of3(1; 2; 3)", 3, true);
    iStat += EqnTest("f1of4(1; 2; 3; 4)", 1, true);
    iStat += EqnTest("f2of4(1; 2; 3; 4)", 2, true);
    iStat += EqnTest("f3of4(1; 2; 3; 4)", 3, true);
    iStat += EqnTest("f4of4(1; 2; 3; 4)", 4, true);
    iStat += EqnTest("f1of5(1; 2; 3; 4; 5)", 1, true);
    iStat += EqnTest("f2of5(1; 2; 3; 4; 5)", 2, true);
    iStat += EqnTest("f3of5(1; 2; 3; 4; 5)", 3, true);
    iStat += EqnTest("f4of5(1; 2; 3; 4; 5)", 4, true);
    iStat += EqnTest("f5of5(1; 2; 3; 4; 5)", 5, true);
    // Too few arguments / Too many arguments
    iStat += EqnTest("1+ping()", 11, true);
    iStat += EqnTest("ping()+1", 11, true);
    iStat += EqnTest("2*ping()", 20, true);
    iStat += EqnTest("ping()*2", 20, true);
    iStat += EqnTest("ping(1;2)", 0, false);
    iStat += EqnTest("1+ping(1;2)", 0, false);
    iStat += EqnTest("f1of1(1;2)", 0, false);
    iStat += EqnTest("f1of1()", 0, false);
    iStat += EqnTest("f1of2(1; 2; 3)", 0, false);
    iStat += EqnTest("f1of2(1)", 0, false);
    iStat += EqnTest("f1of3(1; 2; 3; 4)", 0, false);
    iStat += EqnTest("f1of3(1)", 0, false);
    iStat += EqnTest("f1of4(1; 2; 3; 4; 5)", 0, false);
    iStat += EqnTest("f1of4(1)", 0, false);
    iStat += EqnTest("(1;2;3)", 0, false);
    iStat += EqnTest("1;2;3", 0, false);
    iStat += EqnTest("(1*a;2;3)", 0, false);
    iStat += EqnTest("1;2*a;3", 0, false);

    // correct calculation of arguments
    iStat += EqnTest("min(a; 1)", 1, true);
    iStat += EqnTest("min(3*2; 1)", 1, true);
    iStat += EqnTest("min(3*2; 1)", 6, false);
    iStat += EqnTest("firstArg(2;3;4)", 2, true);
    iStat += EqnTest("lastArg(2;3;4)", 4, true);
    iStat += EqnTest("min(3*a+1; 1)", 1, true);
    iStat += EqnTest("max(3*a+1; 1)", 4, true);
    iStat += EqnTest("max(3*a+1; 1)*2", 8, true);
    iStat += EqnTest("2*max(3*a+1; 1)+2", 10, true);

    // functions with Variable argument count
    iStat += EqnTest("sum(a)", 1, true);
    iStat += EqnTest("sum(1;2;3)", 6, true);
    iStat += EqnTest("sum(a;b;c)", 6, true);
    iStat += EqnTest("sum(1;-max(1;2);3)*2", 4, true);
    iStat += EqnTest("2*sum(1;2;3)", 12, true);
    iStat += EqnTest("2*sum(1;2;3)+2", 14, true);
    iStat += EqnTest("2*sum(-1;2;3)+2", 10, true);
    iStat += EqnTest("2*sum(-1;2;-(-a))+2", 6, true);
    iStat += EqnTest("2*sum(-1;10;-a)+2", 18, true);
    iStat += EqnTest("2*sum(1;2;3)*2", 24, true);
    iStat += EqnTest("sum(1;-max(1;2);3)*2", 4, true);
    iStat += EqnTest("sum(1*3; 4; a+2)", 10, true);
    iStat += EqnTest("sum(1*3; 2*sum(1;2;2); a+2)", 16, true);
    iStat += EqnTest("sum(1;1;1;1;1;1;1;1;1;1;1;1;1;1;1;1;1;1;1;1;1;1;2)", 24, true);

    // some failures
    iStat += EqnTest("sum()", 0, false);
    iStat += EqnTest("sum(;)", 0, false);
    iStat += EqnTest("sum(1;2;)", 0, false);
    iStat += EqnTest("sum(;1;2)", 0, false);

    if (iStat == 0)
    {
        qWarning() << "TestMultiArg passed";
    }
    else
    {
        qWarning() << "\n TestMultiArg failed with " << iStat << " errors";
    }

    return iStat;
}

//---------------------------------------------------------------------------------------------------------------------
auto QmuParserTester::TestInfixOprt() -> int
{
    int iStat(0);
    qWarning() << "testing infix operators...";

    iStat += EqnTest("-1", -1, true);
    iStat += EqnTest("-(-1)", 1, true);
    iStat += EqnTest("-(-1)*2", 2, true);
    iStat += EqnTest("-(-2)*sqrt(4)", 4, true);
    iStat += EqnTest("-_pi", -M_PI, true);
    iStat += EqnTest("-a", -1, true);
    iStat += EqnTest("-(a)", -1, true);
    iStat += EqnTest("-(-a)", 1, true);
    iStat += EqnTest("-(-a)*2", 2, true);
    iStat += EqnTest("-(8)", -8, true);
    iStat += EqnTest("-8", -8, true);
    iStat += EqnTest("-(2+1)", -3, true);
    iStat += EqnTest("-(f1of1(1+2*3)+1*2)", -9, true);
    iStat += EqnTest("-(-f1of1(1+2*3)+1*2)", 5, true);
    iStat += EqnTest("-sin(8)", -0.989358, true);
    iStat += EqnTest("3-(-a)", 4, true);
    iStat += EqnTest("3--a", 4, true);
    iStat += EqnTest("-1*3", -3, true);

    // Postfix / infix priorities
    iStat += EqnTest("~2#", 8, true);
    iStat += EqnTest("~f1of1(2)#", 8, true);
    iStat += EqnTest("~(b)#", 8, true);
    iStat += EqnTest("(~b)#", 12, true);
    iStat += EqnTest("~(2#)", 8, true);
    iStat += EqnTest("~(f1of1(2)#)", 8, true);
    //
    iStat += EqnTest("-2^2", -4, true);
    iStat += EqnTest("-(a+b)^2", -9, true);
    iStat += EqnTest("(-3)^2", 9, true);
    iStat += EqnTest("-(-2^2)", 4, true);
    iStat += EqnTest("3+-3^2", -6, true);
    // The following assumes use of sqr as postfix operator (QString()) together
    // with a sign operator of low priority:
    iStat += EqnTest("-2'", -4, true);
    iStat += EqnTest("-(1+1)'", -4, true);
    iStat += EqnTest("2+-(1+1)'", -2, true);
    iStat += EqnTest("2+-2'", -2, true);
    // This is the classic behaviour of the infix sign operator (here: "$") which is
    // now deprecated:
    iStat += EqnTest("$2^2", 4, true);
    iStat += EqnTest("$(a+b)^2", 9, true);
    iStat += EqnTest("($3)^2", 9, true);
    iStat += EqnTest("$($2^2)", -4, true);
    iStat += EqnTest("3+$3^2", 12, true);

    // infix operators sharing the first few characters
    iStat += EqnTest("~ 123", 123 + 2, true);
    iStat += EqnTest("~~ 123", 123 + 2, true);

    if (iStat == 0)
    {
        qWarning() << "TestInfixOprt passed";
    }
    else
    {
        qWarning() << "\n TestInfixOprt failed with " << iStat << " errors";
    }

    return iStat;
}

//---------------------------------------------------------------------------------------------------------------------
auto QmuParserTester::TestPostFix() -> int
{
    int iStat = 0;
    qWarning() << "testing postfix operators...";

    // application
    iStat += EqnTest("3{m}+5", 5.003, true);
    iStat += EqnTest("1000{m}", 1, true);
    iStat += EqnTest("1000 {m}", 1, true);
    iStat += EqnTest("(a){m}", 1e-3, true);
    iStat += EqnTest("a{m}", 1e-3, true);
    iStat += EqnTest("a {m}", 1e-3, true);
    iStat += EqnTest("-(a){m}", -1e-3, true);
    iStat += EqnTest("-2{m}", -2e-3, true);
    iStat += EqnTest("-2 {m}", -2e-3, true);
    iStat += EqnTest("f1of1(1000){m}", 1, true);
    iStat += EqnTest("-f1of1(1000){m}", -1, true);
    iStat += EqnTest("-f1of1(-1000){m}", 1, true);
    iStat += EqnTest("f4of4(0;0;0;1000){m}", 1, true);
    iStat += EqnTest("2+(a*1000){m}", 3, true);

    // can postfix operators "m" und "meg" be told apart properly?
    iStat += EqnTest("2*3000meg+2", 2 * 3e9 + 2, true);

    // some incorrect results
    iStat += EqnTest("1000{m}", 0.1, false);
    iStat += EqnTest("(a){m}", 2, false);
    // failure due to syntax checking
    iStat += ThrowTest("0x", ecUNASSIGNABLE_TOKEN); // incomplete hex definition
    iStat += ThrowTest("3+", ecUNEXPECTED_EOF);
    iStat += ThrowTest("4 + {m}", ecUNASSIGNABLE_TOKEN);
    iStat += ThrowTest("{m}4", ecUNASSIGNABLE_TOKEN);
    iStat += ThrowTest("sin({m})", ecUNASSIGNABLE_TOKEN);
    iStat += ThrowTest("{m} {m}", ecUNASSIGNABLE_TOKEN);
    iStat += ThrowTest("{m}(8)", ecUNASSIGNABLE_TOKEN);
    iStat += ThrowTest("4,{m}", ecUNASSIGNABLE_TOKEN);
    iStat += ThrowTest("-{m}", ecUNASSIGNABLE_TOKEN);
    iStat += ThrowTest("2(-{m})", ecUNEXPECTED_PARENS);
    iStat += ThrowTest("2({m})", ecUNEXPECTED_PARENS);

    iStat += ThrowTest("multi*1.0", ecUNASSIGNABLE_TOKEN);

    if (iStat == 0)
    {
        qWarning() << "TestPostFix passed";
    }
    else
    {
        qWarning() << "\n TestPostFix failed with " << iStat << " errors";
    }

    return iStat;
}

//---------------------------------------------------------------------------------------------------------------------
auto QmuParserTester::TestExpression() -> int
{
    int iStat = 0;
    qWarning() << "testing expression samples...";

    qreal const b = 2;

    // Optimization
    iStat += EqnTest("2*b*5", 20, true);
    iStat += EqnTest("2*b*5 + 4*b", 28, true);
    iStat += EqnTest("2*a/3", 2.0 / 3.0, true);

    // Addition auf cmVARMUL
    iStat += EqnTest("b--3", b - (-3), true);
    iStat += EqnTest("b-3", b - 3, true);
    iStat += EqnTest("3-b", 3 - b, true);
    iStat += EqnTest("3+b", b + 3, true);
    iStat += EqnTest("b+3", b + 3, true);
    iStat += EqnTest("b*3+2", b * 3 + 2, true);
    iStat += EqnTest("3*b+2", b * 3 + 2, true);
    iStat += EqnTest("2+b*3", b * 3 + 2, true);
    iStat += EqnTest("2+3*b", b * 3 + 2, true);
    iStat += EqnTest("b+3*b", b + 3 * b, true);
    iStat += EqnTest("3*b+b", b + 3 * b, true);

    iStat += EqnTest("2+b*3+b", 2 + b * 3 + b, true);
    iStat += EqnTest("b+2+b*3", b + 2 + b * 3, true);

    iStat += EqnTest("(2*b+1)*4", (2 * b + 1) * 4, true);
    iStat += EqnTest("4*(2*b+1)", (2 * b + 1) * 4, true);
    // 2013-11-27 Issue 2:  https://code.google.com/p/muparser/issues/detail?id=2
    iStat += EqnTest("1+2+3", 6, true);

    // operator precedencs
    iStat += EqnTest("1+2-3*4/5^6", 2.99923, true);
    iStat += EqnTest("1^2/3*4-5+6", 2.33333333, true);
    iStat += EqnTest("1+2*3", 7, true);
    iStat += EqnTest("1+2*3", 7, true);
    iStat += EqnTest("(1+2)*3", 9, true);
    iStat += EqnTest("(1+2)*(-3)", -9, true);
    iStat += EqnTest("2/4", 0.5, true);

    iStat += EqnTest("exp(ln(7))", 7, true);
    iStat += EqnTest("e^ln(7)", 7, true);
    iStat += EqnTest("e^(ln(7))", 7, true);
    iStat += EqnTest("(e^(ln(7)))", 7, true);
    iStat += EqnTest("1-(e^(ln(7)))", -6, true);
    iStat += EqnTest("2*(e^(ln(7)))", 14, true);
    iStat += EqnTest("10^log(5)", 5, true);
    iStat += EqnTest("10^log10(5)", 5, true);
    iStat += EqnTest("2^log2(4)", 4, true);
    iStat += EqnTest("-(sin(0)+1)", -1, true);
    iStat += EqnTest("-(2^1.1)", -2.14354692, true);

    iStat += EqnTest("(cos(2.41)/b)", -0.372056, true);
    iStat += EqnTest("(1*(2*(3*(4*(5*(6*(a+b)))))))", 2160, true);
    iStat += EqnTest("(1*(2*(3*(4*(5*(6*(7*(a+b))))))))", 15120, true);
    iStat += EqnTest("(a/((((b+(((e*(((((pi*((((3.45*((pi+a)+pi))+b)+b)*a))+0.68)+e)+a)/a))+a)+b))+b)*a)-pi))",
                     0.00377999, true);

    // long formula (Reference: Matlab)
    iStat += EqnTest(
        "(((-9))-e/(((((((pi-(((-7)+(-3)/4/e))))/(((-5))-2)-((pi+(-0))*(sqrt((e+e))*(-8))*(((-pi)+(-pi)-(-9)*(6*5))"
        "/(-e)-e))/2)/((((sqrt(2/(-e)+6)-(4-2))+((5/(-2))/(1*(-pi)+3))/8)*pi*((pi/((-2)/(-6)*1*(-1))*(-6)+(-e)))))/"
        "((e+(-2)+(-e)*((((-3)*9+(-e)))+(-9)))))))-((((e-7+(((5/pi-(3/1+pi)))))/e)/(-5))/(sqrt((((((1+(-7))))+((((-"
        "e)*(-e)))-8))*(-5)/((-e)))*(-6)-((((((-2)-(-9)-(-e)-1)/3))))/(sqrt((8+(e-((-6))+(9*(-9))))*(((3+2-8))*(7+6"
        "+(-5))+((0/(-e)*(-pi))+7)))+(((((-e)/e/e)+((-6)*5)*e+(3+(-5)/pi))))+pi))/sqrt((((9))+((((pi))-8+2))+pi))/e"
        "*4)*((-5)/(((-pi))*(sqrt(e)))))-(((((((-e)*(e)-pi))/4+(pi)*(-9)))))))+(-pi)",
        -12.23016549, true);

    // long formula (Reference: Matlab)
    iStat += EqnTest(
        "(atan(sin((((((((((((((((pi/cos((a/((((0.53-b)-pi)*e)/b))))+2.51)+a)-0.54)/0.98)+b)*b)+e)/a)+b)+a)+b)+pi)/e"
        ")+a)))*2.77)",
        -2.16995656, true);

    // long formula (Reference: Matlab)
    iStat += EqnTest("1+2-3*4/5^6*(2*(1-5+(3*7^9)*(4+6*7-3)))+12", -7995810.09926, true);

    if (iStat == 0)
    {
        qWarning() << "TestExpression passed";
    }
    else
    {
        qWarning() << "\n TestExpression failed with " << iStat << " errors";
    }

    return iStat;
}

//---------------------------------------------------------------------------------------------------------------------
auto QmuParserTester::TestIfThenElse() -> int
{
    int iStat = 0;
    qWarning() << "testing if-then-else operator...";

    // Test error detection
    iStat += ThrowTest(":3", ecUNEXPECTED_CONDITIONAL);
    iStat += ThrowTest("? 1 : 2", ecUNEXPECTED_CONDITIONAL);
    iStat += ThrowTest("(a<b) ? (b<c) ? 1 : 2", ecMISSING_ELSE_CLAUSE);
    iStat += ThrowTest("(a<b) ? 1", ecMISSING_ELSE_CLAUSE);
    iStat += ThrowTest("(a<b) ? a", ecMISSING_ELSE_CLAUSE);
    iStat += ThrowTest("(a<b) ? a+b", ecMISSING_ELSE_CLAUSE);
    iStat += ThrowTest("a : b", ecMISPLACED_COLON);
    iStat += ThrowTest("1 : 2", ecMISPLACED_COLON);
    iStat += ThrowTest("(1) ? 1 : 2 : 3", ecMISPLACED_COLON);
    iStat += ThrowTest("(true) ? 1 : 2 : 3", ecUNASSIGNABLE_TOKEN);

    iStat += EqnTest("1 ? 128 : 255", 128, true);
    iStat += EqnTest("1<2 ? 128 : 255", 128, true);
    iStat += EqnTest("a<b ? 128 : 255", 128, true);
    iStat += EqnTest("(a<b) ? 128 : 255", 128, true);
    iStat += EqnTest("(1) ? 10 : 11", 10, true);
    iStat += EqnTest("(0) ? 10 : 11", 11, true);
    iStat += EqnTest("(1) ? a+b : c+d", 3, true);
    iStat += EqnTest("(0) ? a+b : c+d", 1, true);
    iStat += EqnTest("(1) ? 0 : 1", 0, true);
    iStat += EqnTest("(0) ? 0 : 1", 1, true);
    iStat += EqnTest("(a<b) ? 10 : 11", 10, true);
    iStat += EqnTest("(a>b) ? 10 : 11", 11, true);
    iStat += EqnTest("(a<b) ? c : d", 3, true);
    iStat += EqnTest("(a>b) ? c : d", -2, true);

    iStat += EqnTest("(a>b) ? 1 : 0", 0, true);
    iStat += EqnTest("((a>b) ? 1 : 0) ? 1 : 2", 2, true);
    iStat += EqnTest("((a>b) ? 1 : 0) ? 1 : sum((a>b) ? 1 : 2)", 2, true);
    iStat += EqnTest("((a>b) ? 0 : 1) ? 1 : sum((a>b) ? 1 : 2)", 1, true);

    iStat += EqnTest("sum((a>b) ? 1 : 2)", 2, true);
    iStat += EqnTest("sum((1) ? 1 : 2)", 1, true);
    iStat += EqnTest("sum((a>b) ? 1 : 2; 100)", 102, true);
    iStat += EqnTest("sum((1) ? 1 : 2; 100)", 101, true);
    iStat += EqnTest("sum(3; (a>b) ? 3 : 10)", 13, true);
    iStat += EqnTest("sum(3; (a<b) ? 3 : 10)", 6, true);
    iStat += EqnTest("10*sum(3; (a>b) ? 3 : 10)", 130, true);
    iStat += EqnTest("10*sum(3; (a<b) ? 3 : 10)", 60, true);
    iStat += EqnTest("sum(3; (a>b) ? 3 : 10)*10", 130, true);
    iStat += EqnTest("sum(3; (a<b) ? 3 : 10)*10", 60, true);
    iStat += EqnTest("(a<b) ? sum(3; (a<b) ? 3 : 10)*10 : 99", 60, true);
    iStat += EqnTest("(a>b) ? sum(3; (a<b) ? 3 : 10)*10 : 99", 99, true);
    iStat += EqnTest("(a<b) ? sum(3; (a<b) ? 3 : 10;10;20)*10 : 99", 360, true);
    iStat += EqnTest("(a>b) ? sum(3; (a<b) ? 3 : 10;10;20)*10 : 99", 99, true);
    iStat += EqnTest("(a>b) ? sum(3; (a<b) ? 3 : 10;10;20)*10 : sum(3; (a<b) ? 3 : 10)*10", 60, true);

    // todo: auch fr muParserX hinzufgen!
    iStat += EqnTest("(a<b)&&(a<b) ? 128 : 255", 128, true);
    iStat += EqnTest("(a>b)&&(a<b) ? 128 : 255", 255, true);
    iStat += EqnTest("(1<2)&&(1<2) ? 128 : 255", 128, true);
    iStat += EqnTest("(1>2)&&(1<2) ? 128 : 255", 255, true);
    iStat += EqnTest("((1<2)&&(1<2)) ? 128 : 255", 128, true);
    iStat += EqnTest("((1>2)&&(1<2)) ? 128 : 255", 255, true);
    iStat += EqnTest("((a<b)&&(a<b)) ? 128 : 255", 128, true);
    iStat += EqnTest("((a>b)&&(a<b)) ? 128 : 255", 255, true);

    iStat += EqnTest("1>0 ? 1>2 ? 128 : 255 : 1>0 ? 32 : 64", 255, true);
    iStat += EqnTest("1>0 ? 1>2 ? 128 : 255 :(1>0 ? 32 : 64)", 255, true);
    iStat += EqnTest("1>0 ? 1>0 ? 128 : 255 : 1>2 ? 32 : 64", 128, true);
    iStat += EqnTest("1>0 ? 1>0 ? 128 : 255 :(1>2 ? 32 : 64)", 128, true);
    iStat += EqnTest("1>2 ? 1>2 ? 128 : 255 : 1>0 ? 32 : 64", 32, true);
    iStat += EqnTest("1>2 ? 1>0 ? 128 : 255 : 1>2 ? 32 : 64", 64, true);
    iStat += EqnTest("1>0 ? 50 :  1>0 ? 128 : 255", 50, true);
    iStat += EqnTest("1>0 ? 50 : (1>0 ? 128 : 255)", 50, true);
    iStat += EqnTest("1>0 ? 1>0 ? 128 : 255 : 50", 128, true);
    iStat += EqnTest("1>2 ? 1>2 ? 128 : 255 : 1>0 ? 32 : 1>2 ? 64 : 16", 32, true);
    iStat += EqnTest("1>2 ? 1>2 ? 128 : 255 : 1>0 ? 32 :(1>2 ? 64 : 16)", 32, true);
    iStat += EqnTest("1>0 ? 1>2 ? 128 : 255 :  1>0 ? 32 :1>2 ? 64 : 16", 255, true);
    iStat += EqnTest("1>0 ? 1>2 ? 128 : 255 : (1>0 ? 32 :1>2 ? 64 : 16)", 255, true);
    iStat += EqnTest("1 ? 0 ? 128 : 255 : 1 ? 32 : 64", 255, true);

    // assignment operators
    iStat += EqnTest("a= 0 ? 128 : 255; a", 255, true);
    iStat += EqnTest("a=((a>b)&&(a<b)) ? 128 : 255; a", 255, true);
    iStat += EqnTest("c=(a<b)&&(a<b) ? 128 : 255; c", 128, true);
    iStat += EqnTest("0 ? a=a+1 : 666; a", 1, true);
    iStat += EqnTest("1?a=10:a=20; a", 10, true);
    iStat += EqnTest("0?a=10:a=20; a", 20, true);
    iStat += EqnTest("0?a=sum(3;4):10; a", 1, true); // a should not change its value due to lazy calculation

    iStat += EqnTest("a=1?b=1?3:4:5; a", 3, true);
    iStat += EqnTest("a=1?b=1?3:4:5; b", 3, true);
    iStat += EqnTest("a=0?b=1?3:4:5; a", 5, true);
    iStat += EqnTest("a=0?b=1?3:4:5; b", 2, true);

    iStat += EqnTest("a=1?5:b=1?3:4; a", 5, true);
    iStat += EqnTest("a=1?5:b=1?3:4; b", 2, true);
    iStat += EqnTest("a=0?5:b=1?3:4; a", 3, true);
    iStat += EqnTest("a=0?5:b=1?3:4; b", 3, true);

    if (iStat == 0)
    {
        qWarning() << "TestIfThenElse passed";
    }
    else
    {
        qWarning() << "\n TestIfThenElse failed with " << iStat << " errors";
    }

    return iStat;
}

//---------------------------------------------------------------------------------------------------------------------
auto QmuParserTester::TestException() -> int
{
    int iStat = 0;
    qWarning() << "testing error codes...";

    iStat += ThrowTest("3+", ecUNEXPECTED_EOF);
    iStat += ThrowTest("3+)", ecUNEXPECTED_PARENS);
    iStat += ThrowTest("()", ecUNEXPECTED_PARENS);
    iStat += ThrowTest("3+()", ecUNEXPECTED_PARENS);
    iStat += ThrowTest("sin(3;4)", ecTOO_MANY_PARAMS);
    iStat += ThrowTest("sin()", ecTOO_FEW_PARAMS);
    iStat += ThrowTest("(1+2", ecMISSING_PARENS);
    iStat += ThrowTest("sin(3)3", ecUNEXPECTED_VAL);
    iStat += ThrowTest("sin(3)xyz", ecUNASSIGNABLE_TOKEN);
    iStat += ThrowTest("sin(3)cos(3)", ecUNEXPECTED_FUN);
    iStat += ThrowTest("a+b+c=10", ecUNEXPECTED_OPERATOR);
    iStat += ThrowTest("a=b=3", ecUNEXPECTED_OPERATOR);

#if defined(MUP_MATH_EXCEPTIONS)
    // divide by zero whilst constant folding
    iStat += ThrowTest("1/0", ecDIV_BY_ZERO);
    // square root of a negative number
    iStat += ThrowTest("sqrt(-1)", ecDOMAIN_ERROR);
    // logarithms of zero
    iStat += ThrowTest("ln(0)", ecDOMAIN_ERROR);
    iStat += ThrowTest("log2(0)", ecDOMAIN_ERROR);
    iStat += ThrowTest("log10(0)", ecDOMAIN_ERROR);
    iStat += ThrowTest("log(0)", ecDOMAIN_ERROR);
    // logarithms of negative values
    iStat += ThrowTest("ln(-1)", ecDOMAIN_ERROR);
    iStat += ThrowTest("log2(-1)", ecDOMAIN_ERROR);
    iStat += ThrowTest("log10(-1)", ecDOMAIN_ERROR);
    iStat += ThrowTest("log(-1)", ecDOMAIN_ERROR);
#endif

    // functions without parameter
    iStat += ThrowTest("3+ping(2)", ecTOO_MANY_PARAMS);
    iStat += ThrowTest("3+ping(a+2)", ecTOO_MANY_PARAMS);
    iStat += ThrowTest("3+ping(sin(a)+2)", ecTOO_MANY_PARAMS);
    iStat += ThrowTest("3+ping(1+sin(a))", ecTOO_MANY_PARAMS);

    // String function related
    iStat += ThrowTest("valueof(\"xxx\")", 999, false);
    iStat += ThrowTest("valueof()", ecUNEXPECTED_PARENS);
    iStat += ThrowTest("1+valueof(\"abc\"", ecMISSING_PARENS);
    iStat += ThrowTest("valueof(\"abc\"", ecMISSING_PARENS);
    iStat += ThrowTest("valueof(\"abc", ecUNTERMINATED_STRING);
    iStat += ThrowTest("valueof(\"abc\";3)", ecTOO_MANY_PARAMS);
    iStat += ThrowTest("valueof(3)", ecSTRING_EXPECTED);
    iStat += ThrowTest("sin(\"abc\")", ecVAL_EXPECTED);
    iStat += ThrowTest("valueof(\"\\\"abc\\\"\")", 999, false);
    iStat += ThrowTest("\"hello world\"", ecSTR_RESULT);
    iStat += ThrowTest("(\"hello world\")", ecSTR_RESULT);
    iStat += ThrowTest("\"abcd\"+100", ecOPRT_TYPE_CONFLICT);
    iStat += ThrowTest("\"a\"+\"b\"", ecOPRT_TYPE_CONFLICT);
    iStat += ThrowTest("strfun1(\"100\";3)", ecTOO_MANY_PARAMS);
    iStat += ThrowTest("strfun2(\"100\";3;5)", ecTOO_MANY_PARAMS);
    iStat += ThrowTest("strfun3(\"100\";3;5;6)", ecTOO_MANY_PARAMS);
    iStat += ThrowTest("strfun2(\"100\")", ecTOO_FEW_PARAMS);
    iStat += ThrowTest("strfun3(\"100\";6)", ecTOO_FEW_PARAMS);
    iStat += ThrowTest("strfun2(1;1)", ecSTRING_EXPECTED);
    iStat += ThrowTest("strfun2(a;1)", ecSTRING_EXPECTED);
    iStat += ThrowTest("strfun2(1;1;1)", ecTOO_MANY_PARAMS);
    iStat += ThrowTest("strfun2(a;1;1)", ecTOO_MANY_PARAMS);
    iStat += ThrowTest("strfun3(1;2;3)", ecSTRING_EXPECTED);
    iStat += ThrowTest("strfun3(1; \"100\";3)", ecSTRING_EXPECTED);
    iStat += ThrowTest("strfun3(\"1\"; \"100\";3)", ecVAL_EXPECTED);
    iStat += ThrowTest("strfun3(\"1\"; 3; \"100\")", ecVAL_EXPECTED);
    iStat += ThrowTest("strfun3(\"1\"; \"100\"; \"100\"; \"100\")", ecTOO_MANY_PARAMS);

    // assignement operator
    iStat += ThrowTest("3=4", ecUNEXPECTED_OPERATOR);
    iStat += ThrowTest("sin(8)=4", ecUNEXPECTED_OPERATOR);
    iStat += ThrowTest("\"test\"=a", ecUNEXPECTED_OPERATOR);

    // <ibg 20090529>
    // this is now legal, for reference see:
    // https://sourceforge.net/forum/message.php?msg_id=7411373
    //      iStat += ThrowTest( "sin=9"), ecUNEXPECTED_OPERATOR);
    // </ibg>

    iStat += ThrowTest("(8)=5", ecUNEXPECTED_OPERATOR);
    iStat += ThrowTest("(a)=5", ecUNEXPECTED_OPERATOR);
    iStat += ThrowTest("a=\"tttt\"", ecOPRT_TYPE_CONFLICT);

    if (iStat == 0)
    {
        qWarning() << "TestException passed";
    }
    else
    {
        qWarning() << "\n TestException failed with " << iStat << " errors";
    }

    return iStat;
}

//---------------------------------------------------------------------------------------------------------------------
void QmuParserTester::AddTest(testfun_type a_pFun)
{
    m_vTestFun.push_back(a_pFun);
}

//---------------------------------------------------------------------------------------------------------------------
// cppcheck-suppress unusedFunction
void QmuParserTester::Run()
{
    qWarning() << "-----------------------------------------------------------";
    qWarning() << "Running test suite:\n";

    int iStat = 0;
    try
    {
        for (int i = 0; i < m_vTestFun.size(); ++i)
        {
            iStat += (this->*m_vTestFun[i])();
        }
    }
    catch (QmuParserError &e)
    {
        qWarning() << "\n" << e.GetMsg();
        qWarning() << e.GetToken();
        Abort();
        return;
    }
    catch (std::exception &e)
    {
        qWarning() << e.what();
        Abort();
        return;
    }
    catch (...)
    {
        qWarning() << "Internal error";
        Abort();
        return;
    }

    if (iStat == 0)
    {
        qWarning() << "Test passed (" << QmuParserTester::c_iCount << " expressions)";
    }
    else
    {
        qWarning() << "Test failed with " << iStat << " errors (" << QmuParserTester::c_iCount << " expressions)";
    }
    QCoreApplication::exit(iStat);
    QmuParserTester::c_iCount = 0;

    qWarning() << "Done.";
    qWarning() << "-----------------------------------------------------------";
}

//---------------------------------------------------------------------------------------------------------------------
auto QmuParserTester::ThrowTest(const QString &a_str, int a_iErrc, bool a_bFail) -> int
{
    QmuParserTester::c_iCount++;

    try
    {
        qreal fVal[] = {1, 1, 1};
        QmuParser p;

        p.DefineVar("a", &fVal[0]);
        p.DefineVar("b", &fVal[1]);
        p.DefineVar("c", &fVal[2]);
        p.DefinePostfixOprt("{m}", Milli);
        p.DefinePostfixOprt("m", Milli);
        p.DefineFun("ping", Ping);
        p.DefineFun("valueof", ValueOf);
        p.DefineFun("strfun1", StrFun1);
        p.DefineFun("strfun2", StrFun2);
        p.DefineFun("strfun3", StrFun3);
        p.SetExpr(a_str);
        p.Eval();
    }
    catch (const qmu::QmuParserError &e)
    {
        // output the formula in case of an failed test
        if (a_bFail == false || (a_bFail == true && a_iErrc != e.GetCode()))
        {
            qWarning() << "\n  "
                       << "Expression: " << a_str << "  Code:" << e.GetCode() << "(" << e.GetMsg() << ")"
                       << "  Expected:" << a_iErrc;
        }

        return (a_iErrc == e.GetCode()) ? 0 : 1;
    }

    // if a_bFail==false no exception is expected
    bool const bRet((a_bFail == false) ? false : true);
    if (bRet == 1)
    {
        qWarning() << "\n  "
                   << "Expression: " << a_str << "  did evaluate; Expected error:" << a_iErrc;
    }

    return bRet;
}

//---------------------------------------------------------------------------------------------------------------------
/**
 * @brief Evaluate a tet expression.
 *
 * @return 1 in case of a failure, 0 otherwise.
 */
auto QmuParserTester::EqnTestWithVarChange(const QString &a_str, double a_fRes1, double a_fVar1, double a_fRes2,
                                           double a_fVar2) -> int
{
    QmuParserTester::c_iCount++;
    qreal fVal[2] = {-999, -999}; // should be equalinitially

    try
    {
        QmuParser p;

        // variable
        qreal var = 0;
        p.DefineVar("a", &var);
        p.SetExpr(a_str);

        var = a_fRes1;
        fVal[0] = p.Eval();

        var = a_fRes2; //-V519
        fVal[1] = p.Eval();

        if (fabs(a_fVar1 - fVal[0]) > 0.0000000001)
        {
            throw std::runtime_error("incorrect result (first pass)");
        }

        if (fabs(a_fVar2 - fVal[1]) > 0.0000000001)
        {
            throw std::runtime_error("incorrect result (second pass)");
        }
    }
    catch (QmuParserError &e)
    {
        qWarning() << "\n  fail: " << a_str << " (" << e.GetMsg() << ")";
        return 1;
    }
    catch (std::exception &e)
    {
        qWarning() << "\n  fail: " << a_str << " (" << e.what() << ")";
        return 1; // always return a failure since this exception is not expected
    }
    catch (...)
    {
        qWarning() << "\n  fail: " << a_str << " (unexpected exception)";
        return 1; // exceptions other than ParserException are not allowed
    }

    return 0;
}

//---------------------------------------------------------------------------------------------------------------------
/**
 * @brief Evaluate a tet expression.
 *
 * @return 1 in case of a failure, 0 otherwise.
 */
auto QmuParserTester::EqnTest(const QString &a_str, double a_fRes, bool a_fPass) -> int
{
    QmuParserTester::c_iCount++;
    int iRet(0);
    qreal fVal[5] = {-999, -998, -997, -996, -995}; // initially should be different

    try
    {
        std::unique_ptr<QmuParser> p1;
        // three parser objects
        // they will be used for testing copy and assihnment operators
        // p1 is a pointer since i'm going to delete it in order to test if
        // parsers after copy construction still refer to members of it.
        // !! If this is the case this function will crash !!

        p1.reset(new qmu::QmuParser());
        // Add constants
        p1->DefineConst("pi", M_PI);
        p1->DefineConst("e", M_E);
        p1->DefineConst("const", 1);
        p1->DefineConst("const1", 2);
        p1->DefineConst("const2", 3);
        // variables
        qreal vVarVal[] = {1, 2, 3, -2};
        p1->DefineVar("a", &vVarVal[0]);
        p1->DefineVar("aa", &vVarVal[1]);
        p1->DefineVar("b", &vVarVal[1]);
        p1->DefineVar("c", &vVarVal[2]);
        p1->DefineVar("d", &vVarVal[3]);

        // custom value ident functions
        p1->AddValIdent(&QmuParserTester::IsHexVal);

        // functions
        p1->DefineFun("ping", Ping);
        p1->DefineFun("f1of1", f1of1); // one parameter
        p1->DefineFun("f1of2", f1of2); // two parameter
        p1->DefineFun("f2of2", f2of2);
        p1->DefineFun("f1of3", f1of3); // three parameter
        p1->DefineFun("f2of3", f2of3);
        p1->DefineFun("f3of3", f3of3);
        p1->DefineFun("f1of4", f1of4); // four parameter
        p1->DefineFun("f2of4", f2of4);
        p1->DefineFun("f3of4", f3of4);
        p1->DefineFun("f4of4", f4of4);
        p1->DefineFun("f1of5", f1of5); // five parameter
        p1->DefineFun("f2of5", f2of5);
        p1->DefineFun("f3of5", f3of5);
        p1->DefineFun("f4of5", f4of5);
        p1->DefineFun("f5of5", f5of5);

        // binary operators
        p1->DefineOprt("add", add, 0);
        p1->DefineOprt("++", add, 0);
        p1->DefineOprt("&", land, prLAND);

        // sample functions
        p1->DefineFun("min", Min);
        p1->DefineFun("max", Max);
        p1->DefineFun("sum", Sum);
        p1->DefineFun("valueof", ValueOf);
        p1->DefineFun("atof", StrToFloat);
        p1->DefineFun("strfun1", StrFun1);
        p1->DefineFun("strfun2", StrFun2);
        p1->DefineFun("strfun3", StrFun3);
        p1->DefineFun("lastArg", LastArg);
        p1->DefineFun("firstArg", FirstArg);
        p1->DefineFun("order", FirstArg);

        // infix / postfix operator
        // Note: Identifiers used here do not have any meaning
        //       they are mere placeholders to test certain features.
        p1->DefineInfixOprt("$", sign, prPOW + 1); // sign with high priority
        p1->DefineInfixOprt("~", plus2);           // high priority
        p1->DefineInfixOprt("~~", plus2);
        p1->DefinePostfixOprt("{m}", Milli);
        p1->DefinePostfixOprt("{M}", Mega);
        p1->DefinePostfixOprt("m", Milli);
        p1->DefinePostfixOprt("meg", Mega);
        p1->DefinePostfixOprt("#", times3);
        p1->DefinePostfixOprt("'", sqr);
        p1->SetExpr(a_str);

        // Test bytecode integrity
        // String parsing and bytecode parsing must yield the same result
        fVal[0] = p1->Eval(); // result from stringparsing
        fVal[1] = p1->Eval(); // result from bytecode
        if (not QmuFuzzyComparePossibleNulls(fVal[0], fVal[1]))
        {
            throw QmuParserError("Bytecode / string parsing mismatch.");
        }

        // Test copy and assignement operators
        try
        {
            // Test copy constructor
            QVector<qmu::QmuParser> vParser;
            vParser.push_back(*(p1.get()));
            qmu::QmuParser const p2 = vParser[0]; // take parser from vector

            // destroy the originals from p2
            vParser.clear(); // delete the vector
            p1.reset(nullptr);

            fVal[2] = p2.Eval();

            // Test assignement operator
            // additionally  disable Optimizer this time
            qmu::QmuParser p3;
            p3 = p2;
            p3.EnableOptimizer(false);
            fVal[3] = p3.Eval();

            // Test Eval function for multiple return values
            // use p2 since it has the optimizer enabled!
            int nNum;
            qreal *v = p2.Eval(nNum);
            fVal[4] = v[nNum - 1];
        }
        catch (std::exception &e)
        {
            qWarning() << "\n  " << e.what() << "\n";
        }

        // limited floating point accuracy requires the following test
        bool bCloseEnough(true);
        for (unsigned i = 0; i < sizeof(fVal) / sizeof(qreal); ++i)
        {
            bCloseEnough &= (fabs(a_fRes - fVal[i]) <= fabs(fVal[i] * 0.00001));

            // The tests equations never result in infinity, if they do thats a bug.
            // reference:
            // http://sourceforge.net/projects/muparser/forums/forum/462843/topic/5037825
            QT_WARNING_PUSH
            QT_WARNING_DISABLE_MSVC(4127)
            if (std::numeric_limits<qreal>::has_infinity)
                QT_WARNING_POP
                {
                    bCloseEnough &=
                        (not QmuFuzzyComparePossibleNulls(fabs(fVal[i]), std::numeric_limits<qreal>::infinity()));
                }
        }

        iRet = ((bCloseEnough && a_fPass) || (bCloseEnough == false && a_fPass == false)) ? 0 : 1;

        if (iRet == 1)
        {
            qWarning() << "\n  fail: " << a_str << " (incorrect result; expected: " << a_fRes
                       << " ;calculated: " << fVal[0] << "," << fVal[1] << "," << fVal[2] << "," << fVal[3] << ","
                       << fVal[4] << ").";
        }
    }
    catch (QmuParserError &e)
    {
        if (a_fPass)
        {
            if (not QmuFuzzyComparePossibleNulls(fVal[0], fVal[2]) && not QmuFuzzyComparePossibleNulls(fVal[0], -999) &&
                not QmuFuzzyComparePossibleNulls(fVal[1], -998))
            {
                qWarning() << "\n  fail: " << a_str << " (copy construction)";
            }
            else
            {
                qWarning() << "\n  fail: " << a_str << " (" << e.GetMsg() << ")";
            }
            return 1;
        }
    }
    catch (std::exception &e)
    {
        qWarning() << "\n  fail: " << a_str << " (" << e.what() << ")";
        return 1; // always return a failure since this exception is not expected
    }
    catch (...)
    {
        qWarning() << "\n  fail: " << a_str << " (unexpected exception)";
        return 1; // exceptions other than ParserException are not allowed
    }

    return iRet;
}

//---------------------------------------------------------------------------------------------------------------------
/** \brief Test an expression in Bulk Mode. */
auto QmuParserTester::EqnTestBulk(const QString &a_str, double a_fRes[4], bool a_fPass) -> int
{
    QmuParserTester::c_iCount++;
    int iRet(0);

    try
    {
        // Define Bulk Variables
        int const nBulkSize = 4;
        double vVariableA[] = {1, 2, 3, 4}; // variable values
        double vVariableB[] = {2, 2, 2, 2}; // variable values
        double vVariableC[] = {3, 3, 3, 3}; // variable values
        double vResults[] = {0, 0, 0, 0};   // variable values

        QmuParser p;
        p.DefineConst("const1", 1);
        p.DefineConst("const2", 2);
        p.DefineVar("a", vVariableA);
        p.DefineVar("b", vVariableB);
        p.DefineVar("c", vVariableC);

        p.SetExpr(a_str);
        p.Eval(vResults, nBulkSize);

        bool bCloseEnough(true);
        for (int i = 0; i < nBulkSize; ++i)
        {
            bCloseEnough &= (fabs(a_fRes[i] - vResults[i]) <= fabs(a_fRes[i] * 0.00001));
        }

        iRet = ((bCloseEnough && a_fPass) || (!bCloseEnough && !a_fPass)) ? 0 : 1;
        if (iRet == 1)
        {
            qWarning() << "\n  fail: " << a_str << " (incorrect result; expected: {" << a_fRes[0] << "," << a_fRes[1]
                       << "," << a_fRes[2] << "," << a_fRes[3] << "}"
                       << " ;calculated: " << vResults[0] << "," << vResults[1] << "," << vResults[2] << ","
                       << vResults[3] << "}";
        }
    }
    catch (QmuParserError &e)
    {
        if (a_fPass)
        {
            qWarning() << "\n  fail: " << e.GetExpr() << " : " << e.GetMsg();
            iRet = 1;
        }
    }
    catch (...)
    {
        qWarning() << "\n  fail: " << a_str << " (unexpected exception)";
        iRet = 1; // exceptions other than ParserException are not allowed
    }

    return iRet;
}

//---------------------------------------------------------------------------------------------------------------------
/**
 * @brief Internal error in test class Test is going to be aborted.
 */
void QmuParserTester::Abort()
{
    qWarning() << "Test failed (internal error in test class)";
    while (getchar() == false)
        ;
    QCoreApplication::exit(-1);
}
} // namespace qmu::Test
