紧接着上一部分抽象语法树的内容。在这一部分,我们将利用这些定义好的节点(砖块)和抽象语法描述(水泥)搭建起完整的抽象语法树。
同词法分析实现的方式一样,我们首先定义语法分析的类:
class Parser(object):
def __init__(self, lexer):
self.lexer = lexer
# set current token to the first token taken from the input
self.current_token = self.lexer.get_next_token()
def eat(self, token_type):
# compare the current token type with the passed token type
# otherwise raise an exception.
if self.current_token.type == token_type:
self.current_token = self.lexer.get_next_token()
else:
raise Exception(f"token {token_type} is undesired in function")
通过eat
这个函数,可能你已经大概猜到了我们将要实现的语法分析的原理:一个一个的获取token进行比较,要么选择吃掉它,然后获取下一个token;要么抛出错误,终止程序。这种方式也符合我们分析代码的过程,比如括号必须成对出现。通过设置期望的token,从而可以按照语法规则进行token的分析和抽象语法树的搭建。在抛出异常中,我们使用了Python语言的一个功能:F字符串。有兴趣的可以参考Python的官方指南,简单并且强大着。
其实,有了前面归纳的语法描述形式,我们完全可以写出一一对应的函数,使用上一部分定义好的节点来存储这些token,然后按照函数之间的逻辑关系再将这些节点连接起来形成新的节点,最终得到整个的抽象语法树。比如,在前面介绍四则运算表达式的描述的时候,我们已经给出了如何用节点存储这些token。接下来,我们按照声明、语句和表达式三个部分详细阐述抽象语法树构建的原理。
5.1 声明
为了使描述简明扼要,抓住问题核心,我们先暂时省略结构体和枚举类型,以及带有初始值的变量声明的描述,得到如下简化之后的声明的描述:
type_specifier : INT
| CHAR
declarator : direct_declarator
| * declarator
direct_declarator : ID
| direct_declarator ( parameter_type_list )
| direct_declarator ( )
| direct_declarator [ const_expression ]
| direct_declarator [ ]
declarator_list : declarator
| declarator_list , declarator
declaration : type_specifier declarator_list ;
在Parser
类里面,我们以函数的形式定义上面出现的所有描述:
def type_specifier(self):
"""type_spec : INT
| CHAR
"""
token = self.current_token
if token.type in (INT, CHAR):
self.eat(token.type)
return BaseType(token.value)
else:
# no such type
raise Exception(f"undefined type.")
def declarator(self):
"""declarator : * direct_declarator
| direct_declarator
"""
if self.current_token.type == MUL:
self.eat(MUL)
node = self.declarator()
node.set_type(PointerType())
return node
return self.direct_declarator()
def direct_declarator(self):
"""direct_declarator : ID
| direct_declarator [ const_expr ]
| direct_declarator [ ]
| direct_declarator ( )
| direct_declarator ( parameter_type_list )
"""
node = Declaration(self.variable())
if self.current_token.type == LPAREN:
# function type
self.eat(LPAREN)
node.add_type(FunctionType(self.parameter_type_list()))
self.eat(RPAREN)
elif self.current_token.type == LBRACKET:
# array type
self.eat(LBRACKET)
node.add_type(ArrayType(self.const_expression()))
self.eat(RBRACKET)
return node
def variable(self):
"""variable : ID"""
name = self.current_token.value
self.eat(ID)
return name
def declarator_list(self):
"""declarator_list : declarator
| declarator_list, declarator
"""
nodes = VariableList()
while self.current_token.type != SEMI:
child_node = self.declarator()
nodes.add(child_node)
if self.current_token.type == SEMI:
break
self.eat(COMMA)
return nodes
def declaration(self):
"""declarations : type_specifier declarator_list SEMI
"""
nodes = DeclarationList()
type_node = self.type_specifier()
for decl_node in self.declarator_list().nodes:
decl_node.set_type(type_node)
nodes.add(decl_node)
self.eat(SEMI)
return nodes
这里用到的两个list类其实就是前面介绍的NodeList
的变体,如下:
class DeclarationList(NodeList):
"""A declaration list, derived for identification"""
pass
class VariableList(NodeList):
"""A variable list, derived for identification"""
pass
先不去细究parameter_type_list
和const_expression
的实现方式,单从简化过的声明的描述上来看,我们是完全按照描述照葫芦画瓢来实现对应的函数,这个过程中用到了前面定义的节点,以及如何组织它们。同样地,下面介绍地语句和表达式,其实也是类似的实现形式。
5.2 语句
还是从语句的描述入手:
statement : expression_statement
| jump_statement
| iteration_statement
| selection_statement
| compound_statement
| empty_statement
compound_statement : { declaration_list }
| { declaration_list statement_list }
selection_statement : IF ( expression ) statement
| IF ( expression ) statement ELSE statement
...
对应的实现如下:
def statement(self):
"""statement : expression_statement
| jump_statement
| iteration_statement
| selection_statement
| compound_statement
| empty_statement
"""
if self.current_token.type == BEGIN:
node = self.compound_statement()
elif self.current_token.type == IF:
node = self.selection_statement()
elif self.current_token.type in (RETURN, BREAK, CONTINUE):
node = self.jump_statement()
elif self.current_token.type in (FOR, WHILE):
node = self.iteration_statement()
elif self.current_token.type == SEMI:
node = None
else:
node = self.expression_statement()
return node
def compound_statement(self):
"""compound_statement : { declaration_list }
| { declaration_list statement_list }
"""
self.eat(BEGIN)
declaration_nodes = DeclarationList()
statement_nodes = StatementsList()
while True:
while self.current_token.type in (INT, CHAR):
for child in self.declaration().nodes:
declaration_nodes.add(child)
if self.current_token.type == END:
break
statement_nodes.add(self.statement())
self.eat(END)
node = CompoundStatement(declaration_nodes, statement_nodes)
return node
def selection_statement(self):
"""if_statement : IF ( expression ) statement { ELSE statement }
"""
self.eat(IF)
self.eat(LPAREN)
expr_node = self.expression()
self.eat(RPAREN)
then_node = self.statement()
else_node = None
if self.current_token.type == ELSE:
self.eat(ELSE)
else_node = self.statement()
return IfStatement(expr_node, then_node, else_node)
def iteration_statement(self):
"""for_statement : FOR ( expression_statement expression_statement expression ) statement
"""
...
def jump_statement(self):
"""jump_statement : CONTINUE ;
| BREAK ;
| RETURN expression ;
"""
...
def expression_statement(self):
"""expression_statement: expression ;
"""
...
因为函数的定义和实现完全按照描述来做的,没有什么难度。剩下的部分亦可按照这样的方式进行,这里就不一一介绍了。
5.3 表达式
前面介绍表达式的时候,已经给出了四则运算表达式的实现,这里主要分析逻辑和赋值运算。首先,重温一下它们的表述方式:
relation_expression : additive_expression
| relation_expression < additive_expression
| relation_expression <= additive_expression
| relation_expression > additive_expression
| relation_expression >= additive_expression
equality_expression : relation_expression
| equality_expression == relation_expression
| equality_expression != relation_expression
expression : equality_expression
| equality_expression = expression
它们的实现亦可按照这样的表示方法进行:
def relation_expression(self):
"""relation_expression : additive_expression
| relation_expression (< | >| <=| >=) additive_expression
"""
node = self.additive_expression()
while self.current_token.type in (RELT, REGT, RELEQ, REGEQ):
token = self.current_token
self.eat(token.type)
node = BinOp(left=node, op=token.value, right=self.additive_expression())
return node
def equality_expression(self):
"""equality_expression : relation_expression
| equality_expression (== | !=) relation_expression
"""
node = self.relation_expression()
while self.current_token.type in (REEQ, RENEQ):
token = self.current_token
self.eat(token.type)
node = BinOp(left=node, op=token.value, right=self.relation_expression())
return node
def assignment_expression(self):
"""assignment_expression : equality_expression
| unary_expression = assignment_expression
"""
node = self. equality_expression()
token = self.current_token
if token.type == ASSIGN:
self.eat(ASSIGN)
right_node = self.assignment_expression()
node = BinOp(left=node, op=token.value, right=right_node)
return node
这里有一个细节,不知道大家有没有注意到逻辑和赋值表达式实现的区别:我们都用了BinOp
去存储二者之间的逻辑或者赋值关系,但是在赋值表达式中,我们先递归后存储;而逻辑表达式则先存储再递归。这样的区别就是按照前面我们介绍的左结合和右结合的概念实现上的区别,以便符合C语言语法的要求。
5.4 函数定义
再来看一看函数定义的抽象描述:
function_definition : type_specifier declarator compound_statement
对应地,实现如下:
def function_definition(self):
"""function_definition : type_specifier declarator compound_statement
"""
type_node = self.type_specifier()
decl_node.set_type(type_node)
node = FunctionDefn(decl_node, self.compound_statement())
return node
5.5 实例
有了这些描述对应的实现,我们定义下面的类:
class TranslationUnit(NodeList):
"""An unit list, derived for identification"""
pass
作为一个根节点使用。为了对上面介绍的实现细节有一个直观的感受,对于下面的C代码:
int a, b;
int main()
{
a = 1;
if (a > 0)
b = 2;
return 0;
}
用Graphviz库简单绘制创建的节点,可以得到下面的抽象语法树图示:
至此,我们已经一步一步地分析和实现了语法分析的过程。这里面,还有很多上一部分定义的抽象描述没有具体实现。但是,相信经过这部分的分析,大家可以试着补充完整,从而更好地理解语法分析这部分的内容。下一部分,将开启新的篇章,敬请期待。
实现简易的C语言编译器(part 0)
实现简易的C语言编译器(part 1)
实现简易的C语言编译器(part 2)
实现简易的C语言编译器(part 3)
实现简易的C语言编译器(part 4)
实现简易的C语言编译器(part 5)
实现简易的C语言编译器(part 6)
实现简易的C语言编译器(part 8)
实现简易的C语言编译器(part 9)
实现简易的C语言编译器(part 10)
实现简易的C语言编译器(part 11)