/*************************************************************************************************** ** ** 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include "qmudef.h" #include "qmuparser.h" #include "qmuparsererror.h" #include "../vmisc/vmath.h" /** * @file * @brief This file contains the implementation of parser test cases. */ namespace qmu { namespace 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; } //--------------------------------------------------------------------------------------------------------------------- int QmuParserTester::IsHexVal ( const QString &a_szExpr, int *a_iPos, qreal *a_fVal, const QLocale &locale, bool cNumbers, const QChar &decimal, const QChar &thousand ) { 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 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(0)) { return 1; } *a_iPos += static_cast(2 + nPos); *a_fVal = static_cast(iVal); return 1; } //--------------------------------------------------------------------------------------------------------------------- int QmuParserTester::TestInterface() { 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; } //--------------------------------------------------------------------------------------------------------------------- int QmuParserTester::TestStrArg() { 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; } //--------------------------------------------------------------------------------------------------------------------- int QmuParserTester::TestBulkMode() { 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; } //--------------------------------------------------------------------------------------------------------------------- int QmuParserTester::TestBinOprt() { 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 ( "aa", 1, true ); iStat += EqnTest ( "a>a", 0, true ); iStat += EqnTest ( "aa", 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 ( "(aa)", 1, true ); iStat += EqnTest ( "(ab)", 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. */ int QmuParserTester::TestNames() { 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(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; } //--------------------------------------------------------------------------------------------------------------------- int QmuParserTester::TestSyntax() { 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; } //--------------------------------------------------------------------------------------------------------------------- int QmuParserTester::TestVarConst() { 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(); int iCount = static_cast(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; } qmu::varmap_type::const_iterator 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(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(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; } //--------------------------------------------------------------------------------------------------------------------- int QmuParserTester::TestMultiArg() { 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; } //--------------------------------------------------------------------------------------------------------------------- int QmuParserTester::TestInfixOprt() { 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; } //--------------------------------------------------------------------------------------------------------------------- int QmuParserTester::TestPostFix() { 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; } //--------------------------------------------------------------------------------------------------------------------- int QmuParserTester::TestExpression() { int iStat = 0; qWarning() << "testing expression samples..."; qreal 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; } //--------------------------------------------------------------------------------------------------------------------- int QmuParserTester::TestIfThenElse() { 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 ( "(ab) ? 10 : 11", 11, true ); iStat += EqnTest ( "(ab) ? 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; (ab) ? 3 : 10)", 130, true ); iStat += EqnTest ( "10*sum(3; (ab) ? 3 : 10)*10", 130, true ); iStat += EqnTest ( "sum(3; (ab) ? sum(3; (ab) ? sum(3; (ab) ? sum(3; (ab)&&(a2)&&(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 ( "((ab)&&(a0 ? 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 // this is now legal, for reference see: // https://sourceforge.net/forum/message.php?msg_id=7411373 // iStat += ThrowTest( "sin=9"), ecUNEXPECTED_OPERATOR); // 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() << "-----------------------------------------------------------"; } //--------------------------------------------------------------------------------------------------------------------- int QmuParserTester::ThrowTest ( const QString &a_str, int a_iErrc, bool a_bFail ) { 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 bRet ( ( a_bFail == false ) ? 0 : 1 ); 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. */ int QmuParserTester::EqnTestWithVarChange (const QString &a_str, double a_fRes1, double a_fVar1, double a_fRes2, double a_fVar2) { 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. */ int QmuParserTester::EqnTest ( const QString &a_str, double a_fRes, bool a_fPass ) { QmuParserTester::c_iCount++; int iRet ( 0 ); qreal fVal[5] = { -999, -998, -997, -996, -995}; // initially should be different try { std::unique_ptr p1; QmuParser p2, p3; // 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", static_cast(M_PI) ); p1->DefineConst ( "e", static_cast(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 vParser; vParser.push_back ( * ( p1.get() ) ); qmu::QmuParser 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::has_infinity) QT_WARNING_POP { bCloseEnough &= (not QmuFuzzyComparePossibleNulls( fabs ( fVal[i] ), std::numeric_limits::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. */ int QmuParserTester::EqnTestBulk(const QString &a_str, double a_fRes[4], bool a_fPass) { QmuParserTester::c_iCount++; int iRet(0); try { // Define Bulk Variables int 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 test } // namespace qmu