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数据类型:接口
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); 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;
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接口
符号有名字和类型
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(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; 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
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 { 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(); } }
|