From a9d4ca9a4e4f3ac1f673e68e1cef69540f98747c Mon Sep 17 00:00:00 2001 From: Lars Berscheid Date: Mon, 7 Jan 2019 16:41:16 +0100 Subject: [PATCH] fix string view reference movement in bytecode --- README.md | 4 ++-- include/inja/bytecode.hpp | 2 +- include/inja/lexer.hpp | 22 +++++++++++++++------- include/inja/parser.hpp | 6 ------ include/inja/renderer.hpp | 29 +++++++++++++++++++---------- include/inja/template.hpp | 22 +--------------------- test/unit-files.cpp | 16 +++++++++------- test/unit-renderer.cpp | 2 ++ 8 files changed, 49 insertions(+), 54 deletions(-) diff --git a/README.md b/README.md index 746909a..181d710 100644 --- a/README.md +++ b/README.md @@ -82,10 +82,10 @@ The environment class can be configured to your needs. Environment env_default; // With global path to template files and where files will be saved -Environment env_1 = Environment("../path/templates/"); +Environment env_1 {"../path/templates/"}; // With separate input and output path -Environment env_2 = Environment("../path/templates/", "../path/results/"); +Environment env_2 {"../path/templates/", "../path/results/"}; // Choose between dot notation (like Jinja2) and JSON pointer to access elements env.set_element_notation(ElementNotation::Dot); // (default) e.g. time.start diff --git a/include/inja/bytecode.hpp b/include/inja/bytecode.hpp index 724f0b7..8bb2663 100644 --- a/include/inja/bytecode.hpp +++ b/include/inja/bytecode.hpp @@ -116,7 +116,7 @@ struct Bytecode { uint32_t flags: 2; json value; - std::string_view str; + std::string str; Bytecode(): args(0), flags(0) {} explicit Bytecode(Op op, unsigned int args = 0): op(op), args(args), flags(0) {} diff --git a/include/inja/lexer.hpp b/include/inja/lexer.hpp index fee3e9a..cd65738 100644 --- a/include/inja/lexer.hpp +++ b/include/inja/lexer.hpp @@ -208,9 +208,13 @@ class Lexer { Token scan_id() { for (;;) { - if (m_pos >= m_in.size()) break; + if (m_pos >= m_in.size()) { + break; + } char ch = m_in[m_pos]; - if (!std::isalnum(ch) && ch != '.' && ch != '/' && ch != '_' && ch != '-') break; + if (!std::isalnum(ch) && ch != '.' && ch != '/' && ch != '_' && ch != '-') { + break; + } m_pos += 1; } return make_token(Token::Kind::Id); @@ -218,11 +222,14 @@ class Lexer { Token scan_number() { for (;;) { - if (m_pos >= m_in.size()) break; + if (m_pos >= m_in.size()) { + break; + } char ch = m_in[m_pos]; // be very permissive in lexer (we'll catch errors when conversion happens) - if (!std::isdigit(ch) && ch != '.' && ch != 'e' && ch != 'E' && ch != '+' && ch != '-') + if (!std::isdigit(ch) && ch != '.' && ch != 'e' && ch != 'E' && ch != '+' && ch != '-') { break; + } m_pos += 1; } return make_token(Token::Kind::Number); @@ -233,12 +240,13 @@ class Lexer { for (;;) { if (m_pos >= m_in.size()) break; char ch = m_in[m_pos++]; - if (ch == '\\') + if (ch == '\\') { escape = true; - else if (!escape && ch == m_in[m_tok_start]) + } else if (!escape && ch == m_in[m_tok_start]) { break; - else + } else { escape = false; + } } return make_token(Token::Kind::String); } diff --git a/include/inja/parser.hpp b/include/inja/parser.hpp index f4696fa..0ca3e29 100644 --- a/include/inja/parser.hpp +++ b/include/inja/parser.hpp @@ -381,12 +381,6 @@ class Parser { } // sys::path::remove_dots(pathname, true, sys::path::Style::posix); - // parse it only if it's new - // TemplateStorage::iterator included; - // bool is_new {true}; - // std::tie(included, is_new) = m_included_templates.emplace(pathname); - // if (is_new) included->second = parse_template(pathname); - Template include_template = parse_template(pathname); m_included_templates.emplace(pathname, include_template); diff --git a/include/inja/renderer.hpp b/include/inja/renderer.hpp index f3b984d..8f868f7 100644 --- a/include/inja/renderer.hpp +++ b/include/inja/renderer.hpp @@ -28,18 +28,20 @@ class Renderer { std::vector& get_args(const Bytecode& bc) { m_tmp_args.clear(); - bool hasImm = ((bc.flags & Bytecode::Flag::ValueMask) != Bytecode::Flag::ValuePop); + bool has_imm = ((bc.flags & Bytecode::Flag::ValueMask) != Bytecode::Flag::ValuePop); // get args from stack unsigned int pop_args = bc.args; - if (hasImm) --pop_args; + if (has_imm) { + pop_args -= 1; + } for (auto i = std::prev(m_stack.end(), pop_args); i != m_stack.end(); i++) { m_tmp_args.push_back(&(*i)); } // get immediate arg - if (hasImm) { + if (has_imm) { m_tmp_args.push_back(get_imm(bc)); } @@ -48,9 +50,12 @@ class Renderer { void pop_args(const Bytecode& bc) { unsigned int popArgs = bc.args; - if ((bc.flags & Bytecode::Flag::ValueMask) != Bytecode::Flag::ValuePop) - --popArgs; - for (unsigned int i = 0; i < popArgs; ++i) m_stack.pop_back(); + if ((bc.flags & Bytecode::Flag::ValueMask) != Bytecode::Flag::ValuePop) { + popArgs -= 1; + } + for (unsigned int i = 0; i < popArgs; ++i) { + m_stack.pop_back(); + } } const json* get_imm(const Bytecode& bc) { @@ -169,11 +174,13 @@ class Renderer { const auto& bc = tmpl.bytecodes[i]; switch (bc.op) { - case Bytecode::Op::Nop: + case Bytecode::Op::Nop: { break; - case Bytecode::Op::PrintText: + } + case Bytecode::Op::PrintText: { os << bc.str; break; + } case Bytecode::Op::PrintValue: { const json& val = *get_args(bc)[0]; if (val.is_string()) @@ -184,9 +191,10 @@ class Renderer { pop_args(bc); break; } - case Bytecode::Op::Push: + case Bytecode::Op::Push: { m_stack.emplace_back(*get_imm(bc)); break; + } case Bytecode::Op::Upper: { auto result = get_args(bc)[0]->get(); std::transform(result.begin(), result.end(), result.begin(), ::toupper); @@ -443,9 +451,10 @@ class Renderer { m_stack.emplace_back(std::move(result)); break; } - case Bytecode::Op::Jump: + case Bytecode::Op::Jump: { i = bc.args - 1; // -1 due to ++i in loop break; + } case Bytecode::Op::ConditionalJump: { if (!truthy(m_stack.back())) { i = bc.args - 1; // -1 due to ++i in loop diff --git a/include/inja/template.hpp b/include/inja/template.hpp index c40cda3..2e171e1 100644 --- a/include/inja/template.hpp +++ b/include/inja/template.hpp @@ -9,29 +9,9 @@ namespace inja { -class Template { - friend class Parser; - friend class Renderer; - +struct Template { std::vector bytecodes; std::string content; - - public: - Template() {} - Template(const Template& oth): bytecodes(oth.bytecodes), content(oth.content) {} - Template(Template&& oth): bytecodes(std::move(oth.bytecodes)), content(std::move(oth.content)) {} - - Template& operator=(const Template& oth) { - bytecodes = oth.bytecodes; - content = oth.content; - return *this; - } - - Template& operator=(Template&& oth) { - bytecodes = std::move(oth.bytecodes); - content = std::move(oth.content); - return *this; - } }; using TemplateStorage = std::map; diff --git a/test/unit-files.cpp b/test/unit-files.cpp index dab55fc..25d3ca4 100644 --- a/test/unit-files.cpp +++ b/test/unit-files.cpp @@ -5,26 +5,28 @@ using json = nlohmann::json; +const std::string test_file_directory {"../test/data/"}; + TEST_CASE("loading") { - inja::Environment env = inja::Environment(); + inja::Environment env; json data; data["name"] = "Jeff"; SECTION("Files should be loaded") { - CHECK( env.load_file("../test/data/simple.txt") == "Hello {{ name }}." ); + CHECK( env.load_file(test_file_directory + "simple.txt") == "Hello {{ name }}." ); } SECTION("Files should be rendered") { - CHECK( env.render_file("../test/data/simple.txt", data) == "Hello Jeff." ); + CHECK( env.render_file(test_file_directory + "simple.txt", data) == "Hello Jeff." ); } SECTION("File includes should be rendered") { - CHECK( env.render_file("../test/data/include.txt", data) == "Answer: Hello Jeff." ); + CHECK( env.render_file(test_file_directory + "include.txt", data) == "Answer: Hello Jeff." ); } } TEST_CASE("complete-files") { - inja::Environment env = inja::Environment("../test/data/"); + inja::Environment env {test_file_directory}; for (std::string test_name : {"simple-file", "nested", "nested-line", "html"}) { SECTION(test_name) { @@ -34,8 +36,8 @@ TEST_CASE("complete-files") { } TEST_CASE("global-path") { - inja::Environment env = inja::Environment("../test/data/", "./"); - inja::Environment env_result = inja::Environment("./"); + inja::Environment env {test_file_directory, "./"}; + inja::Environment env_result {"./"}; json data; data["name"] = "Jeff"; diff --git a/test/unit-renderer.cpp b/test/unit-renderer.cpp index 12c223a..e78127a 100644 --- a/test/unit-renderer.cpp +++ b/test/unit-renderer.cpp @@ -43,6 +43,7 @@ TEST_CASE("types") { CHECK( env.render("Hello {{ names.1 }}!", data) == "Hello Seb!" ); CHECK( env.render("Hello {{ brother.name }}!", data) == "Hello Chris!" ); CHECK( env.render("Hello {{ brother.daughter0.name }}!", data) == "Hello Maria!" ); + CHECK( env.render("{{ \"{{ no_value }}\" }}", data) == "{{ no_value }}" ); CHECK_THROWS_WITH( env.render("{{unknown}}", data), "[inja.exception.render_error] variable 'unknown' not found" ); } @@ -215,6 +216,7 @@ TEST_CASE("functions") { CHECK( env.render("{{ default(nothing, 0) }}", data) == "0" ); CHECK( env.render("{{ default(name, \"nobody\") }}", data) == "Peter" ); CHECK( env.render("{{ default(surname, \"nobody\") }}", data) == "nobody" ); + CHECK( env.render("{{ default(surname, \"{{ surname }}\") }}", data) == "{{ surname }}" ); CHECK_THROWS_WITH( env.render("{{ default(surname, lastname) }}", data), "[inja.exception.render_error] variable 'lastname' not found" ); }