害虫没有解析我期望的递归语法

时间:2019-06-25 05:04:03

标签: parsing rust

我正在使用pest板条箱在Rust中实现递归语法:

id = _{ ASCII_ALPHA_LOWER ~ (ASCII_ALPHANUMERIC|"_")* }
integer = _{ (ASCII_NONZERO_DIGIT ~ ASCII_DIGIT*)|"0" }
real = _{ ((integer ~ "." ~ ASCII_DIGIT*) | (integer? ~ "." ~ ASCII_DIGIT+)) ~ (("e"|"E") ~ ("-"|"+")? ~ ASCII_DIGIT+)? }

unaryop = _{ "sin"|"cos"|"tan"|"exp"|"ln"|"sqrt" }

inner_exp = _{ real|integer|"pi"|id }

exp = { SOI ~ ( inner_exp | (exp ~ ( "+"|"-"|"*"|"/"|"^" ) ~ inner_exp) | ("-" ~ exp) | ("(" ~ exp ~ ")") | (unaryop ~ "(" ~ exp ~ ")") ) ~ EOI }

但是,我发现有害生物没有像我期望的那样解析语法。例如,2+3给我一个错误:

 --> 1:2
  |
1 | 2+3
  |  ^---
  |
  = expected EOI

似乎正在解析inner_exp选项,然后,在遇到+符号时,解析器不知道要做什么。我很确定我编写exp ~ ( "+"|"-"|"*"|"/"|"^" ) ~ inner_exp选项的方式有问题,但是我不确定是什么导致了问题。如果我用exp ~ ( "+"|"-"|"*"|"/"|"^" ) ~ exp替换该选择,则会收到一条错误消息,指出该表达式是左递归的。如何解决此语法?

1 个答案:

答案 0 :(得分:1)

PEG中的选择运算符是有序的,其工作方式如下:给定e = {alt1 | alt2}

  • 如果可以成功匹配alt1,则将应用alt1,并且永远不会尝试alt2
  • 否则alt2被匹配
  • 如果alt2都不匹配,则e无法匹配

现在e = {e1 ~ e2}的工作方式如下:

  • 如果e1可以被匹配并且e2之后可以被匹配,则两者都将被顺序匹配。
  • 否则e无法匹配。

因此,如果您有类似e = {(e1 | e2) ~ e3}的内容,则会发生以下情况:

  • 如果e1可以匹配:
    • 如果e3之后可以匹配e1,则两者都按顺序匹配
    • 否则,e无法匹配
  • 如果e1不匹配,但是e2可以匹配:
    • 如果e3之后可以匹配e2,则两者都按顺序匹配
    • 否则,e无法匹配

值得注意的是,如果e1成功并且e3失败,它不会返回并尝试匹配e2。因此,如果e1e2都可以产生匹配项,但是只有e2之后才允许e3进行匹配,则(e1 | e2) ~ e3将失败,而(e1 ~ e3) | (e2 ~ e3)会成功。

因此在语法上,您有(inner_exp | ...) ~ EOI。现在为您的输入inner_exp生成一个匹配项,因此根据上述规则,将永远不会尝试其他替代方法,并且接下来将尝试匹配EOIEOI不匹配,因此整个规则失败,并且出现语法错误。

这说明了语法错误,但这不是语法的唯一问题:

您的exp规则是递归的,但是它是通过SOIEOI锚定的,因此除了整个输入之外,它永远无法匹配其他任何内容。这意味着递归调用必然总是失败。要解决此问题,您应该从SOI的定义中删除EOIexp,而应使用像start = {SOI ~ exp ~ EOI}这样的邮件规则。

完成此操作后,您将得到一个错误,指出您的exp规则现在是左递归的,该害虫不支持该规则。要解决此问题,您可以用这样的重复替换左递归(替换inner_expexp ~ (...) ~ inner_exp的替代方式),其中operand是与中缀操作以外的结构匹配的规则:< / p>

operand ~ (( "+"|"-"|"*"|"/"|"^") ~ operand)*

顺便说一句,这也可以解决您当前的问题,因为您现在已经没有inner_exp替代项,该替代项已在替代infix表达式之前尝试过。

您的最后一个问题是您根本没有考虑到运算符的优先级。您可以通过引入inner_expexp之外的其他表达式“级别”来解决此问题,以便在同一规则中仅定义具有相同优先级的运算符,然后每个规则都调用包含以下内容的规则:下一个更高的优先级来解析操作数。看起来像这样:

exp = { summand ~ (("+" | "-") ~ summand)* }
summand = { factor ~ (("*" | "/" | "%") ~ factor)* }
factor = { unary ~ ("^" ~ unary)* }
unary = { "-" ~ unary | unaryop ~ "(" ~ exp ~ ")" | primary }
primary = { "(" ~ exp ~ ")" | real | integer | "pi" | id }