本章学习一种新的作用域,叫做数据聚合作用域(data aggregate scope),和其他作用域一样包含符号,并在scope tree里面占据一个位置。

区别在于:作用域之外的代码能够通过一种特殊的表达式user.name来访问数据成员;以下两个模式分别描述非面向对象语言和面向对象语言的数据聚合作用域。

  • Pattern 18, Symbol Table for Data Aggregates,描述了如何定义和访问简单的数据聚合,比如C struct;
  • Pattern 19, Symbol Table for Classes, 描述了如何处理拥有基类、包含方法的数据聚合。

struct和class非常相似,都是一个符号,自定义类型,一个作用域;最大的区别在于class有一个superclass,相当于一个外围嵌套作用域。、

struct scope

先看一个struct的例子:

// start of global scope
struct A {
int x;
struct B { int y; };
B b;
struct C { int z; };
C c;
};
A a; void f()
{
struct D {
int i;
};
D d;
d.i = a.b.y;
}

对应的scope tree如下:

stuct定义了一个scope,该scope的外围scope就是定义所处的scope;在struct内部,我们像之前一样寻找符号,因此int符号最终引用的是全局的int类型;对于像expr.x这样的表达式,要确定expr的类型,然后在类型对应的scope里面寻找x。使问题变得复杂的地方在于,表达式expr.x在寻找x的时候,只能严格地在对应类型的scope里面寻找,而不能像普通名字一样,继续在外围scope寻找。

class scope

class可以有一个superclass,这就使得class可能有两个父scope,一个是通常的外围嵌套的scope,一个是superclass对应的scope。在做符号引用的时候,追随哪个父scope都有可能。

// start of global scope
class A {
public:
int x;
void foo()
{ ; }
};
class B : public A {
int y;
void foo()
{
int z = x + y;
}
};

上面类定义对应的scope tree如下:



从Scope B指向Scope A的箭头是横向的,这里表达的意思是,ClassB和ClassA的scope处在同一层级。在成员函数内部,面向对象语言一般会优先使用superClass这条路径来搜寻符号。

前置引用

class里面允许提前引用一个名字定义:

class A {
void foo() { x = 3; } // forward reference to field x
int x;
};

解决这个问题,可以使用多轮次处理AST的方式,第一轮只进行符号定义,第二轮再处理符号引用。

但是又会引入另一个问题,有时候前置引用一个名字是非法的,比如以下的C++代码:

// globals
int main() {
x = y; // shouldn't see global x or local y; ERROR!
int y;
}
int x;

解决这个问题的方法是引入token index的概念,在引用local和global scope的场合,如果引用处的token index小于定义处的token index,那么该引用是非法的。

Pattern 18, Symbol Table for Data Aggregates

关于如何实现简单的struct scope,还是用类似上一章的表格来表示,仅添加几项如下:

Upon Actions
struct declaration S def S as a StructSymbol object in the current scope and push it as the current scope.
Member access «expr».x Compute the type of «expr» using the previous rule and this one recursively. Ref x only in that type’s scope, not in any enclosing scopes.

Pattern 19, Symbol Table for Classes

上面说过需要多轮次的AST访问才能构建好Class的scope tree;AST node里面记录对应的scope信息以便下一轮访问。

为了看清楚这种联系,看一段简单的代码:

int main() {
int x;
x = 3;
}

最终的scope tree和AST如下图所示,省略了中间步骤:



x对应的VariableSymbol包含了一个指向定义AST node的指针;x的引用节点和定义节点都指向了同一个Symbol。

Class的scope有两个可能的父scope,因此在scope的类里面定义了两个方法:

/** Where to look next for symbols; superclass or enclosing scope */
public Scope getParentScope();
/** Scope in which this scope defined. For global scope, it's null */
public Scope getEnclosingScope();

对于非Class的scope来说,getParentScope返回的就是EnclosingScope;对于Class的scope来说,getParentScope默认返回superClassScope,如果没有则返回EnclosingScope。

Class的scope tree构造需要两轮AST遍历,第一轮构造出struct的Scope Tree结构:

Upon Actions
Class declaration C 在当前scope构造ClassSymbol,sym,并push sym入栈成为新的current scope,sym的def字段指向对应类名ID的AST节点,ID AST节点的symbol字段指向sym;sym的superclasss scope指向自身

第二轮将superclass scope指针设置好:

Upon Actions
Class declaration C 设t为C的superclasss的ID AST节点. 引用t所定义的scope, 得到sym. 设置t.symbol=sym. 设置C对应scope的superclass指针 =sym.

在class的方法内部访问符号x的规则如下:先在class scope里面寻找,然后顺着superClass链寻找,最后在global scope里面寻找。

通过《expr》.x的方式访问名字x的规则:先在class scope里面寻找,然后顺着superClass链寻找。

具体的实现代码,请参考原书。

《Language Implementation Patterns》之 数据聚合符号表的更多相关文章

  1. 《Language Implementation Patterns》之 符号表

    前面的章节我们学会了如何解析语言.构建AST,如何访问重写AST,有了这些基础,我们可以开始进行"语义分析"了. 在分析语义的一个基本方面是要追踪"符号",符号 ...

  2. 《Language Implementation Patterns》之 解释器

    前面讲述了如何验证语句,这章讲述如何构建一个解释器来执行语句,解释器有两种,高级解释器直接执行语句源码或AST这样的中间结构,低级解释器执行执行字节码(更接近机器指令的形式). 高级解释器比较适合DS ...

  3. 《Language Implementation Patterns》之 构建语法树

    如果要解释执行或转换一段语言,那么就无法在识别语法规则的同时达到目标,只有那些简单的,比如将wiki markup转换成html的功能,可以通过一遍解析来完成,这种应用叫做 syntax-direct ...

  4. 《Language Implementation Patterns》之 强类型规则

    语句的语义取决于其语法结构和相关符号:前者说明了了要"做什么",后者说明了操作"什么对象".所以即使语法结构正确的,如果被操作的对象不合法,语句也是不合法的.语 ...

  5. 《Language Implementation Patterns》之 增强解析模式

    上一章节讲述了基本的语言解析模式,LL(k)足以应付大多数的任务,但是对一些复杂的语言仍然显得不足,已付出更多的复杂度.和运行时效率为代价,我们可以得到能力更强的Parser. Pattern 5 : ...

  6. 《Language Implementation Patterns》之访问&重写语法树

    每个编程的人都学习过树遍历算法,但是AST的遍历并不是开始想象的那么简单.有几个因素会影响遍历算法:1)是否拥有节点的源码:2)是否子节点的访问方式是统一的:3)ast是homogeneous或het ...

  7. 《Language Implementation Patterns》之 语言翻译器

    语言翻译器可以从一种计算机语言翻译成另外一种语言,比如一种DSL的标量乘法axb翻译成java就变成a*b:如果DSL里面有矩阵运算,就需要翻译成for循环.翻译器需要完全理解输入语言的所有结构,并选 ...

  8. Django 08 Django模型基础3(关系表的数据操作、表关联对象的访问、多表查询、聚合、分组、F、Q查询)

    Django 08 Django模型基础3(关系表的数据操作.表关联对象的访问.多表查询.聚合.分组.F.Q查询) 一.关系表的数据操作 #为了能方便学习,我们进入项目的idle中去执行我们的操作,通 ...

  9. iOS 符号表恢复 & 逆向支付宝

    推荐序 本文介绍了恢复符号表的技巧,并且利用该技巧实现了在 Xcode 中对目标程序下符号断点调试,该技巧可以显著地减少逆向分析时间.在文章的最后,作者以支付宝为例,展示出通过在 UIAlertVie ...

随机推荐

  1. 转:SQL进阶之变量、事务、存储过程与触发器

    一.变量那点事儿 1.1 局部变量 (1)声明局部变量 DECLARE @变量名 数据类型 DECLARE @name varchar(20) DECLARE @id int (2)为变量赋值 SET ...

  2. OSS Android SDK

    Android SDK开发包(2014-12-20)  版本号0.2.2   开发包下载地址: http://oss.aliyuncs.com/aliyun_portal_storage/help/o ...

  3. es6学习笔记--解构赋值

    昨天学习了es6语法中解构赋值,解构赋值在声明中和函数传参提高了灵活性和便捷性,值得掌握该语法. 概念: ES6 允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构.   数组的解构 ...

  4. 浅析Java 8新特性Method Reference

    什么是方法引用 我们知道了什么是Lambda Expression以及如何使用,那么,Method References又是什么呢?Oracle Java Docs中这样说: They are com ...

  5. 【Luogu1291】百事世界杯之旅(动态规划,数学期望)

    [Luogu1291]百事世界杯之旅(动态规划,数学期望) 题面 洛谷 题解 设\(f[i]\)表示已经集齐了\(i\)个名字的期望 现在有两种方法: 先说我自己的: \[f[i]=f[i-1]+1+ ...

  6. java支付宝开发-02-手机网站支付

    源码已上传github,欢迎专注:https://github.com/shirayner/alipay-wap 一.基础部分 1.手机网站支付产品介绍 1.1 阅读官方介绍: 手机网站支付产品介绍 ...

  7. IT连创业系列:新的一年,先淫文一篇!

    办公室窗外,有鸟声〜〜 在IT连创业走过的日子里,这是我第一次听见鸟声. 也许,是曾经的忙碌,封锁了自己的心眼. 岁月秒秒: 当初燃烧的火焰,从红,烧成了蓝. 曾经的内心湃澎,化成了平淡坚持. 但这, ...

  8. 初探WebSocket

    初探WebSocket node websocket socket.io 我们平常开发的大部分web页面都是主动'拉'的形式,如果需要更新页面内容,则需要"刷新"一个,但Slack ...

  9. JDK1.8源码(二)——java.lang.Integer 类

    上一篇博客我们介绍了 java.lang 包下的 Object 类,那么本篇博客接着介绍该包下的另一个类 Integer.在前面 浅谈 Integer 类 博客中我们主要介绍了 Integer 类 和 ...

  10. 如何在已安装Python条件下,安装Anaconda,,并将原有Python添加到Anaconda中

    在安装Anaconda之前,有的已经安装过一个Python版本了,但是又不想删除这个Python版本,该怎么办呢? 概括:轻松两步--在系统环境变量中找到对应之前安装Python的路径并删除:直接将你 ...