ch09-语义分析-符号表

image-20230419152824467

符号表 (Symbol Table)

符号表是用于保存各种信息的数据结构

image-20230419152922914

第几行进行的声明和定义,在第几行被用到过

符号表实现的难点:与作用域相关

“领域特定语言” (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数据类型:接口

1
2
3
4
package symtable;

public interface Type {
}

2、Symbol符号:接口:变量和函数名

1
2
3
4
5
6
package symtable;

public interface Symbol {
public String getName();
}

3、Scope作用域:接口

scope第二个方法是为了构建树状结构

全局、局部、函数作用域

每个作用域都维护自己的符号表,从符号的名字映射到符号本身

一个指针指向父作用域,来形成一个树,getEnclosingScope()得到这个指针

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
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接口

符号有名字和类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
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接口

1
2
3
4
5
6
7
8
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)全局可见

1
2
3
4
5
6
7
8
9
10
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

1
2
3
4
5
6
7
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既是符号也是类型,名字就表名类型

1
2
3
4
5
6
7
8
9
10
11
12
13
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

​ 变量符号,有类型有名称

1
2
3
4
5
6
7
8
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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
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();
}
}