从零写一个编译器(九):语义分析之构造抽象语法树(AST)
项目的完整代码在 C2j-Compiler
前言
在上一篇完成了符号表的构建,下一步就是输出抽象语法树(Abstract Syntax Tree,AST)
抽象语法树(abstract syntax tree 或者缩写为 AST),是源代码的抽象语法结构的树状表现形式,这里特指编程语言的源代码。树上的每个节点都表示源代码中的一种结构。
AST对于编译器是至关重要的,现在的编译型语言一般通过AST来生成IR,解释型语言也可以不用虚拟机而直接遍历AST来解释执行,之后要写解释器和编译器都依赖这个AST
这一篇主要文件有:
- AstBuilder.java
- AstNode.java
- AstNodeImpl.java
- NodeKey.java
- NodeFactory.java
主要数据结构
AST节点的表示
public interface AstNode {
AstNode addChild(AstNode node);
AstNode getParent();
ArrayList<AstNode> getChildren();
void setAttribute(NodeKey key, Object value);
Object getAttribute(NodeKey key);
boolean isChildrenReverse();
void reverseChildren();
AstNode copy();
}
这是对AstNode接口的实现,并且继承HashMap,这里的NodeKey是
TokenType, VALUE, SYMBOL, PRODUCTION, TEXT
对应的value,
- TokenType就是非终结符的类型
- Text用来存储解析对象的文本信息
- Symbol对应的就是变量的符号对象
- Value是对应对象解析的值,比如int a = 1,那么value的值就为1
public class AstNodeImpl extends HashMap<NodeKey, Object> implements AstNode {
private Token type;
private AstNodeImpl parent;
private ArrayList<AstNode> children;
String name;
private boolean isChildrenReverse = false;
public AstNodeImpl(Token type) {
this.type = type;
this.parent = null;
this.children = new ArrayList<>();
setAttribute(NodeKey.TokenType, type);
}
@Override
public AstNode addChild(AstNode node) {
if (node != null) {
children.add(node);
((AstNodeImpl) node).parent = this;
}
return node;
}
@Override
public AstNode getParent() {
return parent;
}
@Override
public void reverseChildren() {
if (isChildrenReverse) {
return;
}
Collections.reverse(children);
isChildrenReverse = true;
}
@Override
public boolean isChildrenReverse() {
return isChildrenReverse;
}
@Override
public ArrayList<AstNode> getChildren() {
reverseChildren();
return children;
}
@Override
public void setAttribute(NodeKey key, Object value) {
if (key == NodeKey.TEXT) {
name = (String) value;
}
put(key, value);
}
@Override
public Object getAttribute(NodeKey key) {
return get(key);
}
@Override
public String toString() {
String info = "";
if (get(NodeKey.VALUE) != null) {
info += "Node Value is " + get(NodeKey.VALUE).toString();
}
if (get(NodeKey.TEXT) != null) {
info += "\nNode Text is " + get(NodeKey.TEXT).toString();
}
if (get(NodeKey.SYMBOL) != null) {
info += "\nNode Symbol is " + get(NodeKey.SYMBOL).toString();
}
return info + "\n Node Type is " + type.toString();
}
@Override
public AstNode copy() {
AstNodeImpl copy = (AstNodeImpl) NodeFactory.createICodeNode(type);
Set<Entry<NodeKey, Object>> attributes = entrySet();
Iterator<Map.Entry<NodeKey, Object>> it = attributes.iterator();
while (it.hasNext()) {
Map.Entry<NodeKey, Object> attribute = it.next();
copy.put(attribute.getKey(), attribute.getValue());
}
return copy;
}
}
NodeFactory
NodeFactory就是简单的返回一个节点的实现
public class NodeFactory {
public static AstNode createICodeNode(Token type) {
return new AstNodeImpl(type);
}
}
构造AST
AST的创建也是需要在语法分析过程中根据reduce操作进行操作的。也就是在takeActionForReduce方法中调用AstBuilder的buildSyntaxTree方法
在AstBuilder里面还是需要两个堆栈来辅助操作
private Stack<AstNode> nodeStack = new Stack<>();
private LRStateTableParser parser = null;
private TypeSystem typeSystem = null;
private Stack<Object> valueStack = null;
private String functionName;
private HashMap<String, AstNode> funcMap = new HashMap<>();
private static AstBuilder instance;
构造AST的主要逻辑在buildSyntaxTree方法里,需要注意的是有一些节点在解释执行和代码生成的时候是不一样的,有时代码生成需要的节点解释执行的话并不需要
在这里提一下UNARY这个非终结符,这个非终结符和NAME很像,但是它一般是代表进行运算和一些操作的时候,比如数组,++,--或者函数调用的时候
其实构建AST的过程和符号表的构建过程有点儿类似,都是根据reduce操作来创建信息和组合信息,符号表是组合修饰符说明符等,而AST则是组合节点间的关系变成一棵树
我们只看几个操作
Specifiers_DeclList_Semi_TO_Def
这个节点需要注意的是,从堆栈的什么地方拿到Symbol,这个需要从reduce次数和推导式中得出
* DEF -> SPECIFIERS DECL_LIST SEMI
* DECL -> VAR_DECL
* VAR_DECL -> NEW_NAME
* | VAR_DECL LP RP
* | VAR_DECL LP VAR_LIST RP
* | LP VAR_DECL RP
* | START VAR_DECL
从推导式可以看出,DEF节点的符号应该在valueStack.size() - 3,但是DECL和VAR_DECL没有做reduce操作,所以符号应该在valueStack.size() - 2。这其实和前面的符号表构建算出之前符号的位置是一样的。
TO_UNARY
这里则是变量、数字或者字符串的节点,如果是个变量的号,这个节点就需要一个Symbol的value了
case SyntaxProductionInit.Number_TO_Unary:
case SyntaxProductionInit.Name_TO_Unary:
case SyntaxProductionInit.String_TO_Unary:
node = NodeFactory.createICodeNode(Token.UNARY);
if (production == SyntaxProductionInit.Name_TO_Unary) {
assignSymbolToNode(node, text);
}
node.setAttribute(NodeKey.TEXT, text);
break;
其余的节点无非是把一些语句拆分它的逻辑然后组成节点,真正的求值部分像Name_TO_Unary比较少,更多是比如把一个if else块分成if节点、判断节点、else节点,之后再按照这棵树进行解释执行或者代码生成
public AstNode buildSyntaxTree(int production, String text) {
AstNode node = null;
Symbol symbol = null;
AstNode child = null;
if (Start.STARTTYPE == Start.INTERPRETER) {
int p1 = SyntaxProductionInit.Specifiers_DeclList_Semi_TO_Def;
int p2 = SyntaxProductionInit.Def_To_DefList;
int p3 = SyntaxProductionInit.DefList_Def_TO_DefList;
boolean isReturn = production == p1 || production == p2 || production == p3;
if (isReturn) {
return null;
}
}
switch (production) {
case SyntaxProductionInit.Specifiers_DeclList_Semi_TO_Def:
node = NodeFactory.createICodeNode(Token.DEF);
symbol = (Symbol) valueStack.get(valueStack.size() - 2);
node.setAttribute(NodeKey.SYMBOL, symbol);
break;
case SyntaxProductionInit.Def_To_DefList:
node = NodeFactory.createICodeNode(Token.DEF_LIST);
node.addChild(nodeStack.pop());
break;
case SyntaxProductionInit.DefList_Def_TO_DefList:
node = NodeFactory.createICodeNode(Token.DEF_LIST);
node.addChild(nodeStack.pop());
node.addChild(nodeStack.pop());
break;
case SyntaxProductionInit.Number_TO_Unary:
case SyntaxProductionInit.Name_TO_Unary:
case SyntaxProductionInit.String_TO_Unary:
node = NodeFactory.createICodeNode(Token.UNARY);
if (production == SyntaxProductionInit.Name_TO_Unary) {
assignSymbolToNode(node, text);
}
node.setAttribute(NodeKey.TEXT, text);
break;
case SyntaxProductionInit.Unary_LP_RP_TO_Unary:
node = NodeFactory.createICodeNode(Token.UNARY);
node.addChild(nodeStack.pop());
break;
case SyntaxProductionInit.Unary_LP_ARGS_RP_TO_Unary:
node = NodeFactory.createICodeNode(Token.UNARY);
node.addChild(nodeStack.pop());
node.addChild(nodeStack.pop());
break;
case SyntaxProductionInit.Unary_Incop_TO_Unary:
case SyntaxProductionInit.Unary_DecOp_TO_Unary:
case SyntaxProductionInit.LP_Expr_RP_TO_Unary:
case SyntaxProductionInit.Start_Unary_TO_Unary:
node = NodeFactory.createICodeNode(Token.UNARY);
node.addChild(nodeStack.pop());
break;
case SyntaxProductionInit.Unary_LB_Expr_RB_TO_Unary:
node = NodeFactory.createICodeNode(Token.UNARY);
node.addChild(nodeStack.pop());
node.addChild(nodeStack.pop());
break;
case SyntaxProductionInit.Uanry_TO_Binary:
node = NodeFactory.createICodeNode(Token.BINARY);
child = nodeStack.pop();
node.setAttribute(NodeKey.TEXT, child.getAttribute(NodeKey.TEXT));
node.addChild(child);
break;
case SyntaxProductionInit.Binary_TO_NoCommaExpr:
case SyntaxProductionInit.NoCommaExpr_Equal_NoCommaExpr_TO_NoCommaExpr:
node = NodeFactory.createICodeNode(Token.NO_COMMA_EXPR);
child = nodeStack.pop();
String t = (String) child.getAttribute(NodeKey.TEXT);
node.addChild(child);
if (production == SyntaxProductionInit.NoCommaExpr_Equal_NoCommaExpr_TO_NoCommaExpr) {
child = nodeStack.pop();
t = (String) child.getAttribute(NodeKey.TEXT);
node.addChild(child);
}
break;
case SyntaxProductionInit.Binary_Plus_Binary_TO_Binary:
case SyntaxProductionInit.Binary_DivOp_Binary_TO_Binary:
case SyntaxProductionInit.Binary_Minus_Binary_TO_Binary:
case SyntaxProductionInit.Binary_Start_Binary_TO_Binary:
node = NodeFactory.createICodeNode(Token.BINARY);
node.addChild(nodeStack.pop());
node.addChild(nodeStack.pop());
break;
case SyntaxProductionInit.Binary_RelOP_Binary_TO_Binray:
node = NodeFactory.createICodeNode(Token.BINARY);
node.addChild(nodeStack.pop());
AstNode operator = NodeFactory.createICodeNode(Token.RELOP);
operator.setAttribute(NodeKey.TEXT, parser.getRelOperatorText());
node.addChild(operator);
node.addChild(nodeStack.pop());
break;
case SyntaxProductionInit.NoCommaExpr_TO_Expr:
node = NodeFactory.createICodeNode(Token.EXPR);
node.addChild(nodeStack.pop());
break;
case SyntaxProductionInit.Expr_Semi_TO_Statement:
case SyntaxProductionInit.CompountStmt_TO_Statement:
node = NodeFactory.createICodeNode(Token.STATEMENT);
node.addChild(nodeStack.pop());
break;
case SyntaxProductionInit.LocalDefs_TO_Statement:
node = NodeFactory.createICodeNode(Token.STATEMENT);
if (Start.STARTTYPE == Start.CODEGEN) {
node.addChild(nodeStack.pop());
}
break;
case SyntaxProductionInit.Statement_TO_StmtList:
node = NodeFactory.createICodeNode(Token.STMT_LIST);
if (nodeStack.size() > 0) {
node.addChild(nodeStack.pop());
}
break;
case SyntaxProductionInit.FOR_OptExpr_Test_EndOptExpr_Statement_TO_Statement:
node = NodeFactory.createICodeNode(Token.STATEMENT);
node.addChild(nodeStack.pop());
node.addChild(nodeStack.pop());
node.addChild(nodeStack.pop());
node.addChild(nodeStack.pop());
break;
case SyntaxProductionInit.StmtList_Statement_TO_StmtList:
node = NodeFactory.createICodeNode(Token.STMT_LIST);
node.addChild(nodeStack.pop());
node.addChild(nodeStack.pop());
break;
case SyntaxProductionInit.Expr_TO_Test:
node = NodeFactory.createICodeNode(Token.TEST);
node.addChild(nodeStack.pop());
break;
case SyntaxProductionInit.If_Test_Statement_TO_IFStatement:
node = NodeFactory.createICodeNode(Token.IF_STATEMENT);
node.addChild(nodeStack.pop());
node.addChild(nodeStack.pop());
break;
case SyntaxProductionInit.IfElseStatemnt_Else_Statemenet_TO_IfElseStatement:
node = NodeFactory.createICodeNode(Token.IF_ELSE_STATEMENT);
node.addChild(nodeStack.pop());
node.addChild(nodeStack.pop());
break;
case SyntaxProductionInit.While_LP_Test_Rp_TO_Statement:
case SyntaxProductionInit.Do_Statement_While_Test_To_Statement:
node = NodeFactory.createICodeNode(Token.STATEMENT);
node.addChild(nodeStack.pop());
node.addChild(nodeStack.pop());
break;
case SyntaxProductionInit.Expr_Semi_TO_OptExpr:
case SyntaxProductionInit.Semi_TO_OptExpr:
node = NodeFactory.createICodeNode(Token.OPT_EXPR);
if (production == SyntaxProductionInit.Expr_Semi_TO_OptExpr) {
node.addChild(nodeStack.pop());
}
break;
case SyntaxProductionInit.Expr_TO_EndOpt:
node = NodeFactory.createICodeNode(Token.END_OPT_EXPR);
node.addChild(nodeStack.pop());
break;
case SyntaxProductionInit.LocalDefs_StmtList_TO_CompoundStmt:
node = NodeFactory.createICodeNode(Token.COMPOUND_STMT);
node.addChild(nodeStack.pop());
break;
case SyntaxProductionInit.NewName_LP_RP_TO_FunctDecl:
case SyntaxProductionInit.NewName_LP_VarList_RP_TO_FunctDecl:
node = NodeFactory.createICodeNode(Token.FUNCT_DECL);
node.addChild(nodeStack.pop());
child = node.getChildren().get(0);
functionName = (String) child.getAttribute(NodeKey.TEXT);
symbol = assignSymbolToNode(node, functionName);
break;
case SyntaxProductionInit.NewName_TO_VarDecl:
nodeStack.pop();
break;
case SyntaxProductionInit.NAME_TO_NewName:
node = NodeFactory.createICodeNode(Token.NEW_NAME);
node.setAttribute(NodeKey.TEXT, text);
break;
case SyntaxProductionInit.OptSpecifiers_FunctDecl_CompoundStmt_TO_ExtDef:
node = NodeFactory.createICodeNode(Token.EXT_DEF);
node.addChild(nodeStack.pop());
node.addChild(nodeStack.pop());
funcMap.put(functionName, node);
break;
case SyntaxProductionInit.NoCommaExpr_TO_Args:
node = NodeFactory.createICodeNode(Token.ARGS);
node.addChild(nodeStack.pop());
break;
case SyntaxProductionInit.NoCommaExpr_Comma_Args_TO_Args:
node = NodeFactory.createICodeNode(Token.ARGS);
node.addChild(nodeStack.pop());
node.addChild(nodeStack.pop());
break;
case SyntaxProductionInit.Return_Semi_TO_Statement:
node = NodeFactory.createICodeNode(Token.STATEMENT);
break;
case SyntaxProductionInit.Return_Expr_Semi_TO_Statement:
node = NodeFactory.createICodeNode(Token.STATEMENT);
node.addChild(nodeStack.pop());
break;
case SyntaxProductionInit.Unary_StructOP_Name_TO_Unary:
node = NodeFactory.createICodeNode(Token.UNARY);
node.addChild(nodeStack.pop());
node.setAttribute(NodeKey.TEXT, text);
break;
default:
break;
}
if (node != null) {
node.setAttribute(NodeKey.PRODUCTION, production);
nodeStack.push(node);
}
return node;
}
小结
其实构造AST和创建符号表上非常相似,都是依据reduce操作的信息来完成。在AST的构建中的主要任务就是对源代码语句里的逻辑进行分块,比如对于一个ifelse语句:

上面的图是我依据这个意思话的,和上面构造出来的AST不完全一致
另外我的github博客:https://dejavudwh.cn/
从零写一个编译器(九):语义分析之构造抽象语法树(AST)的更多相关文章
- 从零写一个编译器(十三):代码生成之遍历AST
项目的完整代码在 C2j-Compiler 前言 在上一篇完成对JVM指令的生成,下面就可以真正进入代码生成部分了.通常现代编译器都是先把生成IR,再经过代码优化等等,最后才编译成目标平台代码.但是时 ...
- 从零写一个编译器(十一):代码生成之Java字节码基础
项目的完整代码在 C2j-Compiler 前言 第十一篇,终于要进入代码生成部分了,但是但是在此之前,因为我们要做的是C语言到字节码的编译,所以自然要了解一些字节码,但是由于C语言比较简单,所以只需 ...
- 手把手教你从零写一个简单的 VUE
本系列是一个教程,下面贴下目录~1.手把手教你从零写一个简单的 VUE2.手把手教你从零写一个简单的 VUE--模板篇 今天给大家带来的是实现一个简单的类似 VUE 一样的前端框架,VUE 框架现在应 ...
- 手把手教你从零写一个简单的 VUE--模板篇
教程目录1.手把手教你从零写一个简单的 VUE2.手把手教你从零写一个简单的 VUE--模板篇 Hello,我又回来了,上一次的文章教会了大家如何书写一个简单 VUE,里面实现了VUE 的数据驱动视图 ...
- 自己动手写一个编译器Tiny语言解析器实现
然后,上一篇文章简介Tiny词法分析,实现语言.本文将介绍Tiny的语法分析器的实现. 1 Tiny语言的语法 下图是Tiny在BNF中的文法. 文法的定义能够看出.INNY语言有以下特点: 1 程序 ...
- 学了编译原理能否用 Java 写一个编译器或解释器?
16 个回答 默认排序 RednaxelaFX JavaScript.编译原理.编程 等 7 个话题的优秀回答者 282 人赞同了该回答 能.我一开始学编译原理的时候就是用Java写了好多小编译器和 ...
- 从零写一个Asp.net core手脚架(模型验证)
一个asp.net core项目,一定包含了各种的实体,在RESTful api里面,有很多的参数传递,不建立实体则大量的参数需要自定验证正确性,并且Action上面会写的密密麻麻的参数 在asp.n ...
- 从零写一个Asp.net core手脚架 (异常处理)
既然是手脚架,那么肯定得明白,手脚架是有限资源的一个整合,我们尽可能完善它,并保留可扩展性才是最终目的,尽可能减少硬编码,让业务不满足的情况下,可以自行修改 我们把解决方案取名Asp.netCoreT ...
- AQL Subset Compiler:手把手教你如何写一个完整的编译器
项目地址:https://github.com/laiy/Awesome-Complier. 转载请注明出处. 前言 这是学校里编译原理课程的大作业,此Project十分适合编译原理的学习,让基本不听 ...
随机推荐
- 详细记录登录过程的用户、IP地址、shell命令以及详细操作时间
将下面的代码添加到/etc/profile #history USER_IP=`>/dev/null|awk '{print $NF}'|sed -e 's/[()]//g'` HISTDIR= ...
- 从四个属性的角度来理解C语言的指针也许会更好理解
文章会在文末更新! 关于指针是什么,很多教材已经作出了定义,大多数都会定义为"存放变量内存地址的变量".从这句话中我觉得除了让我知道这个定义有11个字以外,其他就没什么用了.我个人 ...
- CMinpack使用介绍
github: https://github.com/devernay/cminpack 主页: http://devernay.github.io/cminpack/ 使用手册: http://de ...
- springboot快速入门02--Controller编写和测试
02springboot快速入门--Controller编写和测试 1.新建一个HelloController import org.springframework.boot.SpringApplic ...
- Unity游戏神经网络版坦克大战
基于遗传算法优化的神经网络来训练坦克AI是什么体验呢?下面有bulid好的demo. 包括window和安卓两个版本. 这是一个Unity项目. 下载链接:https://pan.baidu.com/ ...
- Excel催化剂开源第37波-音视频文件元数据提取(分辨率,时长,采样率等)
上一篇提到图片元信息Exif的提取,当然还有一类音视频文件,也同样存储着许多宝贵的元数据,那就开源到底呗,虽然自己找寻过程也是蛮艰辛坎坷的,大家看后有收获,只求多多传播下,让前人的工作可以更有价值. ...
- 浏览器和Node 中的Event Loop
前言 js与生俱来的就是单线程无阻塞的脚本语言. 作为单线程语言,js代码执行时都只有一个主线程执行任务. 无阻塞的实现依赖于我们要谈的事件循环.eventloop的规范是真的苦涩难懂,仅仅要理解的话 ...
- python的发展史
python的发展史 1989年,被称为龟叔的Guido在为ABC语言写插件时,产生了写一个简洁又实用的编程语言的想法,并开始着手编写.因为其喜欢Monty Python喜剧团,所以将其命名为pyth ...
- Spring 整合 ibatis
是的,真的是那个不好用的ibatis,不是好用的mybatis. 由于工作需要用到ibatis需要自己搭建环境,遇到了不少的坑,做一下记录. 一.环境配置 Maven JDK1.6 (非常重要,使用S ...
- Linux/UNIX编程:使用C语言实现简单的 ls 命令
刚好把 Linux/UNIX 编程中的文件和IO部分学完了,就想编写个 ls 命令练习一下,本以为很简单,调用个 stat 就完事了,没想到前前后后弄了七八个小时,90%的时间都用在格式化(像 ls ...