Commits

David Mugnai  committed 0c8645d

expr: interpolation support "hello #{name}"

parser + ast + tests

  • Participants
  • Parent commits 2f07e7e

Comments (0)

Files changed (3)

 #include <vector>
 #include "expr/context.h"
 #include "expr/types.h"
+#include "utils.h"
 
 namespace expr {
     namespace ast {
             }
         };
 
+        struct StringifyNode : Node {
+            Node const* child;
+
+            StringifyNode(Node const* c) : child(c) {};
+
+            virtual expr_type evaluate(Context const& ctx) const {
+                auto result = this->child->evaluate(ctx);
+                struct to_string : boost::static_visitor<string> {
+                    string operator()(string const& v) const {
+                        return v;
+                    }
+                    string operator()(double const& v) const {
+                        return supplant("%", v);
+                    }
+                    string operator()(vector<string> const& v) const {
+                        return supplant("{vector of strings size=%}", v.size());
+                    }
+                    string operator()(vector<double> const& v) const {
+                        return supplant("{vector of numbers size=%}", v.size());
+                    }
+                    string operator()(expr::types::callable const& v) const {
+                        return supplant("{callable}");
+                    }
+                };
+                return boost::apply_visitor(to_string(), result);
+            }
+        };
+
         struct BinaryOperatorVisitor : boost::static_visitor<> {
             template<typename T, typename U>
             void operator()(T&, U&) const {

File expr/parser.h

 
             qi::rule<Iterator, ast::FunctionCallNode*(), Skipper> function_call;
             qi::rule<Iterator, ast::SymbolNode const*()> symbol;
-            qi::rule<Iterator, ast::ValueNode<std::string> const*()> string_value;
-            qi::rule<Iterator, ast::Node const*(), Skipper> interpolation;
+
             qi::rule<Iterator, ast::ValueNode<double> const*()> number_value;
+            qi::rule<Iterator, ast::Node const*(), qi::locals<char>> string_value;
+            qi::rule<Iterator, ast::StringifyNode const*()> interpolation;
             qi::rule<Iterator, std::string()> identifier;
+            qi::symbols<char const, char const> escape_chars;
 
             ExprGrammar() : ExprGrammar::base_type(expression) {
                 {
                     using operand_signature = boost::variant<
                         ast::FunctionCallNode const*,
                         ast::SymbolNode const*,
-                        ast::ValueNode<std::string> const*,
+                        ast::Node const*,
                         ast::ValueNode<double> const*>;
                     auto handler = [](operand_signature const& value) -> ast::Node const* {
                         ast::Node const* output;
                         else if(auto p = boost::get<ast::SymbolNode const*>(&value)) {
                             output = *p;
                         }
-                        else if(auto p = boost::get<ast::ValueNode<std::string> const*>(&value)) {
+                        else if(auto p = boost::get<ast::ValueNode<double> const*>(&value)) {
                             output = *p;
                         }
-                        else if(auto p = boost::get<ast::ValueNode<double> const*>(&value)) {
+                        else if(auto p = boost::get<ast::Node const*>(&value)) {
                             output = *p;
                         }
                         return output;
                 {
                     using boost::proto::deep_copy;
 
-                    interpolation = qi::lit('#') >> '{' >> expression >> '}';
-                    /*
+                    using qi::_a;
+                    using qi::_1;
+                    using qi::_r1;
+
+                    using attr_signature = std::vector<boost::variant<expr::ast::StringifyNode const*, char>>;
+                    auto handler = [this](attr_signature const& elements) -> expr::ast::Node const* {
+                        if(elements.size() == 0) {
+                            return new expr::ast::ValueNode<std::string>("");
+                        }
+
+                        std::vector<expr::ast::Node const*> reassembled;
+                        std::string _acc;
+
+                        for(auto& el : elements) {
+                            if(auto n = boost::get<expr::ast::StringifyNode const*>(&el)) {
+                                if(_acc.size()) {
+                                    reassembled.push_back(new expr::ast::ValueNode<std::string>(_acc));
+                                    _acc = "";
+                                }
+                                reassembled.push_back(*n);
+                            }
+                            else if(auto c = boost::get<char>(&el)) {
+                                _acc += *c;
+                            }
+                        }
+                        if(_acc.size()) {
+                            reassembled.push_back(new expr::ast::ValueNode<std::string>(_acc));
+                        }
+
+                        auto it = reassembled.begin();
+                        auto output = *it++;
+                        while(it != reassembled.end()) {
+                            output = this->binary_operation('+', output, *it++);
+                        }
+                        return output;
+                    };
                     string_value =
                         qi::omit[qi::char_("'\"")[_a = _1]]
-                        >> qi::lexeme[(*(escape_chars | (qi::char_ - qi::char_(_a)) ))]
+                        >> (
+                            *(
+                                interpolation
+                                | qi::lexeme[(escape_chars | (qi::char_ - qi::char_(_a)))]
+                            )
+                        )[qi::_val = phx::bind(handler, _1)]
                         >> qi::lit(_a);
-                    */
-                    // "ciao #{mondo}"
-                    string_value = qi::attr_cast(
-                        // deep_copy is a workaround for a segfault
-                        // see: http://boost.2283326.n4.nabble.com/transform-attribute-gt-segfault-td4657399.html
-                        deep_copy(
-                            qi::lexeme['"' > +~qi::char_('"') > '"'] |
-                            qi::lexeme["'" > +~qi::char_("'") > "'"]
-                        )
-                    );
                 }
 
+                interpolation =
+                    qi::lit('#')
+                    >> '{'
+                    >> qi::skip(Skipper())[
+                        expression[qi::_val = phx::new_<ast::StringifyNode>(qi::_1)]]
+                    >> *Skipper()
+                    >>'}';
+
                 number_value = qi::attr_cast(qi::double_);
 
                 identifier = (+qi::alpha >> *qi::alnum)[qi::_val = phx::bind(
                     }, qi::_1)
                 ];
 
+                escape_chars.add
+                    ("\\a", '\a')
+                    ("\\b", '\b')
+                    ("\\f", '\f')
+                    ("\\n", '\n')
+                    ("\\r", '\r')
+                    ("\\t", '\t')
+                    ("\\v", '\v')
+                    ("\\\\", '\\')
+                    ("\\\'", '\'')
+                    ("\\\"", '\"');
 
                 this->expression.name("expression");
                 this->term.name("expression term");
                 //qi::debug(this->operand);
                 //qi::debug(this->function_call);
                 //qi::debug(this->symbol);
-                //qi::debug(this->interpolation);
                 //qi::debug(this->string_value);
+                //qi::debug(this->interpolation);
                 //qi::debug(this->number_value);
                 //qi::debug(this->identifier);
             }

File tests/test_expr.cpp

     void SetUp() {
         SingleRule::SetUp();
         this->context->put("a", 42);
+        this->context->put("b", "hello");
     }
 };
-TEST_P(InterpolationRuleTest, bar) {
+TEST_P(InterpolationRuleTest, test) {
     test_rule(grammar.interpolation, this->GetParam());
 }
 INSTANTIATE_TEST_CASE_P(
     Instance1,
     InterpolationRuleTest,
     ::testing::Values(
-        make_tuple("#{1}", true, 1),
-        make_tuple("#{1+2}", true, 3),
-        make_tuple("#{ 2 + 2 }", true, 4),
-        make_tuple("#{a*2}", true, 84),
-        make_tuple("#{a+a}", true, 84),
-        make_tuple("#{a+a/2}", true, 63),
-        make_tuple("#{(a+a)/0.5}", true, 168),
-        make_tuple("#{", false, 0),
-        make_tuple("#{}", false, 0),
-        make_tuple("{}", false, 0)
+        make_tuple("#{1}", true, "1"),
+        make_tuple("#{1+2}", true, "3"),
+        make_tuple("#{ 2 + 2 }", true, "4"),
+        make_tuple("#{a*2}", true, "84"),
+        make_tuple("#{a+a}", true, "84"),
+        make_tuple("#{a+a/2}", true, "63"),
+        make_tuple("#{(a+a)/0.5}", true, "168"),
+        make_tuple("#{b}", true, "hello"),
+        make_tuple("#{b+b}", true, "hellohello"),
+        make_tuple("#{", false, "0"),
+        make_tuple("#{}", false, "0"),
+        make_tuple("{}", false, "0")
 ));
 
+struct StringRuleTest : SingleRule {
+    void SetUp() {
+        SingleRule::SetUp();
+        using expr::types::wrap_function;
+        this->context->put("a", 42);
+        this->context->put("b", "hello");
+        this->context->put("c", "hello world");
+        this->context->put("vd", vector<double>{1,2,3});
+        this->context->put("vs", vector<string>{"1", "2", "3"});
 
+        std::function<double(double, double)> mul = std::bind(
+            &StringRuleTest::mul, this, std::placeholders::_1, std::placeholders::_2);
+        this->context->put("mul", wrap_function(mul));
+
+    }
+    double mul(double a, double b) {
+        return a*b;
+    }
+};
+TEST_P(StringRuleTest, test) {
+    test_rule(grammar.string_value, this->GetParam());
+}
+INSTANTIATE_TEST_CASE_P(
+    Instance1,
+    StringRuleTest,
+    ::testing::Values(
+        make_tuple("\"abc\"", true, "abc"),
+        make_tuple("'abc'", true, "abc"),
+        make_tuple("'hello world'", true, "hello world"),
+        make_tuple(R"('hello \"world\"')", true, "hello \"world\""),
+        make_tuple("'#{3}'", true, "3"),
+        make_tuple("'#{3+3}'", true, "6"),
+        make_tuple("'#{ 3 + 3 }'", true, "6"),
+        make_tuple("'#{a}'", true, "42"),
+        make_tuple("'hello #{a}'", true, "hello 42"),
+        make_tuple("'#{a+a}'", true, "84"),
+        make_tuple("'#{b}'", true, "hello"),
+        make_tuple("'#{b+b}'", true, "hellohello"),
+        make_tuple("'#{c}'", true, "hello world"),
+        make_tuple("'#{b} #{a}'", true, "hello 42"),
+        make_tuple("'#{b}#{b}'", true, "hellohello"),
+        make_tuple("' #{a} '", true, " 42 "),
+        make_tuple("'#{vd}'", true, "{vector of numbers size=3}"),
+        make_tuple("'#{vs}'", true, "{vector of strings size=3}"),
+        make_tuple("'#{mul}'", true, "{callable}"),
+        make_tuple("'#{mul(21, 2)}'", true, "42"),
+        make_tuple("'#{mul(a, 2)}'", true, "84"),
+        make_tuple("'#{mul(a, a)}'", true, "1764")
+));
 
 using expr::parser::parse;
 using expr::context::Context;