базовое семантическое действие Boost Spirit не компилируется

Я пытаюсь добавить оператор больше чем > к ast: код на 95% идентичен коду в документах.

Две достопримечательности ниже

  • Блок кода, в котором я пытаюсь написать поддержку больше, чем: прокомментирован в приведенном ниже коде.
  • Единственная строка в синтаксическом анализе для term, которая не компилируется, потому что я еще не понимаю семантические действия: не знаю, как связать lhs из lhs > rhs с помощью феникса и семантических действий.

Решение должно быть тривиальным для обычных пользователей Spirit, но я все еще учусь и пока только на примерах.

Любая помощь будет оценена по достоинству. ТИА.

Код

#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;
}

person kfmfe04    schedule 23.11.2012    source источник
comment
+1 @llonesmiz вау - заслуживает +10 за работу с Spirit Voodoo - я почти боялся опубликовать это на SO. Наверное, я не должен стесняться...   -  person kfmfe04    schedule 23.11.2012
comment
проверено для работы с g ++ 4.7.2 с использованием -std = c ++ 11 - @llonesmiz - если мы не получим известие от sehe в течение недели, я сообщу вам позже, чтобы использовать ваше решение в качестве ответа ... ty для вашего помощь!   -  person kfmfe04    schedule 23.11.2012
comment
@llonesmiz (lol...) -- конечно, decltype здесь жульничает :) Я предоставил ответ с кучей информации о том, что было не так, почему и как починить это. (FWIW, в последнее время я в основном бегаю с decltype)   -  person sehe    schedule 24.11.2012


Ответы (1)


TL;DR использовать

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

внутри структуры binary_expr. Вот почему:


Вы объявляете объект-функтор, который будет использоваться в качестве ленивого актера Phoenix.

Функтор — это то, что в документации Boost известно как Deferred/Polymorphic Calleable Object (PCE). Это означает, что шаблоны выражений, создающие акторы Phoenix, фактически будут выполнять фактическое разрешение перегрузки/вывод типа только для аргументов функции в фактическое время приложения.

Тип возвращаемого значения функции нельзя вывести (как и в обычном (неленивом) C++). Именно здесь библиотеки повышения используют протокол BOOST_RESULT_OF.

Примечание Протокол RESULT_OF устарел/избыточен при использовании C++11, поскольку C++11 имеет decltype. Чтобы включить это, на большинстве компиляторов вам нужно

#define BOOST_RESULT_OF_USE_DECLTPYE

(хотя примечания к выпуску v1_52_0 ясно дают понять, что decltype становится значением по умолчанию для компиляторов, которые достаточно хорошо его поддерживают).

Что влечет за собой этот протокол, так это:

  • Когда вы используете Polymorphic Calleable Object с параметрами n, boost будет искать вложенный type typedef внутри шаблона вложенного класса result, параметризованного с помощью n фактические типы аргументов. Это будет тип возвращаемого значения
  • Фактический вызов функтора вызывает operator() с этими аргументами. Здесь C++ останется для разрешения перегрузки.

В общем, вы должны написать свой функтор в полностью параметризованном стиле:

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 ));
    }
};

Это работает. Однако, поскольку фактическое разрешение перегрузки для типов аргументов выполняется компилятором, вы можете изменить сигнатуру operator(), чтобы использовать фиксированные типы:

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

При желании вы можете отказаться от (некоторых) параметров шаблона:

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

Или даже вовсе не шаблон функции:

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

Все хорошо, если одна перегрузка соответствует переданным фактически типам аргументов. Важный момент заключается в том, что result<...>::type оценивается независимо от подписи operator(), и поэтому требуется точное количество ожидаются аргументы.

Обратите также внимание, что таким образом вы можете комбинировать функторы разной арности:

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;

Это работает, потому что и result<T>::type, и result<T,U>::type являются допустимыми выражениями типа с этим определением.


Бонусные примечания:

  1. У тебя была странность там, где ты сказал

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

    вместо

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

    Это связано с тем, что тип возвращаемого значения фактически не должен меняться в зависимости от фактического типа аргумента. В вашем примере тип аргумента был бы обычно одним и тем же, но технически это не имело смысла.

  2. Вы можете делать что-то без протокола BOOST_RESULT_OF, если используете decltype. Это означает, что вы можете просто удалить вложенную структуру result

  3. Вы также можете адаптировать обычные функции в качестве акторов Phoenix:

    #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)
    

    Это в основном запишет объекты функтора для вас, включая биты протокола RESULT_OF.

  4. Наконец, вы можете использовать стандартные актеры Phoenix вместо определения пользовательских. В этом случае ничего из вышеперечисленного не требуется:

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

Заворачивать

Полный код находится здесь: http://ideone.com/Xv9IH1 и был протестирован. на

  • MSVC 2012, повышение 1_52_0, Win64
  • GCC 4.8, повышение 1_52_0, Win64
person sehe    schedule 23.11.2012
comment
+1 свято курит - сехе есть ответ - бб потом переварить! - person kfmfe04; 24.11.2012
comment
Я в восторге - это нужно включить в какой-то учебник / документы - слишком ценно, чтобы быть похороненным, потерянным в SO. помечено. - person kfmfe04; 24.11.2012
comment
FWIW Это уже есть в документации: boost.org/doc /libs/1_52_0/libs/utility/utility.htm#result_of (который также покажет некоторую информацию, которую я пропустил). Я просто написал это с учетом вашего конкретного использования, я думаю :) (также: будет достаточно просто проголосовать: вещи не будут похоронены. Я мог бы пометить это соответствующими тегами повышения, если они существуют) - person sehe; 24.11.2012