绕来绕去,千辛万苦,我们终于创建了抽象语法树,完成了对整个源代码结构性的分析,似乎可以喘一口气了。但是,对于下面的代码:

int main()

{

a = 1;

return;

}

可以得到下面的抽象语法树图示:

图6-1. 抽象语法树

但并不代表代码已经没有语法错误了。很明显,因为这里并没有定义变量a,而且函数也没有返回值。针对这样的情况,还需要对代码进行进一步的检查。

在进行检查之前,我们先研究如何遍历这棵树。回顾一下,我们使用AST派生出多种节点来存储各种不同的表达式、声明和语句。比如存储函数定义的节点FunctionDefn:

class FunctionDefn(AST):

"""A node representing a function definition"""

def __init__(self, declaration, body):

self.name = declaration.name

self.type = declaration.type

self.return_type = declaration.type.child

self.body = body

是由多个其它类型的节点组成,并且相互之间存在联系。对访问函数体而言,其实就是访问CompoundStatement节点。而该节点则由更多的声明节点Declaration和具体的语句节点如IfStatement组成。因此,可以从根节点出发,按照节点的具体类型进行逐次地遍历。基于这种方法,我们引入python中的属性函数getattr(),进行如下设计:

修改模板节点

class AST(object):

...

def visit(self, visitor):

return self.dig_visit(self.__class__, visitor)

def dig_visit(self, node, visitor):

"""appending the class' name with 'visit_'"""

method_name = 'visit_' + node.__name__

visitor_method = getattr(visitor, method_name, None)

if visitor_method is None:

# call the class's superclass

bases = node.__bases__

last = None

for child in bases:

last = self.dig_visit(child, visitor)

return last

else:

return visitor_method(self)

我们将从当前节点出发,遍历所有用visit_前缀和具体节点类名组成的定义在visitor中的函数,称之为节点函数,如果没有对应的实现,则会往父类方向遍历,直到模板节点AST为止,因为我们会定义一个模板节点的函数,以阻止遍历往更基类的方向进行。

有了上面遍历抽象语法树的方式,我们将语义分析分成声明检查、流程检查和类型检查三个部分进行分别检查。

6.1 声明检查

简单地说,就是看变量使用之前是否已经被声明。由于C语言允许在不同作用域定义同名变量。因此,这部分检查还需要考虑作用域,其一般用大括号对{}划分。我们先定义一个这样的作用域类:

class ScopedSymbolTable:

"""scoped symbol table for look up"""

def __init__(self, enclosing_scope=None):

self._symbols = {}

self.enclosing_scope = enclosing_scope

# insert symbols

def insert(self, name, value):

"""add symbol with the given value"""

if name in self._symbols:

if not self._symbols[name].type == value.type:

comment = f"Variable '{name}' has declared before."

raise Exception(comment)

self._symbols[name] = value

# look up symbols

def lookup(self, name):

symbol = self._symbols.get(name)

if symbol is not None:

return symbol

# recursively go up the chain and lookup the name

if self.enclosing_scope is not None:

return self.enclosing_scope.lookup(name)

else:

return None

其中,symbols用来存储所有声明的变量,而每一个作用域enclosing_scope都会有自己的_symbols。整个类型检查的逻辑就是:变量在声明的时候,会被插入到当前作用域的_symbols中。当某个变量被使用的时候,则用lookup进行查找,如果不能在当前或者更上一层的作用域范围内找到,则可以判断该变量没有被声明。那么,剩下的任务就是遍历抽象语法树,找到可能存在变量声明和使用的地方。

接下来,我们开始遍历抽象语法树。首先定义如下的类:

class NodeVisitor(object):

def visit(self, node):

return node.visit(self)

def visit_list(self, _list):

"""like NodeList in parser"""

last = None

for child in _list:

last = child.visit(self)

return last

class SymbolTableVisitor(NodeVisitor):

def __init__(self):

self.current_scope = None

def push_table(self, node):

"""push the current_scope into stack and create a child scope"""

self.current_scope = ScopedSymbolTable(

enclosing_scope=self.current_scope

)

def pop_table(self):

"""restore the parent scope"""

self.current_scope = self.current_scope.enclosing_scope

基类NodeVisitor的引入有助于我们调用getattr()获取当前的visit_函数。同时,我们使用push和pop方法来保护当前父作用域,同时创建出新的子作用域。例如,CompoundStatement节点中会引入大括号,从而将引入新的作用域,因此访问这个节点函数时,我们需要先将当前作用域压入栈,创建新的作用域,然后访问组成它的节点,访问完毕再从栈中弹出父作用域,这样就有效地保护了不同作用域声明的变量。

考虑这部分最开头的源代码,我们在SymbolTableVisitor中定义所关心的可能会包含变量的节点函数:

def visit_AST(self, node):

pass

"""root"""

def visit_TranslationUnit(self, node):

"""the entry of the file"""

self.current_scope = ScopedSymbolTable(

enclosing_scope=self.current_scope

)

self.visit_NodeList(node)

node.scope_symbol = self.current_scope

"""for all list derived from NodeList, eg. DeclarationList."""

def visit_NodeList(self, node):

self.visit_list(node.nodes)

"""expressions"""

def visit_BinOp(self, node):

node.left.visit(self)

node.right.visit(self)

"""functions"""

def visit_FunctionDefn(self, node):

self.add_symbol(node)

self.push_table(node)

node.body.visit(self)

self.pop_table()

"""statements"""

def visit_CompoundStatement(self, node):

self.push_table(node)

node.declarations.visit(self)

node.statements.visit(self)

self.pop_table()

def visit_ExpressionStatement(self, node):

node.expr.visit(self)

def visit_ReturnStatement(self, node):

node.expr.visit(self)

上面函数名前缀为visit_的即为节点函数。从visit_TranslationUnit进入,通过visit函数,我们会遍历以下的节点函数:

visit_TranslationUnit -> visit_FunctionDefn->visit_CompoundStatement\

->visit_ExpressionStatement->visit_BinOp->visit_AST

那么,我们如何判断变量是否使用之前已经声明了呢?

def visit_Id(self, node):

symbol = self.current_scope.lookup(node.expr)

if symbol is None:

comment = f"Identifier '{node.expr}' not found."

raise Exception(comment)

由于所有的变量都用节点Id存储,访问BinOp之后将访问Id,从而检查出对应的问题。如果对源代码进行修改如果,在最上面添加:

int a;

那么,我们遍历到声明节点,并通过visit_Declaration插入变量:

"""declarations"""

def visit_Declaration(self, node):

self.current_scope.insert(node.name, node)

当再次经由BinOp访问Id时,就能够找到对应的已经声明的变量了。

但是,我们研究的这段源代码中还有一个问题尚未解决:返回值检查。这部分内容,将在下一部分的流程检查中进行。

简易的c语言编译器,实现简易的C语言编译器(part 8)相关推荐

  1. 用c语言编写一个简易的编译器,面向教学的简易c语言编译器的设计与实现(54页)-原创力文档...

    目录 TOC \o "1-5" \h \z \o "Current Document" 摘要I ABSTRACTII \o "Current Docu ...

  2. c语言mc协议,easymc - C语言开发的简易消息通道库

    easymc是一个C语言开发的简易的消息通道库,目前提供请求/回应.订阅/发布两种通信模式,同机器的通信采取IPC方式,跨机的通信采取TCP方式,源码包含开源项目jemalloc库. 代码地址:htt ...

  3. C语言如何做出简易贪食蛇

    如何使用C语言做出来简易的贪食蛇 #include<stdio.h> #include<graphics.h> //easy图形库 #include<conio.h> ...

  4. 十位数连加 c语言,用C语言编写一个简易计算器可实现加减乘除,连加连减,连乖连除....

    用C语言编写一个简易计算器可实现加减乘除,连加连减,连乖连除. 用C语言编写一个简易计算器可实现加减乘除,连加连减,连乖连除. 人气:435 ℃时间:2020-04-10 06:55:13 优质解答 ...

  5. c语言按给定成绩查询,C语言学生成绩管理系统(简易版)

    #include #include #include int readstudents(struct students stu[]); //读取学生信息 int readsexcode(struct ...

  6. c语言程序水准路线,C++语言在水准路线简易平差中的应用实例(17页)-原创力文档...

    C语言在水准路线简易平差中的应用实例C语言在水准路线简易平差中的应用实例 PAGE C++语言在水准路线简易平差中的应用实例 [摘 要]:水准测量工作分为野外数据采集和内业计算,白天测得的大量数据,晚 ...

  7. c语言编程基础------0.0.1c语言简易介绍(百度百科)

    本文主要来自百度百科 C语言是目前世界上流行.使用最广泛的 面向过程的高级 程序设计语言. C语言对操作系统和系统使用程序以及需要对硬件进行操作的场合,用C语言明显优于其它高级语言,许多大型应用软件都 ...

  8. 基于java平台简易计算器_基于java的简易计算器的设计

    基于java的简易计算器的设计 基于java的简易计算器的设计 摘要 自从java语言诞生以来,java语言就以不可抵挡的趋势很快成为国际上广泛流行的面向对象编程语言,它既具有高级语言的特点,又少了C ...

  9. 自定义View-实现简易车速器(真的够简易)

    自定义View-实现简易车速器(真的够简易) 学习自定义View挺久了,好久没用都快忘了,这里实现一个简易的车速器算是一个回顾,项目比较简单,代码较少,但自定义View的流程基本都涉及到了.本文不是一 ...

  10. c语言编译程序的软件下载,c语言编译器(wintc)

    c语言编译器(wintc)是一款针对c语言编译的开发工具.c语言编译器(wintc)支持Windows平台开发程序,这款软件利用TC2为内核,提供了Windows平台的开发界面.这款具编译器具有语法加 ...

最新文章

  1. go标准命令详解0.2 go install
  2. 如何在.NET应用程序中分析CPU使用率过高的问题
  3. 深度学习在医学影像中的研究进展及发展趋势
  4. 史上最通俗分布式锁解读
  5. 学习 TList 类的实现[6]
  6. Memcached 学习---(4)Memcached 连接
  7. Android异常总结---1.异常原因: java.lang.IllegalArgumentException: URI: content://com.android.contacts/con
  8. java进行图片和字符串的互相转换
  9. 引用 一个较优雅的GridView隐藏列取值解决方案
  10. Redis配置文件redis.conf参数详解
  11. 最快理解使用CSS弹性盒子
  12. kafka应用场景_从未如此简单:10分钟带你逆袭Kafka!
  13. MATLAB VideoReader读取视频出错解决办法
  14. C语言入门知识集合。
  15. 关于MySQL数据类型定义的几个细节-INT(N)/VARCHAR(N)/DECIMAL(M,N)
  16. 计算机知识竞赛 翻译,英文简历之常见学科竞赛中英文翻译
  17. 生产系统规划仿真软件
  18. 高、低成本MEMS惯导系统姿态、位置、速度更新算法的对比
  19. 佟鑫 PHP,【盖佟鑫】姓名测试打分,起名字测试打分盖佟鑫,盖佟鑫名字打分测试,盖佟鑫测名字打分,【盖佟鑫】名字测分,姓名测试网...
  20. 与苹果相反 三星向所有第三方应用开放指纹识别功能

热门文章

  1. 深度学习之自编码器AutoEncoder
  2. python TypeError: Expected int32, got list containing Tensors of type '_Message' instead.
  3. 结构体 CString QString 成员赋值出错
  4. 理解图像的傅里叶变换(细心分析)
  5. python二叉树深度 判断平衡树
  6. mysql 分页排序
  7. java stack empty_java.util.Stack.empty()方法实例
  8. Linux设备中文件系统位置,【linux之设备,分区,文件系统】
  9. 计算机故障报告怎么写,计算机这样的诊断报告是否正常
  10. 三菱d700变频器接线图_昆明市三菱恒压供水变频器接线图