【问题标题】:Left Associativity vs Left Recursion左关联性与左递归
【发布时间】:2016-10-19 21:56:44
【问题描述】:

我正在尝试为 C 编写一个编译器(虽然语法更简单)。

有些事情我已经坚持了一段时间。如果我低估了,那么所有二进制操作都是关联的。所以如果我们有我们“x+y+z”,x+y首先出现,然后是加z。

但是,强制左关联性不会导致无限左递归吗?

到目前为止,我检查过的所有解决方案要么是左关联的,要么没有左递归,但不是两者都。是否有可能拥有同时具有这两个属性的语法。有没有可能?

例子:

左联想:

Expr = Term | Expr + Term
Term = Element | Term ∗ Element
Element = x|y|z|(Expr)

左递归消除:

Expr = Term ExprTail
ExprTail = epsilon | + Term ExprTail

Term = Element TermTail
TermTail = epsilon | * Element TermTail

Element = x|y|z|(Expr)

有什么想法吗?

【问题讨论】:

  • 您可以在不丢失左结合性的情况下消除左递归,这是LL解析器中的标准过程。求幂运算符(如果有的话)是二元右结合的。
  • 在 C 中,赋值运算符是右结合的,仅此而已。
  • @EJP 你是说有一种方法可以在没有左递归的情况下重写问题中的语法,这样生成的解析树将是左关联的,而无需对那个树?如果是这样:如何?

标签: parsing compiler-construction grammar left-recursion


【解决方案1】:

如果一个运算符是左结合的,那么相应的产生式将是左递归的。

如果您使用 LR 解析器生成器,则没有问题。 LR 算法在处理左递归时没有问题(对于任何其他类型的递归也没什么问题,尽管它可能需要更多的堆栈空间)。

您也可以使用其他自下而上的技术,例如经典的运算符优先算法(即分流场),但 LR 解析严格来说更具表现力,并且解析器生成器使实现相对简单。

如果您坚持递归下降解析,那是可能的,因为您可以使用循环(理论上是右递归)解析重复的模式,但从左到右组合元素。从某种理论上讲,这是对 AST 的树形重写,但我怀疑很多程序员在没有注意到树形修复的情况下编写了代码。

【讨论】:

  • "您可以使用循环解析重复的模式" 如果使用像 ANTLR 这样的 LL 解析器生成器,它会转换为在语法中使用像 * 这样的重复运算符。这将为您提供一个带有列表的解析树,然后您可以在任何您想要的方向上循环。
  • 谢谢。是的,我正在使用递归下降解析。我感觉使用循环是正确的想法。你能指导我在哪里(如何)准确地使用循环吗?是否正如@sepp2k 提到的那样,例如,只要我有'+',我只需将我拥有的任何元素添加到列表中,然后当添加结束时,我返回该列表并递归它?我理解对了吗?
  • @Babak 如果您是手写代码,则无需查看列表。您可以直接在循环中构建树。类似:result = parsePrimaryExpression; if(result) while(parseToken(STAR)) { result = new Multiplication(result, parsePrimaryExpression()); } return result;。你知道,加上错误处理和除法运算符等。
  • @babak:基本上就像 sepp2k 说的那样。请注意,严格来说,减少确实 nkt 对应于语法,因为如果你做了一个真正的尾调用,你最终会正确关联。因此,将累加器(在 sepp2k 的示例中为result)传递到循环中会在构造树时有效地反转树。但这是一个微妙的点,可能对实际编码没什么兴趣。就个人而言,我会使用 LR 解析器生成器,但口味不同。
  • @Babak 不,在第一次迭代后,结果将是Multiplication(x,y),所以当它在第二次迭代中第二次result = new Mult(result, parsePrimaryExpression()) 时,结果将是Mult(Mult(x,y), z)。跨度>
猜你喜欢
  • 1970-01-01
  • 2014-06-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-11-10
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多