diff --git a/doc/logo.jpg b/doc/logo.jpg index c7aa137..4181d4d 100644 Binary files a/doc/logo.jpg and b/doc/logo.jpg differ diff --git a/src/inja.hpp b/src/inja.hpp index 4dc911f..66b3b11 100644 --- a/src/inja.hpp +++ b/src/inja.hpp @@ -111,7 +111,7 @@ inline Match search(const std::string& input, std::vector regexes, size_t } } - int number_regex = 0, number_inner = 1; + int number_regex = -1, number_inner = 1; for (int i = 1; i < search_match.size(); i++) { if (search_match.length(i) > 0) { number_inner = i; @@ -122,11 +122,11 @@ inline Match search(const std::string& input, std::vector regexes, size_t search_match.setGroupOffset(number_inner); search_match.setRegexNumber(number_regex); - search_match.setRegex(regexes[number_regex]); + if (number_regex >= 0) { search_match.setRegex(regexes[number_regex]); } return search_match; } -inline MatchClosed search_closed_match_on_level(const std::string& input, Regex regex_statement, Regex regex_level_up, Regex regex_level_down, Regex regex_search, Match open_match) { +inline MatchClosed search_closed_on_level(const std::string& input, Regex regex_statement, Regex regex_level_up, Regex regex_level_down, Regex regex_search, Match open_match) { int level = 0; size_t current_position = open_match.end_position(); @@ -145,8 +145,21 @@ inline MatchClosed search_closed_match_on_level(const std::string& input, Regex return MatchClosed(open_match, match_delimiter); } -inline MatchClosed search_closed_match(std::string input, Regex regex_statement, Regex regex_open, Regex regex_close, Match open_match) { - return search_closed_match_on_level(input, regex_statement, regex_open, regex_close, regex_close, open_match); +inline MatchClosed search_closed(std::string input, Regex regex_statement, Regex regex_open, Regex regex_close, Match open_match) { + return search_closed_on_level(input, regex_statement, regex_open, regex_close, regex_close, open_match); +} + +inline Match match(std::string input, std::vector regexes) { + Match match; + match.setRegexNumber(-1); + for (int i = 0; i < regexes.size(); i++) { + if (std::regex_match(input, match, regexes[i])) { + match.setRegexNumber(i); + match.setRegex(regexes[i]); + break; + } + } + return match; } @@ -176,32 +189,66 @@ public: {Delimiter::Comment, Regex{"\\{#\\s*(.*?)\\s*#\\}"}} }; - const Regex regex_loop_open{"for (.*)"}; + enum class Statement { + Loop, + Condition, + Include + }; + + const std::map regex_map_statement_openers = { + {Statement::Loop, Regex{"for (.*)"}}, + {Statement::Condition, Regex{"if (.*)"}}, + {Statement::Include, Regex{"include \"(.*)\""}} + }; + + const Regex regex_loop_open = regex_map_statement_openers.at(Statement::Loop); const Regex regex_loop_in_list{"for (\\w+) in (.+)"}; const Regex regex_loop_close{"endfor"}; - const Regex regex_include{"include \"(.*)\""}; - - const Regex regex_condition_open{"if (.*)"}; + const Regex regex_condition_open = regex_map_statement_openers.at(Statement::Condition); const Regex regex_condition_else_if{"else if (.*)"}; const Regex regex_condition_else{"else"}; const Regex regex_condition_close{"endif"}; - const Regex regex_condition_not{"not (.+)"}; - const Regex regex_condition_and{"(.+) and (.+)"}; - const Regex regex_condition_or{"(.+) or (.+)"}; - const Regex regex_condition_in{"(.+) in (.+)"}; - const Regex regex_condition_equal{"(.+) == (.+)"}; - const Regex regex_condition_greater{"(.+) > (.+)"}; - const Regex regex_condition_less{"(.+) < (.+)"}; - const Regex regex_condition_greater_equal{"(.+) >= (.+)"}; - const Regex regex_condition_less_equal{"(.+) <= (.+)"}; - const Regex regex_condition_different{"(.+) != (.+)"}; + enum class ConditionOperators { + Not, + And, + Or, + In, + Equal, + Greater, + Less, + GreaterEqual, + LessEqual, + Different + }; - const Regex regex_function_upper{"upper\\(\\s*(.*?)\\s*\\)"}; - const Regex regex_function_lower{"lower\\(\\s*(.*?)\\s*\\)"}; - const Regex regex_function_range{"range\\(\\s*(.*?)\\s*\\)"}; - const Regex regex_function_length{"length\\(\\s*(.*?)\\s*\\)"}; + const std::map regex_map_condition_operators = { + {ConditionOperators::Not, Regex{"not (.+)"}}, + {ConditionOperators::And, Regex{"(.+) and (.+)"}}, + {ConditionOperators::Or, Regex{"(.+) or (.+)"}}, + {ConditionOperators::In, Regex{"(.+) in (.+)"}}, + {ConditionOperators::Equal, Regex{"(.+) == (.+)"}}, + {ConditionOperators::Greater, Regex{"(.+) > (.+)"}}, + {ConditionOperators::Less, Regex{"(.+) < (.+)"}}, + {ConditionOperators::GreaterEqual, Regex{"(.+) >= (.+)"}}, + {ConditionOperators::LessEqual, Regex{"(.+) <= (.+)"}}, + {ConditionOperators::Different, Regex{"(.+) != (.+)"}} + }; + + enum class Function { + Upper, + Lower, + Range, + Length + }; + + const std::map regex_map_functions = { + {Function::Upper, Regex{"upper\\(\\s*(.*?)\\s*\\)"}}, + {Function::Lower, Regex{"lower\\(\\s*(.*?)\\s*\\)"}}, + {Function::Range, Regex{"range\\(\\s*(.*?)\\s*\\)"}}, + {Function::Length, Regex{"length\\(\\s*(.*?)\\s*\\)"}}, + }; Parser() { } @@ -229,48 +276,51 @@ public: case Delimiter::Statement: case Delimiter::LineStatement: { - Match inner_match_delimiter; - // Loop - if (std::regex_match(delimiter_inner, inner_match_delimiter, regex_loop_open)) { - MatchClosed loop_match = search_closed_match(input, match_delimiter.regex(), regex_loop_open, regex_loop_close, match_delimiter); + Match match_statement = match(delimiter_inner, get_values(regex_map_statement_openers)); + switch ( static_cast(match_statement.regex_number()) ) { + case Statement::Loop: { + MatchClosed loop_match = search_closed(input, match_delimiter.regex(), regex_loop_open, regex_loop_close, match_delimiter); - current_position = loop_match.end_position(); - std::string loop_command = inner_match_delimiter.str(0); - result += element(Type::Loop, {{"command", loop_command}, {"inner", loop_match.inner()}}); - } - // Include - else if (std::regex_match(delimiter_inner, inner_match_delimiter, regex_include)) { - std::string filename = inner_match_delimiter.str(1); - result += element(Type::Include, {{"filename", filename}}); - } - // Condition - else if (std::regex_match(delimiter_inner, inner_match_delimiter, regex_condition_open)) { - json condition_result = element(Parser::Type::Condition, {{"children", json::array()}}); - - Match condition_match = match_delimiter; - - MatchClosed else_if_match = search_closed_match_on_level(input, match_delimiter.regex(), regex_condition_open, regex_condition_close, regex_condition_else_if, condition_match); - while (else_if_match.found()) { - condition_match = else_if_match.close_match; - - condition_result["children"] += element(Type::ConditionBranch, {{"command", else_if_match.open_match.str(1)}, {"inner", else_if_match.inner()}}); - - else_if_match = search_closed_match_on_level(input, match_delimiter.regex(), regex_condition_open, regex_condition_close, regex_condition_else_if, condition_match); + current_position = loop_match.end_position(); + std::string loop_command = match_statement.str(0); + result += element(Type::Loop, {{"command", loop_command}, {"inner", loop_match.inner()}}); + break; } + case Statement::Condition: { + json condition_result = element(Parser::Type::Condition, {{"children", json::array()}}); - MatchClosed else_match = search_closed_match_on_level(input, match_delimiter.regex(), regex_condition_open, regex_condition_close, regex_condition_else, condition_match); - if (else_match.found()) { - condition_match = else_match.close_match; + Match condition_match = match_delimiter; - condition_result["children"] += element(Type::ConditionBranch, {{"command", else_match.open_match.str(1)}, {"inner", else_match.inner()}}); + MatchClosed else_if_match = search_closed_on_level(input, match_delimiter.regex(), regex_condition_open, regex_condition_close, regex_condition_else_if, condition_match); + while (else_if_match.found()) { + condition_match = else_if_match.close_match; + + condition_result["children"] += element(Type::ConditionBranch, {{"command", else_if_match.open_match.str(1)}, {"inner", else_if_match.inner()}}); + + else_if_match = search_closed_on_level(input, match_delimiter.regex(), regex_condition_open, regex_condition_close, regex_condition_else_if, condition_match); + } + + MatchClosed else_match = search_closed_on_level(input, match_delimiter.regex(), regex_condition_open, regex_condition_close, regex_condition_else, condition_match); + if (else_match.found()) { + condition_match = else_match.close_match; + + condition_result["children"] += element(Type::ConditionBranch, {{"command", else_match.open_match.str(1)}, {"inner", else_match.inner()}}); + } + + MatchClosed last_if_match = search_closed(input, match_delimiter.regex(), regex_condition_open, regex_condition_close, condition_match); + + condition_result["children"] += element(Type::ConditionBranch, {{"command", last_if_match.open_match.str(1)}, {"inner", last_if_match.inner()}}); + + current_position = last_if_match.end_position(); + result += condition_result; + break; } - - MatchClosed last_if_match = search_closed_match(input, match_delimiter.regex(), regex_condition_open, regex_condition_close, condition_match); - - condition_result["children"] += element(Type::ConditionBranch, {{"command", last_if_match.open_match.str(1)}, {"inner", last_if_match.inner()}}); - - current_position = last_if_match.end_position(); - result += condition_result; + case Statement::Include: { + std::string filename = match_statement.str(1); + result += element(Type::Include, {{"filename", filename}}); + break; + } + default: { throw std::runtime_error("Parser error: Unknown statement."); } } break; @@ -335,32 +385,34 @@ public: // Json Raw Data if ( json::accept(input) ) { return json::parse(input); } - Match match_function; - if (std::regex_match(input, match_function, parser.regex_function_upper)) { - json str = eval_variable(match_function.str(1), data); - if (not str.is_string()) { throw std::runtime_error("Argument in upper function is not a string."); } - std::string data = str.get(); - std::transform(data.begin(), data.end(), data.begin(), toupper); - return data; - } - else if (std::regex_match(input, match_function, parser.regex_function_lower)) { - json str = eval_variable(match_function.str(1), data); - if (not str.is_string()) { throw std::runtime_error("Argument in lower function is not a string."); } - std::string data = str.get(); - std::transform(data.begin(), data.end(), data.begin(), tolower); - return data; - } - else if (std::regex_match(input, match_function, parser.regex_function_range)) { - json number = eval_variable(match_function.str(1), data); - if (not number.is_number()) { throw std::runtime_error("Argument in range function is not a number."); } - std::vector result(number.get()); - std::iota(std::begin(result), std::end(result), 0); - return result; - } - else if (std::regex_match(input, match_function, parser.regex_function_length)) { - json list = eval_variable(match_function.str(1), data); - if (not list.is_array()) { throw std::runtime_error("Argument in length function is not a list."); } - return list.size(); + Match match_function = match(input, get_values(parser.regex_map_functions)); + switch ( static_cast(match_function.regex_number()) ) { + case Parser::Function::Upper: { + json str = eval_variable(match_function.str(1), data); + if (not str.is_string()) { throw std::runtime_error("Argument in upper function is not a string."); } + std::string data = str.get(); + std::transform(data.begin(), data.end(), data.begin(), toupper); + return data; + } + case Parser::Function::Lower: { + json str = eval_variable(match_function.str(1), data); + if (not str.is_string()) { throw std::runtime_error("Argument in lower function is not a string."); } + std::string data = str.get(); + std::transform(data.begin(), data.end(), data.begin(), tolower); + return data; + } + case Parser::Function::Range: { + json number = eval_variable(match_function.str(1), data); + if (not number.is_number()) { throw std::runtime_error("Argument in range function is not a number."); } + std::vector result(number.get()); + std::iota(std::begin(result), std::end(result), 0); + return result; + } + case Parser::Function::Length: { + json list = eval_variable(match_function.str(1), data); + if (not list.is_array()) { throw std::runtime_error("Argument in length function is not a list."); } + return list.size(); + } } if (input[0] != '/') { input.insert(0, "/"); } @@ -372,50 +424,52 @@ public: } bool eval_condition(std::string condition, json data) { - Match match_condition; - if (std::regex_match(condition, match_condition, parser.regex_condition_not)) { - return not eval_condition(match_condition.str(1), data); - } - else if (std::regex_match(condition, match_condition, parser.regex_condition_and)) { - return (eval_condition(match_condition.str(1), data) and eval_condition(match_condition.str(2), data)); - } - else if (std::regex_match(condition, match_condition, parser.regex_condition_or)) { - return (eval_condition(match_condition.str(1), data) or eval_condition(match_condition.str(2), data)); - } - else if (std::regex_match(condition, match_condition, parser.regex_condition_in)) { - json item = eval_variable(match_condition.str(1), data); - json list = eval_variable(match_condition.str(2), data); - return (std::find(list.begin(), list.end(), item) != list.end()); - } - else if (std::regex_match(condition, match_condition, parser.regex_condition_equal)) { - json comp1 = eval_variable(match_condition.str(1), data); - json comp2 = eval_variable(match_condition.str(2), data); - return comp1 == comp2; - } - else if (std::regex_match(condition, match_condition, parser.regex_condition_greater)) { - json comp1 = eval_variable(match_condition.str(1), data); - json comp2 = eval_variable(match_condition.str(2), data); - return comp1 > comp2; - } - else if (std::regex_match(condition, match_condition, parser.regex_condition_less)) { - json comp1 = eval_variable(match_condition.str(1), data); - json comp2 = eval_variable(match_condition.str(2), data); - return comp1 < comp2; - } - else if (std::regex_match(condition, match_condition, parser.regex_condition_greater_equal)) { - json comp1 = eval_variable(match_condition.str(1), data); - json comp2 = eval_variable(match_condition.str(2), data); - return comp1 >= comp2; - } - else if (std::regex_match(condition, match_condition, parser.regex_condition_less_equal)) { - json comp1 = eval_variable(match_condition.str(1), data); - json comp2 = eval_variable(match_condition.str(2), data); - return comp1 <= comp2; - } - else if (std::regex_match(condition, match_condition, parser.regex_condition_different)) { - json comp1 = eval_variable(match_condition.str(1), data); - json comp2 = eval_variable(match_condition.str(2), data); - return comp1 != comp2; + Match match_condition = match(condition, get_values(parser.regex_map_condition_operators)); + switch ( static_cast(match_condition.regex_number()) ) { + case Parser::ConditionOperators::Not: { + return not eval_condition(match_condition.str(1), data); + } + case Parser::ConditionOperators::And: { + return (eval_condition(match_condition.str(1), data) and eval_condition(match_condition.str(2), data)); + } + case Parser::ConditionOperators::Or: { + return (eval_condition(match_condition.str(1), data) or eval_condition(match_condition.str(2), data)); + } + case Parser::ConditionOperators::In: { + json item = eval_variable(match_condition.str(1), data); + json list = eval_variable(match_condition.str(2), data); + return (std::find(list.begin(), list.end(), item) != list.end()); + } + case Parser::ConditionOperators::Equal: { + json comp1 = eval_variable(match_condition.str(1), data); + json comp2 = eval_variable(match_condition.str(2), data); + return comp1 == comp2; + } + case Parser::ConditionOperators::Greater: { + json comp1 = eval_variable(match_condition.str(1), data); + json comp2 = eval_variable(match_condition.str(2), data); + return comp1 > comp2; + } + case Parser::ConditionOperators::Less: { + json comp1 = eval_variable(match_condition.str(1), data); + json comp2 = eval_variable(match_condition.str(2), data); + return comp1 < comp2; + } + case Parser::ConditionOperators::GreaterEqual: { + json comp1 = eval_variable(match_condition.str(1), data); + json comp2 = eval_variable(match_condition.str(2), data); + return comp1 >= comp2; + } + case Parser::ConditionOperators::LessEqual: { + json comp1 = eval_variable(match_condition.str(1), data); + json comp2 = eval_variable(match_condition.str(2), data); + return comp1 <= comp2; + } + case Parser::ConditionOperators::Different: { + json comp1 = eval_variable(match_condition.str(1), data); + json comp2 = eval_variable(match_condition.str(2), data); + return comp1 != comp2; + } } json var = eval_variable(condition, data, false); diff --git a/test/src/unit-parser.cpp b/test/src/unit-parser.cpp index 87911d6..5bb5b50 100644 --- a/test/src/unit-parser.cpp +++ b/test/src/unit-parser.cpp @@ -75,11 +75,11 @@ TEST_CASE("Parse structure") { } SECTION("Basic conditional") { - std::string test = "{% if true %}dfgh{% endif %}"; + std::string test = "{% if true %}Hello{% endif %}"; json result = { {{"type", Type::Condition}, {"children", { {{"type", Type::ConditionBranch}, {"command", "if true"}, {"children", { - {{"type", Type::String}, {"text", "dfgh"}} + {{"type", Type::String}, {"text", "Hello"}} }}} }}} }; diff --git a/test/src/unit-renderer.cpp b/test/src/unit-renderer.cpp index 83fa702..f6b2d3a 100644 --- a/test/src/unit-renderer.cpp +++ b/test/src/unit-renderer.cpp @@ -3,12 +3,11 @@ #include "inja.hpp" -using Environment = inja::Environment; using json = nlohmann::json; TEST_CASE("Renderer") { - Environment env = Environment(); + inja::Environment env = inja::Environment(); json data; data["name"] = "Peter"; data["city"] = "Brunswick"; diff --git a/test/src/unit-string-helper.cpp b/test/src/unit-string-helper.cpp index 1e1c8f2..eff0001 100644 --- a/test/src/unit-string-helper.cpp +++ b/test/src/unit-string-helper.cpp @@ -59,7 +59,7 @@ TEST_CASE("Search on level") { CHECK( open_match.end_position() == 8 ); CHECK( open_match.str(1) == "up" ); - inja::MatchClosed match = inja::search_closed_match_on_level(input, regex_statement, regex_level_up, regex_level_down, regex_search, open_match); + inja::MatchClosed match = inja::search_closed_on_level(input, regex_statement, regex_level_up, regex_level_down, regex_search, open_match); CHECK( match.position() == 0 ); CHECK( match.end_position() == 109 ); } @@ -71,7 +71,7 @@ TEST_CASE("Search on level") { CHECK( open_match.end_position() == 16 ); CHECK( open_match.str(1) == "up" ); - inja::MatchClosed match = inja::search_closed_match_on_level(input, regex_statement, regex_level_up, regex_level_down, regex_search, open_match); + inja::MatchClosed match = inja::search_closed_on_level(input, regex_statement, regex_level_up, regex_level_down, regex_search, open_match); CHECK( match.open_match.position() == 8 ); CHECK( match.open_match.end_position() == 16 );