diff --git a/.ecode/project_build.json b/.ecode/project_build.json index 2e783344b..4412d3ba4 100644 --- a/.ecode/project_build.json +++ b/.ecode/project_build.json @@ -223,7 +223,6 @@ "args": "", "command": "${project_root}/bin/unit_tests/eepp-unit_tests-debug", "name": "eepp-unit_tests-debug", - "run_in_terminal": true, "working_dir": "${project_root}/bin/unit_tests/" }, { diff --git a/include/eepp/system/functionstring.hpp b/include/eepp/system/functionstring.hpp index fde8ecadf..4e2300baf 100644 --- a/include/eepp/system/functionstring.hpp +++ b/include/eepp/system/functionstring.hpp @@ -18,13 +18,12 @@ class EE_API FunctionString { const std::vector& getParameters() const; - bool parameterWasString( const Uint32& index ) const; + bool parameterWasString( Uint32 index ) const; bool isEmpty() const; protected: std::string name; - std::vector parameters; std::vector typeStringData; }; diff --git a/src/eepp/system/functionstring.cpp b/src/eepp/system/functionstring.cpp index bf8a43549..924c554ac 100644 --- a/src/eepp/system/functionstring.cpp +++ b/src/eepp/system/functionstring.cpp @@ -5,72 +5,91 @@ namespace EE { namespace System { FunctionString FunctionString::parse( const std::string& function ) { - size_t posFuncStart = function.find_first_of( '(' ); - if ( posFuncStart == std::string::npos ) + size_t funcSep = function.find( '(' ); + if ( funcSep == std::string::npos ) return FunctionString( "", {}, {} ); - size_t posFuncEnd = function.find_last_of( ')' ); - std::string funcName; - std::vector parameters; + + std::string funcName = function.substr( 0, funcSep ); + String::trimInPlace( funcName ); + + std::vector funcParameters; std::vector typeStringData; - if ( std::string::npos != posFuncStart && std::string::npos != posFuncEnd && posFuncStart > 1 && - posFuncStart + 1 < function.size() ) { - funcName = function.substr( 0, posFuncStart ); + std::string parametersString = function.substr( funcSep + 1 ); + size_t paramClose = parametersString.find_last_of( ')' ); + if ( paramClose == std::string::npos ) + return FunctionString( "", {}, {} ); - if ( !funcName.empty() ) { - std::string funcParameters = - function.substr( posFuncStart + 1, posFuncEnd - posFuncStart - 1 ); - std::string curParameter = ""; - bool parsingString = false; - bool lastWasBackslash = false; + parametersString = parametersString.substr( 0, paramClose ); - funcParameters = String::trim( funcParameters ); + bool stateParsingString = false; + std::string buffer = ""; + char prevChar = 0; + bool currentParamIsString = false; + int parenDepth = 0; - for ( size_t i = 0; i < funcParameters.size(); i++ ) { - const char& curChar = funcParameters.at( i ); + for ( size_t i = 0; i < parametersString.length(); ++i ) { + char c = parametersString[i]; - if ( !parsingString ) { - if ( ',' == curChar ) { - curParameter = String::trim( curParameter ); - - if ( !curParameter.empty() ) { - parameters.push_back( curParameter ); - typeStringData.push_back( false ); - curParameter = ""; - } - } else if ( '"' == curChar ) { - parsingString = true; - curParameter = ""; - } else { - curParameter += curChar; + if ( !stateParsingString ) { + if ( c == '(' ) { + parenDepth++; + buffer += c; + } else if ( c == ')' ) { + if ( parenDepth > 0 ) + parenDepth--; + buffer += c; + } else if ( c == ',' ) { + if ( parenDepth == 0 ) { + if ( !buffer.empty() ) { + if ( !currentParamIsString ) + String::trimInPlace( buffer ); + funcParameters.push_back( buffer ); + typeStringData.push_back( currentParamIsString ); + buffer = ""; + currentParamIsString = false; } } else { - if ( '"' == curChar && !lastWasBackslash ) { - parsingString = false; - - if ( !curParameter.empty() ) { - parameters.push_back( curParameter ); - typeStringData.push_back( true ); - curParameter = ""; - } - } else if ( '\\' != curChar || lastWasBackslash ) { - curParameter += curChar; - } + buffer += c; } - - lastWasBackslash = '\\' == curChar; + } else if ( c == '"' ) { + stateParsingString = true; + if ( parenDepth == 0 && buffer.empty() ) { + currentParamIsString = true; + } else { + buffer += c; + } + } else if ( c == ' ' ) { + if ( buffer.empty() || currentParamIsString ) + continue; + else + buffer += c; + } else { + buffer += c; } - - String::trimInPlace( curParameter ); - - if ( !curParameter.empty() ) { - parameters.push_back( curParameter ); - typeStringData.push_back( false ); + } else { + if ( c == '\\' && i + 1 < parametersString.length() && + parametersString[i + 1] == '"' ) { + } else if ( prevChar != '\\' && c == '"' ) { + stateParsingString = false; + if ( !currentParamIsString ) { + buffer += c; + } + } else { + buffer += c; } + prevChar = c; } } - return FunctionString( funcName, parameters, typeStringData ); + if ( !buffer.empty() ) { + if ( !currentParamIsString ) + String::trimInPlace( buffer ); + funcParameters.push_back( buffer ); + typeStringData.push_back( currentParamIsString ); + } + + return FunctionString( funcName, funcParameters, typeStringData ); } FunctionString::FunctionString( const std::string& name, const std::vector& parameters, @@ -85,7 +104,7 @@ const std::vector& FunctionString::getParameters() const { return parameters; } -bool FunctionString::parameterWasString( const Uint32& index ) const { +bool FunctionString::parameterWasString( Uint32 index ) const { eeASSERT( index < parameters.size() ); eeASSERT( index < typeStringData.size() ); return typeStringData[index]; diff --git a/src/tests/unit_tests/functionstring.cpp b/src/tests/unit_tests/functionstring.cpp new file mode 100644 index 000000000..098ab3d58 --- /dev/null +++ b/src/tests/unit_tests/functionstring.cpp @@ -0,0 +1,54 @@ +#include "utest.hpp" +#include + +using namespace std::literals; + +using namespace EE; +using namespace EE::System; + +UTEST( FunctionString, functionString ) { + const auto testCase = [&]( const std::string& in, const std::string& funcName, + const std::vector& parameters ) { + UTEST_PRINT_STEP( funcName.c_str() ); + auto ret = FunctionString::parse( in ); + EXPECT_STDSTREQ( funcName, ret.getName() ); + for ( size_t i = 0; i < parameters.size(); i++ ) { + if ( i < ret.getParameters().size() ) + EXPECT_STDSTREQ( parameters[i], ret.getParameters()[i] ); + else + EXPECT_STDSTREQ( parameters[i], "" ); + } + EXPECT_EQ( parameters.size(), ret.getParameters().size() ); + }; + + testCase( R"(test1(12, 14))", "test1", { "12", "14" } ); + testCase( R"(test2("string", "string2"))", "test2", { "string", "string2" } ); + testCase( R"(test3 (16, 14, 18))", "test3", { "16", "14", "18" } ); + testCase( R"(test4(2.0, 14))", "test4", { "2.0", "14" } ); + testCase( R"(test5("str ing", "stri ng2"))", "test5", { "str ing", "stri ng2" } ); + testCase( R"(test6("str\"ing", "stri\"ng2"))", "test6", { "str\"ing", "stri\"ng2" } ); + testCase( R"(test7("str,ing", "stri,ng2"))", "test7", { "str,ing", "stri,ng2" } ); + testCase( R"(test8("str\\ing", "stri\\ng2"))", "test8", { "str\\\\ing", "stri\\\\ng2" } ); + testCase( R"(test9("12" , "14" ))", "test9", { "12", "14" } ); + testCase( R"(test10(12 , 14 ))", "test10", { "12", "14" } ); + testCase( R"(test11( "12 " , " 14 " ))", "test11", { "12 ", " 14 " } ); + testCase( R"(test12( "12 \" " , " \" 14 \" " ))", "test12", { "12 \" ", " \" 14 \" " } ); + testCase( R"(test13( "\"\"" , "\"\"" ))", "test13", { "\"\"", "\"\"" } ); + testCase( R"(test14( ",,," , 1 , ",,," ))", "test14", { ",,,", "1", ",,," } ); + testCase( R"( test15 ( ",,," , 1 , ",,," ))", "test15", + { ",,,", "1", ",,," } ); + testCase( R"(test16(1,2) )", "test16", { "1", "2" } ); + testCase( R"(test17(func(12,32),2) )", "test17", { "func(12,32)", "2" } ); + testCase( R"(test17(func(12,32),2) )", "test17", { "func(12,32)", "2" } ); + testCase( R"(test18( func(12,32) , 2 ) )", "test18", { "func(12,32)", "2" } ); + testCase( R"(test19(func( "test" , "string" ), call(12,42)))", "test19", + { "func( \"test\" , \"string\" )", "call(12,42)" } ); + testCase( R"(test20(var(--font), var(--back)))", "test20", { "var(--font)", "var(--back)" } ); + testCase( R"(test21( str ing , stri ng2 ))", "test21", { "str ing", "stri ng2" } ); + testCase( R"(test22(" 12 " , " 14 " ))", "test22", + { " 12 ", " 14 " } ); + testCase( R"(test23( s t r i n g , s t r i n g 2 ))", "test23", + { "s t r i n g", "s t r i n g 2" } ); + testCase( R"(test24( func( "test" , "string" ) , call(12,42) ))", "test24", + { "func( \"test\" , \"string\" )", "call(12,42)" } ); +}