Search code examples
boost-proto

Matching operator somewhere in Proto expression


Why doesn't the following Boost.Proto grammar match dereference operator, and what's the correct way to do this?

#include <iostream>
#include <boost/proto/proto.hpp>

namespace proto = boost::proto;
using proto::_;

struct has_deref : proto::or_<
    proto::dereference<_>,
    proto::nary_expr<_, proto::vararg<has_deref>>
    >
{};

template<class Expr>
void test_expr(const Expr &e)
{
    proto::display_expr(e);
    std::cout << "matches? " << std::boolalpha << proto::matches<Expr,
has_deref>::value << std::endl;
}

int main()
{
    test_expr(proto::lit(1) + *proto::lit(2));
}

Solution

  • According to your definition your has_deref grammar matches expressions that are either:

    • A dereference operator applied to ANY expression
    • ANY n-ary (which can be unary like: complement, negate or unary_plus; binary like: subscript or plus; ternary like if_else_, or directly n-ary like function) expression whose arguments recursively match the has_deref grammar.

    In your example you have a plus<terminal<int>,dereference<terminal<int> > > which is something like binary_expr<tag::plus,terminal<int>,unary_expr<tag::dereference,terminal<int> > >. When trying to match your expression (I think) Proto tries first the first element of your grammar (dereference<_>) and obviously fails. Then tries the second one and binary_expr<tag::plus,...> matches nary_expr<_,...> and so it recursively tries to match the grammar to both argument expressions of the plus. The second one matches directly, but the first one (terminal<int>) does not match either of the possibilities and so the whole expression fails to match.

    One possible (and sadly clunky) approach to do what you want could be something like:

    struct has_deref : proto::or_< //ignoring if_then_else and function
        proto::dereference<_>, 
        proto::unary_expr<_, has_deref>,
        proto::binary_expr<_, has_deref, _>, 
        proto::binary_expr<_, _, has_deref>
        > 
    {};
    

    that matches expressions that are either:

    • A dereference operator applied to ANY expression.
    • A unary operator applied to expressions that recursively match has_deref.
    • A binary operator whose first operand is an expression that matches has_deref and its second is ANY expression (including expressions that match has_deref) .
    • A binary operator whose first operand is an expression that does not match has_deref and its second is one that does.

    Here is an example that checks that several expressions match this new grammar (extended to also check for functions with at most 2 arguments):

    #include <iostream> 
    #include <boost/proto/proto.hpp> 
    
    namespace proto = boost::proto; 
    using proto::_; 
    
    template <typename Grammar>
    struct function_contains_at_least_one : proto::or_<//this is awful, there should be a better way
        proto::function<Grammar>,
        proto::function<Grammar,_>,
        proto::function<_,Grammar>,
        proto::function<Grammar,_,_>,
        proto::function<_,Grammar,_>,
        proto::function<_,_,Grammar>
        >
    {};
    
    struct has_deref : proto::or_< //ignoring if_else_
        proto::dereference<_>, 
        proto::unary_expr<_,has_deref>,
        proto::binary_expr<_, _,has_deref>, 
        proto::binary_expr<_,has_deref,_>,
        function_contains_at_least_one<has_deref>
        > 
    {}; 
    
    template<class Expr> 
    void test_expr(const std::string& repr, const Expr &e) 
    { 
        std::cout << repr << " matches 'has_deref'? " << std::boolalpha << proto::matches<Expr, 
    has_deref>::value << std::endl; 
        //display_expr(e);
    } 
    
    
    #define TEST_EXPR( EXPR ) test_expr(#EXPR,EXPR)
    
    int main() 
    { 
        using proto::lit;
        TEST_EXPR(lit(1) + lit(2));
        TEST_EXPR(lit(1) + *lit(2)); 
        TEST_EXPR(*lit(1) + *lit(2));
        TEST_EXPR(*(lit(1) * (lit("foo")+lit(2))));
        TEST_EXPR(+-*lit(1)[~*lit(2)++]);
    
        //testing functions
        TEST_EXPR( lit(1)() );
        TEST_EXPR( (*lit(1))() );
        TEST_EXPR( lit(1)(lit(2)) );
        TEST_EXPR( (*lit(1))(lit(2)) );
        TEST_EXPR( lit(1)(*lit(2)) );
        TEST_EXPR( lit(1)(lit(2),lit(3)) );
        TEST_EXPR( (*lit(1))(*lit(2),*lit(3)) );
    }