【问题标题】:Boost::Spirit Expression ParserBoost::Spirit 表达式解析器
【发布时间】:2012-01-17 21:29:10
【问题描述】:

我的 boost::spirit 解析器还有另一个问题。

template<typename Iterator>
struct expression: qi::grammar<Iterator, ast::expression(), ascii::space_type> {
    expression() :
        expression::base_type(expr) {
        number %= lexeme[double_];
        varname %= lexeme[alpha >> *(alnum | '_')];

        binop = (expr >> '+' >> expr)[_val = construct<ast::binary_op<ast::add>>(_1,_2)]
              | (expr >> '-' >> expr)[_val = construct<ast::binary_op<ast::sub>>(_1,_2)]
              | (expr >> '*' >> expr)[_val = construct<ast::binary_op<ast::mul>>(_1,_2)]
              | (expr >> '/' >> expr)[_val = construct<ast::binary_op<ast::div>>(_1,_2)] ;

        expr %= number | varname | binop;
    }

    qi::rule<Iterator, ast::expression(), ascii::space_type> expr;
    qi::rule<Iterator, ast::expression(), ascii::space_type> binop;
    qi::rule<Iterator, std::string(), ascii::space_type> varname;
    qi::rule<Iterator, double(), ascii::space_type> number;
};

这是我的解析器。它解析了"3.1415""var" 就好了,但是当我尝试解析"1+2" 它告诉我parse failed。然后我尝试将binop 规则更改为

    binop = expr >>
           (('+' >> expr)[_val = construct<ast::binary_op<ast::add>>(_1, _2)]
          | ('-' >> expr)[_val = construct<ast::binary_op<ast::sub>>(_1, _2)]
          | ('*' >> expr)[_val = construct<ast::binary_op<ast::mul>>(_1, _2)]
          | ('/' >> expr)[_val = construct<ast::binary_op<ast::div>>(_1, _2)]);

但是现在它当然不能构建 AST,因为 _1_2 的设置不同。我只看到提到过_r1 之类的东西,但作为一个新手,我不太能理解boost::phoenixboost::spirit 是如何交互的。

如何解决?

【问题讨论】:

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


【解决方案1】:

我并不完全清楚您要达到的目标。最重要的是,您不担心运算符关联性吗?我将仅展示基于使用右递归的简单答案 - 这会导致 左关联运算符 被解析。

visible 问题的直接答案是处理 fusion::vector2&lt;char, ast::expression&gt; - 这真的没什么好玩的,尤其是在 Phoenix lambda 语义动作中。 (我将在下面展示它的外观)。

同时我认为你应该阅读 Spirit 文档

  • old Spirit 文档中的here(消除左递归);尽管语法不再适用,Spirit 仍然生成 LL 递归下降解析器,因此左递归背后的概念仍然适用。下面的代码显示了这个应用于灵气
  • here:Qi 示例包含三个 calculator 示例,它们应该会提示您为什么运算符关联性很重要,以及您将如何表达捕获二元运算符的关联性的语法。显然,它还展示了如何支持带括号的表达式来覆盖默认的评估顺序。

代码:

我有三个版本的代码可以工作,解析输入如下:

std::string input("1/2+3-4*5");

ast::expression 分组(使用 BOOST_SPIRIT_DEBUG):

<expr>
  ....
  <success></success>
  <attributes>[[1, [2, [3, [4, 5]]]]]</attributes>
</expr>

代码链接在这里:

第一步:Reduce semantic actions

首先,我将摆脱每个运算符的替代解析表达式;这会导致过度回溯1。此外,正如您所发现的,它使语法难以维护。因此,这里有一个更简单的变体,它使用函数来执行语义操作:

1使用 BOOST_SPIRIT_DEBUG 检查!

static ast::expression make_binop(char discriminant, 
     const ast::expression& left, const ast::expression& right)
{
    switch(discriminant)
    {
        case '+': return ast::binary_op<ast::add>(left, right);
        case '-': return ast::binary_op<ast::sub>(left, right);
        case '/': return ast::binary_op<ast::div>(left, right);
        case '*': return ast::binary_op<ast::mul>(left, right);
    }
    throw std::runtime_error("unreachable in make_binop");
}

// rules:
number %= lexeme[double_];
varname %= lexeme[alpha >> *(alnum | '_')];

simple = varname | number;
binop = (simple >> char_("-+*/") >> expr) 
    [ _val = phx::bind(make_binop, qi::_2, qi::_1, qi::_3) ]; 

expr = binop | simple;

第二步:Remove redundant rules, use _val

如您所见,这有可能降低复杂性。现在只是一小步,移除 binop 中间体(已经变得相当多余):

number %= lexeme[double_];
varname %= lexeme[alpha >> *(alnum | '_')];

simple = varname | number;
expr = simple [ _val = _1 ] 
    > *(char_("-+*/") > expr) 
            [ _val = phx::bind(make_binop, qi::_1, _val, qi::_2) ]
    > eoi;

如你所见,

  • expr 规则中,_val 惰性占位符用作累积二进制操作的伪局部变量。跨规则,您必须使用qi::locals&lt;ast::expression&gt; 来实现这种方法。 (这是您关于_r1 的问题)。
  • 现在有明确的期望点,使语法更加健壮
  • expr 规则不再需要是自动规则(expr = 而不是 expr %=

步骤0:Wrestle fusion types directly

最后,为了有趣和血腥,让我展示一下您如何处理建议的代码,以及 _1、_2 等的移动绑定:

static ast::expression make_binop(
        const ast::expression& left, 
        const boost::fusion::vector2<char, ast::expression>& op_right)
{
    switch(boost::fusion::get<0>(op_right))
    {
        case '+': return ast::binary_op<ast::add>(left, boost::fusion::get<1>(op_right));
        case '-': return ast::binary_op<ast::sub>(left, boost::fusion::get<1>(op_right));
        case '/': return ast::binary_op<ast::div>(left, boost::fusion::get<1>(op_right));
        case '*': return ast::binary_op<ast::mul>(left, boost::fusion::get<1>(op_right));
    }
    throw std::runtime_error("unreachable in make_op");
}

// rules:
expression::base_type(expr) {
number %= lexeme[double_];
varname %= lexeme[alpha >> *(alnum | '_')];

simple = varname | number;
binop %= (simple >> (char_("-+*/") > expr)) 
    [ _val = phx::bind(make_binop, qi::_1, qi::_2) ]; // note _2!!!

expr %= binop | simple;

如您所见,以这种方式编写 make_binop 函数并没有那么有趣!

【讨论】:

    猜你喜欢
    • 2014-09-13
    • 2011-01-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多