【问题标题】:Relation between grammar and operator associativity语法和运算符关联性之间的关系
【发布时间】:2011-05-27 06:39:09
【问题描述】:

一些编译器书籍/文章/论文讨论了语法的设计及其运算符关联性的关系。我是自上而下的忠实粉丝,尤其是递归下降、解析器和迄今为止我编写的大多数(如果不是全部)编译器都使用以下表达式语法:

Expr   ::= Term { ( "+" | "-" ) Term }
Term   ::= Factor { ( "*" | "/" ) Factor }
Factor ::= INTEGER | "(" Expr ")"

这是此 BNF 的 EBNF 表示:

Expr  ::= Term Expr'
Expr' ::= ( "+" | "-" ) Term Expr' | ε
Term  ::= Factor Term'
Term' ::= ( "*" | "/" ) Factor Term' | ε
Factor = INTEGER | "(" Expr ")"

根据我读到的内容,一些人认为这种语法是“错误的”,因为操作符关联性的变化(这 4 个操作符从左到右)被不断增长的解析树证明是向右而不是向左。对于通过属性语法实现的解析器,这可能是正确的,因为 l 属性值要求首先创建该值,然后将其传递给子节点。但是,当使用正常的递归下降解析器实现时,由我决定是先构造该节点然后传递给子节点(自上而下)还是先创建子节点然后将返回值添加为该节点的子节点(传递在此节点的构造函数中)(自下而上)。这里应该有一些我想念的东西,因为我不同意说这种语法是“错误的”的说法,而且这种语法已经在许多语言中使用,尤其是。威斯安的。通常(或全部?)读物说它促进 LR 解析而不是 LL。

【问题讨论】:

  • 很高兴知道您在哪里读到这是“错误的”。

标签: parsing operator-keyword associativity


【解决方案1】:

我认为这里的问题是一种语言有一个抽象语法,就像:

E ::= E + E | E - E | E * E | E / E | Int | (E)

但这实际上是通过用于指定关联性和优先级的具体语法实现的。因此,如果您正在编写一个体面的递归解析,那么您会在进行过程中隐式地将具体语法写入其中,这很好,尽管将其完全指定为短语结构语法也可能会很好!

如果要成为成熟的具体语法,您的语法会有几个问题。首先,您需要添加产生式以“进入下一级”,因此请稍微放松一下语法:

Expr ::= Term + Term | Term - Term | Term
Term ::= Factor * Factor | Factor / Factor | Factor
Factor ::= INTEGER | (Expr)

否则无法从开始符号(在本例中为 Expr)推导出有效句子。例如,如果没有这些额外的产生式,您将如何推导出“1 * 2”?

Expr -> Term
     -> Factor * Factor
     -> 1 * Factor
     -> 1 * 2

我们可以看到其他语法以稍微不同的方式处理这个问题:

Expr -> Term Expr'
     -> Factor Term' Expr'
     -> 1 Term' Expr'
     -> 1 * Factor Term' Expr'
     -> 1 * 2 Term' Expr'
     -> 1 * 2 ε Expr'
     -> 1 * 2 ε ε
      = 1 * 2

但是这样可以达到同样的效果。

您的解析器实际上是非关联的。要查看此问题,请询问如何解析 E + E + E 并发现它不能。无论+首先被消耗,我们在一侧得到E,在另一侧得到E + E,但是我们试图将E + E解析为Term,这是不可能的。等效地,考虑从开始符号派生该表达式,同样不可能。

Expr -> Term + Term
     -> ? (can't get another + in here)

另一种语法是左结合ebcase,可以推导出任意长的E + E + ... + E

无论如何,总而言之,你是对的,在编写 RDP 时,你可以实现你喜欢的任何 具体 版本的 abstract 语法,你可能知道比我更多。但是在尝试生成准确描述您的 RDP 的语法时会出现这些问题。希望对您有所帮助!

【讨论】:

  • 在尝试将基于 EBNF 的 RDP 重新实现为仅接受 BNF 的解析器生成器时,我确实遇到了问题,而且我既没有使用显式关联特性也没有使用抽象语法。也许解决方案是开始使用它们。
  • 是的,例如,如果您使用 Yacc,它具有特殊的关联性选项,您基本上可以只提供抽象语法上下文无关语法。谢谢采纳!
【解决方案2】:

要获得关联树,您确实需要以运算符为子树根节点形成树,并且具有相似根的子节点。

你的实现语法:

Expr  ::= Term Expr'
Expr' ::= ( "+" | "-" ) Term Expr' | ε
Term  ::= Factor Term'
Term' ::= ( "*" | "/" ) Factor Term' | ε
Factor ::= INTEGER | "(" Expr ")"

一定会让这很尴尬;如果您对此实现递归下降,则 Expr 例程无法访问“左孩子”,因此无法构建树。您总是可以通过传递碎片来修补它(在这种情况下,将树的部分传递给递归),但这看起来很尴尬。你可以选择这个作为语法:

Expr  ::= Term  ( ("+"|"-") Term )*;
Term  ::= Factor ( ( "*" | "/" ) Factor )* ;
Factor ::= INTEGER | "(" Expr ")"

以递归方式编写代码同样容易(更容易?),但现在您可以轻松形成所需的树。

真的不会让你产生联想;它只是塑造树木,以便它可以被允许。关联性意味着树 ( + (+ a b) c) 的含义与 (+ a (+ b c)) 相同;它实际上是一个语义属性(肯定不适用于“-”,但所提出的语法无法区分)。

我们有一个工具(DMS Software Reengineering Toolkit),其中包括解析器术语重写(使用源到源转换),其中明确表达了关联性。我们会写你的语法:

Expr  ::= Term ;
[Associative Commutative] Expr  ::= Expr "+" Term ;
Expr  ::= Expr "-" Term ;
Term  ::= Factor ;
[Associative Commutative] Term  ::= Term "*" Factor ;
Term  ::= Term "/" Factor ;
Factor ::= INTEGER ;
Factor ::= "(" Expr ")" ;

这样的语法看起来更长更笨拙,但实际上它允许我们分解特殊情况并根据需要标记它们。特别是,我们现在可以区分具有关联性和不具有关联性的运算符,并相应地标记它们。使用该语义标记,我们的树重写引擎会自动考虑关联性和交换性。您可以看到full example of such DMS rules being used to symbolically simplify high-school algebra 在典型的表达式语法上使用显式重写规则,而不必考虑此类语义属性。这是内置在重写引擎中的。

【讨论】:

  • 是的,EBNF 版本确实从设计和实现的角度进行了改进。我实际上试图避免显式指定关联性的左递归解决方案(就像在许多解析器生成器中所做的那样)。我去看看那个 DMS 工具,看起来很有趣 :)
猜你喜欢
  • 1970-01-01
  • 2014-10-21
  • 2015-05-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-04-14
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多