“变量引用的消解”是指确定具体指向哪个变量。例如变量“i”可能是全局变量i,也可能是静态变量i,还可能是局部变量i。通过这个过程来消除这样的不确定性,确定所引用的到底是哪个变量。

为了消除这样的不确定性,我们需要将所有的变量和它们的定义关联起来,这样的处理称为“变量引用的消解”。具体来说,就是为抽象语法树中所有表示引用变量的VariableNode 对象添加该变量的定义(Variable 对象)的信息。

LocalResolver就是用来处理变量引用消解的类,继承自Visitor(“变量引用的消解”和“静态类型检查”等一连串的处理)。入口方法为:

    /*入口
* 生成了以ToplevelScope 为根节点的
Scope 对象的树,并且将所有VariableNode 和其定义关联起来了。
*/
// #@@range/resolve{
public void resolve(AST ast) throws SemanticException {
/*
* 第1 部分先生成ToplevelScope 对象, 然后将生成的ToplevelScope 对象用
scopeStack.add(toplevel) 添加到scopeStack。这样栈里面就有了1 个Scope 对象
*/
ToplevelScope toplevel = new ToplevelScope();
scopeStack.add(toplevel); /*变量定义的添加.
* 两个foreach 语句都是将全局变量、函数以及类型添加到ToplevelScope 中。
* 两者都是调用ToplevelScope#declareEntity 往ToplevelScope 对象中添加定义或声明。
* 第1个foreach 语句添加导入文件(*.hb)中声明的外部变量和函数
* 第2 个foreach 语句用于导入所编译文件中定义的变量和函数
*/
// #@@range/declareToplevel{
for (Entity decl : ast.declarations()) {
toplevel.declareEntity(decl);
}
for (Entity ent : ast.definitions()) {
toplevel.defineEntity(ent);
}
// #@@}
// #@@range/resolveRefs{
resolveGvarInitializers(ast.definedVariables());//遍历全局变量
resolveConstantValues(ast.constants());//遍历常量的初始化表达式
resolveFunctions(ast.definedFunctions());//最重要的
// #@@}
toplevel.checkReferences(errorHandler);
if (errorHandler.errorOccured()) {
throw new SemanticException("compile failed.");
} /*
* 在最后部分中,将在此类中生成的ToplevelScope 对象和ConstantTable 对象保存到
AST 对象中。这两个对象在生成代码时会用到,为了将信息传给下一阶段,所以保存到AST 对
象中。
*/
ast.setScope(toplevel);
ast.setConstantTable(constantTable);
/*
* 至此为止变量引用的消解处理就结束了,上述处理生成了以ToplevelScope 为根节点的
Scope 对象的树,并且将所有VariableNode 和其定义关联起来了。
*/
}

先往栈中添加ToplevelScope,ToplevelScope表示程序顶层的作用域,保存有函数和全局变量。

然后往这个顶层的作用域添加各种全局变量和函数。

这里着重说下resolveFunctions(ast.definedFunctions());这个方法,对抽象语法树中的所有函数的消解:

    /*
* 函数定义的处理.
*/
// #@@range/resolveFunctions{
private void resolveFunctions(List<DefinedFunction> funcs) {
for (DefinedFunction func : funcs) {
//调用pushScope 方法,生成包含函数形参的作用域,并将作用域压到栈(scopeStack)中
pushScope(func.parameters());
//用resolve(func.body()) 方法来遍历函数自身的语法树
resolve(func.body());
//调用popScope 方法弹出刚才压入栈的Scope 对象,将该Scope 对象用func.setScope
//添加到函数中
func.setScope(popScope());
}
}

遍历所有的函数节点,首先将单个函数节点里的所有形参或者已定义的局部变量压入一个临时变量的作用域。然后再将这个临时变量的作用域LocalScope压入scopeStack:

    //pushScope 方法是将新的LocalScope 对象压入作用域栈的方法
// #@@range/pushScope{
private void pushScope(List<? extends DefinedVariable> vars) {
//生成以currentScope() 为父作用域的LocalScope 对象
//currentScope 是返回当前栈顶的Scope 对象的方法
LocalScope scope = new LocalScope(currentScope());
/*
* 接着用foreach 语句将变量vars 添加到LocalScope 对象中。也就是说,向LocalScope
对象添加在这个作用域上所定义的变量。特别是在函数最上层的LocalScope 中,要添加形参
的定义。
*/
for (DefinedVariable var : vars) {
//先用scope.isDefinedLocally 方法检查是否已经定义了同名的变量
if (scope.isDefinedLocally(var.name())) {
error(var.location(),
"duplicated variable in scope: " + var.name());
}
//然后再进行添加。向LocalScope 对象添加变量时使用defineVariable 方法
else {
scope.defineVariable(var);
}
}
/*
* 最后通过调用scopeStack.addLast(scope) 将生成的LocalScope 对象压到作用域
的栈顶。这样就能表示作用域的嵌套了。
*/
scopeStack.addLast(scope);
}

然后resolve(func.body());是消解当前函数的函数体,也就是BlockNode节点,根据java的多态特性,最终是调用以下方法:

    /*
* 添加临时作用域.
* C 语言(C♭)中的程序块({...}block)也会引入新的变量作用域。
*/
// #@@range/BlockNode{
public Void visit(BlockNode node) {
/*
* 首先调用pushScope 方法,生成存储着这个作用域上定义的变量的Scope 对象,然后压
入作用域栈。
*/
pushScope(node.variables());
/*
* 接着执行super.visit(node);,执行在基类Visitor 中定义的处理,即对程序块的
代码进行遍历。
visit(VariableNode node)
*/
super.visit(node);
/*
* 最后用popScope 方法弹出栈顶的Scope 对象,调用BlockNode 对象的setScope 方
法来保存节点所对应的Scope 对象。
*/
node.setScope(popScope());
return null;
}

又是一个pushScope(node.variables());将函数体中的临时变量声明的列表保存在一个LocalScope中,然后将这个LocalScope压入scopeStack栈中。然后最关键的来了,super.visit(node);调用Visitor类中的visit方法来对函数体中的局部变量进行消解,

    public Void visit(BlockNode node) {
for (DefinedVariable var : node.variables()) {
if (var.hasInitializer()) {
visitExpr(var.initializer());
}
}
visitStmts(node.stmts());
return null;
}

visitStmts是关键,因为变量的引用是保存在VariableNode节点中,最终会调用:

    /*
* 建立VariableNode 和变量定义的关联.
* 使用之前的代码已经顺利生成了Scope 对象的树,下面只要实现树的查找以及引用消解的
代码就可以了。
*/
// #@@range/VariableNode{
public Void visit(VariableNode node) {
try {
//先用currentScope().get 在当前的作用域中查找变量的定义
Entity ent = currentScope().get(node.name());
/*
* 取得定义后,通过调用ent.refered() 来记录定义的引用信息,这样当变量没有被用到
时就能够给出警告。
*/
ent.refered();
/*
* 还要用node.setEntity(ent) 将定义保存到变量节点中,以便随时能够从VariableNode
取得变量的定义。
建立VariableNode 和变量定义的关联
*/
node.setEntity(ent);
}
/*
* 如果找不到变量的定义,currentScope().get 会抛出SemanticException 异常,
将其捕捉后输出到错误消息中。
*/
catch (SemanticException ex) {
error(node, ex.getMessage());
}
return null;
}

首先查找离栈最近的一层currentScope(),如果找到了就调用setEntity建立VariableNode 和变量定义的关联。这里比较关键的是这个get方法,它首先调用LocalScope的get:

    /*从作用域树取得变量定义.
* LocalScope#get 是从作用域树获取变量定义的方法。
* 首先调用variables.get 在符号表中查找名为name 的变量,如果找到的话就返回
该变量,找不到的话则调用父作用域(parent)的get 方法继续查找。如果父作用域是
LocalScope 对象,则调用相同的方法进行递归查找。
如果父作用域是ToplevelScope 的话,则调用ToplevelScope#get
*/
// #@@range/get{
public Entity get(String name) throws SemanticException {
DefinedVariable var = variables.get(name);
if (var != null) {
return var;
}
else {
return parent.get(name);
}
}

如果找不到就一直往父作用域上找,一直找到ToplevelScope的get:

    /*
* 如果在ToplevelScope 通过查找entities 找不到变量的定义, 就会抛出
SemanticException 异常,因为已经没有更上层的作用域了。
*/
/** Searches and gets entity searching scopes upto ToplevelScope. */
// #@@range/get{
public Entity get(String name) throws SemanticException {
Entity ent = entities.get(name);
if (ent == null) {
throw new SemanticException("unresolved reference: " + name);
}
return ent;
}

如果还找不到就抛出异常。所以总体来说,变量的引用是发现最近作用域的定义。

编译器开发系列--Ocelot语言2.变量引用的消解的更多相关文章

  1. 编译器开发系列--Ocelot语言3.类型名称的消解

    "类型名称的消解"即类型的消解.类型名称由TypeRef 对象表示,类型由Type 对象表示.类型名称的消解就是将TypeRef 对象转换为Type 对象. TypeResolve ...

  2. 编译器开发系列--Ocelot语言1.抽象语法树

    从今天开始研究开发自己的编程语言Ocelot,从<自制编译器>出发,然后再自己不断完善功能并优化. 编译器前端简单,就不深入研究了,直接用现成的一款工具叫JavaCC,它可以生成抽象语法树 ...

  3. 编译器开发系列--Ocelot语言7.中间代码

    Ocelot的中间代码是仿照国外编译器相关图书Modern Compiler Implementation 中所使用的名为Tree 的中间代码设计的.顾名思义,Tree 是一种树形结构,其特征是简单, ...

  4. 编译器开发系列--Ocelot语言6.静态类型检查

    关于"静态类型检查",想必使用C 或Java 的各位应该非常熟悉了.在此过程中将检查表达式的类型,发现类型不正确的操作时就会报错.例如结构体之间无法用+ 进行加法运算,指针和数值之 ...

  5. 编译器开发系列--Ocelot语言5.表达式的有效性检查

    本篇将对"1=3""&5"这样无法求值的不正确的表达式进行检查. 将检查如下这些问题.●为无法赋值的表达式赋值(例:1 = 2 + 2)●使用非法的函数 ...

  6. 编译器开发系列--Ocelot语言4.类型定义的检查

    这里主要介绍一下检查循环定义的结构体.联合体.是对成员中包含自己本身的结构体.联合体进行检查.所谓"成员中包含自己本身",举例来说,就是指下面这样的定义. struct point ...

  7. iOS开发系列--C语言之基础知识

    概览 当前移动开发的趋势已经势不可挡,这个系列希望浅谈一下个人对IOS开发的一些见解,这个IOS系列计划从几个角度去说IOS开发: C语言 OC基础 IOS开发(iphone/ipad) Swift ...

  8. iOS开发系列--Swift语言

    概述 Swift是苹果2014年推出的全新的编程语言,它继承了C语言.ObjC的特性,且克服了C语言的兼容性问题.Swift发展过程中不仅保留了ObjC很多语法特性,它也借鉴了多种现代化语言的特点,在 ...

  9. iOS开发系列--C语言之存储方式和作用域

    概述 基本上每种语言都要讨论这个话题,C语言也不例外,因为只有你完全了解每个变量或函数存储方式.作用范围和销毁时间才可能正确的使用这门语言.今天将着重介绍C语言中变量作用范围.存储方式.生命周期.作用 ...

随机推荐

  1. 微信小程序开发心得

    微信小程序也已出来有一段时间了,最近写了几款微信小程序项目,今天来说说感受. 首先开发一款微信小程序,最主要的就是针对于公司来运营的,因为,在申请appid(微信小程序ID号)时候,需要填写相关的公司 ...

  2. docker for mac 学习记录

    docker基本命令 docker run -d -p 80:80 --name webserver nginx 运行容器并起别名 docker ps 展示目前启动的容器 docker ps -a 展 ...

  3. Photoshop将普通照片快速制作二次元漫画风格效果

    今天为大家分享Photoshop将普通照片快速制作二次元漫画风格效果,教程很不错,对于喜欢漫画的朋友可以参考本文,希望能对大家有所帮助! 一提到日本动画电影,大家第一印象肯定是宫崎骏,但是日本除了宫崎 ...

  4. H5程序员如何利用cordova开发跨平台应用

    什么是Cordova? Cordova以前也叫PhoneGap,它提供了一组设备相关的API,通过这组API,移动应用能够以JavaScript访问原生的设备功能,如摄像头.麦克风等.Cordova还 ...

  5. pdo的使用

    PHP 数据对象 (PDO) 扩展为PHP访问数据库定义了一个轻量级的一致接口. PDO 提供了一个数据访问抽象层,这意味着,不管使用哪种数据库,都可以用相同的函数(方法)来查询和获取数据. PDO随 ...

  6. sql的那些事(一)

    一.概述 书写sql是我们程序猿在开发中必不可少的技能,优秀的sql语句,执行起来吊炸天,性能杠杠的.差劲的sql,不仅使查询效率降低,维护起来也十分不便.一切都是为了性能,一切都是为了业务,你觉得你 ...

  7. 封装集合(Encapsulate Collection)

    封装就是将相关的方法或者属性抽象成为一个对象. 封装的意义: 对外隐藏内部实现,接口不变,内部实现自由修改. 只返回需要的数据和方法. 提供一种方式防止数据被修改. 更好的代码复用. 当一个类的属性类 ...

  8. 装饰者模式 Decoration

    1.什么是装饰者模式 动态给对象增加功能,从一个对象的外部来给对象添加功能,相当于改变了对象的外观,比用继承的方式更加的灵活.当使用装饰后,从外部系统的角度看,就不再是原来的那个对象了,而是使用一系列 ...

  9. BPM流程中心解决方案分享

    一.需求分析 在过去办公自动化的浪潮中,很多企业已经实施了OA流程,但随着客户的发展和对流程管理的越来越重视, 客户对流程应用需求越来越深 入,您可能面临以下需求: 1.流程功能不能满足需求,包括流程 ...

  10. BZOJ 1391: [Ceoi2008]order [最小割]

    1391: [Ceoi2008]order Time Limit: 10 Sec  Memory Limit: 64 MBSubmit: 1509  Solved: 460[Submit][Statu ...