在本章中,我们将以第一章中开发的词法分析器为基础,为Kaleidoscope语言开发一个完整的语法解析器。搞定语法解析器之后,我们就开始定义并构造抽象语法树(AST,Abstract Syntax Tree)。
在解析Kaleidoscope的语法时,我们将综合运用递归下降解析和运算符优先级解析两种技术(后者用于解析二元表达式,前者用于其他部分)。正式开始解析之前,不妨先来看一看解析器的输出:抽象语法树。
抽象语法树的作用在于牢牢抓住程序的脉络,从而方便编译过程的后续环节(如代码生成)对程序进行解读。AST就是开发者为语言量身定制的一套模型,基本上语言中的每种结构都与一种AST对象相对应。Kaleidoscope语言中的语法结构包括表达式、函数原型和函数对象。我们不妨先从表达式入手:
/// ExprAST - Base class for all expression nodes. |
上述代码定义了基类ExprAST
和一个用于表示数值常量的子类。其中子类NumberExprAST
将数值常量的值存放在成员变量中,以备编译器后续查询。
直到目前为止,我们还只搭出了AST的架子,尚未定义任何能够体现AST实用价值的成员方法。例如,只需添加一套虚方法,我们就可以轻松实现代码的格式化打印。以下定义了Kaleidoscope语言最基本的部分所要用到的其他各种表达式的AST节点:
/// VariableExprAST - Expression class for referencing a variable, like "a". |
我们(特意)将这几个类设计得简单明了:VariableExprAST
用于保存变量名,BinaryExprAST
用于保存运算符(如+
),CallExprAST
用于保存函数名和用作参数的表达式列表。这样设计AST有一个优势,那就是我们无须关注语法便可直接抓住语言本身的特性。注意这里还没有涉及到二元运算符的优先级和词法结构等问题。
定义完毕这几种表达式节点,就足以描述Kaleidoscope语言中的几个最基本的结构了。由于我们还没有实现条件控制流程,它还不算图灵完备;后续还会加以完善。接下来,还有两种结构需要描述,即函数的接口和函数本身:
/// PrototypeAST - This class represents the "prototype" for a function, |
在Kaleidoscope中,函数的类型是由参数的个数决定的。由于所有的值都是双精度浮点数,没有必要保存参数的类型。在更强大、更实用的语言中,ExprAST
类多半还会需要一个类型字段。
有了这些作为基础,我们就可以开始解析Kaleidoscope的表达式和函数体了。