ch09-语义分析-符号表
符号表 (Symbol Table)
符号表是用于保存各种信息的数据结构
第几行进行的声明和定义,在第几行被用到过
符号表实现的难点:与作用域相关
“领域特定语言” (DSL) 通常只有单作用域 (全局作用域)
-
DSL: Domain-Specific Language
-
为DSL语言写符号表很简单:使用hashmap即可
“通用程序设计语言” (GPL) 通常需要嵌套作用域
- 比如c、java
- 图中一种颜色就是一种作用域
- 一般把嵌套的作用域当做一个数据结构,黄色的作用域包含了灰色的作用域,使用树形结构表达嵌套关系
- 符号表:接下来就要每个作用域有一个符号表,要维护一个符号表之间的树形结构
作用域
全局作用域
-
x,y两个变量,定义在全局,是全局作用域
-
a,b函数也在全局作用域
局部作用域
以( 或者 { 开头,)或者 } 结束的作用域
-
3行就是局部作用域
-
4行也是局部作用域,嵌套在局部作用域里
-
第6行一对{}括起来的也是局部作用域
函数作用域
将错就错
-
花括号开启的是一个局部作用域
-
int z:c和java中不构成单独作用域(可以通过在{}中再写一个int z看报不报错验证),但是处理起来稍微复杂
-
所以按照书中的错误理解:认为参数单独构成一个作用域:function作用域
画图
上面是名字
下面是这个作用域新定义的符号
- x/y/a/b:全局作用域
- 3、4、6:局部作用域
- 不同作用域y不冲突
- 函数的作用域:函数的参数在c和java中和函数的作用域是同一个,但在现在讲的例子里是在不同的作用域(代码写起来方便)
全局作用域、函数/方法作用域、局部作用域的名字
- 方法名或函数名
- 一个函数带来了两个作用域:参数是一个作用域,函数体是一个作用域
- 例如,对于函数a来说,形参symbols为空,对于函数b,形参symbols为[z]
需要一个指针指向父作用域:当前作用域没有需要被解析的符号,就去父作用域找,沿着指针一直找到根节点
关于Scope:一个Scope里面对应于一张符号表,定义的所有符号都放在Scope中
- define:在scope中定义一些变量需要用到的:(int a),把a放到维护的符号表中
- resolve:解析,(int x = y),解析y之前有没有定义过,包括自己的父作用域、祖父Scope,顺着父节点指针一直往上找,找最近的父Scope有没有定义过y,如果没有,未定义就使用,就报错
代码实现
实现目标
- 一个functionSymbol即是符号Symbol也是开启作用域的Scope
例子的主要机制是Listener
1、Type数据类型:接口
package symtable;
public interface Type {
}
2、Symbol符号:接口:变量和函数名
package symtable;
public interface Symbol {
public String getName();
}
3、Scope作用域:接口
scope第二个方法是为了构建树状结构
全局、局部、函数作用域
每个作用域都维护自己的符号表,从符号的名字映射到符号本身
一个指针指向父作用域,来形成一个树,getEnclosingScope()得到这个指针
package symtable;
import java.util.Map;
public interface Scope {
public String getName();//名称
public void setName(String name);//设置名字
public Scope getEnclosingScope();//外部作用域
public Map<String, Symbol> getSymbols();//拿定义符号
public void define(Symbol symbol);
//在作用域中定义符号
//把新解析到的符号名put到map里
public Symbol resolve(String name);
//根据名称查找、解析
//如果符号在多个作用域中有重名,需要解析到底指的是哪一个
//先在当前作用域找,找不到再去父作用域找
//到了全局作用域中还是找不到,就报错
}
3.1、BaseScope类:实现了Scope接口
public class BaseScope implements Scope {
private final Scope enclosingScope;//父指针
private final Map<String, Symbol> symbols = new LinkedHashMap<>();
private String name;//如果是函数作用域,name就是符号名
public BaseScope(String name, Scope enclosingScope) {
this.name = name;
this.enclosingScope = enclosingScope;
}
@Override
public String getName() {
return this.name;
}
@Override
public void setName(String name) {
this.name = name;
}
@Override
public Scope getEnclosingScope() {
return this.enclosingScope;
}
public Map<String, Symbol> getSymbols() {
return this.symbols;
}
@Override
//在作用域中定义符号
public void define(Symbol symbol) {
// TODO:
//把名字放到Symbol符号表
this.symbols.put(symbol.getName(), symbol);
//输出:加到了符号表
System.out.println("+ " + symbol.getName());
}
@Override
//根据名称查找
public Symbol resolve(String name) {
// TODO:
//先在当前作用域中找
Symbol symbol = symbols.get(name);
//如果找到了,直接返回:正在解析符号
if (symbol != null) {
System.out.println("*" + symbol.getName());
return symbol;
}
//没找到,到父节点递归查找
if (enclosingScope != null) {
return enclosingScope.resolve(name);
}
//全局作用域也没有找到,返回一个null
System.out.println("Cannot find " + name);
return null;
}
@Override
public String toString() {
return MoreObjects.toStringHelper(this)
.add("name", name)
.add("symbols", symbols.values().toString())
.toString();
}
}
2.1、BaseSymbol.java :实现了Symbol接口
符号有名字和类型
package symtable;
import com.google.common.base.MoreObjects;
public class BaseSymbol implements Symbol {
final String name;
final Type type;
public BaseSymbol(String name, Type type) {
this.name = name;
this.type = type;
}
public String getName() {
return name;
}
public Type getType() {
return type;
}
public String toString() {
return MoreObjects.toStringHelper(this)
.add("name", name)
.add("type", type)
.toString();
}
}
2.2(3.1)、FunctionSymbol.java 继承BaseScop、实现Symbol接口
package symtable;
public class FunctionSymbol extends BaseScope implements Symbol {
public FunctionSymbol(String name, Scope enclosingScope) {
//super是调用父类的BaseScope的构造函数
super(name, enclosingScope);
}
}
3.2、GlobalScope.java
GlobalScope中把int也放进去了,是因为语言中内置的类型符号(如int)全局可见
package symtable;
public class GlobalScope extends BaseScope {
public GlobalScope(Scope enclosingScope) {
super("GlobalScope", enclosingScope);
define(new BasicTypeSymbol("int"));
define(new BasicTypeSymbol("double"));
define(new BasicTypeSymbol("void"));
}
}
3.3、LocalScope.java
package symtable;
public class LocalScope extends BaseScope {
public LocalScope(Scope enclosingScope) {
super("LocalScope", enclosingScope);
}
}
1.1(2.1.1) BasicTypeSymbol.java
基本类型符号:本身就是类型,只有名称,int、double、void
int、double既是符号也是类型,名字就表名类型
package symtable;
public class BasicTypeSymbol extends BaseSymbol implements Type {
public BasicTypeSymbol(String name) {
super(name, null);
}
@Override
public String toString() {
return name;
}
}
2.1.2 VariableSymbol
变量符号,有类型有名称
package symtable;
public class VariableSymbol extends BaseSymbol {
public VariableSymbol(String name, Type type) {
super(name, type);
}
}
-
三个enter方法开启三个作用域
-
三个exit退回到上一个作用域
-
graph是要构建的树结构(树状作用域)
-
globalScope根节点
-
currentScope当前作用域
SymbolTableListener.java
- 什么时候开启一个新的作用域、怎么进入:当前指针指向作用域就算进入,enter prog即是全局作用域 ,{}开启局部作用域,所以覆写enter block,函数作用域在函数声明的地方:enter functionDecl
- 什么时候退出一个作用域(当前作用域):把current指针往上指一层,指向父节点
- 我们在作用域里的时候,什么时候定义符号:formalParameter函数形参、varDecl变量声明,已经到了树的最底层,进入时还是退出时都可以
- 解析符号:a = b这种使用变量,需要从当前节点到根节点解析一遍,看有没有定义过,文法上表现为expr中的id(例如stat的赋值),已经到了叶子结点
- 什么时候加入图:作图用,lab3中用不到,enterprog的时候不能加node,因为作用域刚刚创建,符号symbol、变量、函数名都还没有加入,符号表为空。只有当全部解析完,符号表已经填完了,才能加node打印,所以要退出的时候打印
- 什么时候加边:创建了scope,把它挂到书上的时候添加edge
package symtable;
import cymbol.CymbolBaseListener;
import cymbol.CymbolParser;
public class SymbolTableListener extends CymbolBaseListener {
private final SymbolTableTreeGraph graph = new SymbolTableTreeGraph();
private GlobalScope globalScope = null;
//current指针
private Scope currentScope = null;
//局部作用域计数器
private int localScopeCounter = 0;
@Override
public void enterProg(CymbolParser.ProgContext ctx) {
//全局作用域:根节点所以没有父节点
globalScope = new GlobalScope(null);
//进入即更新currentScope
currentScope = globalScope;
}
@Override
public void exitProg(CymbolParser.ProgContext ctx) {
//退出全局作用域
//currentScope = currentScope.getEnclosingScope();
//加入graph的node
graph.addNode(SymbolTableTreeGraph.toDot(currentScope));
}
@Override
public void exitVarDecl(CymbolParser.VarDeclContext ctx) {
//拿到类型名
String typeName = ctx.type().getText();
Type type = (Type) globalScope.resolve(typeName);
//拿到变量名
//符号 = 类型名 + 符号名
String varName = ctx.ID().getText();
VariableSymbol varSymbol = new VariableSymbol(varName, type);
//放入当前作用域的符号表
currentScope.define(varSymbol);
}
@Override
public void exitId(CymbolParser.IdContext ctx) {
//解析ID
//拿到ID名,到当前作用域的符号表中查询
String varName = ctx.ID().getText();
currentScope.resolve(varName);
}
@Override
public void enterFunctionDecl(CymbolParser.FunctionDeclContext ctx) {
//拿到函数名、返回类型名
//还需要解析类型名
String retType = ctx.type().getText();
//解析类型名
globalScope.resolve(retType);
String funcName = ctx.ID().getText();
//当前函数是currentScope内嵌的作用域,所以父节点即为currentScope
FunctionSymbol func = new FunctionSymbol(funcName, currentScope);
//创建完成,把它挂到父节点下
graph.addEdge(funcName, currentScope.getName());
//函数本身也是符号,放到父作用域的符号表中
currentScope.define(func);
//进入,更新
currentScope = func;
}
@Override
public void exitFunctionDecl(CymbolParser.FunctionDeclContext ctx) {
//退出函数作用域
graph.addNode(SymbolTableTreeGraph.toDot(currentScope));
currentScope = currentScope.getEnclosingScope();
}
@Override
public void exitFormalParameter(CymbolParser.FormalParameterContext ctx) {
//和变量声明一样的流程
String typeName = ctx.type().getText();
Type type = (Type) globalScope.resolve(typeName);
String varName = ctx.ID().getText();
VariableSymbol varSymbol = new VariableSymbol(varName, type);
currentScope.define(varSymbol);
}
@Override
public void enterBlock(CymbolParser.BlockContext ctx) {
//局部作用域
LocalScope localScope = new LocalScope(currentScope);
//名字为原来的名字加上一个counter
String localScopeName = localScope.getName() + localScopeCounter;
localScope.setName(localScopeName);
localScopeCounter++;
//挂到父节点下
graph.addEdge(localScopeName, currentScope.getName());
//进入局部作用域
currentScope = localScope;
}
@Override
public void exitBlock(CymbolParser.BlockContext ctx) {
//退出局部作用域
graph.addNode(SymbolTableTreeGraph.toDot(currentScope));
currentScope = currentScope.getEnclosingScope();
}
public SymbolTableTreeGraph getGraph() {
return graph;
}
}
SymbolTableTreeGraph.java
package symtable;
import org.antlr.v4.runtime.misc.MultiMap;
import org.antlr.v4.runtime.misc.OrderedHashSet;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
public class SymbolTableTreeGraph {
//node集合
private final Set<String> nodes = new LinkedHashSet<>();
// edge from child to parent; unnecessary to use MultiMap
//edge边集合
private final Map<String, String> edges = new LinkedHashMap<>();
public static String toDot(Scope scope) {
//把scope中所有符号用表格的形式打印出来
String symbols = scope.getSymbols().values()
.stream()
.map(Symbol::getName)
.collect(Collectors.joining("</TD><TD>", "<TR><TD>", "</TD></TR>"));
return scope.getName() +
" [label = <<TABLE BORDER=\"0\" CELLBORDER=\"1\" CELLSPACING=\"0\">" +
"<TR><TD COLSPAN = \"" + scope.getSymbols().size() + "\">" + scope.getName() + "</TD></TR>" +
symbols +
"</TABLE>>]";
}
public void addNode(String node) {
nodes.add(node);
}
public void addEdge(String child, String parent) {
edges.put(child, parent);
}
public String toDot() {
//node和edge已经添加完毕,打印成dot文件,用graphwith自动作图做出图来
StringBuilder buf = new StringBuilder();
buf.append("digraph G {\n")
.append(" rankdir = BT\n")
.append(" ranksep = 0.25\n")
.append(" edge [arrowsize = 0.5]\n")
.append(" node [shape = none]\n\n");
//把node转化成graphwith能接受的字符串
buf.append(String.join(";\n", nodes)).append(";\n\n");
//把edge转化成graphwith能接受的字符串
buf.append(edges.entrySet().stream()
.map(edge -> String.format("%s -> %s", edge.getKey(), edge.getValue()))
.collect(Collectors.joining(";\n", "", ";\n"))).append("}");
return buf.toString();
}
}
buf.append("digraph G {\n")
.append(" rankdir = BT\n")
.append(" ranksep = 0.25\n")
.append(" edge [arrowsize = 0.5]\n")
.append(" node [shape = none]\n\n");
//把node转化成graphwith能接受的字符串
buf.append(String.join(";\n", nodes)).append(";\n\n");
//把edge转化成graphwith能接受的字符串
buf.append(edges.entrySet().stream()
.map(edge -> String.format("%s -> %s", edge.getKey(), edge.getValue()))
.collect(Collectors.joining(";\n", "", ";\n"))).append("}");
return buf.toString();
}
}