我们已经重新抽象描述了C语言的表达式、语句和声明,并且进行了实现。如果大家对在实现中出现的并没有过多解释的Binop
和Negative
这样的结构还有一些印象,那么这一部分,我们将解释并且引入更多的类似的东西,让它们帮助我们串接起词法分析中得到的所有token。这就是语法分析第二步的内容:构建抽象语法树(Abstract Syntax Tree, AST)。
概念性的东西这里就略过了,只需要记住这是一种层级表示关系,由众多的节点组成,节点之间通过父节点产生关联,包含了整个源代码的结构信息。我们将要逐步介绍的所有过程,比如语义分析和产生汇编代码,都将基于这棵树进行的。
为了充分利用面向对象编程语言的特点,实现多态操作,首先定义一个模版节点:
class AST(object):
"""the base AST node"""
pass
剩下的工作则是从这个节点派生出具有具体行为内容的节点。我们仍然按照表达式、语句和声明的分类方法具体讨论。
4.1 表达式(Expression)
回顾我们之前分析的表达式的描述,可以将它们简单地总结为两类:单目运算符和双目运算符的表达式。
4.1.1 单目运算符
类似于包含取地址符(&)和解除引用符(*)这样的表达式。它们由单目运算符和表达式组合而成。首先,定义一个针对该表达式的通用基类:
class UnaryOp(AST):
"""any generic unary operator"""
def __init__(self, node):
self.expr = node
表达式有了,如何知道具体的单目运算符呢?答案是:通过单独为它们创建具体的节点来区别对待不同的运算符,例如:
class Pointer(UnaryOp):
"""a pointer refer, e.g. '*a'"""
pass
class AddrOf(UnaryOp):
"""an address-of operator, e.g. '&a'"""
pass
class Negative(UnaryOp):
"""A negative unary operator, e.g. '-5'."""
pass
我们再考虑两个特殊情况:
-
- +b
显然,+b
和b
是完全等价的,所以我们并没有单独派生出一个Positive
的类,完全可以使用基类UnaryOp
替代。
- +b
-
- **a
对于这种嵌套的单目运算符表达式,也可以采用嵌套的方式得到具体的节点,比如使用Pointer(Pointer)
来表示。
- **a
-
- !a
我们这个编译器忽略了这样的结构,但是如果大家有兴趣,完全可以再派生出一个新的类来表示。同时,记得抽象出具体的表达式描述。
- !a
4.1.2 双目运算符
典型地,所有的四则运算、逻辑和条件操作都是双目运算。与前面介绍的单目表达式节点定义策略不同,由于双目运算符众多,如果针对具体运算符派生节点,节点类将非常多。事实上,这些双目运算符操作都非常类似,与单目运算符大不同。因此,直接将表达式中的运算符存在节点之中,这样的节点可以定义为:
class BinOp(AST):
"""any binary operator, (+, -, *, /)"""
def __init__(self, left, op, right):
self.left = left
self.op = op
self.right = right
赋值操作也是一个双目运算符,完全可以采用上面节点来表示。
4.1.3 特殊运算符
除了单目和双目运算符,C语言中有且仅有一个三目运算符:?:
,但我们这里不作考虑,因为它完全可以用条件语句来替代。这里重点考虑几个特殊的运算符。在结构体中,我们通常用.
或者->
来指示具体的成员变量,虽然也可以当成一种双目操作,但是含义上仍然与其不同,定义新的一个节点为:
class StructOp(AST):
"""any binary operator for struct, ., ->"""
TO_OPS = ['.', '->']
def __init__(self, left, op, right):
self.parent = left
self.op = op
self.member = right
4.2 语句(Statement)
对于语句,我们重点突出它们的结构特征,仍然按照前面的分类进行讨论。
4.2.1 空语句
单独;
组成的语句,虽然没有实际意义,但具有特殊的含义。定义如下:
class EmptyStatement(AST):
"""empty statement"""
pass
4.2.2 选择语句
判断条件和具体的执行内容就是我们关心的。于是,可以定义为:
class IfStatement(AST):
"""if else statement"""
def __init__(self, expr, then_stmt, else_stmt):
self.expr = expr
self.then_stmt = then_stmt
self.else_stmt = else_stmt
4.2.3 跳转语句
break
和continue
并不包含附属的信息,但本身蕴涵着信息。因此,需要具体的节点表示。而return
可能存在返回值,于是,它们可以表示为:
class BreakStatement(AST):
"""A break statement"""
pass
class ContinueStatement(AST):
"""A continue statement"""
pass
class ReturnStatement(AST):
"""return statement"""
def __init__(self, expr=None):
self.expr = expr if expr is not None else EmptyStatement()
4.2.4 迭代语句
和选择语句类似,可以表示为:
class WhileLoop(AST):
"""while loop"""
def __init__(self, expr, stmt):
self.expr = expr
self.stmt = stmt
class ForLoop(AST):
"""for loop"""
def __init__(self, begin_stmt, expr, end_stmt, stmt):
self.begin_stmt = begin_stmt
self.end_stmt = end_stmt
self.expr = expr
self.stmt = stmt
4.2.5 复合语句
复合语句的大括号中包含着由一系列的声明和语句组成的集合。对于这样的集合,其实就是我们前面的描述中出现的各种xxx_list
,比如:
statement_list : statement
| statement_list statement
我们将这些集合统一表示为一个list模板节点,抽象描述中介绍的所有list都从这个节点派生:
class NodeList(AST):
"""A list of nodes"""
def __init__(self, node=None):
self.nodes = []
if node is not None:
self.nodes.append(node)
def add(self, node):
self.nodes.append(node)
那么这里用到的declaration_list, statement_list
就可以照猫画虎了:
class statement_list(NodeList):
"""A list of nodes of statement"""
pass
class declaration_list(NodeList):
"""A list of nodes of declaration"""
pass
没有具体对应的操作,只是方便我们记忆和处理。进而,符合语句就可以表示为:
class CompoundStatement(AST):
"""A compound statement, e.g. '{ int i; i += 1; }'."""
def __init__(self, declaration_list, statement_list):
self.declarations = declaration_list
self.statements = statement_list
初始化复合语句时,将使用声明和语句的集合。
4.2.6 表达式语句
表达式语句由表达式和分号组成,可表示为:
class ExpressionStatement(AST):
"""A expression statement"""
def __init__(self, expr):
self.expr = expr
4.3 声明(Declaration)
声明,简单地理解就是由类型符和变量名组成。
4.3.1 类型符
为了表示数据的类型,我们仿照单目运算符的策略,定义一个模板类型的类:
class Type(AST):
"""assign a type node to variable"""
pass
通过派生具体的数据类型,如int, char
,就可以表示对应的类型。但是,由于指针类型的存在,比如:struct Point* point; int* array[3]; int** a;
这样的声明变量的方式,必须对这个模板类做出一些调整,使得它能够很好地表示这种嵌套的数据类型。为此,我们为这个模板类型增加一个child
的成员变量,进而表示如下:
class Type(AST):
"""assign a type node to variable"""
def __init__(self, child=None):
self.child = child
def set_base_type(self, type):
if self.child is None:
self.child = type
else:
self.child.set_base_type(type)
成员函数child
的存在,方便了我们嵌套地包含更多的数据类型。对于int** a;
,就可以表示为:Type(Type(Type)) -> * ( * ( int ))
。其中,括号内的类型就是外层类型的child
。此时,通过派生出的具体的类型,就可以逐层地描述变量。具体地,我们可以派生出如下的类型:
- 基本数据类型
class BaseType(Type):
"""A base type representing ints, chars, etc..."""
def __init__(self, type_str, child=None):
Type.__init__(self, child)
self.type_str = type_str
- 指针类型
class PointerType(Type):
"""A type representing a pointer to another (nested) type."""
def __init__(self, child=None):
Type.__init__(self, child)
- 结构体类型
class StructType(Type):
"""A type represent a struct"""
def __init__(self, struct_name, expr_list=None, child=None):
Type.__init__(self, child)
self.name = struct_name
self.exprs = expr_list
- 枚举类型
class EnumType(Type):
"""A type represent an enum"""
def __init__(self, enum_name=None, expr_list=None, child=None):
Type.__init__(self, child)
self.name = enum_name
self.exprs = expr_list
4.3.2 声明
有了类型符,加上变量的名字,就构成了广义的声明,可以表示为:
class Declaration(AST):
"""A node representing a declaration of a function or variable."""
def __init__(self, name, type=None):
self.type = type
self.name = name
def set_type(self, type):
if self.type is None:
self.type = type
else:
self.type.set_base_type(type)
但是,变量不单单是名字而已,还可以有其它的类型,比如还可以是数组或者函数:
direct_declarator : ID
| direct_declarator ( parameter_type_list )
| direct_declarator ( )
| direct_declarator [ const_expression ]
| direct_declarator [ ]
因此,为了能够表示这两种后缀形式的变量,在声明中定义下面的方法来添加这些后缀,并且是以上面定义的类型符来表示的:
def add_type(self, type):
type.set_base_type(self.type)
self.type = type
这样,就需要对应地添加数组或者函数类型:
class ArrayType(Type):
"""A type representing an array, e.g. a[], a[5]"""
def __init__(self, index, child=None):
Type.__init__(self, child)
self.index = index
class FunctionType(Type):
"""A type representing a function"""
def __init__(self, parms=None, child=None):
Type.__init__(self, child)
self.parms = parms
def get_return_type(self):
"""Returns the return type of the function."""
return self.child
虽然没有数组类型或者函数类型这样一种说法,但是将变量前(类型符)后(数组或函数)都当成一种类型,方便我们可以灵活地处理声明。
4.3.3 函数定义
有了前面关于类型与变量的定义和理解,我们可以单独用一个类来表示函数的定义:
class FunctionDefn(AST):
"""A node representing a function definition"""
def __init__(self, declaration, body):
self.name = declaration.name
self.function = declaration.type
self.return_type = declaration.type.child
self.body = body
这里就可以看出,函数类型的返回值也是一个类型,我们就可以从声明中直接获得,简化了编程的过程。
准备工作终于做好,下一部分,我们将解决如何用这些抽象出的具体的AST节点组成整个语法结构的高楼,也就是语法树。
实现简易的C语言编译器(part 0)
实现简易的C语言编译器(part 1)
实现简易的C语言编译器(part 2)
实现简易的C语言编译器(part 3)
实现简易的C语言编译器(part 4)
实现简易的C语言编译器(part 5)
实现简易的C语言编译器(part 5)
实现简易的C语言编译器(part 7)
实现简易的C语言编译器(part 8)
实现简易的C语言编译器(part 9)
实现简易的C语言编译器(part 10)
实现简易的C语言编译器(part 11)