diff --git a/.github/workflows/workflow.yaml b/.github/workflows/workflow.yaml index e9319b0641..37d82b5326 100644 --- a/.github/workflows/workflow.yaml +++ b/.github/workflows/workflow.yaml @@ -5,7 +5,7 @@ jobs: common: # TODO - Start using clang-tidy - requires a bit more setup though - http://clang.llvm.org/docs/HowToSetupToolingForLLVM.html name: Linting & Formatting - runs-on: ubuntu-latest + runs-on: ubuntu-20.04 steps: - name: Checkout Repository uses: actions/checkout@v2 @@ -19,7 +19,7 @@ jobs: ./third-party/run-clang-format/run-clang-format.py -r common decompiler game goalc test --color always build-linux: name: (Linux) Build & Test - runs-on: ubuntu-latest + runs-on: ubuntu-20.04 defaults: run: shell: bash @@ -50,7 +50,7 @@ jobs: build-windows: # Very good resource - https://cristianadam.eu/20191222/using-github-actions-with-c-plus-plus-and-cmake/ name: (Windows) Build & Test - runs-on: windows-latest + runs-on: windows-2019 defaults: run: shell: powershell diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 299930c765..43c0cdc3ee 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -1,3 +1,5 @@ +set(CMAKE_CXX_STANDARD 17) + include("goalc/CMakeLists.txt") add_executable(goalc-test diff --git a/test/goalc/framework/test_runner.cpp b/test/goalc/framework/test_runner.cpp index dd97c3abe3..a9cc841cff 100644 --- a/test/goalc/framework/test_runner.cpp +++ b/test/goalc/framework/test_runner.cpp @@ -11,7 +11,7 @@ #include "goalc/listener/Listener.h" #include "goalc/compiler/Compiler.h" -#include "common\util\FileUtil.h" +#include "common/util/FileUtil.h" #include namespace GoalTest { diff --git a/test/goalc/framework/test_runner.h b/test/goalc/framework/test_runner.h index 4358e42a02..4a47954b7e 100644 --- a/test/goalc/framework/test_runner.h +++ b/test/goalc/framework/test_runner.h @@ -5,7 +5,6 @@ #include "third-party/inja.hpp" #include "goalc/compiler/Compiler.h" -#include "common\util\FileUtil.h" namespace GoalTest { diff --git a/test/goalc/test_arithmetic.cpp b/test/goalc/test_arithmetic.cpp index a423a2693a..6f0014aac4 100644 --- a/test/goalc/test_arithmetic.cpp +++ b/test/goalc/test_arithmetic.cpp @@ -10,9 +10,8 @@ #include "third-party/inja.hpp" #include "third-party/json.hpp" -#include -#include +#include #include #include diff --git a/test/goalc/test_collections.cpp b/test/goalc/test_collections.cpp index ac80aaf318..dc38d944a1 100644 --- a/test/goalc/test_collections.cpp +++ b/test/goalc/test_collections.cpp @@ -8,9 +8,7 @@ #include "third-party/inja.hpp" #include "third-party/json.hpp" -#include - -#include +#include #include #include diff --git a/test/goalc/test_control_statements.cpp b/test/goalc/test_control_statements.cpp index 75376b9247..2f920a3f0d 100644 --- a/test/goalc/test_control_statements.cpp +++ b/test/goalc/test_control_statements.cpp @@ -8,9 +8,7 @@ #include "third-party/inja.hpp" #include "third-party/json.hpp" -#include - -#include +#include #include #include diff --git a/test/goalc/test_float.cpp b/test/goalc/test_float.cpp index 9770313a3f..24819176ac 100644 --- a/test/goalc/test_float.cpp +++ b/test/goalc/test_float.cpp @@ -8,9 +8,7 @@ #include "third-party/inja.hpp" #include "third-party/json.hpp" -#include - -#include +#include #include #include diff --git a/test/goalc/test_functions.cpp b/test/goalc/test_functions.cpp index 2ba12fcad4..eddee8a985 100644 --- a/test/goalc/test_functions.cpp +++ b/test/goalc/test_functions.cpp @@ -8,9 +8,7 @@ #include "third-party/inja.hpp" #include "third-party/json.hpp" -#include - -#include +#include #include #include diff --git a/test/goalc/test_library.cpp b/test/goalc/test_library.cpp index b3a9078ca8..045e881b10 100644 --- a/test/goalc/test_library.cpp +++ b/test/goalc/test_library.cpp @@ -8,9 +8,7 @@ #include "third-party/inja.hpp" #include "third-party/json.hpp" -#include - -#include +#include #include #include diff --git a/test/goalc/test_logic.cpp b/test/goalc/test_logic.cpp index 0057f7b5e6..326a2f97d1 100644 --- a/test/goalc/test_logic.cpp +++ b/test/goalc/test_logic.cpp @@ -8,9 +8,7 @@ #include "third-party/inja.hpp" #include "third-party/json.hpp" -#include - -#include +#include #include #include diff --git a/test/goalc/test_loop_recur.cpp b/test/goalc/test_loop_recur.cpp index cbe9c68f6d..feac01ba33 100644 --- a/test/goalc/test_loop_recur.cpp +++ b/test/goalc/test_loop_recur.cpp @@ -8,9 +8,7 @@ #include "third-party/inja.hpp" #include "third-party/json.hpp" -#include - -#include +#include #include #include diff --git a/test/goalc/test_macros.cpp b/test/goalc/test_macros.cpp index ffe0397542..d92ca35f2b 100644 --- a/test/goalc/test_macros.cpp +++ b/test/goalc/test_macros.cpp @@ -8,9 +8,7 @@ #include "third-party/inja.hpp" #include "third-party/json.hpp" -#include - -#include +#include #include #include diff --git a/test/goalc/test_methods.cpp b/test/goalc/test_methods.cpp index 92b52faaa8..d3026475f0 100644 --- a/test/goalc/test_methods.cpp +++ b/test/goalc/test_methods.cpp @@ -8,9 +8,7 @@ #include "third-party/inja.hpp" #include "third-party/json.hpp" -#include - -#include +#include #include #include diff --git a/test/goalc/test_pointers.cpp b/test/goalc/test_pointers.cpp index a42c753db1..98b75a85f5 100644 --- a/test/goalc/test_pointers.cpp +++ b/test/goalc/test_pointers.cpp @@ -8,9 +8,7 @@ #include "third-party/inja.hpp" #include "third-party/json.hpp" -#include - -#include +#include #include #include diff --git a/test/goalc/test_strings.cpp b/test/goalc/test_strings.cpp index fff413a03f..9db9fcab5c 100644 --- a/test/goalc/test_strings.cpp +++ b/test/goalc/test_strings.cpp @@ -8,9 +8,7 @@ #include "third-party/inja.hpp" #include "third-party/json.hpp" -#include - -#include +#include #include #include diff --git a/test/goalc/test_symbols.cpp b/test/goalc/test_symbols.cpp index 198804d614..196121881d 100644 --- a/test/goalc/test_symbols.cpp +++ b/test/goalc/test_symbols.cpp @@ -8,9 +8,7 @@ #include "third-party/inja.hpp" #include "third-party/json.hpp" -#include - -#include +#include #include #include diff --git a/test/goalc/test_variables.cpp b/test/goalc/test_variables.cpp index 6c6d093377..6caa32202a 100644 --- a/test/goalc/test_variables.cpp +++ b/test/goalc/test_variables.cpp @@ -8,9 +8,7 @@ #include "third-party/inja.hpp" #include "third-party/json.hpp" -#include - -#include +#include #include #include diff --git a/test/goalc/test_with_game.cpp b/test/goalc/test_with_game.cpp index 3daf11b176..94dc1ad3b2 100644 --- a/test/goalc/test_with_game.cpp +++ b/test/goalc/test_with_game.cpp @@ -8,9 +8,8 @@ #include "third-party/inja.hpp" #include "third-party/json.hpp" -#include - -#include +#include "common/util/FileUtil.h" +#include #include #include diff --git a/third-party/inja.hpp b/third-party/inja.hpp index b286ce0264..c32b5ca75e 100644 --- a/third-party/inja.hpp +++ b/third-party/inja.hpp @@ -1,10 +1,8 @@ -// Copyright (c) 2019 Pantor. All rights reserved. +// Copyright (c) 2020 Pantor. All rights reserved. #ifndef INCLUDE_INJA_INJA_HPP_ #define INCLUDE_INJA_INJA_HPP_ -#include - #include // #include "environment.hpp" @@ -14,6 +12,7 @@ #define INCLUDE_INJA_ENVIRONMENT_HPP_ #include +#include #include #include #include @@ -1461,7 +1460,9 @@ struct LexerConfig { std::string statement_close_force_rstrip {"-%}"}; std::string line_statement {"##"}; std::string expression_open {"{{"}; + std::string expression_open_force_lstrip {"{{-"}; std::string expression_close {"}}"}; + std::string expression_close_force_rstrip {"-}}"}; std::string comment_open {"{#"}; std::string comment_close {"#}"}; std::string open_chars {"#{"}; @@ -1486,6 +1487,9 @@ struct LexerConfig { if (open_chars.find(expression_open[0]) == std::string::npos) { open_chars += expression_open[0]; } + if (open_chars.find(expression_open_force_lstrip[0]) == std::string::npos) { + open_chars += expression_open_force_lstrip[0]; + } if (open_chars.find(comment_open[0]) == std::string::npos) { open_chars += comment_open[0]; } @@ -1527,6 +1531,7 @@ using json = nlohmann::json; using Arguments = std::vector; using CallbackFunction = std::function; +using VoidCallbackFunction = std::function; /*! * \brief Class for builtin functions and user-defined callbacks. @@ -1583,14 +1588,15 @@ public: None, }; - const int VARIADIC {-1}; - struct FunctionData { - Operation operation; - - CallbackFunction callback; + explicit FunctionData(const Operation &op, const CallbackFunction &cb = CallbackFunction{}) : operation(op), callback(cb) {} + const Operation operation; + const CallbackFunction callback; }; +private: + const int VARIADIC {-1}; + std::map, FunctionData> function_storage = { {std::make_pair("at", 2), FunctionData { Operation::At }}, {std::make_pair("default", 2), FunctionData { Operation::Default }}, @@ -1642,7 +1648,7 @@ public: } } - return { Operation::None }; + return FunctionData { Operation::None }; } }; @@ -1682,39 +1688,35 @@ struct SourceLocation { }; struct InjaError : public std::runtime_error { - std::string type; - std::string message; + const std::string type; + const std::string message; - bool has_location {false}; - SourceLocation location; + const SourceLocation location; - InjaError(const std::string &type, const std::string &message) - : std::runtime_error("[inja.exception." + type + "] " + message), type(type), message(message) {} + explicit InjaError(const std::string &type, const std::string &message) + : std::runtime_error("[inja.exception." + type + "] " + message), type(type), message(message), location({0, 0}) {} - InjaError(const std::string &type, const std::string &message, SourceLocation location) + explicit InjaError(const std::string &type, const std::string &message, SourceLocation location) : std::runtime_error("[inja.exception." + type + "] (at " + std::to_string(location.line) + ":" + std::to_string(location.column) + ") " + message), - type(type), message(message), has_location(true), location(location) {} + type(type), message(message), location(location) {} }; struct ParserError : public InjaError { - ParserError(const std::string &message) : InjaError("parser_error", message) {} - ParserError(const std::string &message, SourceLocation location) : InjaError("parser_error", message, location) {} + explicit ParserError(const std::string &message, SourceLocation location) : InjaError("parser_error", message, location) {} }; struct RenderError : public InjaError { - RenderError(const std::string &message) : InjaError("render_error", message) {} - RenderError(const std::string &message, SourceLocation location) : InjaError("render_error", message, location) {} + explicit RenderError(const std::string &message, SourceLocation location) : InjaError("render_error", message, location) {} }; struct FileError : public InjaError { - FileError(const std::string &message) : InjaError("file_error", message) {} - FileError(const std::string &message, SourceLocation location) : InjaError("file_error", message, location) {} + explicit FileError(const std::string &message) : InjaError("file_error", message) {} + explicit FileError(const std::string &message, SourceLocation location) : InjaError("file_error", message, location) {} }; struct JsonError : public InjaError { - JsonError(const std::string &message) : InjaError("json_error", message) {} - JsonError(const std::string &message, SourceLocation location) : InjaError("json_error", message, location) {} + explicit JsonError(const std::string &message, SourceLocation location) : InjaError("json_error", message, location) {} }; } // namespace inja @@ -1874,7 +1876,7 @@ inline SourceLocation get_source_location(nonstd::string_view content, size_t po size_t search_start = 0; while (search_start <= sliced.size()) { search_start = sliced.find("\n", search_start) + 1; - if (search_start <= 0) { + if (search_start == 0) { break; } count_lines += 1; @@ -1897,6 +1899,7 @@ class Lexer { enum class State { Text, ExpressionStart, + ExpressionStartForceLstrip, ExpressionBody, LineStart, LineBody, @@ -2146,7 +2149,7 @@ class Lexer { } public: - explicit Lexer(const LexerConfig &config) : config(config) {} + explicit Lexer(const LexerConfig &config) : config(config), state(State::Text), minus_state(MinusState::Number) {} SourceLocation current_position() const { return get_source_location(m_in, tok_start); @@ -2158,6 +2161,11 @@ public: pos = 0; state = State::Text; minus_state = MinusState::Number; + + // Consume byte order mark (BOM) for UTF-8 + if (inja::string_view::starts_with(m_in, "\xEF\xBB\xBF")) { + m_in = m_in.substr(3); + } } Token scan() { @@ -2184,7 +2192,12 @@ public: nonstd::string_view open_str = m_in.substr(pos); bool must_lstrip = false; if (inja::string_view::starts_with(open_str, config.expression_open)) { - state = State::ExpressionStart; + if (inja::string_view::starts_with(open_str, config.expression_open_force_lstrip)) { + state = State::ExpressionStartForceLstrip; + must_lstrip = true; + } else { + state = State::ExpressionStart; + } } else if (inja::string_view::starts_with(open_str, config.statement_open)) { if (inja::string_view::starts_with(open_str, config.statement_open_no_lstrip)) { state = State::StatementStartNoLstrip; @@ -2198,8 +2211,7 @@ public: } else if (inja::string_view::starts_with(open_str, config.comment_open)) { state = State::CommentStart; must_lstrip = config.lstrip_blocks; - } else if ((pos == 0 || m_in[pos - 1] == '\n') && - inja::string_view::starts_with(open_str, config.line_statement)) { + } else if ((pos == 0 || m_in[pos - 1] == '\n') && inja::string_view::starts_with(open_str, config.line_statement)) { state = State::LineStart; } else { pos += 1; // wasn't actually an opening sequence @@ -2221,6 +2233,11 @@ public: pos += config.expression_open.size(); return make_token(Token::Kind::ExpressionOpen); } + case State::ExpressionStartForceLstrip: { + state = State::ExpressionBody; + pos += config.expression_open_force_lstrip.size(); + return make_token(Token::Kind::ExpressionOpen); + } case State::LineStart: { state = State::LineBody; pos += config.line_statement.size(); @@ -2247,7 +2264,7 @@ public: return make_token(Token::Kind::CommentOpen); } case State::ExpressionBody: - return scan_body(config.expression_close, Token::Kind::ExpressionClose); + return scan_body(config.expression_close, Token::Kind::ExpressionClose, config.expression_close_force_rstrip); case State::LineBody: return scan_body("\n", Token::Kind::LineStatementClose); case State::StatementBody: @@ -2313,6 +2330,7 @@ class ForArrayStatementNode; class ForObjectStatementNode; class IfStatementNode; class IncludeStatementNode; +class SetStatementNode; class NodeVisitor { @@ -2330,6 +2348,7 @@ public: virtual void visit(const ForObjectStatementNode& node) = 0; virtual void visit(const IfStatementNode& node) = 0; virtual void visit(const IncludeStatementNode& node) = 0; + virtual void visit(const SetStatementNode& node) = 0; }; /*! @@ -2359,7 +2378,7 @@ public: class TextNode : public AstNode { public: - size_t length; + const size_t length; explicit TextNode(size_t pos, size_t length): AstNode(pos), length(length) { } @@ -2379,7 +2398,7 @@ public: class LiteralNode : public ExpressionNode { public: - nlohmann::json value; + const nlohmann::json value; explicit LiteralNode(const nlohmann::json& value, size_t pos) : ExpressionNode(pos), value(value) { } @@ -2390,8 +2409,8 @@ public: class JsonNode : public ExpressionNode { public: - std::string name; - std::string ptr {""}; + const std::string name; + const json::json_pointer ptr; static std::string convert_dot_to_json_ptr(nonstd::string_view ptr_name) { std::string result; @@ -2404,9 +2423,7 @@ public: return result; } - explicit JsonNode(nonstd::string_view ptr_name, size_t pos) : ExpressionNode(pos), name(ptr_name) { - ptr = convert_dot_to_json_ptr(ptr_name); - } + explicit JsonNode(nonstd::string_view ptr_name, size_t pos) : ExpressionNode(pos), name(ptr_name), ptr(json::json_pointer(convert_dot_to_json_ptr(ptr_name))) { } void accept(NodeVisitor& v) const { v.visit(*this); @@ -2537,18 +2554,18 @@ class ForStatementNode : public StatementNode { public: ExpressionListNode condition; BlockNode body; - BlockNode *parent; + BlockNode *const parent; - ForStatementNode(size_t pos) : StatementNode(pos) { } + ForStatementNode(BlockNode *const parent, size_t pos) : StatementNode(pos), parent(parent) { } virtual void accept(NodeVisitor& v) const = 0; }; class ForArrayStatementNode : public ForStatementNode { public: - nonstd::string_view value; + const std::string value; - explicit ForArrayStatementNode(nonstd::string_view value, size_t pos) : ForStatementNode(pos), value(value) { } + explicit ForArrayStatementNode(const std::string& value, BlockNode *const parent, size_t pos) : ForStatementNode(parent, pos), value(value) { } void accept(NodeVisitor& v) const { v.visit(*this); @@ -2557,10 +2574,10 @@ public: class ForObjectStatementNode : public ForStatementNode { public: - nonstd::string_view key; - nonstd::string_view value; + const std::string key; + const std::string value; - explicit ForObjectStatementNode(nonstd::string_view key, nonstd::string_view value, size_t pos) : ForStatementNode(pos), key(key), value(value) { } + explicit ForObjectStatementNode(const std::string& key, const std::string& value, BlockNode *const parent, size_t pos) : ForStatementNode(parent, pos), key(key), value(value) { } void accept(NodeVisitor& v) const { v.visit(*this); @@ -2572,13 +2589,13 @@ public: ExpressionListNode condition; BlockNode true_statement; BlockNode false_statement; - BlockNode *parent; + BlockNode *const parent; - bool is_nested; + const bool is_nested; bool has_false_statement {false}; - explicit IfStatementNode(size_t pos) : StatementNode(pos), is_nested(false) { } - explicit IfStatementNode(bool is_nested, size_t pos) : StatementNode(pos), is_nested(is_nested) { } + explicit IfStatementNode(BlockNode *const parent, size_t pos) : StatementNode(pos), parent(parent), is_nested(false) { } + explicit IfStatementNode(bool is_nested, BlockNode *const parent, size_t pos) : StatementNode(pos), parent(parent), is_nested(is_nested) { } void accept(NodeVisitor& v) const { v.visit(*this); @@ -2587,7 +2604,7 @@ public: class IncludeStatementNode : public StatementNode { public: - std::string file; + const std::string file; explicit IncludeStatementNode(const std::string& file, size_t pos) : StatementNode(pos), file(file) { } @@ -2596,6 +2613,18 @@ public: }; }; +class SetStatementNode : public StatementNode { +public: + const std::string key; + ExpressionListNode expression; + + explicit SetStatementNode(const std::string& key, size_t pos) : StatementNode(pos), key(key) { } + + void accept(NodeVisitor& v) const { + v.visit(*this); + }; +}; + } // namespace inja #endif // INCLUDE_INJA_NODE_HPP_ @@ -2672,6 +2701,8 @@ class StatisticsVisitor : public NodeVisitor { void visit(const IncludeStatementNode&) { } + void visit(const SetStatementNode&) { } + public: unsigned int variable_counter; @@ -2835,14 +2866,14 @@ class Parser { add_json_literal(tmpl.content.c_str()); } + // Operator + } else if (tok.text == "and" || tok.text == "or" || tok.text == "in" || tok.text == "not") { + goto parse_operator; + // Functions } else if (peek_tok.kind == Token::Kind::LeftParen) { operator_stack.emplace(std::make_shared(static_cast(tok.text), tok.text.data() - tmpl.content.c_str())); - function_stack.emplace(operator_stack.top().get(), current_paren_level); - - // Operator - } else if (tok.text == "and" || tok.text == "or" || tok.text == "in" || tok.text == "not") { - goto parse_operator; + function_stack.emplace(operator_stack.top().get(), current_paren_level); // Variables } else { @@ -2964,12 +2995,12 @@ class Parser { } break; case Token::Kind::RightParen: { current_paren_level -= 1; - while (operator_stack.top()->operation != FunctionStorage::Operation::ParenLeft) { + while (!operator_stack.empty() && operator_stack.top()->operation != FunctionStorage::Operation::ParenLeft) { current_expression_list->rpn_output.emplace_back(operator_stack.top()); operator_stack.pop(); } - if (operator_stack.top()->operation == FunctionStorage::Operation::ParenLeft) { + if (!operator_stack.empty() && operator_stack.top()->operation == FunctionStorage::Operation::ParenLeft) { operator_stack.pop(); } @@ -2984,6 +3015,12 @@ class Parser { func->callback = function_data.callback; } + if (operator_stack.empty()) { + throw_parser_error("internal error at function " + func->name); + } + + current_expression_list->rpn_output.emplace_back(operator_stack.top()); + operator_stack.pop(); function_stack.pop(); } } @@ -3010,9 +3047,8 @@ class Parser { if (tok.text == static_cast("if")) { get_next_token(); - auto if_statement_node = std::make_shared(tok.text.data() - tmpl.content.c_str()); + auto if_statement_node = std::make_shared(current_block, tok.text.data() - tmpl.content.c_str()); current_block->nodes.emplace_back(if_statement_node); - if_statement_node->parent = current_block; if_statement_stack.emplace(if_statement_node.get()); current_block = &if_statement_node->true_statement; current_expression_list = &if_statement_node->condition; @@ -3035,9 +3071,8 @@ class Parser { if (tok.kind == Token::Kind::Id && tok.text == static_cast("if")) { get_next_token(); - auto if_statement_node = std::make_shared(true, tok.text.data() - tmpl.content.c_str()); + auto if_statement_node = std::make_shared(true, current_block, tok.text.data() - tmpl.content.c_str()); current_block->nodes.emplace_back(if_statement_node); - if_statement_node->parent = current_block; if_statement_stack.emplace(if_statement_node.get()); current_block = &if_statement_node->true_statement; current_expression_list = &if_statement_node->condition; @@ -3086,15 +3121,14 @@ class Parser { value_token = tok; get_next_token(); - for_statement_node = std::make_shared(key_token.text, value_token.text, tok.text.data() - tmpl.content.c_str()); + for_statement_node = std::make_shared(static_cast(key_token.text), static_cast(value_token.text), current_block, tok.text.data() - tmpl.content.c_str()); // Array type } else { - for_statement_node = std::make_shared(value_token.text, tok.text.data() - tmpl.content.c_str()); + for_statement_node = std::make_shared(static_cast(value_token.text), current_block, tok.text.data() - tmpl.content.c_str()); } current_block->nodes.emplace_back(for_statement_node); - for_statement_node->parent = current_block; for_statement_stack.emplace(for_statement_node.get()); current_block = &for_statement_node->body; current_expression_list = &for_statement_node->condition; @@ -3145,6 +3179,29 @@ class Parser { get_next_token(); + } else if (tok.text == static_cast("set")) { + get_next_token(); + + if (tok.kind != Token::Kind::Id) { + throw_parser_error("expected variable name, got '" + tok.describe() + "'"); + } + + std::string key = static_cast(tok.text); + get_next_token(); + + auto set_statement_node = std::make_shared(key, tok.text.data() - tmpl.content.c_str()); + current_block->nodes.emplace_back(set_statement_node); + current_expression_list = &set_statement_node->expression; + + if (tok.text != static_cast("=")) { + throw_parser_error("expected '=', got '" + tok.describe() + "'"); + } + get_next_token(); + + if (!parse_expression(tmpl, closing)) { + return false; + } + } else { return false; } @@ -3292,32 +3349,30 @@ class Renderer : public NodeVisitor { const json *json_input; std::ostream *output_stream; - json json_loop_data; - json* current_loop_data = &json_loop_data["loop"]; + json json_additional_data; + json* current_loop_data = &json_additional_data["loop"]; std::vector> json_tmp_stack; std::stack json_eval_stack; std::stack not_found_stack; bool truthy(const json* data) const { - if (data->empty()) { - return false; + if (data->is_boolean()) { + return data->get(); } else if (data->is_number()) { return (*data != 0); - } else if (data->is_string()) { - return !data->empty(); - } - - try { - return data->get(); - } catch (json::type_error &e) { - throw JsonError(e.what()); + } else if (data->is_null()) { + return false; } + return !data->empty(); } - void print_json(const json* value) { + void print_json(const std::shared_ptr value) { if (value->is_string()) { - *output_stream << value->get_ref(); + *output_stream << value->get_ref(); + } else if (value->is_number_integer()) { + *output_stream << value->get(); + } else if (value->is_null()) { } else { *output_stream << value->dump(); } @@ -3330,9 +3385,7 @@ class Renderer : public NodeVisitor { if (json_eval_stack.empty()) { throw_renderer_error("empty expression", expression_list); - } - - if (json_eval_stack.size() != 1) { + } else if (json_eval_stack.size() != 1) { throw_renderer_error("malformed expression", expression_list); } @@ -3416,17 +3469,13 @@ class Renderer : public NodeVisitor { } void visit(const JsonNode& node) { - auto ptr = json::json_pointer(node.ptr); - - try { - // First try to evaluate as a loop variable - if (json_loop_data.contains(ptr)) { - json_eval_stack.push(&json_loop_data.at(ptr)); - } else { - json_eval_stack.push(&json_input->at(ptr)); - } - - } catch (std::exception &) { + if (json_additional_data.contains(node.ptr)) { + json_eval_stack.push(&(json_additional_data[node.ptr])); + + } else if (json_input->contains(node.ptr)) { + json_eval_stack.push(&(*json_input)[node.ptr]); + + } else { // Try to evaluate as a no-argument callback auto function_data = function_storage.find_function(node.name, 0); if (function_data.operation == FunctionStorage::Operation::Callback) { @@ -3509,7 +3558,7 @@ class Renderer : public NodeVisitor { case Op::Add: { auto args = get_arguments<2>(node); if (args[0]->is_string() && args[1]->is_string()) { - result_ptr = std::make_shared(args[0]->get() + args[1]->get()); + result_ptr = std::make_shared(args[0]->get_ref() + args[1]->get_ref()); json_tmp_stack.push_back(result_ptr); } else if (args[0]->is_number_integer() && args[1]->is_number_integer()) { result_ptr = std::make_shared(args[0]->get() + args[1]->get()); @@ -3741,7 +3790,7 @@ class Renderer : public NodeVisitor { } void visit(const ExpressionListNode& node) { - print_json(eval_expression_list(node).get()); + print_json(eval_expression_list(node)); } void visit(const StatementNode&) { } @@ -3759,24 +3808,31 @@ class Renderer : public NodeVisitor { (*current_loop_data)["parent"] = std::move(tmp); } + size_t index = 0; + (*current_loop_data)["is_first"] = true; + (*current_loop_data)["is_last"] = (result->size() <= 1); for (auto it = result->begin(); it != result->end(); ++it) { - json_loop_data[static_cast(node.value)] = *it; + json_additional_data[static_cast(node.value)] = *it; - size_t index = std::distance(result->begin(), it); (*current_loop_data)["index"] = index; (*current_loop_data)["index1"] = index + 1; - (*current_loop_data)["is_first"] = (index == 0); - (*current_loop_data)["is_last"] = (index == result->size() - 1); + if (index == 1) { + (*current_loop_data)["is_first"] = false; + } + if (index == result->size() - 1) { + (*current_loop_data)["is_last"] = true; + } node.body.accept(*this); + ++index; } - json_loop_data[static_cast(node.value)].clear(); + json_additional_data[static_cast(node.value)].clear(); if (!(*current_loop_data)["parent"].empty()) { auto tmp = (*current_loop_data)["parent"]; *current_loop_data = std::move(tmp); } else { - current_loop_data = &json_loop_data["loop"]; + current_loop_data = &json_additional_data["loop"]; } } @@ -3790,25 +3846,32 @@ class Renderer : public NodeVisitor { (*current_loop_data)["parent"] = std::move(*current_loop_data); } + size_t index = 0; + (*current_loop_data)["is_first"] = true; + (*current_loop_data)["is_last"] = (result->size() <= 1); for (auto it = result->begin(); it != result->end(); ++it) { - json_loop_data[static_cast(node.key)] = it.key(); - json_loop_data[static_cast(node.value)] = it.value(); + json_additional_data[static_cast(node.key)] = it.key(); + json_additional_data[static_cast(node.value)] = it.value(); - size_t index = std::distance(result->begin(), it); (*current_loop_data)["index"] = index; (*current_loop_data)["index1"] = index + 1; - (*current_loop_data)["is_first"] = (index == 0); - (*current_loop_data)["is_last"] = (index == result->size() - 1); + if (index == 1) { + (*current_loop_data)["is_first"] = false; + } + if (index == result->size() - 1) { + (*current_loop_data)["is_last"] = true; + } node.body.accept(*this); + ++index; } - json_loop_data[static_cast(node.key)].clear(); - json_loop_data[static_cast(node.value)].clear(); + json_additional_data[static_cast(node.key)].clear(); + json_additional_data[static_cast(node.value)].clear(); if (!(*current_loop_data)["parent"].empty()) { *current_loop_data = std::move((*current_loop_data)["parent"]); } else { - current_loop_data = &json_loop_data["loop"]; + current_loop_data = &json_additional_data["loop"]; } } @@ -3826,12 +3889,16 @@ class Renderer : public NodeVisitor { auto included_template_it = template_storage.find(node.file); if (included_template_it != template_storage.end()) { - sub_renderer.render_to(*output_stream, included_template_it->second, *json_input, &json_loop_data); + sub_renderer.render_to(*output_stream, included_template_it->second, *json_input, &json_additional_data); } else if (config.throw_at_missing_includes) { throw_renderer_error("include '" + node.file + "' not found", node); } } + void visit(const SetStatementNode& node) { + json_additional_data[node.key] = *eval_expression_list(node.expression); + } + public: Renderer(const RenderConfig& config, const TemplateStorage &template_storage, const FunctionStorage &function_storage) : config(config), template_storage(template_storage), function_storage(function_storage) { } @@ -3841,7 +3908,8 @@ public: current_template = &tmpl; json_input = &data; if (loop_data) { - json_loop_data = *loop_data; + json_additional_data = *loop_data; + current_loop_data = &json_additional_data["loop"]; } current_template->root.accept(*this); @@ -3906,7 +3974,9 @@ public: /// Sets the opener and closer for template expressions void set_expression(const std::string &open, const std::string &close) { lexer_config.expression_open = open; + lexer_config.expression_open_force_lstrip = open + "-"; lexer_config.expression_close = close; + lexer_config.expression_close_force_rstrip = "-" + close; lexer_config.update_open_chars(); } @@ -4015,7 +4085,14 @@ public: @brief Adds a variadic callback */ void add_callback(const std::string &name, const CallbackFunction &callback) { - function_storage.add_callback(name, -1, callback); + add_callback(name, -1, callback); + } + + /*! + @brief Adds a variadic void callback + */ + void add_void_callback(const std::string &name, const VoidCallbackFunction &callback) { + add_void_callback(name, -1, callback); } /*! @@ -4025,6 +4102,13 @@ public: function_storage.add_callback(name, num_args, callback); } + /*! + @brief Adds a void callback with given number or arguments + */ + void add_void_callback(const std::string &name, int num_args, const VoidCallbackFunction &callback) { + function_storage.add_callback(name, num_args, [callback](Arguments& args) { callback(args); return json(); }); + } + /** Includes a template with a given name into the environment. * Then, a template can be rendered in another template using the * include "" syntax.