diff --git a/parser/equationsParser.cpp b/parser/equationsParser.cpp index a053cc2..707594d 100644 --- a/parser/equationsParser.cpp +++ b/parser/equationsParser.cpp @@ -137,4 +137,11 @@ void CalcArray(vector equations, vector &out) { } } +Value PureCalc(string input) { + Value result; + ParserX parser(pckALL_NON_COMPLEX); + parser.SetExpr(input); + return parser.Eval(); +} + EQUATIONS_PARSER_END diff --git a/parser/equationsParser.h b/parser/equationsParser.h index 7ea6317..fe163e6 100644 --- a/parser/equationsParser.h +++ b/parser/equationsParser.h @@ -15,6 +15,7 @@ void ReplaceAll(std::string& source, const std::string& from, const std::string& std::string Calc(std::string input); std::string CalcJson(std::string input); void CalcArray(std::vector in, std::vector &out); +mup::Value PureCalc(std::string input); EQUATIONS_PARSER_END diff --git a/parser/mpFuncCommon.cpp b/parser/mpFuncCommon.cpp index 2f28f58..137df7e 100644 --- a/parser/mpFuncCommon.cpp +++ b/parser/mpFuncCommon.cpp @@ -43,11 +43,21 @@ #include "mpValue.h" #include "mpParserBase.h" +#include "equationsParser.h" #define ONE_DAY (24 * 60 * 60) MUP_NAMESPACE_START + void raise_error (EErrorCodes error, int position, const ptr_val_type *arguments) { + ErrorContext err; + err.Errc = error; + err.Arg = position; + err.Type1 = arguments[position - 1]->GetType(); + err.Type2 = 's'; + throw ParserError(err); + } + //------------------------------------------------------------------------------ // // FunParserID @@ -399,6 +409,93 @@ MUP_NAMESPACE_START return new FunMask(*this); } + //---------------------------------------------------------------------------------- + // | + // Class FunCase | + // Function calculate custom switch case | + // Usage: case("variable", "exp1;res1", "exp2;res2", "default;defres") | + // Optional: '@' token in exp = evaluate(variable + exp1) | + // Optional: '@' token in res = evaluate(res) | + // | + //---------------------------------------------------------------------------------- + + FunCase::FunCase() + :ICallback(cmFUNC, _T("case"), -1) + {} + + Value get_case_value(string_type expression) { + Value result; + if(expression[0] == '@'){ + result = EquationsParser::PureCalc(expression.substr(1)); + } else { + result = expression; + } + return result; + } + + bool compare_variable_to_operand(string_type variable, string_type operand, int err_pos, const ptr_val_type *a_pArg) { + bool comparison_result; + if(operand[0] == '@'){ + Value result = EquationsParser::PureCalc(variable + operand.substr(1)); + if(result.GetType() != 'b'){ + raise_error(ecNOT_BOOL_CASE, err_pos, a_pArg); + } + comparison_result = result.GetBool(); + } else { + comparison_result = (variable.compare(operand) == 0); + } + return comparison_result; + } + + void FunCase::Eval(ptr_val_type &ret, const ptr_val_type *a_pArg, int a_iArgc) + { + if (a_iArgc < 2) { + throw ParserError(ErrorContext(ecTOO_FEW_PARAMS, GetExprPos(), GetIdent())); + } + + string_type variable = a_pArg[0]->GetString(); + string_type operand, result, case_arguments; + bool found_match = false; + for(int i=1; i < a_iArgc && !found_match ; i++) { + case_arguments = a_pArg[i]->GetString(); + size_t pos = case_arguments.find(';'); + if(pos == std::string::npos || pos == 0 || pos == case_arguments.size()){ + raise_error(ecMISSING_CASE_SEPARATOR, i, a_pArg); + } + operand = case_arguments.substr(0, pos); + result = case_arguments.substr(pos + 1); + if(compare_variable_to_operand(variable, operand, i, a_pArg)) { + *ret = get_case_value(result); + found_match = true; + } + } + + if(!found_match){ + case_arguments = a_pArg[a_iArgc - 1]->GetString(); + size_t pos = case_arguments.find(';'); + if(pos == std::string::npos || pos == 0 || pos == case_arguments.size()){ + raise_error(ecMISSING_CASE_SEPARATOR, a_iArgc - 1, a_pArg); + } + operand = case_arguments.substr(0, pos); + result = case_arguments.substr(pos + 1); + if(operand.compare("default") == 0) { + *ret = get_case_value(result); + } else { + raise_error(ecMISSING_CASE_DEFAULT, a_iArgc - 1, a_pArg); + } + } + } + + const char_type* FunCase::GetDesc() const + { + return _T("case(variable, expression_list) - Returns the result of a switch case on variable."); + } + + ////------------------------------------------------------------------------------ + IToken* FunCase::Clone() const + { + return new FunCase(*this); + } //------------------------------------------------------------------------------ // | // Below we have the section related to Date functions | @@ -449,15 +546,6 @@ MUP_NAMESPACE_START *date = *localtime(&new_day); } - void raise_error (EErrorCodes error, int position, const ptr_val_type *arguments) { - ErrorContext err; - err.Errc = error; - err.Arg = position; - err.Type1 = arguments[position - 1]->GetType(); - err.Type2 = 's'; - throw ParserError(err); - } - string_type localized_weekday(int week_day, const ptr_val_type *a_pArg) { string_type locale = a_pArg[1]->GetString(); string_type ret = ""; diff --git a/parser/mpFuncCommon.h b/parser/mpFuncCommon.h index e2c4226..069db81 100644 --- a/parser/mpFuncCommon.h +++ b/parser/mpFuncCommon.h @@ -133,6 +133,19 @@ MUP_NAMESPACE_START virtual IToken* Clone() const override; }; // class FunMask + //------------------------------------------------------------------------------ + /** \brief Returns the result of a case operation. + \ingroup functions + */ + class FunCase: public ICallback + { + public: + FunCase(); + virtual void Eval(ptr_val_type &ret, const ptr_val_type *a_pArg, int a_iArgc) override; + virtual const char_type* GetDesc() const override; + virtual IToken* Clone() const override; + }; // class FunCase + //------------------------------------------------------------------------------ /** \brief Determine the difference in days between two dates. \ingroup functions @@ -238,7 +251,7 @@ MUP_NAMESPACE_START }; // class FunWeekYear //------------------------------------------------------------------------------ - /** \brief Return the week of year of a date. + /** \brief Return the weekday of a date. \ingroup functions */ class FunWeekDay: public ICallback diff --git a/parser/mpPackageCommon.cpp b/parser/mpPackageCommon.cpp index 1add84c..6c04dc1 100644 --- a/parser/mpPackageCommon.cpp +++ b/parser/mpPackageCommon.cpp @@ -93,6 +93,7 @@ void PackageCommon::AddToParser(ParserXBase *pParser) // Special functions pParser->DefineFun(new FunMask()); + pParser->DefineFun(new FunCase()); // Date functions pParser->DefineFun(new FunDaysDiff()); diff --git a/parser/mpParserMessageProvider.cpp b/parser/mpParserMessageProvider.cpp index 73995cc..7a40c0e 100644 --- a/parser/mpParserMessageProvider.cpp +++ b/parser/mpParserMessageProvider.cpp @@ -113,6 +113,9 @@ MUP_NAMESPACE_START m_vErrMsg[ecINVALID_TYPES_MATCH] = _T("Both values of the default(x, y) function should have the same type"); m_vErrMsg[ecUKNOWN_LOCALE] = _T("The chosen locale is not supported"); m_vErrMsg[ecINVALID_TIME_FORMAT] = _T("Invalid time format on parameter(s). Please use the \"HH:MM:SS\" format."); + m_vErrMsg[ecMISSING_CASE_SEPARATOR] = _T("Missing \";\" separator. Please separate the comparison from the result with \";\""); + m_vErrMsg[ecMISSING_CASE_DEFAULT] = _T("Missing default in case operator. Please make sure case has a default or that one or more of the conditions are true"); + m_vErrMsg[ecNOT_BOOL_CASE] = _T("Case comparison not resulting in true or false"); } #if defined(_UNICODE) diff --git a/parser/mpTypes.h b/parser/mpTypes.h index edcfbc3..219b848 100644 --- a/parser/mpTypes.h +++ b/parser/mpTypes.h @@ -375,6 +375,10 @@ enum EErrorCodes // time related errors ecINVALID_TIME_FORMAT = 60, ///< Invalid time format + ecMISSING_CASE_SEPARATOR = 61, ///< Missing ; case separator + ecMISSING_CASE_DEFAULT = 62, ///< Missing default in case operation + ecNOT_BOOL_CASE = 63, ///< Case comparison not resulting in boolean + // The last two are special entries ecCOUNT, ///< This is no error code, It just stores the total number of error codes ecUNDEFINED = -1 ///< Undefined message, placeholder to detect unassigned error messages diff --git a/tests.sh b/tests.sh index 8fdf8e3..9c8c1cf 100755 --- a/tests.sh +++ b/tests.sh @@ -343,6 +343,38 @@ test_eval 'weekday("2019-03-21", "invalidlocale")' 'The chosen locale is not sup test_eval 'weekday("2024-32-21")' 'Invalid format on the parameter(s). Please use two "yyyy-mm-dd" for dates OR two "yyyy-mm-ddTHH:MM" for date_times.' test_eval 'weekday("2024-09-17", "en", "one too many params")' 'Too many parameters passed to function "weekday".' +# case operation tests + +# case textmatch +test_eval 'case("textmatch", "textmatch;matched")' '"matched"' +test_eval 'case("textmatch", "textmatch;matched", "default;not matched")' '"matched"' +test_eval 'case("textmatch", "textmatc;matched", "default;not matched")' '"not matched"' +test_eval 'case("textmatch", "textmatch;@5*5")' '25' +test_eval 'case("textmatch", "textmatc;@5*5", "default;@-1")' '-1' + +# case comparison with variable + +test_eval 'case("5", "@ > 4;yes it is")' '"yes it is"' +test_eval 'case("5", "@ < 4;yes it is", "default;no it is not")' '"no it is not"' +test_eval 'case("5", "@ > 4;@5*6")' '30' +test_eval 'case("5", "@ < 4;@5*6", "default;@-1")' '-1' + +# mix and match +test_eval 'case("5", "@ < 4;yes it is", "5;we can still text match")' '"we can still text match"' +test_eval 'case("5", "@ < 4;yes it is", "default;and use normal default")' '"and use normal default"' +test_eval 'case("5", "@ < 4;yes it is", "5;@5*5")' '25' + +# function inside function +test_eval 'case("textmatch", "textmatch;@sum(1,2,3)")' '6' +test_eval 'case("textmatch", "textmatch;@case(\"7\", \"@>sum(1,2,3);caseception\")")' '"caseception"' +test_eval 'case("textmatch", "textmatch;@case(\"5\", \"@>sum(1,2,3);caseception\", \"default;caseceptionpt2\")")' '"caseceptionpt2"' + +# case errors +test_eval 'case("5", "@ < 4;yes it is")' 'Missing default in case operator. Please make sure case has a default or that one or more of the conditions are true' +test_eval 'case("5", "@ *5;not a boolean")' 'Case comparison not resulting in true or false' +test_eval 'case("5", "@ *5;not a boolean", "default;should fail even with a default")' 'Case comparison not resulting in true or false' +test_eval 'case("textmatch", "textmatch")' 'Missing ";" separator. Please separate the comparison from the result with ";"' +test_eval 'case("textmatch", "textmatch", "default;should fail even with a default")' 'Missing ";" separator. Please separate the comparison from the result with ";"' # Regex match test match_regex 'current_time(5)' '\b([01]?[0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9]\b' match_regex 'current_time()' '\b([01]?[0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9]\b' diff --git a/value_test/mpError.cpp b/value_test/mpError.cpp index eb0d26d..90aa9b4 100644 --- a/value_test/mpError.cpp +++ b/value_test/mpError.cpp @@ -119,6 +119,10 @@ MUP_NAMESPACE_START m_vErrMsg[ecINVALID_TYPES_MATCH] = _T("Both values of the default(x, y) function should have the same type"); m_vErrMsg[ecUKNOWN_LOCALE] = _T("The chosen locale is not supported"); m_vErrMsg[ecINVALID_TIME_FORMAT] = _T("Invalid time format on parameter(s). Please use the \"HH:MM:SS\" format."); + m_vErrMsg[ecMISSING_CASE_SEPARATOR] = _T("Missing \";\" separator. Please separate the comparison from the result with \";\""); + m_vErrMsg[ecMISSING_CASE_DEFAULT] = _T("Missing default in case operator. Please make sure case has a default or that one or more of the conditions are true"); + m_vErrMsg[ecNOT_BOOL_CASE] = _T("Case comparison not resulting in true or false"); + #if defined(_DEBUG) for (int i=0; i