basic boost spirit semantic action doesn't compile

余生长醉 提交于 2019-12-12 09:04:42

问题


I am trying to add a greater than operator > to a ast: the code is 95% identical to the code in the docs.

Two points of interest below

  • A block of code where I'm trying to write support for greater than: commented in the code below.
  • A single line in the parse for term which fails to compile because I don't yet understand semantic actions yet: not sure how to bind the lhs of lhs > rhs through phoenix and semantic actions.

The solution should be trivial for regular users of Spirit, but I am still learning, and only getting by through examples so far.

Any help would be appreciated. TIA.

Code

#define BOOST_SPIRIT_DEBUG

#include <boost/spirit/include/qi.hpp>
#include <boost/config/warning_disable.hpp>
#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/phoenix_core.hpp>
#include <boost/spirit/include/phoenix_operator.hpp>
#include <boost/spirit/include/phoenix_stl.hpp>
#include <boost/spirit/include/classic_symbols.hpp>
#include <boost/fusion/include/adapt_struct.hpp>
#include <boost/fusion/include/io.hpp>
#include <boost/algorithm/string.hpp>
#include <boost/regex.hpp>   // std::regex not fully implemented in stdc++ yet

#include <string>
#include <map>
#include <utility>
#include <functional>
#include <iostream>
#include <string>
#include <vector>

namespace client
{
    namespace qi = boost::spirit::qi;
    namespace ascii = boost::spirit::ascii;

    struct binary_op;
    struct unary_op;
    struct nil {};

    struct expression_ast
    {
        typedef
        boost::variant<
        nil // can't happen!
        , double
        , std::string
        , boost::recursive_wrapper<expression_ast>
        , boost::recursive_wrapper<binary_op>
        , boost::recursive_wrapper<unary_op>
        >
        type;

        expression_ast()
            : m_expr(nil()) {}

        template <typename Expr>
        expression_ast(Expr const& expr)
            : m_expr(expr) {}

        expression_ast& operator+=(expression_ast const& rhs);
        expression_ast& operator-=(expression_ast const& rhs);
        expression_ast& operator*=(expression_ast const& rhs);
        expression_ast& operator/=(expression_ast const& rhs);


        type m_expr;
    };

    struct binary_op
    {
        binary_op(
            char op
            , expression_ast const& left
            , expression_ast const& right)
            : m_op(op), m_left(left), m_right(right) {}

        char m_op;
        expression_ast m_left;
        expression_ast m_right;
    };

    struct unary_op
    {
        unary_op(
            char op
            , expression_ast const& subject)
            : m_op(op), m_subject(subject) {}

        char m_op;
        expression_ast m_subject;
    };

    expression_ast& expression_ast::operator+=(expression_ast const& rhs)
    {
        m_expr = binary_op('+', m_expr, rhs);
        return *this;
    }

    expression_ast& expression_ast::operator-=(expression_ast const& rhs)
    {
        m_expr = binary_op('-', m_expr, rhs);
        return *this;
    }

    expression_ast& expression_ast::operator*=(expression_ast const& rhs)
    {
        m_expr = binary_op('*', m_expr, rhs);
        return *this;
    }

    expression_ast& expression_ast::operator/=(expression_ast const& rhs)
    {
        m_expr = binary_op('/', m_expr, rhs);
        return *this;
    }

    // We should be using expression_ast::operator-. There's a bug
    // in phoenix type deduction mechanism that prevents us from
    // doing so. Phoenix will be switching to BOOST_TYPEOF. In the
    // meantime, we will use a phoenix::function below:
    struct negate_expr
    {
        template <typename T>
        struct result
        {
            typedef T type;
        };

        expression_ast operator()(expression_ast const& expr) const
        {
            return expression_ast(unary_op('-', expr));
        }
    };

    static boost::phoenix::function<negate_expr> neg;

    struct ast_print
    {
        typedef std::string result_type;

        std::string operator()(qi::info::nil) const
        {
            return "";
        }
        std::string operator()(std::string const& str) const
        {
            return str;
        }
        std::string operator()(double d) const
        {
            std::ostringstream oss;
            oss << d;
            return oss.str();
        }

        std::string operator()(expression_ast const& ast) const
        {
            return boost::apply_visitor(*this, ast.m_expr);
        }

        std::string operator()(binary_op const& expr) const
        {
            std::ostringstream oss;
            oss << "op:" << expr.m_op << "(";
            oss << boost::apply_visitor(*this, expr.m_left.m_expr);
            oss << ", ";
            oss << boost::apply_visitor(*this, expr.m_right.m_expr);
            oss << ')';
            return oss.str();
        }

        std::string operator()(unary_op const& expr) const
        {
            std::ostringstream oss;
            oss << "op:" << expr.m_op << "(";
            oss << boost::apply_visitor(*this, expr.m_subject.m_expr);
            oss << ')';
            return oss.str();
        }
    };

    std::ostream& operator << (std::ostream& stream, const expression_ast& expr)
    {
        ast_print printer;
        stream << printer(expr) << std::endl;
        return stream;
    }

    // CODE ADDED HERE ------------------------------------------------------------
    template< char OP >
    struct binary_expr
    {
        template <typename T>
        struct result
        {
            typedef T type;
        };

        expression_ast operator()(expression_ast const& lhs,expression_ast const& rhs) const
        {
            return expression_ast(binary_op( OP, lhs, rhs ));
        }
    };

    static boost::phoenix::function<binary_expr<'>'>> gt;
    // CODE ADDED END HERE -------------------------------------------------------

    template <typename Iterator>
    struct ParserGenerator : qi::grammar<Iterator, expression_ast(), ascii::space_type>
    {
        ParserGenerator() : ParserGenerator::base_type(expression)
    {
        using qi::_val;
        using qi::_1;
        using qi::double_;
        using qi::iso8859_1::char_;
        using qi::iso8859_1::space;
        using qi::eol;
        using boost::spirit::ascii::string;

        comment =
            space >> ("//" >> *(char_ - eol) >> eol)
            ;

        expression =
            term                            [_val = _1]
            >> *(   ('+' >> term            [_val += _1])
                |   ('-' >> term            [_val -= _1])
                )
            ;

        term =
            factor                          [_val = _1]
            >> *(   ('*' >> factor          [_val *= _1])
                |   ('/' >> factor          [_val /= _1])
//          |   ('>' >> factor          [_val = gt(qi::_val,_1)]) // PROBLEM HERE!
                )
            ;

        factor =
            symbol                          [_val = _1]
            | double_                       [_val = _1]
            |   '(' >> expression           [_val = _1] >> ')'
            |   ('-' >> factor              [_val = neg(_1)])
            |   ('+' >> factor              [_val = _1])
            ;

        symbol %= 
            (symbol_raw 
            >> *( string("[") >> +qi::digit >> string("]"))
            >> *( string(".") >> symbol ))
            ;

        symbol_raw %= 
            +(qi::alpha | qi::char_( "_" ))
            ;

        BOOST_SPIRIT_DEBUG_NODE(expression);
        BOOST_SPIRIT_DEBUG_NODE(term);
        BOOST_SPIRIT_DEBUG_NODE(factor);
        BOOST_SPIRIT_DEBUG_NODE(comment);
        BOOST_SPIRIT_DEBUG_NODE(symbol);
        BOOST_SPIRIT_DEBUG_NODE(symbol_raw);
    }

    qi::rule<Iterator, expression_ast(), ascii::space_type>
        expression, term, factor, comment;

    qi::rule<Iterator, std::string(), ascii::space_type>
        symbol, symbol_raw;
    };
}

int main(int argc, char* argv[])
{
    using boost::spirit::ascii::space;
    using client::expression_ast;
    using client::ast_print;

    typedef std::string::const_iterator iterator_type;
    typedef client::ParserGenerator<iterator_type> ParserGenerator;

    ParserGenerator pg;   // our grammar
    std::string predicate( "i_.c>x[0]" );
    expression_ast  ast;
    ast_print       printer;

    iterator_type iter = predicate.begin(), end = predicate.end();
    if ( phrase_parse( iter, end, pg, space, ast ))
    {
        std::cerr << printer( ast ) << std::endl;
    }

    return 0;
}

回答1:


TL;DR use

template <typename, typename> struct result { typedef expression_ast type; };

inside binary_expr struct. Here's why:


You declare a functor object, to be used as a Phoenix lazy actor.

The functor is what's known as a Deferred/Polymorphic Calleable Object (PCE) in the Boost documentation. This means that the expression templates building the Phoenix actors will actually only do actual overload resolution/type deduction for the function arguments at the actual time of application.

The function return type cannot be deduced (just like it cannot in regular (non-lazy) C++). This is where boost libraries employ the BOOST_RESULT_OF protocol.

Note The RESULT_OF protocol is obsolete/redundant when using C++11, since C++11 has decltype. To enable that, on most compilers you need to

#define BOOST_RESULT_OF_USE_DECLTPYE

(allthough the release notes of v1_52_0 make it clear that decltype is becoming the default for compilers that support it well enough).

What this protocol entails, is this:

  • When you use Polymorphic Calleable Object with n parameters, boost will look for a nested type typedef inside a nested class template result, parameterized with the n actual argument types. This will be the return type
  • The actual functor invocation calls operator() with those arguments. Here, C++ will be left to do the overload resolution.

In general, you are expected to write your functor in fully parameterized style:

template<char OP>
struct binary_expr
{
    template <typename, typename> struct result { typedef expression_ast type; };

    template <typename A, typename B>
    typename result<A,B>::type operator()(A const&a,B const&b) const {
        return expression_ast(binary_op( OP, a, b ));
    }
};

This works. However, because the actual overload resolution on argument types is done by the compiler, you can change the operator() signature to use fixed types:

template <typename A, typename B>
expression_ast operator()(A const&a,B const&b) const {
    return binary_op(OP, a, b);
}

If you wish, you can do away with (some) template parameters:

template <typename E>
expression_ast operator()(E const&a,E const&b) const {
    return binary_op(OP, a, b);
}

Or even not a function template at all:

expression_ast operator()(expression_ast const&,expression_ast const&) const;

Everything is fine, as long as one overload matches the actual argument types passed. The important bit, though, is that the result<...>::type is getting evaluated regardless of the signature of operator(), and as such it will need precisely the number of arguments expected.

Note also that you can, in this fashion, combine functors of different arity:

template <char OP> struct operator_expr 
{
    template <typename T, typename=T> struct result 
        { typedef expression_ast type; };

    expression_ast operator()(expression_ast const& expr) const
    { return expression_ast(unary_op(OP, expr)); }

    expression_ast operator()(expression_ast const&a, expression_ast const&b) const 
    { return binary_op(OP, a, b); }
};

static boost::phoenix::function<operator_expr<'-'>> neg;
static boost::phoenix::function<operator_expr<'>'>> gt;

This works, because both result<T>::type and result<T,U>::type are valid type expressions with this definition.


Bonus notes:

  1. You had a strangeness there where you said

    template <typename T> struct result { typedef T type; };
    

    instead of

    template <typename> struct result { typedef expression_ast type; };
    

    This is because the return type shouls, in fact, not vary depending on the actual argument type. In your sample, the argument type would normally be the same, but it didn't make sense technically.

  2. You can do things without the BOOST_RESULT_OF protocol if you use decltype. This means you can just drop the nested result struct

  3. You can also adapt normal functions as Phoenix actors:

    #define BOOST_SPIRIT_USE_PHOENIX_V3
    
    // ...
    
    expression_ast neg_expr(expression_ast const&a)                         { return unary_op ('-', a); }
    expression_ast gt_expr (expression_ast const&a, expression_ast const&b) { return binary_op('>', a, b); }
    
    BOOST_PHOENIX_ADAPT_FUNCTION(expression_ast, neg, neg_expr, 1)
    BOOST_PHOENIX_ADAPT_FUNCTION(expression_ast, gt,  gt_expr,  2)
    

    This will basically write the functor objects for you, including the RESULT_OF protocol bits.

  4. Finally, you can use standard Phoenix actors instead of defining custom ones. In that case, there is no need for any of the above:

    using phx = boost::phoenix;
    // ...
       |   ('>' >> factor [_val = phx::construct<binary_op>('>', _val, _1)]) // PROBLEM HERE!
    // ...
       |   ('-' >> factor [_val = phx::construct<unary_op>('-', _1)])
    

Wrap-up

The complete code is here: http://ideone.com/Xv9IH1 and was tested on

  • MSVC 2012, boost 1_52_0, Win64
  • GCC 4.8, boost 1_52_0, Win64


来源:https://stackoverflow.com/questions/13530994/basic-boost-spirit-semantic-action-doesnt-compile

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!