diff --git a/CMakeLists.txt b/CMakeLists.txt index 87691d4..30dc824 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,7 +1,7 @@ cmake_minimum_required(VERSION 3.5) -project(inja LANGUAGES CXX VERSION 3.1.0) +project(inja LANGUAGES CXX VERSION 3.3.0) option(INJA_USE_EMBEDDED_JSON "Use the shipped json header if not available on the system" ON) diff --git a/doc/Doxyfile b/doc/Doxyfile index 10bbe55..d00832e 100644 --- a/doc/Doxyfile +++ b/doc/Doxyfile @@ -38,7 +38,7 @@ PROJECT_NAME = "Inja" # could be handy for archiving the generated documentation or if some version # control system is used. -PROJECT_NUMBER = 3.1.0 +PROJECT_NUMBER = 3.3.0 # Using the PROJECT_BRIEF tag one can provide an optional one line description # for a project that appears at the top of each page and should give viewer a diff --git a/include/inja/node.hpp b/include/inja/node.hpp index 84a8b8a..be0bf98 100644 --- a/include/inja/node.hpp +++ b/include/inja/node.hpp @@ -144,76 +144,94 @@ public: std::string name; int number_args; // Should also be negative -> -1 for unknown number + std::vector> arguments; CallbackFunction callback; explicit FunctionNode(nonstd::string_view name, size_t pos) : ExpressionNode(pos), precedence(8), associativity(Associativity::Left), operation(Op::Callback), name(name), number_args(1) { } explicit FunctionNode(Op operation, size_t pos) : ExpressionNode(pos), operation(operation), number_args(1) { switch (operation) { case Op::Not: { + number_args = 1; precedence = 4; associativity = Associativity::Left; } break; case Op::And: { + number_args = 2; precedence = 1; associativity = Associativity::Left; } break; case Op::Or: { + number_args = 2; precedence = 1; associativity = Associativity::Left; } break; case Op::In: { + number_args = 2; precedence = 2; associativity = Associativity::Left; } break; case Op::Equal: { + number_args = 2; precedence = 2; associativity = Associativity::Left; } break; case Op::NotEqual: { + number_args = 2; precedence = 2; associativity = Associativity::Left; } break; case Op::Greater: { + number_args = 2; precedence = 2; associativity = Associativity::Left; } break; case Op::GreaterEqual: { + number_args = 2; precedence = 2; associativity = Associativity::Left; } break; case Op::Less: { + number_args = 2; precedence = 2; associativity = Associativity::Left; } break; case Op::LessEqual: { + number_args = 2; precedence = 2; associativity = Associativity::Left; } break; case Op::Add: { + number_args = 2; precedence = 3; associativity = Associativity::Left; } break; case Op::Subtract: { + number_args = 2; precedence = 3; associativity = Associativity::Left; } break; case Op::Multiplication: { + number_args = 2; precedence = 4; associativity = Associativity::Left; } break; case Op::Division: { + number_args = 2; precedence = 4; associativity = Associativity::Left; } break; case Op::Power: { + number_args = 2; precedence = 5; associativity = Associativity::Right; } break; case Op::Modulo: { + number_args = 2; precedence = 4; associativity = Associativity::Left; } break; case Op::AtId: { + number_args = 2; precedence = 8; associativity = Associativity::Left; } break; @@ -231,7 +249,7 @@ public: class ExpressionListNode : public AstNode { public: - std::vector> rpn_output; + std::shared_ptr root; explicit ExpressionListNode() : AstNode(0) { } explicit ExpressionListNode(size_t pos) : AstNode(pos) { } diff --git a/include/inja/parser.hpp b/include/inja/parser.hpp index 6266c4a..3cf8de4 100644 --- a/include/inja/parser.hpp +++ b/include/inja/parser.hpp @@ -45,16 +45,17 @@ class Parser { BlockNode *current_block {nullptr}; ExpressionListNode *current_expression_list {nullptr}; std::stack> function_stack; + std::vector> arguments; std::stack> operator_stack; std::stack if_statement_stack; std::stack for_statement_stack; - void throw_parser_error(const std::string &message) { + inline void throw_parser_error(const std::string &message) { INJA_THROW(ParserError(message, lexer.current_position())); } - void get_next_token() { + inline void get_next_token() { if (have_peek_tok) { tok = peek_tok; have_peek_tok = false; @@ -63,16 +64,27 @@ class Parser { } } - void get_peek_token() { + inline void get_peek_token() { if (!have_peek_tok) { peek_tok = lexer.scan(); have_peek_tok = true; } } - void add_json_literal(const char* content_ptr) { + inline void add_json_literal(const char* content_ptr) { nonstd::string_view json_text(json_literal_start.data(), tok.text.data() - json_literal_start.data() + tok.text.size()); - current_expression_list->rpn_output.emplace_back(std::make_shared(json::parse(json_text), json_text.data() - content_ptr)); + arguments.emplace_back(std::make_shared(json::parse(json_text), json_text.data() - content_ptr)); + } + + inline void add_operator() { + auto function = operator_stack.top(); + operator_stack.pop(); + + for (size_t i = 0; i < function->number_args; ++i) { + function->arguments.insert(function->arguments.begin(), arguments.back()); + arguments.pop_back(); + } + arguments.emplace_back(function); } bool parse_expression(Template &tmpl, Token::Kind closing) { @@ -150,7 +162,7 @@ class Parser { // Variables } else { - current_expression_list->rpn_output.emplace_back(std::make_shared(static_cast(tok.text), tok.text.data() - tmpl.content.c_str())); + arguments.emplace_back(std::make_shared(static_cast(tok.text), tok.text.data() - tmpl.content.c_str())); } // Operators @@ -231,8 +243,7 @@ class Parser { auto function_node = std::make_shared(operation, tok.text.data() - tmpl.content.c_str()); while (!operator_stack.empty() && ((operator_stack.top()->precedence > function_node->precedence) || (operator_stack.top()->precedence == function_node->precedence && function_node->associativity == FunctionNode::Associativity::Left)) && (operator_stack.top()->operation != FunctionStorage::Operation::ParenLeft)) { - current_expression_list->rpn_output.emplace_back(operator_stack.top()); - operator_stack.pop(); + add_operator(); } operator_stack.emplace(function_node); @@ -269,8 +280,7 @@ class Parser { case Token::Kind::RightParen: { current_paren_level -= 1; while (!operator_stack.empty() && operator_stack.top()->operation != FunctionStorage::Operation::ParenLeft) { - current_expression_list->rpn_output.emplace_back(operator_stack.top()); - operator_stack.pop(); + add_operator(); } if (!operator_stack.empty() && operator_stack.top()->operation == FunctionStorage::Operation::ParenLeft) { @@ -292,8 +302,7 @@ class Parser { throw_parser_error("internal error at function " + func->name); } - current_expression_list->rpn_output.emplace_back(operator_stack.top()); - operator_stack.pop(); + add_operator(); function_stack.pop(); } } @@ -305,10 +314,17 @@ class Parser { } while (!operator_stack.empty()) { - current_expression_list->rpn_output.emplace_back(operator_stack.top()); - operator_stack.pop(); + add_operator(); } + if (arguments.size() == 1) { + current_expression_list->root = arguments[0]; + arguments = {}; + + } else if (arguments.size() > 1) { + throw_parser_error("malformed expression"); + } + return true; } diff --git a/include/inja/renderer.hpp b/include/inja/renderer.hpp index 7bc518f..ad04f27 100644 --- a/include/inja/renderer.hpp +++ b/include/inja/renderer.hpp @@ -63,10 +63,12 @@ class Renderer : public NodeVisitor { } const std::shared_ptr eval_expression_list(const ExpressionListNode& expression_list) { - for (auto& expression : expression_list.rpn_output) { - expression->accept(*this); + if (!expression_list.root) { + throw_renderer_error("empty expression", expression_list); } + expression_list.root->accept(*this); + if (json_eval_stack.empty()) { throw_renderer_error("empty expression", expression_list); } else if (json_eval_stack.size() != 1) { @@ -94,8 +96,16 @@ class Renderer : public NodeVisitor { INJA_THROW(RenderError(message, loc)); } - template - std::array get_arguments(const AstNode& node) { + template + std::array get_arguments(const FunctionNode& node) { + if (node.arguments.size() < N_start + N) { + throw_renderer_error("function needs " + std::to_string(N_start + N) + " variables, but has only found " + std::to_string(node.arguments.size()), node); + } + + for (size_t i = N_start; i < N_start + N; i += 1) { + node.arguments[i]->accept(*this); + } + if (json_eval_stack.size() < N) { throw_renderer_error("function needs " + std::to_string(N) + " variables, but has only found " + std::to_string(json_eval_stack.size()), node); } @@ -118,7 +128,12 @@ class Renderer : public NodeVisitor { } template - Arguments get_argument_vector(size_t N, const AstNode& node) { + Arguments get_argument_vector(const FunctionNode& node) { + const size_t N = node.arguments.size(); + for (auto a: node.arguments) { + a->accept(*this); + } + if (json_eval_stack.size() < N) { throw_renderer_error("function needs " + std::to_string(N) + " variables, but has only found " + std::to_string(json_eval_stack.size()), node); } @@ -190,14 +205,12 @@ class Renderer : public NodeVisitor { json_eval_stack.push(result_ptr.get()); } break; case Op::And: { - auto args = get_arguments<2>(node); - result_ptr = std::make_shared(truthy(args[0]) && truthy(args[1])); + result_ptr = std::make_shared(truthy(get_arguments<1, 0>(node)[0]) && truthy(get_arguments<1, 1>(node)[0])); json_tmp_stack.push_back(result_ptr); json_eval_stack.push(result_ptr.get()); } break; case Op::Or: { - auto args = get_arguments<2>(node); - result_ptr = std::make_shared(truthy(args[0]) || truthy(args[1])); + result_ptr = std::make_shared(truthy(get_arguments<1, 0>(node)[0]) || truthy(get_arguments<1, 1>(node)[0])); json_tmp_stack.push_back(result_ptr); json_eval_stack.push(result_ptr.get()); } break; @@ -308,13 +321,14 @@ class Renderer : public NodeVisitor { json_eval_stack.push(result_ptr.get()); } break; case Op::AtId: { - json_eval_stack.pop(); // Pop id nullptr - auto container = get_arguments<1, false>(node)[0]; + auto container = get_arguments<1, 0, false>(node)[0]; + node.arguments[1]->accept(*this); if (not_found_stack.empty()) { throw_renderer_error("could not find element with given name", node); } auto id_node = not_found_stack.top(); not_found_stack.pop(); + json_eval_stack.pop(); json_eval_stack.push(&container->at(id_node->name)); } break; case Op::At: { @@ -322,9 +336,8 @@ class Renderer : public NodeVisitor { json_eval_stack.push(&args[0]->at(args[1]->get())); } break; case Op::Default: { - auto default_arg = get_arguments<1>(node)[0]; - auto test_arg = get_arguments<1, false>(node)[0]; - json_eval_stack.push(test_arg ? test_arg : default_arg); + auto test_arg = get_arguments<1, 0, false>(node)[0]; + json_eval_stack.push(test_arg ? test_arg : get_arguments<1, 1>(node)[0]); } break; case Op::DivisibleBy: { auto args = get_arguments<2>(node); @@ -465,7 +478,7 @@ class Renderer : public NodeVisitor { json_eval_stack.push(result_ptr.get()); } break; case Op::Callback: { - auto args = get_argument_vector(node.number_args, node); + auto args = get_argument_vector(node); result_ptr = std::make_shared(node.callback(args)); json_tmp_stack.push_back(result_ptr); json_eval_stack.push(result_ptr.get()); diff --git a/include/inja/statistics.hpp b/include/inja/statistics.hpp index 71fc719..045d1a5 100644 --- a/include/inja/statistics.hpp +++ b/include/inja/statistics.hpp @@ -26,14 +26,16 @@ class StatisticsVisitor : public NodeVisitor { variable_counter += 1; } - void visit(const FunctionNode&) { } - - void visit(const ExpressionListNode& node) { - for (auto& n : node.rpn_output) { + void visit(const FunctionNode& node) { + for (auto& n : node.arguments) { n->accept(*this); } } + void visit(const ExpressionListNode& node) { + node.root->accept(*this); + } + void visit(const StatementNode&) { } void visit(const ForStatementNode&) { } diff --git a/single_include/inja/inja.hpp b/single_include/inja/inja.hpp index 42dea1c..e6499c5 100644 --- a/single_include/inja/inja.hpp +++ b/single_include/inja/inja.hpp @@ -2455,76 +2455,94 @@ public: std::string name; int number_args; // Should also be negative -> -1 for unknown number + std::vector> arguments; CallbackFunction callback; explicit FunctionNode(nonstd::string_view name, size_t pos) : ExpressionNode(pos), precedence(8), associativity(Associativity::Left), operation(Op::Callback), name(name), number_args(1) { } explicit FunctionNode(Op operation, size_t pos) : ExpressionNode(pos), operation(operation), number_args(1) { switch (operation) { case Op::Not: { + number_args = 1; precedence = 4; associativity = Associativity::Left; } break; case Op::And: { + number_args = 2; precedence = 1; associativity = Associativity::Left; } break; case Op::Or: { + number_args = 2; precedence = 1; associativity = Associativity::Left; } break; case Op::In: { + number_args = 2; precedence = 2; associativity = Associativity::Left; } break; case Op::Equal: { + number_args = 2; precedence = 2; associativity = Associativity::Left; } break; case Op::NotEqual: { + number_args = 2; precedence = 2; associativity = Associativity::Left; } break; case Op::Greater: { + number_args = 2; precedence = 2; associativity = Associativity::Left; } break; case Op::GreaterEqual: { + number_args = 2; precedence = 2; associativity = Associativity::Left; } break; case Op::Less: { + number_args = 2; precedence = 2; associativity = Associativity::Left; } break; case Op::LessEqual: { + number_args = 2; precedence = 2; associativity = Associativity::Left; } break; case Op::Add: { + number_args = 2; precedence = 3; associativity = Associativity::Left; } break; case Op::Subtract: { + number_args = 2; precedence = 3; associativity = Associativity::Left; } break; case Op::Multiplication: { + number_args = 2; precedence = 4; associativity = Associativity::Left; } break; case Op::Division: { + number_args = 2; precedence = 4; associativity = Associativity::Left; } break; case Op::Power: { + number_args = 2; precedence = 5; associativity = Associativity::Right; } break; case Op::Modulo: { + number_args = 2; precedence = 4; associativity = Associativity::Left; } break; case Op::AtId: { + number_args = 2; precedence = 8; associativity = Associativity::Left; } break; @@ -2542,7 +2560,7 @@ public: class ExpressionListNode : public AstNode { public: - std::vector> rpn_output; + std::shared_ptr root; explicit ExpressionListNode() : AstNode(0) { } explicit ExpressionListNode(size_t pos) : AstNode(pos) { } @@ -2681,14 +2699,16 @@ class StatisticsVisitor : public NodeVisitor { variable_counter += 1; } - void visit(const FunctionNode&) { } - - void visit(const ExpressionListNode& node) { - for (auto& n : node.rpn_output) { + void visit(const FunctionNode& node) { + for (auto& n : node.arguments) { n->accept(*this); } } + void visit(const ExpressionListNode& node) { + node.root->accept(*this); + } + void visit(const StatementNode&) { } void visit(const ForStatementNode&) { } @@ -2781,16 +2801,17 @@ class Parser { BlockNode *current_block {nullptr}; ExpressionListNode *current_expression_list {nullptr}; std::stack> function_stack; + std::vector> arguments; std::stack> operator_stack; std::stack if_statement_stack; std::stack for_statement_stack; - void throw_parser_error(const std::string &message) { + inline void throw_parser_error(const std::string &message) { INJA_THROW(ParserError(message, lexer.current_position())); } - void get_next_token() { + inline void get_next_token() { if (have_peek_tok) { tok = peek_tok; have_peek_tok = false; @@ -2799,16 +2820,27 @@ class Parser { } } - void get_peek_token() { + inline void get_peek_token() { if (!have_peek_tok) { peek_tok = lexer.scan(); have_peek_tok = true; } } - void add_json_literal(const char* content_ptr) { + inline void add_json_literal(const char* content_ptr) { nonstd::string_view json_text(json_literal_start.data(), tok.text.data() - json_literal_start.data() + tok.text.size()); - current_expression_list->rpn_output.emplace_back(std::make_shared(json::parse(json_text), json_text.data() - content_ptr)); + arguments.emplace_back(std::make_shared(json::parse(json_text), json_text.data() - content_ptr)); + } + + inline void add_operator() { + auto function = operator_stack.top(); + operator_stack.pop(); + + for (size_t i = 0; i < function->number_args; ++i) { + function->arguments.insert(function->arguments.begin(), arguments.back()); + arguments.pop_back(); + } + arguments.emplace_back(function); } bool parse_expression(Template &tmpl, Token::Kind closing) { @@ -2886,7 +2918,7 @@ class Parser { // Variables } else { - current_expression_list->rpn_output.emplace_back(std::make_shared(static_cast(tok.text), tok.text.data() - tmpl.content.c_str())); + arguments.emplace_back(std::make_shared(static_cast(tok.text), tok.text.data() - tmpl.content.c_str())); } // Operators @@ -2967,8 +2999,7 @@ class Parser { auto function_node = std::make_shared(operation, tok.text.data() - tmpl.content.c_str()); while (!operator_stack.empty() && ((operator_stack.top()->precedence > function_node->precedence) || (operator_stack.top()->precedence == function_node->precedence && function_node->associativity == FunctionNode::Associativity::Left)) && (operator_stack.top()->operation != FunctionStorage::Operation::ParenLeft)) { - current_expression_list->rpn_output.emplace_back(operator_stack.top()); - operator_stack.pop(); + add_operator(); } operator_stack.emplace(function_node); @@ -3005,8 +3036,7 @@ class Parser { case Token::Kind::RightParen: { current_paren_level -= 1; while (!operator_stack.empty() && operator_stack.top()->operation != FunctionStorage::Operation::ParenLeft) { - current_expression_list->rpn_output.emplace_back(operator_stack.top()); - operator_stack.pop(); + add_operator(); } if (!operator_stack.empty() && operator_stack.top()->operation == FunctionStorage::Operation::ParenLeft) { @@ -3028,8 +3058,7 @@ class Parser { throw_parser_error("internal error at function " + func->name); } - current_expression_list->rpn_output.emplace_back(operator_stack.top()); - operator_stack.pop(); + add_operator(); function_stack.pop(); } } @@ -3041,10 +3070,17 @@ class Parser { } while (!operator_stack.empty()) { - current_expression_list->rpn_output.emplace_back(operator_stack.top()); - operator_stack.pop(); + add_operator(); } + if (arguments.size() == 1) { + current_expression_list->root = arguments[0]; + arguments = {}; + + } else if (arguments.size() > 1) { + throw_parser_error("malformed expression"); + } + return true; } @@ -3388,10 +3424,12 @@ class Renderer : public NodeVisitor { } const std::shared_ptr eval_expression_list(const ExpressionListNode& expression_list) { - for (auto& expression : expression_list.rpn_output) { - expression->accept(*this); + if (!expression_list.root) { + throw_renderer_error("empty expression", expression_list); } + expression_list.root->accept(*this); + if (json_eval_stack.empty()) { throw_renderer_error("empty expression", expression_list); } else if (json_eval_stack.size() != 1) { @@ -3419,8 +3457,16 @@ class Renderer : public NodeVisitor { INJA_THROW(RenderError(message, loc)); } - template - std::array get_arguments(const AstNode& node) { + template + std::array get_arguments(const FunctionNode& node) { + if (node.arguments.size() < N_start + N) { + throw_renderer_error("function needs " + std::to_string(N_start + N) + " variables, but has only found " + std::to_string(node.arguments.size()), node); + } + + for (size_t i = N_start; i < N_start + N; i += 1) { + node.arguments[i]->accept(*this); + } + if (json_eval_stack.size() < N) { throw_renderer_error("function needs " + std::to_string(N) + " variables, but has only found " + std::to_string(json_eval_stack.size()), node); } @@ -3443,7 +3489,12 @@ class Renderer : public NodeVisitor { } template - Arguments get_argument_vector(size_t N, const AstNode& node) { + Arguments get_argument_vector(const FunctionNode& node) { + const size_t N = node.arguments.size(); + for (auto a: node.arguments) { + a->accept(*this); + } + if (json_eval_stack.size() < N) { throw_renderer_error("function needs " + std::to_string(N) + " variables, but has only found " + std::to_string(json_eval_stack.size()), node); } @@ -3515,14 +3566,12 @@ class Renderer : public NodeVisitor { json_eval_stack.push(result_ptr.get()); } break; case Op::And: { - auto args = get_arguments<2>(node); - result_ptr = std::make_shared(truthy(args[0]) && truthy(args[1])); + result_ptr = std::make_shared(truthy(get_arguments<1, 0>(node)[0]) && truthy(get_arguments<1, 1>(node)[0])); json_tmp_stack.push_back(result_ptr); json_eval_stack.push(result_ptr.get()); } break; case Op::Or: { - auto args = get_arguments<2>(node); - result_ptr = std::make_shared(truthy(args[0]) || truthy(args[1])); + result_ptr = std::make_shared(truthy(get_arguments<1, 0>(node)[0]) || truthy(get_arguments<1, 1>(node)[0])); json_tmp_stack.push_back(result_ptr); json_eval_stack.push(result_ptr.get()); } break; @@ -3633,13 +3682,14 @@ class Renderer : public NodeVisitor { json_eval_stack.push(result_ptr.get()); } break; case Op::AtId: { - json_eval_stack.pop(); // Pop id nullptr - auto container = get_arguments<1, false>(node)[0]; + auto container = get_arguments<1, 0, false>(node)[0]; + node.arguments[1]->accept(*this); if (not_found_stack.empty()) { throw_renderer_error("could not find element with given name", node); } auto id_node = not_found_stack.top(); not_found_stack.pop(); + json_eval_stack.pop(); json_eval_stack.push(&container->at(id_node->name)); } break; case Op::At: { @@ -3647,9 +3697,8 @@ class Renderer : public NodeVisitor { json_eval_stack.push(&args[0]->at(args[1]->get())); } break; case Op::Default: { - auto default_arg = get_arguments<1>(node)[0]; - auto test_arg = get_arguments<1, false>(node)[0]; - json_eval_stack.push(test_arg ? test_arg : default_arg); + auto test_arg = get_arguments<1, 0, false>(node)[0]; + json_eval_stack.push(test_arg ? test_arg : get_arguments<1, 1>(node)[0]); } break; case Op::DivisibleBy: { auto args = get_arguments<2>(node); @@ -3790,7 +3839,7 @@ class Renderer : public NodeVisitor { json_eval_stack.push(result_ptr.get()); } break; case Op::Callback: { - auto args = get_argument_vector(node.number_args, node); + auto args = get_argument_vector(node); result_ptr = std::make_shared(node.callback(args)); json_tmp_stack.push_back(result_ptr); json_eval_stack.push(result_ptr.get());