【问题标题】:boost spirit qi on_error pass error_handler struct by reference提神气 on_error 通过引用传递error_handler struct
【发布时间】:2013-08-27 21:42:00
【问题描述】:

我还有一个灵气的阻碍问题。

我在一个名为 error_handler 的仿函数结构中实现了错误处理。 这通过引用传递给语法构造函数(参见 Qi 的 MiniC 示例)。

然后我在语法的构造函数中定义了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) 的变体,但只有编译错误。

当然,必须有一个简单的解决方案来通过引用传递处理程序?

如有任何意见,我将不胜感激。感谢您的帮助。

【问题讨论】:

  • 我刚刚意识到phx::function&lt;decltype(phx::ref(err_handler))&gt; 是您phx::function&lt;error_handler&lt;&gt;&amp; &gt; 想法的明显替代品。请参阅我的新答案。

标签: c++ reference boost-spirit boost-spirit-qi boost-phoenix


【解决方案1】:

是的,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]这不是我第一次不得不想象相关代码

【讨论】:

  • 感谢您的回复和示例。我会在早上尝试第一件事。你是如何在前面的例子上继续构建的,有点有趣:] Spirit 确实是一条陡峭的学习曲线;特别是由于它对其他 boost 库的高度依赖以及难以解码编译器错误,但最终还是值得的。再次感谢您的耐心等待!
  • @namezero 哈哈。我只是为了经济!我不喜欢从头开始输入随机代码示例:/
  • 另外,要小心那里的意见偏见。我得出的结论是手动滚动解析器有时更可取。然而,一旦你掌握了它的窍门,Spirit 在快速原型设计方面是无与伦比的(并且不喜欢使用 Antlr/CoCo/...)。
  • 谢谢,确实有效!我没有偶然发现使用 phoenix::bind 的示例。至于 [1],error_handler 就像 MiniC 示例中的一样,只是我没有输出到 std::cout。我知道这个解析器不使用 Spirit 会更容易,但是我们遇到了更复杂的事情,所以我认为这将是开始理解库的好方法。再次感谢!
  • @namezero 我并不真正关心我必须查找的样本。至少,你说它是有状态的。所以我写了我能想到的最小的有状态函子:)
【解决方案2】:

我记得有一个迟到的想法,想检查一下:

当然,

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&lt;&gt; 不能从 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_;

which... 与 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;
}

【讨论】:

  • +1。我认为您也可以使用phx::function&lt; phx::expression::reference&lt; my_error_handler&lt;&gt; &gt;::type &gt; err_handler_;。我想我更喜欢简单地将proof 作为在构造函数中初始化的引用,但这种方式非常有趣。
猜你喜欢
  • 2019-03-07
  • 1970-01-01
  • 1970-01-01
  • 2015-01-12
  • 2013-09-10
  • 1970-01-01
  • 2020-04-01
  • 1970-01-01
  • 2011-06-28
相关资源
最近更新 更多