повысить дух qi on_error передать структуру error_handler по ссылке

У меня есть еще одна проблема с блокировщиком Spirit Qi.

Я реализовал обработку ошибок в структуре функтора под названием error_handler. Это передается конструктору грамматики по ссылке (см. пример MiniC для Qi).

Затем у меня есть on_error<fail>s, определенные в конструкторе грамматики:

typedef boost::phoenix::function<error_handler<> > error_handler_function;
on_error<fail>(gr_instruction,
        error_handler_function(err_handler)(L"Error: Expecting ", _4, _3));
        // more on_error<fail>s...

Однако у моего error_handler есть частные члены. Кажется, что каждый раз, когда вызывается on_error, объект err_handler копируется, поэтому, как только функтор уходит, измененные локальные переменные уничтожаются.

Я попытался передать обработчик по ссылке:

typedef boost::phoenix::function<error_handler<>& > error_handler_function; // <--- Note the ampersand!

on_error<fail>(gr_instruction,
        error_handler_function(err_handler)(L"Error: Expecting ", _4, _3));
        // more on_error<fail>s...

Однако проблема остается: on_error() работает на копиях err_handler, а не на одном экземпляре!!

Я также пробовал варианты boost::phoenix::ref(err_handler) только с ошибками компиляции.

Конечно, должно быть простое решение для передачи обработчика по ссылке?

Буду признателен за любой вклад. Спасибо за помощь.


person namezero    schedule 27.08.2013    source источник
comment
Я только что понял, что phx::function<decltype(phx::ref(err_handler))> — очевидная альтернатива твоей мысли phx::function<error_handler<>& >. Смотрите мой новый ответ.   -  person sehe    schedule 31.08.2013


Ответы (2)


Да, phx::bind и phx::function‹> по умолчанию будут принимать вызываемые объекты-оболочки по значению. Однако.

Скажем,[1], у вас есть такой обработчик ошибок... минималистичный пример:

template <typename=void> struct my_error_handler {
    my_error_handler() = default;
    my_error_handler(my_error_handler const&) = delete;

    template<typename...> struct result { typedef void type; };
    template<typename... T> void operator()(T&&...) const { 
        std::cerr << "my_error_handler invoked " << proof++ << "\n";
    }
    mutable int proof = 0;
};

(Как видите, я сделал его явно не копируемым, чтобы гарантировать, что компилятор не будет автоматически генерировать код за моей спиной.)

Теперь я не уверен, что это комбинация, которую вы случайно пропустили, но:

on_error<fail>(function,       phx::bind(phx::ref(err_handler), L"Error: Expecting ", _4, _3));
on_error<fail>(start,          phx::bind(phx::ref(err_handler), L"Error: Expecting ", _4, _3));
on_error<fail>(gr_instruction, phx::bind(phx::ref(err_handler), L"Error: Expecting ", _4, _3));

хорошо работает, как и

auto ll = phx::bind(phx::ref(err_handler), L"Error: Expecting ", _4, _3);
on_error<fail>(function,       ll);
on_error<fail>(start,          ll);
on_error<fail>(gr_instruction, ll);

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

Когда я передам ему ввод для отказа, моя программа сможет распечатать proof вызовов my_error_handler:

std::cout << "The 'proof' in the err_handler instance is: " << p.err_handler.proof << "\n";

Как всегда, полностью интегрированный образец программы:

#define BOOST_SPIRIT_USE_PHOENIX_V3
//#define BOOST_SPIRIT_DEBUG
#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/phoenix.hpp>

namespace qi  = boost::spirit::qi;
namespace phx = boost::phoenix;

namespace asmast
{
    typedef std::string label;
}

template <typename=void> struct my_error_handler {
    my_error_handler() = default;
    my_error_handler(my_error_handler const&) = delete;

    template<typename...> struct result { typedef void type; };
    template<typename... T> void operator()(T&&...) const { 
        std::cerr << "my_error_handler invoked " << proof++ << "\n";
    }
    mutable int proof = 0;
};

template <typename It, typename Skipper = qi::blank_type>
    struct parser : qi::grammar<It, Skipper>
{
    parser() : 
        parser::base_type(start)
    {
        using namespace qi;

        start = lexeme["Func" >> !(alnum | '_')] > function;
        function = gr_identifier
                    >> "{"
                    >> -(
                              gr_instruction
                            | gr_label
                          //| gr_vardecl
                          //| gr_paramdecl
                        ) % eol
                    > "}";

        gr_instruction_names.add("Mov", unused);
        gr_instruction_names.add("Push", unused);
        gr_instruction_names.add("Exit", unused);

        gr_instruction = lexeme [ gr_instruction_names >> !(alnum|"_") ] > gr_operands;
        gr_operands = -(gr_operand % ',');

        gr_identifier = lexeme [ alpha >> *(alnum | '_') ];
        gr_operand    = gr_identifier | gr_string;
        gr_string     = lexeme [ '"' >> *("\"\"" | ~char_("\"")) >> '"' ];

        gr_newline = +( char_('\r')
                       |char_('\n')
                      );

        gr_label = gr_identifier >> ':' > gr_newline;

#if 1
        on_error<fail>(function,       phx::bind(phx::ref(err_handler), L"Error: Expecting ", _4, _3));
        on_error<fail>(start,          phx::bind(phx::ref(err_handler), L"Error: Expecting ", _4, _3));
        on_error<fail>(gr_instruction, phx::bind(phx::ref(err_handler), L"Error: Expecting ", _4, _3));
#else
        auto ll = phx::bind(phx::ref(err_handler), L"Error: Expecting ", _4, _3);
        on_error<fail>(function,       ll);
        on_error<fail>(start,          ll);
        on_error<fail>(gr_instruction, ll);
#endif
        // more on_error<fail>s...

        BOOST_SPIRIT_DEBUG_NODES((start)(function)(gr_instruction)(gr_operands)(gr_identifier)(gr_operand)(gr_string));
    }

    my_error_handler<> err_handler;
  private:
    qi::symbols<char, qi::unused_type> gr_instruction_names;
    qi::rule<It, Skipper> start, function, gr_instruction, gr_operands, gr_operand, gr_string;
    qi::rule<It, qi::unused_type()> gr_newline;
    qi::rule<It, asmast::label(), Skipper> gr_label, gr_identifier;
};

int main()
{
    typedef boost::spirit::istream_iterator It;
    std::cin.unsetf(std::ios::skipws);
    It f(std::cin), l;

    parser<It, qi::blank_type> p;

    try
    {
        bool ok = qi::phrase_parse(f,l,p,qi::blank);
        if (ok)   std::cout << "parse success\n";
        else      std::cerr << "parse failed: '" << std::string(f,l) << "'\n";

        if (f!=l) std::cerr << "trailing unparsed: '" << std::string(f,l) << "'\n";

        std::cout << "The 'proof' in the err_handler instance is: " << p.err_handler.proof << "\n";
        return ok;
    } catch(const qi::expectation_failure<It>& e)
    {
        std::string frag(e.first, e.last);
        std::cerr << e.what() << "'" << frag << "'\n";
    }

    return false;
}

При подаче на вход

Func Ident{
    Mov name, "hello" 
    Push 5
    Exit
}

Он печатает (как первая/последняя строки)

my_error_handler invoked 0
my_error_handler invoked 1
...
The 'proof' in the err_handler instance is: 2

[1] Мне не в первый раз приходится придумывать релевантный код

person sehe    schedule 29.08.2013
comment
Спасибо за ответ и пример. Я попробую это первым делом утром. Довольно забавно, как вы продолжаете опираться на предыдущие примеры:] Spirit — это действительно крутая кривая обучения; особенно из-за его высокой зависимости от других библиотек повышения и сложности для декодирования ошибок компилятора, но, в конце концов, оно того стоит. Еще раз спасибо за ваше терпение! - person namezero; 29.08.2013
comment
@namezero, лол. Я просто экономист! Я не люблю набирать случайные образцы кода с нуля :/ - person sehe; 29.08.2013
comment
Кроме того, будьте осторожны с предвзятостью мнения. Я пришел к выводу, что ручные анализаторы иногда предпочтительнее. Тем не менее, Spirit... непобедим для быстрого прототипирования, как только вы его освоите (и предпочитаете не использовать Antlr/CoCo/...). - person sehe; 29.08.2013
comment
Спасибо, это действительно работает! Я не наткнулся на пример использования phoenix::bind для этого. Что касается [1], то error_handler такой же, как и в примере MiniC, только я не вывожу в std::cout. Я знаю, что было бы проще не использовать Spirit для этого синтаксического анализатора, но у нас впереди кое-что посложнее, поэтому я подумал, что это будет отличный способ начать понимать библиотеку. Спасибо еще раз! - person namezero; 29.08.2013
comment
@namezero Меня не волнует образец, который мне нужно найти. По крайней мере, вы сказали, что это состояние. Поэтому я написал самый минимальный функтор с состоянием, который только мог придумать :) - person sehe; 29.08.2013

Я вспомнил, что поздно подумал об этом и хотел проверить это:

Конечно,

my_error_handler<> err_handler;
phx::function<my_error_handler<> > err_handler_(err_handler);

будет работать (но пытается скопировать экземпляр err_handler, а это не то, что вам нужно). Теперь,

phx::function<my_error_handler<> > err_handler_(phx::ref(err_handler));

не будет летать (поскольку my_error<> не может построить из phx::ref(err_handler)), поэтому, по логике вещей, вам действительно нужно было просто:

namespace P = boost::proto;
phx::function<const phx::actor<P::exprns_::basic_expr<
    P::tagns_::tag::terminal, 
    P::argsns_::term<boost::reference_wrapper<my_error_handler<> > >, 
    0l> 
> > err_handler_;

который... работает точно так же, как phx::bind, но с большим количеством синтаксического сахара:

    on_error<fail>(function,       err_handler_(L"Error: Expecting ", _4, _3));
    on_error<fail>(start,          err_handler_(L"Error: Expecting ", _4, _3));
    on_error<fail>(gr_instruction, err_handler_(L"Error: Expecting ", _4, _3));

Теперь, с некоторым C++11, это можно написать чуть менее подробно:

my_error_handler<> err_handler;
phx::function<decltype(phx::ref(err_handler))> err_handler_;

Посмотрите, как это работает Live on Coliru с кодом ниже :

#define BOOST_SPIRIT_USE_PHOENIX_V3
//#define BOOST_SPIRIT_DEBUG
#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/phoenix.hpp>

namespace qi  = boost::spirit::qi;
namespace phx = boost::phoenix;

namespace asmast
{
    typedef std::string label;
}

template <typename=void> struct my_error_handler {
    my_error_handler() = default;
    my_error_handler(my_error_handler const&) = delete;

    template<typename...> struct result { typedef void type; };
    template<typename... T> void operator()(T&&...) const { 
        std::cerr << "my_error_handler invoked " << proof++ << "\n";
    }
    mutable int proof = 0;
};

template <typename It, typename Skipper = qi::blank_type>
    struct parser : qi::grammar<It, Skipper>
{
    parser() : 
        parser::base_type(start),
        err_handler(),
        err_handler_(phx::ref(err_handler))
    {
        using namespace qi;

        start = lexeme["Func" >> !(alnum | '_')] > function;
        function = gr_identifier
                    >> "{"
                    >> -(
                              gr_instruction
                            | gr_label
                          //| gr_vardecl
                          //| gr_paramdecl
                        ) % eol
                    > "}";

        gr_instruction_names.add("Mov", unused);
        gr_instruction_names.add("Push", unused);
        gr_instruction_names.add("Exit", unused);

        gr_instruction = lexeme [ gr_instruction_names >> !(alnum|"_") ] > gr_operands;
        gr_operands = -(gr_operand % ',');

        gr_identifier = lexeme [ alpha >> *(alnum | '_') ];
        gr_operand    = gr_identifier | gr_string;
        gr_string     = lexeme [ '"' >> *("\"\"" | ~char_("\"")) >> '"' ];

        gr_newline = +( char_('\r')
                       |char_('\n')
                      );

        gr_label = gr_identifier >> ':' > gr_newline;

#if 0
        on_error<fail>(function,       phx::bind(phx::ref(err_handler), L"Error: Expecting ", _4, _3));
        on_error<fail>(start,          phx::bind(phx::ref(err_handler), L"Error: Expecting ", _4, _3));
        on_error<fail>(gr_instruction, phx::bind(phx::ref(err_handler), L"Error: Expecting ", _4, _3));
#else
        on_error<fail>(function,       err_handler_(L"Error: Expecting ", _4, _3));
        on_error<fail>(start,          err_handler_(L"Error: Expecting ", _4, _3));
        on_error<fail>(gr_instruction, err_handler_(L"Error: Expecting ", _4, _3));
#endif
        // more on_error<fail>s...

        BOOST_SPIRIT_DEBUG_NODES((start)(function)(gr_instruction)(gr_operands)(gr_identifier)(gr_operand)(gr_string));
    }

    my_error_handler<> err_handler;
    phx::function<decltype(phx::ref(err_handler))> err_handler_;
  private:
    qi::symbols<char, qi::unused_type> gr_instruction_names;
    qi::rule<It, Skipper> start, function, gr_instruction, gr_operands, gr_operand, gr_string;
    qi::rule<It, qi::unused_type()> gr_newline;
    qi::rule<It, asmast::label(), Skipper> gr_label, gr_identifier;
};

int main()
{
    typedef boost::spirit::istream_iterator It;
    std::cin.unsetf(std::ios::skipws);
    It f(std::cin), l;

    parser<It, qi::blank_type> p;

    try
    {
        bool ok = qi::phrase_parse(f,l,p,qi::blank);
        if (ok)   std::cout << "parse success\n";
        else      std::cerr << "parse failed: '" << std::string(f,l) << "'\n";

        if (f!=l) std::cerr << "trailing unparsed: '" << std::string(f,l) << "'\n";

        std::cout << "The 'proof' in the err_handler instance is: " << p.err_handler.proof << "\n";
        return ok;
    } catch(const qi::expectation_failure<It>& e)
    {
        std::string frag(e.first, e.last);
        std::cerr << e.what() << "'" << frag << "'\n";
    }

    return false;
}
person sehe    schedule 31.08.2013
comment
+1. Я думаю, что вы также можете использовать phx::function< phx::expression::reference< my_error_handler<> >::type > err_handler_;. Я думаю, что предпочитаю просто иметь proof в качестве ссылки, которая инициализируется в конструкторе, но этот способ очень интересен. - person llonesmiz; 31.08.2013