将解析树转换为 AST
Converting a parse tree to AST
让我先提出问题:我能否将实现此特定语法的解析树简单地转换为 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
但是,由于 |FIRST(expression2)|,此文法不是 LL(1) = |{文字,+}| > 1.
这引出了一个问题:(1) 转换此解析树的最优雅和最简单的方法是什么? (2) 我构建解析树是否完全是在浪费我应该开始编写 AST 的语法的时间?
我相信你希望生成如下 AST:
ADD
/ \
1 ADD
/ \
2 ADD
/ \
3 4
但您可能已经注意到,您的解析树实际上是一个平面列表,并不是在单次传递中对运算符及其操作数进行分组的简单起点。无论如何,编写一个更高级的解析器、识别树结构和应用语法简化都不是一项简单的任务。
因此,在开始这样做之前,您可能希望考虑一些现有的解析器生成器,如 yacc 或 ANTLR。您可能需要重写语法以创建以运算符为中心的规则,而不是将它们视为递归实用程序。不是经典 LL(1) 的语法可能不是一个大问题,因为现代生成器(如 ANTLR)可以处理具有更大前瞻性、谓词等的 LL(k) 语法,并且只是绕过该类型的问题。
如果您仍然坚持手动编码,请考虑使用堆栈来存储符号,并在收集到表达式后将它们转换为 AST 节点,但这同样不是一项简单的任务。
让我先提出问题:我能否将实现此特定语法的解析树简单地转换为 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
但是,由于 |FIRST(expression2)|,此文法不是 LL(1) = |{文字,+}| > 1.
这引出了一个问题:(1) 转换此解析树的最优雅和最简单的方法是什么? (2) 我构建解析树是否完全是在浪费我应该开始编写 AST 的语法的时间?
我相信你希望生成如下 AST:
ADD
/ \
1 ADD
/ \
2 ADD
/ \
3 4
但您可能已经注意到,您的解析树实际上是一个平面列表,并不是在单次传递中对运算符及其操作数进行分组的简单起点。无论如何,编写一个更高级的解析器、识别树结构和应用语法简化都不是一项简单的任务。
因此,在开始这样做之前,您可能希望考虑一些现有的解析器生成器,如 yacc 或 ANTLR。您可能需要重写语法以创建以运算符为中心的规则,而不是将它们视为递归实用程序。不是经典 LL(1) 的语法可能不是一个大问题,因为现代生成器(如 ANTLR)可以处理具有更大前瞻性、谓词等的 LL(k) 语法,并且只是绕过该类型的问题。
如果您仍然坚持手动编码,请考虑使用堆栈来存储符号,并在收集到表达式后将它们转换为 AST 节点,但这同样不是一项简单的任务。