diff --git a/README.md b/README.md index ae147c5..e649ad7 100644 --- a/README.md +++ b/README.md @@ -103,15 +103,17 @@ Output: C++11 lambda expressions can be used to add logic to your templates. Like a `const char*` literal, lambdas can be implicitly converted to `bool`, so they -must be wrapped in a `mstch::lambda` object when used in a `mstch::node`. +must be wrapped in a `mstch::lambda` object when used in a `mstch::node`. The +lambda expression passed to `mstch::lambda` must itself return a `mstch::node`. +The returned node will be rendered to a string, then it will be parsed as a +template. -The lambda expression passed to `mstch::lambda` returns a `std::string` and -accepts either no parameters: +The lambda expression accepts either no parameters: ```c++ std::string view{"Hello {{lambda}}!"}; mstch::map context{ - {"lambda", mstch::lambda{[]() { + {"lambda", mstch::lambda{[]() -> mstch::node { return std::string{"World"}; }}} }; @@ -125,14 +127,13 @@ Output: Hello World! ``` -Or it accepts a `const std::string&` that gets the unrendered literal block. -The returned string will be parsed in both cases: +Or it accepts a `const std::string&` that gets the unrendered literal block: ```c++ std::string view{"{{#bold}}{{yay}} :){{/bold}}"}; mstch::map context{ {"yay", std::string{"Yay!"}}, - {"bold", mstch::lambda{[](const std::string& text) { + {"bold", mstch::lambda{[](const std::string& text) -> mstch::node { return "" + text + ""; }}} }; diff --git a/include/mstch/mstch.hpp b/include/mstch/mstch.hpp index b5675f6..4c677f5 100644 --- a/include/mstch/mstch.hpp +++ b/include/mstch/mstch.hpp @@ -35,7 +35,7 @@ class object_t { mutable std::map cache; }; -template +template class is_fun { private: using not_fun = char; @@ -43,9 +43,9 @@ class is_fun { using fun_with_args = char[3]; template struct really_has; template static fun_without_args& test( - really_has*); + really_has*); template static fun_with_args& test( - really_has*); template static not_fun& test(...); @@ -54,36 +54,48 @@ class is_fun { static bool const has_args = sizeof(test(0)) == sizeof(fun_with_args); }; -} +template +using node_renderer = std::function; -class lambda { +template +class lambda_t { public: template - lambda(F f, typename std::enable_if::no_args>::type* =0): - fun([f](const std::string&){return f();}) + lambda_t(F f, typename std::enable_if::no_args>::type* = 0): + fun([f](node_renderer renderer, const std::string&) { + return renderer(f()); + }) { } template - lambda(F f, typename std::enable_if::has_args>::type* =0): - fun(f) + lambda_t(F f, typename std::enable_if::has_args>::type* = 0): + fun([f](node_renderer renderer, const std::string& text) { + return renderer(f(text)); + }) { } - std::string operator()(const std::string& text = "") const { - return fun(text); + std::string operator()(node_renderer renderer, + const std::string& text = "") const + { + return fun(renderer, text); } private: - std::function fun; + std::function renderer, const std::string&)> fun; }; +} + using node = boost::make_recursive_variant< - std::nullptr_t, std::string, int, double, bool, lambda, + std::nullptr_t, std::string, int, double, bool, + internal::lambda_t, std::shared_ptr>, std::map, std::vector>::type; using object = internal::object_t; +using lambda = internal::lambda_t; using map = std::map; using array = std::vector; diff --git a/src/visitor/render_node.hpp b/src/visitor/render_node.hpp index 63b7682..aef8027 100644 --- a/src/visitor/render_node.hpp +++ b/src/visitor/render_node.hpp @@ -37,7 +37,10 @@ class render_node: public boost::static_visitor { } std::string operator()(const lambda& value) const { - auto rendered = render_context::push(ctx).render(template_type{value()}); + template_type interpreted{value([this](const mstch::node& n) { + return visit(render_node(ctx), n); + })}; + auto rendered = render_context::push(ctx).render(interpreted); return (m_flag == flag::escape_html) ? html_escape(rendered) : rendered; } diff --git a/src/visitor/render_section.hpp b/src/visitor/render_section.hpp index 25592ad..e2f6049 100644 --- a/src/visitor/render_section.hpp +++ b/src/visitor/render_section.hpp @@ -5,6 +5,7 @@ #include "render_context.hpp" #include "mstch/mstch.hpp" #include "utils.hpp" +#include "render_node.hpp" namespace mstch { @@ -28,8 +29,10 @@ class render_section: public boost::static_visitor { std::string section_str; for(auto& token: section) section_str += token.raw(); - - return render_context::push(ctx).render(template_type{fun(section_str)}); + template_type interpreted{fun([this](const mstch::node& n) { + return visit(render_node(ctx), n); + }, section_str)}; + return render_context::push(ctx).render(interpreted); } std::string operator()(const array& array) const { diff --git a/test/data/comments.hpp b/test/data/comments.hpp index ca97699..96baccb 100644 --- a/test/data/comments.hpp +++ b/test/data/comments.hpp @@ -1,3 +1,3 @@ const mstch::node comments_data = mstch::map{ - {"title", mstch::lambda{[](){return std::string{"A Comedy of Errors"};}}} + {"title", mstch::lambda{[]()->mstch::node{return std::string{"A Comedy of Errors"};}}} }; \ No newline at end of file diff --git a/test/data/escaped.hpp b/test/data/escaped.hpp index b6aecf3..bcb21bb 100644 --- a/test/data/escaped.hpp +++ b/test/data/escaped.hpp @@ -1,4 +1,4 @@ const mstch::node escaped_data = mstch::map{ - {"title", mstch::lambda{[](){ return std::string{"Bear > Shark"}; }}}, - {"entities", mstch::lambda{[](){ return std::string{"" \"'<>/"}; }}} + {"title", mstch::lambda{[]()->mstch::node{ return std::string{"Bear > Shark"}; }}}, + {"entities", mstch::lambda{[]()->mstch::node{ return std::string{"" \"'<>/"}; }}} }; \ No newline at end of file diff --git a/test/data/higher_order_sections.hpp b/test/data/higher_order_sections.hpp index 5a81935..d99a5a5 100644 --- a/test/data/higher_order_sections.hpp +++ b/test/data/higher_order_sections.hpp @@ -19,7 +19,7 @@ class higher_order_sections: public mstch::object { } mstch::node bolder() { - return mstch::lambda{[this](const std::string& text) { + return mstch::lambda{[this](const std::string& text) -> mstch::node { return "" + text + " " + m_helper; }}; } diff --git a/test/data/nested_higher_order_sections.hpp b/test/data/nested_higher_order_sections.hpp index fc56a4a..26ed72a 100644 --- a/test/data/nested_higher_order_sections.hpp +++ b/test/data/nested_higher_order_sections.hpp @@ -1,5 +1,5 @@ const mstch::node nested_higher_order_sections_data = mstch::map{ - {"bold", mstch::lambda{[](const std::string& text) { + {"bold", mstch::lambda{[](const std::string& text) -> mstch::node { return std::string{""} + text + std::string{""}; }}}, {"person", mstch::map{{"name", std::string{"Jonas"}}}} diff --git a/test/data/partial_template.hpp b/test/data/partial_template.hpp index 932b936..88c659b 100644 --- a/test/data/partial_template.hpp +++ b/test/data/partial_template.hpp @@ -1,4 +1,4 @@ const mstch::node partial_template_data = mstch::map{ - {"title", mstch::lambda{[](){ return std::string{"Welcome"}; }}}, - {"again", mstch::lambda{[](){ return std::string{"Goodbye"}; }}}, + {"title", mstch::lambda{[]()->mstch::node{ return std::string{"Welcome"}; }}}, + {"again", mstch::lambda{[]()->mstch::node{ return std::string{"Goodbye"}; }}}, }; \ No newline at end of file diff --git a/test/data/section_functions_in_partials.hpp b/test/data/section_functions_in_partials.hpp index 57fa9b5..1fc4434 100644 --- a/test/data/section_functions_in_partials.hpp +++ b/test/data/section_functions_in_partials.hpp @@ -1,5 +1,5 @@ const mstch::node section_functions_in_partials_data = mstch::map{ - {"bold", mstch::lambda{[](const std::string& text) { + {"bold", mstch::lambda{[](const std::string& text) -> mstch::node { return std::string{""} + text + std::string{""}; }}} }; \ No newline at end of file diff --git a/test/data/unescaped.hpp b/test/data/unescaped.hpp index c4e6ef3..66914a9 100644 --- a/test/data/unescaped.hpp +++ b/test/data/unescaped.hpp @@ -1,3 +1,3 @@ const mstch::node unescaped_data = mstch::map{ - {"title", mstch::lambda{[](){ return std::string{"Bear > Shark"}; }}} + {"title", mstch::lambda{[]()->mstch::node{ return std::string{"Bear > Shark"}; }}} }; \ No newline at end of file diff --git a/test/specs_lambdas.hpp b/test/specs_lambdas.hpp index 951b1b4..13bf4ca 100644 --- a/test/specs_lambdas.hpp +++ b/test/specs_lambdas.hpp @@ -1,26 +1,26 @@ -std::map> specs_lambdas { +std::map> specs_lambdas { {"Interpolation", [](const std::string&) { - return "world"; + return std::string{"world"}; }}, {"Interpolation - Expansion", [](const std::string&) { - return "{{planet}}"; + return std::string{"{{planet}}"}; }}, {"Interpolation - Multiple Calls", [](const std::string&) { - static int calls = 0; return std::to_string(++calls); + static int calls = 0; return ++calls; }}, {"Escaping", [](const std::string&) { - return ">"; + return std::string{">"}; }}, {"Section", [](const std::string& txt) { - return (txt == "{{x}}") ? "yes" : "no"; + return std::string{(txt == "{{x}}") ? "yes" : "no"}; }}, {"Section - Expansion", [](const std::string& txt) { - return txt + "{{planet}}" + txt; + return txt + std::string{"{{planet}}"} + txt; }}, {"Section - Multiple Calls", [](const std::string& txt) { return "__" + txt + "__"; }}, {"Inverted Section", [](const std::string& txt) { - return ""; + return false; }} }; \ No newline at end of file