Commits

David Mugnai committed a3ee386 Merge

Merge branch 'f_interpolation'

  • Participants
  • Parent commits cb291d0, 0c8645d

Comments (0)

Files changed (4)

 #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::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;
 
-                    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_("'") > "'"]
-                        )
-                    );
+                    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]]
+                        >> (
+                            *(
+                                interpolation
+                                | qi::lexeme[(escape_chars | (qi::char_ - qi::char_(_a)))]
+                            )
+                        )[qi::_val = phx::bind(handler, _1)]
+                        >> qi::lit(_a);
                 }
 
+                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(
                         return std::string(x.data(), x.size());
                     }, 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");
+                this->factor.name("expression factor");
+                this->operand.name("operand");
+                this->function_call.name("function_call");
+                this->symbol.name("symbol");
+                this->interpolation.name("interpolation");
+                this->string_value.name("string");
+                this->number_value.name("number");
+                this->identifier.name("identifier");
+
+                //qi::debug(this->expression);
+                //qi::debug(this->term);
+                //qi::debug(this->factor);
+                //qi::debug(this->operand);
+                //qi::debug(this->function_call);
+                //qi::debug(this->symbol);
+                //qi::debug(this->string_value);
+                //qi::debug(this->interpolation);
+                //qi::debug(this->number_value);
+                //qi::debug(this->identifier);
             }
 
             ast::Node const* binary_operation(char op, ast::Node const* first, ast::Node const* second) {
                 }
                 return output;
             }
-
-            void enable_debug() {
-                this->expression.name("expression");
-                this->term.name("expression term");
-                this->factor.name("expression factor");
-                this->operand.name("operand");
-                this->function_call.name("function_call");
-                this->symbol.name("symbol");
-                this->string_value.name("string");
-                this->number_value.name("number");
-                this->identifier.name("identifier");
-
-                qi::debug(this->expression);
-                qi::debug(this->term);
-                qi::debug(this->factor);
-                qi::debug(this->operand);
-                qi::debug(this->function_call);
-                qi::debug(this->symbol);
-                qi::debug(this->string_value);
-                qi::debug(this->number_value);
-                qi::debug(this->identifier);
-            }
         };
 
         ast::Node const* parse(std::string const& src) {
             ExprGrammar<std::string::const_iterator> g;
-            //g.enable_debug();
             auto start = src.begin();
             auto end = src.end();
             ast::Node const* a = nullptr;

File tests/test_expr.cpp

 #include <functional>
 #include <string>
+#include <tuple>
+#include <vector>
 #include "expr.h"
 #include "gtest/gtest.h"
+#include "tests/utils.h"
 
+using std::make_tuple;
 using std::string;
+using std::tuple;
+using std::vector;
+
+using expr::ast::Node;
+using expr::parser::ExprGrammar;
+
+using expr::types::expr_type;
+using SingleRule = slam::test::SingleGrammarRuleTest<ExprGrammar<string::const_iterator>, expr_type>;
+
+struct InterpolationRuleTest : SingleRule {
+    void SetUp() {
+        SingleRule::SetUp();
+        this->context->put("a", 42);
+        this->context->put("b", "hello");
+    }
+};
+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("#{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;

File tests/utils.h

+#pragma once
+#include <string>
+#include <tuple>
+#include "gtest/gtest.h"
+#include <boost/spirit/include/qi.hpp>
+#include "expr.h"
+
+namespace slam { namespace test {
+
+using std::get;
+using std::string;
+using std::tuple;
+using std::make_tuple;
+
+template<typename TGrammar, typename TResult>
+struct SingleGrammarRuleTest : ::testing::TestWithParam<tuple<char const*, bool, TResult>> {
+    using PType = tuple<char const*, bool, TResult>;
+    TGrammar grammar;
+    expr::context::Context* context;
+
+    struct are_strict_equals : boost::static_visitor<bool>
+    {
+        template<typename T>
+        bool operator()( T const& lhs, T const& rhs ) const {
+            EXPECT_EQ(lhs, rhs);
+            return lhs == rhs;
+        }
+
+        template<typename T, typename U>
+        bool operator()(T const&, U const&) const {
+            ADD_FAILURE() << "cannot compare different types";
+            return false;
+        }
+
+        template<typename T>
+        bool operator()(T const&, expr::types::callable const&) const {
+            ADD_FAILURE() << "callable cannot be compared";
+            return false;
+        }
+
+        template<typename T>
+        bool operator()(expr::types::callable const&, T const&) const {
+            ADD_FAILURE() << "callable cannot be compared";
+            return false;
+        }
+
+        bool operator()(expr::types::callable const&, expr::types::callable const&) const {
+            ADD_FAILURE() << "callable cannot be compared";
+            return false;
+        }
+    };
+
+    virtual void SetUp() {
+        context = new expr::context::Context();
+    }
+    virtual void TearDown() {
+        delete context;
+        context = nullptr;
+    }
+
+    template<typename T>
+    tuple<bool, typename T::attr_type> parse(string const& input, T rule) {
+        typename T::attr_type result;
+        bool parsed = false;
+
+        namespace qi = boost::spirit::qi;
+        try {
+            parsed = qi::phrase_parse(input.begin(), input.end(), rule, qi::space, result);
+        } catch(qi::expectation_failure<string::const_iterator>& e) {
+            result = typename T::attr_type{};
+        }
+        return make_tuple(parsed, result);
+    }
+
+    template<typename R>
+    void test_rule(R const& rule, PType const& test) {
+        auto parsing_result = this->parse(get<0>(test), rule);
+        auto success = get<0>(parsing_result);
+
+        EXPECT_EQ(get<1>(test), success) << " input --> " << get<0>(test);
+        if(get<1>(test) && success) {
+            auto node = get<1>(parsing_result);
+
+            auto expected_value = get<2>(test);
+            auto value = node->evaluate(*this->context);
+
+            boost::apply_visitor(are_strict_equals(), expected_value, value);
+        }
+    }
+};
+
+}};