【问题标题】:Converting a parse tree to AST将解析树转换为 AST
【发布时间】:2017-07-13 14:42:28
【问题描述】:

让我先提出一个问题:我可以将实现这种特定语法的解析树简单地转换为 AST。

我被赋予了构建解析树的语法:

literal := INTEGER | FLOAT | TRUE | FALSE .

designator := IDENTIFIER { "[" expression0 "]" } .

op0 := ">=" | "<=" | "!=" | "==" | ">" | "<" .
op1 := "+" | "-" | "or" .
op2 := "*" | "/" | "and" .

expression0 := expression1 [ op0 expression1 ] .
expression1 := expression2 { op1  expression2 } .
expression2 := expression3 { op2 expression3 } .
expression3 := "not" expression3
       | "(" expression0 ")"
       | designator
       | call-expression
       | literal .

对于这个特定的例子:

func main() : void {
    let a = 1 + 2 + 3 + 4;
}

我的解析器将生成(部分)解析树

            EXPRESSION1
                EXPRESSION2
                  EXPRESSION3
                    LITERAL
                      INTEGER(1)(lineNum:2, charPos:10)
                OP1
                  ADD(lineNum:2, charPos:12)
                EXPRESSION2
                  EXPRESSION3
                    LITERAL
                      INTEGER(2)(lineNum:2, charPos:14)
                OP1
                  ADD(lineNum:2, charPos:16)
                EXPRESSION2
                  EXPRESSION3
                    LITERAL
                      INTEGER(3)(lineNum:2, charPos:18)
                OP1
                  ADD(lineNum:2, charPos:20)
                EXPRESSION2
                  EXPRESSION3
                    LITERAL
                      INTEGER(4)(lineNum:2, charPos:22)

请注意 EXPRESSION1 下的这些树枝是如何运行的:

EXPRESSION2 + EXPRESSION2 + EXPRESSION2 + EXPRESSION2

运算符 + 不对应它的两个操作数。所以在我看来,在 AST 转换中,我无法通过简单地拉起操作符来替换非终端 EXPRESSION1 来帮助生成 3 地址 IR 代码的 AST。

为了实现这个目标,我为这种语言编写的语法会变成这样

expression1 := expression2 | expression1 + expression2  (1)
expression2 := expression3 | expression2 * expression3  (2)
expression3 := literal                                  (3)

EXPRESSION1下的分支只有哪些

EXPRESSION1 + EXPRESSION2

然而,这个文法不是 LL(1),因为 |FIRST(expression2)| = |{字面量,+}| > 1.

这引出了一个问题,即 (1) 转换此解析树的最优雅和最简单的方法是什么? (2) 对于我应该开始编写 AST 的语法,我构建解析树是否完全浪费了时间?

【问题讨论】:

  • 您可能会考虑解析树和抽象语法树之间的真正区别。看我的回答:stackoverflow.com/questions/1888854/…
  • 这看起来很不错。我听说过一些关于 GLR 的不错的特性。现在,有压缩的 CST 来提供帮助。感谢您提供信息。
  • 嗯,这是我在截止日期前无法弄清楚的一些学校作业。我必须遵循一些具体的指导方针。我太愚蠢了,没有意识到表达式语法存在问题,即目标 AST 不仅仅是 CST 的“简化”版本。树结构不同。我只是有点生气。
  • 这就是 AST 的问题:它们不与 CST 同构。这意味着您必须定义从解析器生成的 CST 到有人认为不错的 AST 的映射,然后您必须通过遍历 CST 和/或在进行归约时构建 AST 子树来实现该映射。由“nice”定义的那张地图只是您必须做的临时和额外的工作。有了大语法,这是一个皇家 PIA。我的同情。去过那里,做到了。

标签: parsing compiler-construction context-free-grammar


【解决方案1】:

我相信您希望生成这样的 AST:

     ADD
     /  \
    1   ADD
        /  \  
       2   ADD
           / \
          3   4

但您可能注意到,您的解析树实际上是一个平面列表,并不代表一个简单的起点,可以将运算符及其操作数一次性分组。无论如何,编写一个更高级的解析器、识别树结构和应用语法简化都不是一件容易的事。

因此,在开始之前,您可能希望考虑一些现有的解析器生成器,如 yacc 或 ANTLR。可能您需要重写语法以创建以运算符为中心的规则,而不是将它们视为递归实用程序。不是经典 LL(1) 的语法可能不是一个大问题,因为现代生成器(如 ANTLR)可以处理具有更大前瞻、谓词等的 LL(k) 语法,并且只是绕过该类型的问题。

如果您仍然坚持手动编码,请考虑使用堆栈来存储符号,并在收集到表达式后将它们转换为 AST 节点,但这又不是一项简单的任务。

【讨论】:

  • 这是否意味着坚持使用 LL(1) 语法本质上是一个糟糕的设计,因为它处理运算符优先级但在运算符关联性方面失败了?
猜你喜欢
  • 2011-02-11
  • 2016-09-20
  • 2018-03-20
  • 1970-01-01
  • 2013-02-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多