我正在使用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
替换该选择,则会收到一条错误消息,指出该表达式是左递归的。如何解决此语法?
答案 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
。因此,如果e1
和e2
都可以产生匹配项,但是只有e2
之后才允许e3
进行匹配,则(e1 | e2) ~ e3
将失败,而(e1 ~ e3) | (e2 ~ e3)
会成功。
因此在语法上,您有(inner_exp | ...) ~ EOI
。现在为您的输入inner_exp
生成一个匹配项,因此根据上述规则,将永远不会尝试其他替代方法,并且接下来将尝试匹配EOI
。 EOI
不匹配,因此整个规则失败,并且出现语法错误。
这说明了语法错误,但这不是语法的唯一问题:
您的exp
规则是递归的,但是它是通过SOI
和EOI
锚定的,因此除了整个输入之外,它永远无法匹配其他任何内容。这意味着递归调用必然总是失败。要解决此问题,您应该从SOI
的定义中删除EOI
和exp
,而应使用像start = {SOI ~ exp ~ EOI}
这样的邮件规则。
完成此操作后,您将得到一个错误,指出您的exp
规则现在是左递归的,该害虫不支持该规则。要解决此问题,您可以用这样的重复替换左递归(替换inner_exp
和exp ~ (...) ~ inner_exp
的替代方式),其中operand
是与中缀操作以外的结构匹配的规则:< / p>
operand ~ (( "+"|"-"|"*"|"/"|"^") ~ operand)*
顺便说一句,这也可以解决您当前的问题,因为您现在已经没有inner_exp
替代项,该替代项已在替代infix表达式之前尝试过。
您的最后一个问题是您根本没有考虑到运算符的优先级。您可以通过引入inner_exp
和exp
之外的其他表达式“级别”来解决此问题,以便在同一规则中仅定义具有相同优先级的运算符,然后每个规则都调用包含以下内容的规则:下一个更高的优先级来解析操作数。看起来像这样:
exp = { summand ~ (("+" | "-") ~ summand)* }
summand = { factor ~ (("*" | "/" | "%") ~ factor)* }
factor = { unary ~ ("^" ~ unary)* }
unary = { "-" ~ unary | unaryop ~ "(" ~ exp ~ ")" | primary }
primary = { "(" ~ exp ~ ")" | real | integer | "pi" | id }