热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

c语言可以编译ce工具,实现简易的C语言编译器(part7)

紧接着上一部分抽象语法树的内容。在这一部分,我们将利用这些定义好的节点(砖块)和抽象语法描述(水泥)搭建起完整的抽象语法树。同词法分析实现的方式一样,我

紧接着上一部分抽象语法树的内容。在这一部分,我们将利用这些定义好的节点(砖块)和抽象语法描述(水泥)搭建起完整的抽象语法树。

同词法分析实现的方式一样,我们首先定义语法分析的类:

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

| relation_expression <&#61; additive_expression

| relation_expression > additive_expression

| relation_expression >&#61; additive_expression

equality_expression : relation_expression

| equality_expression &#61;&#61; relation_expression

| equality_expression !&#61; relation_expression

expression : equality_expression

| equality_expression &#61; expression

它们的实现亦可按照这样的表示方法进行&#xff1a;

def relation_expression(self):

"""relation_expression : additive_expression

| relation_expression (<| >| <&#61;| >&#61;) additive_expression

"""

node &#61; self.additive_expression()

while self.current_token.type in (RELT, REGT, RELEQ, REGEQ):

token &#61; self.current_token

self.eat(token.type)

node &#61; BinOp(left&#61;node, op&#61;token.value, right&#61;self.additive_expression())

return node

def equality_expression(self):

"""equality_expression : relation_expression

| equality_expression (&#61;&#61; | !&#61;) relation_expression

"""

node &#61; self.relation_expression()

while self.current_token.type in (REEQ, RENEQ):

token &#61; self.current_token

self.eat(token.type)

node &#61; BinOp(left&#61;node, op&#61;token.value, right&#61;self.relation_expression())

return node

def assignment_expression(self):

"""assignment_expression : equality_expression

| unary_expression &#61; assignment_expression

"""

node &#61; self. equality_expression()

token &#61; self.current_token

if token.type &#61;&#61; ASSIGN:

self.eat(ASSIGN)

right_node &#61; self.assignment_expression()

node &#61; BinOp(left&#61;node, op&#61;token.value, right&#61;right_node)

return node

这里有一个细节&#xff0c;不知道大家有没有注意到逻辑和赋值表达式实现的区别&#xff1a;我们都用了BinOp去存储二者之间的逻辑或者赋值关系&#xff0c;但是在赋值表达式中&#xff0c;我们先递归后存储&#xff1b;而逻辑表达式则先存储再递归。这样的区别就是按照前面我们介绍的左结合和右结合的概念实现上的区别&#xff0c;以便符合C语言语法的要求。

5.4 函数定义

再来看一看函数定义的抽象描述&#xff1a;

function_definition : type_specifier declarator compound_statement

对应地&#xff0c;实现如下&#xff1a;

def function_definition(self):

"""function_definition : type_specifier declarator compound_statement

"""

type_node &#61; self.type_specifier()

decl_node.set_type(type_node)

node &#61; FunctionDefn(decl_node, self.compound_statement())

return node

5.5 实例

有了这些描述对应的实现&#xff0c;我们定义下面的类&#xff1a;

class TranslationUnit(NodeList):

"""An unit list, derived for identification"""

pass

作为一个根节点使用。为了对上面介绍的实现细节有一个直观的感受&#xff0c;对于下面的C代码&#xff1a;

int a, b;

int main()

{

a &#61; 1;

if (a > 0)

b &#61; 2;

return 0;

}

用Graphviz库简单绘制创建的节点&#xff0c;可以得到下面的抽象语法树图示&#xff1a;

7afa69264ce1

图5-1 抽象语法树

至此&#xff0c;我们已经一步一步地分析和实现了语法分析的过程。这里面&#xff0c;还有很多上一部分定义的抽象描述没有具体实现。但是&#xff0c;相信经过这部分的分析&#xff0c;大家可以试着补充完整&#xff0c;从而更好地理解语法分析这部分的内容。下一部分&#xff0c;将开启新的篇章&#xff0c;敬请期待。



推荐阅读
  • Python正则表达式学习记录及常用方法
    本文记录了学习Python正则表达式的过程,介绍了re模块的常用方法re.search,并解释了rawstring的作用。正则表达式是一种方便检查字符串匹配模式的工具,通过本文的学习可以掌握Python中使用正则表达式的基本方法。 ... [详细]
  • 向QTextEdit拖放文件的方法及实现步骤
    本文介绍了在使用QTextEdit时如何实现拖放文件的功能,包括相关的方法和实现步骤。通过重写dragEnterEvent和dropEvent函数,并结合QMimeData和QUrl等类,可以轻松实现向QTextEdit拖放文件的功能。详细的代码实现和说明可以参考本文提供的示例代码。 ... [详细]
  • 微软头条实习生分享深度学习自学指南
    本文介绍了一位微软头条实习生自学深度学习的经验分享,包括学习资源推荐、重要基础知识的学习要点等。作者强调了学好Python和数学基础的重要性,并提供了一些建议。 ... [详细]
  • 展开全部下面的代码是创建一个立方体Thisexamplescreatesanddisplaysasimplebox.#Thefirstlineloadstheinit_disp ... [详细]
  • VScode格式化文档换行或不换行的设置方法
    本文介绍了在VScode中设置格式化文档换行或不换行的方法,包括使用插件和修改settings.json文件的内容。详细步骤为:找到settings.json文件,将其中的代码替换为指定的代码。 ... [详细]
  • 本文介绍了brain的意思、读音、翻译、用法、发音、词组、同反义词等内容,以及脑新东方在线英语词典的相关信息。还包括了brain的词汇搭配、形容词和名词的用法,以及与brain相关的短语和词组。此外,还介绍了与brain相关的医学术语和智囊团等相关内容。 ... [详细]
  • 本文介绍了设计师伊振华受邀参与沈阳市智慧城市运行管理中心项目的整体设计,并以数字赋能和创新驱动高质量发展的理念,建设了集成、智慧、高效的一体化城市综合管理平台,促进了城市的数字化转型。该中心被称为当代城市的智能心脏,为沈阳市的智慧城市建设做出了重要贡献。 ... [详细]
  • Linux重启网络命令实例及关机和重启示例教程
    本文介绍了Linux系统中重启网络命令的实例,以及使用不同方式关机和重启系统的示例教程。包括使用图形界面和控制台访问系统的方法,以及使用shutdown命令进行系统关机和重启的句法和用法。 ... [详细]
  • Java序列化对象传给PHP的方法及原理解析
    本文介绍了Java序列化对象传给PHP的方法及原理,包括Java对象传递的方式、序列化的方式、PHP中的序列化用法介绍、Java是否能反序列化PHP的数据、Java序列化的原理以及解决Java序列化中的问题。同时还解释了序列化的概念和作用,以及代码执行序列化所需要的权限。最后指出,序列化会将对象实例的所有字段都进行序列化,使得数据能够被表示为实例的序列化数据,但只有能够解释该格式的代码才能够确定数据的内容。 ... [详细]
  • IB 物理真题解析:比潜热、理想气体的应用
    本文是对2017年IB物理试卷paper 2中一道涉及比潜热、理想气体和功率的大题进行解析。题目涉及液氧蒸发成氧气的过程,讲解了液氧和氧气分子的结构以及蒸发后分子之间的作用力变化。同时,文章也给出了解题技巧,建议根据得分点的数量来合理分配答题时间。最后,文章提供了答案解析,标注了每个得分点的位置。 ... [详细]
  • 如何使用Java获取服务器硬件信息和磁盘负载率
    本文介绍了使用Java编程语言获取服务器硬件信息和磁盘负载率的方法。首先在远程服务器上搭建一个支持服务端语言的HTTP服务,并获取服务器的磁盘信息,并将结果输出。然后在本地使用JS编写一个AJAX脚本,远程请求服务端的程序,得到结果并展示给用户。其中还介绍了如何提取硬盘序列号的方法。 ... [详细]
  • 本文介绍了[从头学数学]中第101节关于比例的相关问题的研究和修炼过程。主要内容包括[机器小伟]和[工程师阿伟]一起研究比例的相关问题,并给出了一个求比例的函数scale的实现。 ... [详细]
  • 本文介绍了一种划分和计数油田地块的方法。根据给定的条件,通过遍历和DFS算法,将符合条件的地块标记为不符合条件的地块,并进行计数。同时,还介绍了如何判断点是否在给定范围内的方法。 ... [详细]
  • 本文介绍了多因子选股模型在实际中的构建步骤,包括风险源分析、因子筛选和体系构建,并进行了模拟实证回测。在风险源分析中,从宏观、行业、公司和特殊因素四个角度分析了影响资产价格的因素。具体包括宏观经济运行和宏经济政策对证券市场的影响,以及行业类型、行业生命周期和行业政策对股票价格的影响。 ... [详细]
  • 本文介绍了解决二叉树层序创建问题的方法。通过使用队列结构体和二叉树结构体,实现了入队和出队操作,并提供了判断队列是否为空的函数。详细介绍了解决该问题的步骤和流程。 ... [详细]
author-avatar
紫凝
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有