From 8e6a8428fa0d628bc58b939204ec7019e5ad1bd3 Mon Sep 17 00:00:00 2001 From: Tom Date: Fri, 22 Nov 2019 11:12:28 +0100 Subject: [PATCH] Undo PIMPL pattern in Environment (#127) * resync single_include * Undo PIMPL pattern in Environment * Environment now supports copy construction and assignment * Add test for copying Environment Closes #126 --- include/inja/environment.hpp | 84 ++++------ single_include/inja/inja.hpp | 315 ++++++++++++++++++----------------- test/unit.cpp | 30 ++++ 3 files changed, 232 insertions(+), 197 deletions(-) diff --git a/include/inja/environment.hpp b/include/inja/environment.hpp index 05bb5e8..a8dd22d 100644 --- a/include/inja/environment.hpp +++ b/include/inja/environment.hpp @@ -28,84 +28,64 @@ using json = nlohmann::json; * \brief Class for changing the configuration. */ class Environment { - class Impl { - public: - std::string input_path; - std::string output_path; - - LexerConfig lexer_config; - ParserConfig parser_config; - - FunctionStorage callbacks; - TemplateStorage included_templates; - }; - - std::unique_ptr m_impl; - public: Environment(): Environment("") { } - explicit Environment(const std::string& global_path): m_impl(stdinja::make_unique()) { - m_impl->input_path = global_path; - m_impl->output_path = global_path; - } + explicit Environment(const std::string& global_path): m_input_path(global_path), m_output_path(global_path) {} - explicit Environment(const std::string& input_path, const std::string& output_path): m_impl(stdinja::make_unique()) { - m_impl->input_path = input_path; - m_impl->output_path = output_path; - } + Environment(const std::string& input_path, const std::string& output_path): m_input_path(input_path), m_output_path(output_path) {} /// Sets the opener and closer for template statements void set_statement(const std::string& open, const std::string& close) { - m_impl->lexer_config.statement_open = open; - m_impl->lexer_config.statement_close = close; - m_impl->lexer_config.update_open_chars(); + m_lexer_config.statement_open = open; + m_lexer_config.statement_close = close; + m_lexer_config.update_open_chars(); } /// Sets the opener for template line statements void set_line_statement(const std::string& open) { - m_impl->lexer_config.line_statement = open; - m_impl->lexer_config.update_open_chars(); + m_lexer_config.line_statement = open; + m_lexer_config.update_open_chars(); } /// Sets the opener and closer for template expressions void set_expression(const std::string& open, const std::string& close) { - m_impl->lexer_config.expression_open = open; - m_impl->lexer_config.expression_close = close; - m_impl->lexer_config.update_open_chars(); + m_lexer_config.expression_open = open; + m_lexer_config.expression_close = close; + m_lexer_config.update_open_chars(); } /// Sets the opener and closer for template comments void set_comment(const std::string& open, const std::string& close) { - m_impl->lexer_config.comment_open = open; - m_impl->lexer_config.comment_close = close; - m_impl->lexer_config.update_open_chars(); + m_lexer_config.comment_open = open; + m_lexer_config.comment_close = close; + m_lexer_config.update_open_chars(); } /// Sets whether to remove the first newline after a block void set_trim_blocks(bool trim_blocks) { - m_impl->lexer_config.trim_blocks = trim_blocks; + m_lexer_config.trim_blocks = trim_blocks; } /// Sets whether to strip the spaces and tabs from the start of a line to a block void set_lstrip_blocks(bool lstrip_blocks) { - m_impl->lexer_config.lstrip_blocks = lstrip_blocks; + m_lexer_config.lstrip_blocks = lstrip_blocks; } /// Sets the element notation syntax void set_element_notation(ElementNotation notation) { - m_impl->parser_config.notation = notation; + m_parser_config.notation = notation; } Template parse(nonstd::string_view input) { - Parser parser(m_impl->parser_config, m_impl->lexer_config, m_impl->included_templates); + Parser parser(m_parser_config, m_lexer_config, m_included_templates); return parser.parse(input); } Template parse_template(const std::string& filename) { - Parser parser(m_impl->parser_config, m_impl->lexer_config, m_impl->included_templates); - return parser.parse_template(m_impl->input_path + static_cast(filename)); + Parser parser(m_parser_config, m_lexer_config, m_included_templates); + return parser.parse_template(m_input_path + static_cast(filename)); } std::string render(nonstd::string_view input, const json& data) { @@ -128,13 +108,13 @@ class Environment { } void write(const std::string& filename, const json& data, const std::string& filename_out) { - std::ofstream file(m_impl->output_path + filename_out); + std::ofstream file(m_output_path + filename_out); file << render_file(filename, data); file.close(); } void write(const Template& temp, const json& data, const std::string& filename_out) { - std::ofstream file(m_impl->output_path + filename_out); + std::ofstream file(m_output_path + filename_out); file << render(temp, data); file.close(); } @@ -150,24 +130,24 @@ class Environment { } std::ostream& render_to(std::ostream& os, const Template& tmpl, const json& data) { - Renderer(m_impl->included_templates, m_impl->callbacks).render_to(os, tmpl, data); + Renderer(m_included_templates, m_callbacks).render_to(os, tmpl, data); return os; } std::string load_file(const std::string& filename) { - Parser parser(m_impl->parser_config, m_impl->lexer_config, m_impl->included_templates); - return parser.load_file(m_impl->input_path + filename); + Parser parser(m_parser_config, m_lexer_config, m_included_templates); + return parser.load_file(m_input_path + filename); } json load_json(const std::string& filename) { - std::ifstream file = open_file_or_throw(m_impl->input_path + filename); + std::ifstream file = open_file_or_throw(m_input_path + filename); json j; file >> j; return j; } void add_callback(const std::string& name, unsigned int numArgs, const CallbackFunction& callback) { - m_impl->callbacks.add_callback(name, numArgs, callback); + m_callbacks.add_callback(name, numArgs, callback); } /** Includes a template with a given name into the environment. @@ -175,8 +155,18 @@ class Environment { * include "" syntax. */ void include_template(const std::string& name, const Template& tmpl) { - m_impl->included_templates[name] = tmpl; + m_included_templates[name] = tmpl; } + + private: + std::string m_input_path; + std::string m_output_path; + + LexerConfig m_lexer_config; + ParserConfig m_parser_config; + + FunctionStorage m_callbacks; + TemplateStorage m_included_templates; }; /*! diff --git a/single_include/inja/inja.hpp b/single_include/inja/inja.hpp index beb284e..8d565b5 100644 --- a/single_include/inja/inja.hpp +++ b/single_include/inja/inja.hpp @@ -1,5 +1,7 @@ -#ifndef PANTOR_INJA_HPP -#define PANTOR_INJA_HPP +// Copyright (c) 2019 Pantor. All rights reserved. + +#ifndef INCLUDE_INJA_INJA_HPP_ +#define INCLUDE_INJA_INJA_HPP_ #include #include @@ -12,8 +14,10 @@ #include // #include "environment.hpp" -#ifndef PANTOR_INJA_ENVIRONMENT_HPP -#define PANTOR_INJA_ENVIRONMENT_HPP +// Copyright (c) 2019 Pantor. All rights reserved. + +#ifndef INCLUDE_INJA_ENVIRONMENT_HPP_ +#define INCLUDE_INJA_ENVIRONMENT_HPP_ #include #include @@ -23,8 +27,10 @@ #include // #include "config.hpp" -#ifndef PANTOR_INJA_CONFIG_HPP -#define PANTOR_INJA_CONFIG_HPP +// Copyright (c) 2019 Pantor. All rights reserved. + +#ifndef INCLUDE_INJA_CONFIG_HPP_ +#define INCLUDE_INJA_CONFIG_HPP_ #include #include @@ -1400,19 +1406,23 @@ struct ParserConfig { ElementNotation notation {ElementNotation::Dot}; }; -} +} // namespace inja -#endif // PANTOR_INJA_CONFIG_HPP +#endif // INCLUDE_INJA_CONFIG_HPP_ // #include "function_storage.hpp" -#ifndef PANTOR_INJA_FUNCTION_STORAGE_HPP -#define PANTOR_INJA_FUNCTION_STORAGE_HPP +// Copyright (c) 2019 Pantor. All rights reserved. + +#ifndef INCLUDE_INJA_FUNCTION_STORAGE_HPP_ +#define INCLUDE_INJA_FUNCTION_STORAGE_HPP_ #include // #include "bytecode.hpp" -#ifndef PANTOR_INJA_BYTECODE_HPP -#define PANTOR_INJA_BYTECODE_HPP +// Copyright (c) 2019 Pantor. All rights reserved. + +#ifndef INCLUDE_INJA_BYTECODE_HPP_ +#define INCLUDE_INJA_BYTECODE_HPP_ #include #include @@ -1543,7 +1553,7 @@ struct Bytecode { } // namespace inja -#endif // PANTOR_INJA_BYTECODE_HPP +#endif // INCLUDE_INJA_BYTECODE_HPP_ // #include "string_view.hpp" @@ -1551,7 +1561,7 @@ struct Bytecode { namespace inja { -using namespace nlohmann; +using json = nlohmann::json; using Arguments = std::vector; using CallbackFunction = std::function; @@ -1616,11 +1626,13 @@ class FunctionStorage { } -#endif // PANTOR_INJA_FUNCTION_STORAGE_HPP +#endif // INCLUDE_INJA_FUNCTION_STORAGE_HPP_ // #include "parser.hpp" -#ifndef PANTOR_INJA_PARSER_HPP -#define PANTOR_INJA_PARSER_HPP +// Copyright (c) 2019 Pantor. All rights reserved. + +#ifndef INCLUDE_INJA_PARSER_HPP_ +#define INCLUDE_INJA_PARSER_HPP_ #include #include @@ -1634,8 +1646,10 @@ class FunctionStorage { // #include "function_storage.hpp" // #include "lexer.hpp" -#ifndef PANTOR_INJA_LEXER_HPP -#define PANTOR_INJA_LEXER_HPP +// Copyright (c) 2019 Pantor. All rights reserved. + +#ifndef INCLUDE_INJA_LEXER_HPP_ +#define INCLUDE_INJA_LEXER_HPP_ #include #include @@ -1643,8 +1657,10 @@ class FunctionStorage { // #include "config.hpp" // #include "token.hpp" -#ifndef PANTOR_INJA_TOKEN_HPP -#define PANTOR_INJA_TOKEN_HPP +// Copyright (c) 2019 Pantor. All rights reserved. + +#ifndef INCLUDE_INJA_TOKEN_HPP_ +#define INCLUDE_INJA_TOKEN_HPP_ #include @@ -1710,11 +1726,13 @@ struct Token { } -#endif // PANTOR_INJA_TOKEN_HPP +#endif // INCLUDE_INJA_TOKEN_HPP_ // #include "utils.hpp" -#ifndef PANTOR_INJA_UTILS_HPP -#define PANTOR_INJA_UTILS_HPP +// Copyright (c) 2019 Pantor. All rights reserved. + +#ifndef INCLUDE_INJA_UTILS_HPP_ +#define INCLUDE_INJA_UTILS_HPP_ #include #include @@ -1761,11 +1779,11 @@ namespace string_view { inline bool starts_with(nonstd::string_view view, nonstd::string_view prefix) { return (view.size() >= prefix.size() && view.compare(0, prefix.size(), prefix) == 0); } -} // namespace string +} // namespace string_view } // namespace inja -#endif // PANTOR_INJA_UTILS_HPP +#endif // INCLUDE_INJA_UTILS_HPP_ @@ -1835,7 +1853,7 @@ class Lexer { inja::string_view::starts_with(open_str, m_config.line_statement)) { m_state = State::LineStart; } else { - m_pos += 1; // wasn't actually an opening sequence + m_pos += 1; // wasn't actually an opening sequence goto again; } @@ -2063,11 +2081,13 @@ class Lexer { } -#endif // PANTOR_INJA_LEXER_HPP +#endif // INCLUDE_INJA_LEXER_HPP_ // #include "template.hpp" -#ifndef PANTOR_INJA_TEMPLATE_HPP -#define PANTOR_INJA_TEMPLATE_HPP +// Copyright (c) 2019 Pantor. All rights reserved. + +#ifndef INCLUDE_INJA_TEMPLATE_HPP_ +#define INCLUDE_INJA_TEMPLATE_HPP_ #include #include @@ -2089,9 +2109,9 @@ struct Template { using TemplateStorage = std::map; -} +} // namespace inja -#endif // PANTOR_INJA_TEMPLATE_HPP +#endif // INCLUDE_INJA_TEMPLATE_HPP_ // #include "token.hpp" @@ -2644,11 +2664,13 @@ class Parser { } // namespace inja -#endif // PANTOR_INJA_PARSER_HPP +#endif // INCLUDE_INJA_PARSER_HPP_ // #include "polyfill.hpp" -#ifndef PANTOR_INJA_POLYFILL_HPP -#define PANTOR_INJA_POLYFILL_HPP +// Copyright (c) 2019 Pantor. All rights reserved. + +#ifndef INCLUDE_INJA_POLYFILL_HPP_ +#define INCLUDE_INJA_POLYFILL_HPP_ #if __cplusplus < 201402L @@ -2660,48 +2682,51 @@ class Parser { namespace stdinja { - template struct _Unique_if { - typedef std::unique_ptr _Single_object; - }; - template struct _Unique_if { - typedef std::unique_ptr _Unknown_bound; - }; +template struct _Unique_if { + typedef std::unique_ptr _Single_object; +}; - template struct _Unique_if { - typedef void _Known_bound; - }; +template struct _Unique_if { + typedef std::unique_ptr _Unknown_bound; +}; - template - typename _Unique_if::_Single_object - make_unique(Args&&... args) { - return std::unique_ptr(new T(std::forward(args)...)); - } +template struct _Unique_if { + typedef void _Known_bound; +}; - template - typename _Unique_if::_Unknown_bound - make_unique(size_t n) { - typedef typename std::remove_extent::type U; - return std::unique_ptr(new U[n]()); - } - - template - typename _Unique_if::_Known_bound - make_unique(Args&&...) = delete; +template +typename _Unique_if::_Single_object +make_unique(Args&&... args) { + return std::unique_ptr(new T(std::forward(args)...)); } +template +typename _Unique_if::_Unknown_bound +make_unique(size_t n) { + typedef typename std::remove_extent::type U; + return std::unique_ptr(new U[n]()); +} + +template +typename _Unique_if::_Known_bound +make_unique(Args&&...) = delete; + +} // namespace stdinja + #else namespace stdinja = std; -#endif // memory */ +#endif // memory */ - -#endif // PANTOR_INJA_POLYFILL_HPP +#endif // INCLUDE_INJA_POLYFILL_HPP_ // #include "renderer.hpp" -#ifndef PANTOR_INJA_RENDERER_HPP -#define PANTOR_INJA_RENDERER_HPP +// Copyright (c) 2019 Pantor. All rights reserved. + +#ifndef INCLUDE_INJA_RENDERER_HPP_ +#define INCLUDE_INJA_RENDERER_HPP_ #include #include @@ -2843,22 +2868,21 @@ class Renderer { enum class Type { Map, Array }; Type loop_type; - nonstd::string_view key_name; // variable name for keys - nonstd::string_view value_name; // variable name for values - json data; // data with loop info added + nonstd::string_view key_name; // variable name for keys + nonstd::string_view value_name; // variable name for values + json data; // data with loop info added - json values; // values to iterate over + json values; // values to iterate over // loop over list - size_t index; // current list index - size_t size; // length of list + size_t index; // current list index + size_t size; // length of list // loop over map using KeyValue = std::pair; using MapValues = std::vector; - MapValues map_values; // values to iterate over - MapValues::iterator map_it; // iterator over values - + MapValues map_values; // values to iterate over + MapValues::iterator map_it; // iterator over values }; std::vector m_loop_stack; @@ -3213,7 +3237,8 @@ class Renderer { for (auto it = level.values.begin(), end = level.values.end(); it != end; ++it) { level.map_values.emplace_back(it.key(), &it.value()); } - std::sort(level.map_values.begin(), level.map_values.end(), [](const LoopLevel::KeyValue& a, const LoopLevel::KeyValue& b) { return a.first < b.first; }); + auto sort_lambda = [](const LoopLevel::KeyValue& a, const LoopLevel::KeyValue& b) { return a.first < b.first; }; + std::sort(level.map_values.begin(), level.map_values.end(), sort_lambda); level.map_it = level.map_values.begin(); } else { if (!level.values.is_array()) { @@ -3281,7 +3306,7 @@ class Renderer { } // namespace inja -#endif // PANTOR_INJA_RENDERER_HPP +#endif // INCLUDE_INJA_RENDERER_HPP_ // #include "string_view.hpp" @@ -3293,91 +3318,71 @@ class Renderer { namespace inja { -using namespace nlohmann; +using json = nlohmann::json; /*! * \brief Class for changing the configuration. */ class Environment { - class Impl { - public: - std::string input_path; - std::string output_path; - - LexerConfig lexer_config; - ParserConfig parser_config; - - FunctionStorage callbacks; - TemplateStorage included_templates; - }; - - std::unique_ptr m_impl; - public: Environment(): Environment("") { } - explicit Environment(const std::string& global_path): m_impl(stdinja::make_unique()) { - m_impl->input_path = global_path; - m_impl->output_path = global_path; - } + explicit Environment(const std::string& global_path): m_input_path(global_path), m_output_path(global_path) {} - explicit Environment(const std::string& input_path, const std::string& output_path): m_impl(stdinja::make_unique()) { - m_impl->input_path = input_path; - m_impl->output_path = output_path; - } + Environment(const std::string& input_path, const std::string& output_path): m_input_path(input_path), m_output_path(output_path) {} /// Sets the opener and closer for template statements void set_statement(const std::string& open, const std::string& close) { - m_impl->lexer_config.statement_open = open; - m_impl->lexer_config.statement_close = close; - m_impl->lexer_config.update_open_chars(); + m_lexer_config.statement_open = open; + m_lexer_config.statement_close = close; + m_lexer_config.update_open_chars(); } /// Sets the opener for template line statements void set_line_statement(const std::string& open) { - m_impl->lexer_config.line_statement = open; - m_impl->lexer_config.update_open_chars(); + m_lexer_config.line_statement = open; + m_lexer_config.update_open_chars(); } /// Sets the opener and closer for template expressions void set_expression(const std::string& open, const std::string& close) { - m_impl->lexer_config.expression_open = open; - m_impl->lexer_config.expression_close = close; - m_impl->lexer_config.update_open_chars(); + m_lexer_config.expression_open = open; + m_lexer_config.expression_close = close; + m_lexer_config.update_open_chars(); } /// Sets the opener and closer for template comments void set_comment(const std::string& open, const std::string& close) { - m_impl->lexer_config.comment_open = open; - m_impl->lexer_config.comment_close = close; - m_impl->lexer_config.update_open_chars(); + m_lexer_config.comment_open = open; + m_lexer_config.comment_close = close; + m_lexer_config.update_open_chars(); } /// Sets whether to remove the first newline after a block void set_trim_blocks(bool trim_blocks) { - m_impl->lexer_config.trim_blocks = trim_blocks; + m_lexer_config.trim_blocks = trim_blocks; } /// Sets whether to strip the spaces and tabs from the start of a line to a block void set_lstrip_blocks(bool lstrip_blocks) { - m_impl->lexer_config.lstrip_blocks = lstrip_blocks; + m_lexer_config.lstrip_blocks = lstrip_blocks; } /// Sets the element notation syntax void set_element_notation(ElementNotation notation) { - m_impl->parser_config.notation = notation; + m_parser_config.notation = notation; } Template parse(nonstd::string_view input) { - Parser parser(m_impl->parser_config, m_impl->lexer_config, m_impl->included_templates); + Parser parser(m_parser_config, m_lexer_config, m_included_templates); return parser.parse(input); } Template parse_template(const std::string& filename) { - Parser parser(m_impl->parser_config, m_impl->lexer_config, m_impl->included_templates); - return parser.parse_template(m_impl->input_path + static_cast(filename)); - } + Parser parser(m_parser_config, m_lexer_config, m_included_templates); + return parser.parse_template(m_input_path + static_cast(filename)); + } std::string render(nonstd::string_view input, const json& data) { return render(parse(input), data); @@ -3390,55 +3395,55 @@ class Environment { } std::string render_file(const std::string& filename, const json& data) { - return render(parse_template(filename), data); - } + return render(parse_template(filename), data); + } std::string render_file_with_json_file(const std::string& filename, const std::string& filename_data) { - const json data = load_json(filename_data); - return render_file(filename, data); - } + const json data = load_json(filename_data); + return render_file(filename, data); + } void write(const std::string& filename, const json& data, const std::string& filename_out) { - std::ofstream file(m_impl->output_path + filename_out); - file << render_file(filename, data); - file.close(); - } + std::ofstream file(m_output_path + filename_out); + file << render_file(filename, data); + file.close(); + } void write(const Template& temp, const json& data, const std::string& filename_out) { - std::ofstream file(m_impl->output_path + filename_out); - file << render(temp, data); - file.close(); - } + std::ofstream file(m_output_path + filename_out); + file << render(temp, data); + file.close(); + } - void write_with_json_file(const std::string& filename, const std::string& filename_data, const std::string& filename_out) { - const json data = load_json(filename_data); - write(filename, data, filename_out); - } + void write_with_json_file(const std::string& filename, const std::string& filename_data, const std::string& filename_out) { + const json data = load_json(filename_data); + write(filename, data, filename_out); + } - void write_with_json_file(const Template& temp, const std::string& filename_data, const std::string& filename_out) { - const json data = load_json(filename_data); - write(temp, data, filename_out); - } + void write_with_json_file(const Template& temp, const std::string& filename_data, const std::string& filename_out) { + const json data = load_json(filename_data); + write(temp, data, filename_out); + } std::ostream& render_to(std::ostream& os, const Template& tmpl, const json& data) { - Renderer(m_impl->included_templates, m_impl->callbacks).render_to(os, tmpl, data); + Renderer(m_included_templates, m_callbacks).render_to(os, tmpl, data); return os; } std::string load_file(const std::string& filename) { - Parser parser(m_impl->parser_config, m_impl->lexer_config, m_impl->included_templates); - return parser.load_file(m_impl->input_path + filename); - } + Parser parser(m_parser_config, m_lexer_config, m_included_templates); + return parser.load_file(m_input_path + filename); + } json load_json(const std::string& filename) { - std::ifstream file = open_file_or_throw(m_impl->input_path + filename); - json j; - file >> j; - return j; - } + std::ifstream file = open_file_or_throw(m_input_path + filename); + json j; + file >> j; + return j; + } void add_callback(const std::string& name, unsigned int numArgs, const CallbackFunction& callback) { - m_impl->callbacks.add_callback(name, numArgs, callback); + m_callbacks.add_callback(name, numArgs, callback); } /** Includes a template with a given name into the environment. @@ -3446,8 +3451,18 @@ class Environment { * include "" syntax. */ void include_template(const std::string& name, const Template& tmpl) { - m_impl->included_templates[name] = tmpl; + m_included_templates[name] = tmpl; } + + private: + std::string m_input_path; + std::string m_output_path; + + LexerConfig m_lexer_config; + ParserConfig m_parser_config; + + FunctionStorage m_callbacks; + TemplateStorage m_included_templates; }; /*! @@ -3467,7 +3482,7 @@ inline void render_to(std::ostream& os, nonstd::string_view input, const json& d } -#endif // PANTOR_INJA_ENVIRONMENT_HPP +#endif // INCLUDE_INJA_ENVIRONMENT_HPP_ // #include "string_view.hpp" @@ -3479,4 +3494,4 @@ inline void render_to(std::ostream& os, nonstd::string_view input, const json& d -#endif // PANTOR_INJA_HPP +#endif // INCLUDE_INJA_INJA_HPP_ diff --git a/test/unit.cpp b/test/unit.cpp index 96c87b1..fa3e3d5 100644 --- a/test/unit.cpp +++ b/test/unit.cpp @@ -2,3 +2,33 @@ #define CATCH_CONFIG_MAIN #include "catch/catch.hpp" +#include "inja/inja.hpp" + + +using json = nlohmann::json; + + +TEST_CASE("copy-environment") { + inja::Environment env; + env.add_callback("double", 1, [](inja::Arguments& args) { + int number = args.at(0)->get(); + return 2 * number; + }); + + inja::Template t1 = env.parse("{{ double(2) }}"); + env.include_template("tpl", t1); + std::string test_tpl = "{% include \"tpl\" %}"; + + REQUIRE(env.render(test_tpl, json()) == "4"); + + inja::Environment copy(env); + CHECK(copy.render(test_tpl, json()) == "4"); + + // overwrite template in source env + inja::Template t2 = env.parse("{{ double(4) }}"); + env.include_template("tpl", t2); + REQUIRE(env.render(test_tpl, json()) == "8"); + + // template is unchanged in copy + CHECK(copy.render(test_tpl, json()) == "4"); +}