编译原理ch09-语义分析-符号表

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();

}
}


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值