请求一个解析器语法,使用Boost Spirit Qi更好。

3

我正在尝试使用boost::spirit::qi来解析表达式。

这个表达式很简单,它可以是:

  1. 标识符,比如x
  2. 对象的成员,比如obj.x
  3. 数组的元素,比如arr[2]
  4. 函数调用的结果,func(x, y)

对象的成员可能是数组或函数类型,因此x.y[2]x.y()都是合法的。

函数结果可能是数组或对象,因此func(x,y).valuefunc(x)[4]也是合法的。

数组元素可能是对象或函数类型,因此arr[5].yarr[3](x, y)也是合法的。

将这些组合在一起,以下表达式应该是合法的:

x[1]().y(x, y, x.z, z[4].y)().q[2][3].fun()[5].x.y.z

所有这些[...](...).具有相同的优先级,从左到右进行运算。

我的语法如下:

expression
    = postfix_expr
    | member_expr
    ;

postfix_expr = elem_expr | call_expr | id;
elem_expr = postfix_expr >> "[" >> qi::int_ >> "]";
call_expr = postfix_expr >> "(" >> expression_list >> ")";
member_expr = id >> *("." >> member_expr);

expression_list
    = -(expression % ",")

但是它总是崩溃,我认为可能有无限循环的地方。
请给我一些关于如何解析这个语法的建议。
编辑 后续问题: 谢谢cadrian,它起作用了!
现在表达式可以正确解析,但我想引入一个新的 ref_exp ,它也是一个表达式,但不以 () 结尾,因为函数结果不能放置在赋值的左边。
我的定义是:
    ref_exp
        = id
        | (id >> *postfix_exp >> (memb_exp | elem_exp))
        ;

    postfix_exp
        = memb_exp
        | elem_exp
        | call_exp
        ;

    memb_exp = "." >> id;
    elem_exp = "[" >> qi::uint_ >> "]";
    call_exp = ("(" >> expression_list >> ")");

但是 boost::spirit::qi 无法解析这个表达式,我认为原因是 (memb_exp | elem_exp)postfix_exp 的一部分,如何使其不解析所有内容,只匹配最后一个部分的 (memb_exp | elem_exp)ref_exp 的例子: xx.yx()[12][21]f(x, y, z).x[2],而非 ref_expf()x.y()x[12]()

我建议您将后续内容单独发布。如果您能够包含一个可工作的示例(即使它演示了问题),我很乐意查看它。 - sehe
2个回答

3

boost::spirit::qi是一种自下而上的解析器;你的语法不应该是左递归的。

请参见这个问题

在这里,你明显有一个左递归的语法:后缀表达式 -> 元素表达式 -> 后缀表达式

编辑 修复方式之一。

据我所见,你的表达式是一串标识符,可能带有后缀:[]().

expression = id >> *cont_expr;
cont_expr = elem_expr | call_expr | member_expr
elem_expr = "[" >> qi::int_ >> "]";
call_expr = "(" >> expression_list >> ")";
member_expr = "." >> expression;
expression_list = -(expression % ",")

编辑2 如果你想强制优先级 - 例如使用括号:

expression = prefix_expr >> *cont_expr;
prefix_expr = id | par_expr
par_expr = "(" >> expression >> ")"

这样,您甚至可以编写像x.(y[3].foo)[5](fun(), foo(bar))这样的表达式——如果有意义的话。

编辑3我在这里回答您的评论。

您需要使赋值左侧不是函数。这意味着您必须为左手表达式指定特定的后缀。让我们将该规则称为ref_exp,如您的评论所述。

ref_exp = id >> -( *cont_expr >> cont_ref );
cont_ref = elem_expr | member_expr;

是的,它可以工作,但我有一个后续问题。因为函数结果不能放在赋值的左边,当引入新的 ref_exp 时,我如何告诉以 id[] 结尾的表达式是 ref_exp - 0xFFFFFFFF
这个不行,我之前试过了。例如 f[2].y()*cont_expr 会匹配所有的 [2].y(),当它解析到 cont_ref 时,就没有剩下的内容了,解析失败。 - 0xFFFFFFFF
哎呀。你说得对,我引入了非确定性 :-/ - cadrian

2

最后我认为我解决了这个问题,但是这个解决方案有一个副作用,它会改变运算符的结合性。

    lvalue_exp
        = id >> -(ref_exp);
        ;

    ref_exp
        = (postfix_exp >> ref_exp)
        | memb_exp
        | elem_exp
        ;

    postfix_exp
        = call_exp
        | memb_exp
        | elem_exp
        ;

    memb_exp
        = ("." >> id)
        ;

    elem_exp
        = ("[" >> qi::uint_ >> "]")
        ;

    call_exp
        = ("(" >> expression_list >> ")")
        ;

因此,对于表达式 f().y()[0] 的解析如下:

  1. fref_exp - ().y()[0]
  2. ().y()[0] 解析为 ().y()[0]
  3. ().y() 解析为 ().y()
  4. .y() 解析为 .y()

如果我不区分左值和右值,则表达式 f().y()[0] 的解析如下:

  1. f().y()[0]
  2. ().y()[0]
  3. .y()[0]
  4. ()[0]

因此,在生成抽象语法树时,我将使用第二种方法并检查引用。

感谢 @cadrian


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接