前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >C++|编译器|语义分析-符号表

C++|编译器|语义分析-符号表

作者头像
朝闻君
发布2021-11-22 11:27:46
9650
发布2021-11-22 11:27:46
举报

Reference: Compilers in IPADS SE SJTU + Tiger book

通过词法分析和语法分析,我们可以将程序转换为一棵抽象语法树,根节点为statement,并递归子节点为statement或者expression,叶节点为terminal(如'A')。然而,我们并不仅仅需要语法本身,同时要考虑语法的实际含义。编译器进入语义分析阶段。

语义分析- 将变量的定义与各个使用联系起来,type check,并且将抽象语法转换为更简单的适合生成机器代码表示。

符号表

符号表是由一组绑定组成的集合(又称环境environment),例如{g->string,a->int},每一个绑定都具有一定的作用域。新增的绑定会覆盖先前的绑定。(这个很容易实现,只要你新增的绑定先于旧的被找到就行)

为了实现符号表的改变,存在两种风格

函数式风格- 在每次符号表改变时,并不改变原符号表,保持数据unmutable。

命令式风格- 共用一个environment,符号表改变会破坏性更新原符号表,但是提供给一个撤销栈(存储撤销破坏性更新的信息). 环境中添加符号时,同时也会加入撤销栈中,在作用域结束点,撤销栈弹出符号并且删除绑定,恢复到之前的符号表。

在某些语言中,可以同时存在多个活跃的环境,module/class/record各自拥有自己的符号表

命令式风格符号表实现

需求1:查找迅速- hash

需求2: 易撤销

open hashing: 拉链法(我们选择链表,容易实现作用域的删除)

closed hashing: 开放寻址法(数组中你玩delete不是在作死么, 舍弃)

新增的binding加入链表头,如果要撤销,删除第一个binding就行。

HINT

代码语言:javascript
复制
void pop(string key)
 {
 int index = hash(key) % SIZE ;
 table[index] = table[index]->next;
 }

我们可能会奇怪,上面的代码为什么直接删除index处的首节点而不是第一个找到的key节点。难道不会出现我pop("a")而实际上pop了一个key为“c”的节点么。 因为有了撤销栈作为辅助,我们在撤销时,pop的key必定是该索引处最后加入的key,因此首节点也就是第一个找到的key节点。

函数式风格符号表实现

原先的表不变,新增的部分链接到之前的表的首节点(避免拷贝之前的表的开销)

由于所有数据都是unmutable的,可以放心地进行引用

我们还可以使用二分查找树加速查找,这样的话,如果新增的binding深度为d,那么我们需要复制向上的d个节点,但不需要复制整棵树(logn),插入和查找开销相同。

在新表中插入Mouse的binding

撤销栈

除了hash自带的链表之外,另外存在着一个链表top处理作用域。下文binder_的prevtop就是按照插入顺序对于所有节点进行了链接。沿着prevtop遍历我们可以得到FILO的撤销栈。

代码语言:javascript
复制
struct binder_ {
 void *key; void *value;  /* a binding */
 void *prevtop; /* to implement a stack */
 binder next;  /* imperative style */
};
struct TAB_table_ {
	binder table[TABSIZE]; 
	void *top; 	
};

在TAB表中我们通过top指针可以获取撤销栈的栈顶。我们用ADT S_table进行一些接口的封装。

当新的作用域开始时,不会插入binder,而是插入一个特殊的marker哨兵。

当作用域结束时,沿着top不断进行pop,直到pop最顶端的哨兵。这样一来整个作用域中新增的符号就全部被撤销了。

代码语言:javascript
复制
void S_enter(S_table t, S_symbol sym, void* value) {
 TAB_enter(t, sym, value); 
}
void S_beginScope(S_table t) {
 S_enter(t, &marksym, NULL); 
}
void S_endScope(S_table t) {
S_symbl s;
do s=TAB_pop(t); while (s != &marksym);
}

type environment//value environment

在程序中,绑定的不仅是类型,还有值。因此作用域中同时存在两个S_table。类型中通过使用<field,type*>指针,可以实现嵌套的类型,例如结构体和类。

需要注意的是,在程序中写的type本身就是symbol,而不是实际的type,因此我们需要在venv中先获取typename(如果一个变量的类型是type,例如typedef?),再根据typename在t_env获取对应的实际的type。(这段有点不太清晰,得复习下)

base environment

初始化built-in环境

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 符号表
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档