ch09-语义分析-符号表

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

第几行进行的声明和定义,在第几行被用到过
符号表实现的难点:与作用域相关
“领域特定语言” (DSL) 通常只有单作用域 (全局作用域)
“通用程序设计语言” (GPL) 通常需要嵌套作用域

- 比如c、java
- 图中一种颜色就是一种作用域
- 一般把嵌套的作用域当做一个数据结构,黄色的作用域包含了灰色的作用域,使用树形结构表达嵌套关系
- 符号表:接下来就要每个作用域有一个符号表,要维护一个符号表之间的树形结构
作用域
全局作用域
- 
x,y两个变量,定义在全局,是全局作用域 
- 
a,b函数也在全局作用域 
局部作用域
以( 或者 { 开头,)或者 } 结束的作用域
- 
3行就是局部作用域 
- 
4行也是局部作用域,嵌套在局部作用域里 
- 
第6行一对{}括起来的也是局部作用域 
函数作用域
将错就错

画图
上面是名字
下面是这个作用域新定义的符号

- 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数据类型:接口
| 12
 3
 4
 
 | package symtable;
 public interface Type {
 }
 
 | 
2、Symbol符号:接口:变量和函数名
| 12
 3
 4
 5
 6
 
 | package symtable;
 public interface Symbol {
 public String getName();
 }
 
 
 | 
3、Scope作用域:接口
scope第二个方法是为了构建树状结构
全局、局部、函数作用域
每个作用域都维护自己的符号表,从符号的名字映射到符号本身
一个指针指向父作用域,来形成一个树,getEnclosingScope()得到这个指针
| 12
 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);
 
 
 public Symbol resolve(String name);
 
 
 
 
 }
 
 | 
3.1、BaseScope类:实现了Scope接口
| 12
 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;
 
 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) {
 
 
 this.symbols.put(symbol.getName(), symbol);
 
 System.out.println("+ " + symbol.getName());
 }
 
 @Override
 
 public Symbol resolve(String name) {
 
 
 Symbol symbol = symbols.get(name);
 
 if (symbol != null) {
 System.out.println("*" + symbol.getName());
 return symbol;
 }
 
 if (enclosingScope != null) {
 return enclosingScope.resolve(name);
 }
 
 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接口
符号有名字和类型
| 12
 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接口
| 12
 3
 4
 5
 6
 7
 8
 
 | package symtable;
 public class FunctionSymbol extends BaseScope implements Symbol {
 public FunctionSymbol(String name, Scope enclosingScope) {
 
 super(name, enclosingScope);
 }
 }
 
 | 
3.2、GlobalScope.java
GlobalScope中把int也放进去了,是因为语言中内置的类型符号(如int)全局可见
| 12
 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
| 12
 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既是符号也是类型,名字就表名类型
| 12
 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
	变量符号,有类型有名称
| 12
 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
| 12
 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;
 
 private Scope currentScope = null;
 
 private int localScopeCounter = 0;
 
 @Override
 public void enterProg(CymbolParser.ProgContext ctx) {
 
 globalScope = new GlobalScope(null);
 
 currentScope = globalScope;
 }
 
 @Override
 public void exitProg(CymbolParser.ProgContext ctx) {
 
 
 
 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) {
 
 
 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();
 
 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);
 
 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
| 12
 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 {
 
 private final Set<String> nodes = new LinkedHashSet<>();
 
 
 private final Map<String, String> edges = new LinkedHashMap<>();
 
 public static String toDot(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() {
 
 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");
 
 buf.append(String.join(";\n", nodes)).append(";\n\n");
 
 buf.append(edges.entrySet().stream()
 .map(edge -> String.format("%s -> %s", edge.getKey(), edge.getValue()))
 .collect(Collectors.joining(";\n", "", ";\n"))).append("}");
 
 return buf.toString();
 }
 }
 
 |