项目的完整代码在 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,

  1. TokenType就是非终结符的类型
  2. Text用来存储解析对象的文本信息
  3. Symbol对应的就是变量的符号对象
  4. 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);}@Overridepublic AstNode addChild(AstNode node) {if (node != null) {children.add(node);((AstNodeImpl) node).parent = this;}return node;}@Overridepublic AstNode getParent() {return parent;}@Overridepublic void reverseChildren() {if (isChildrenReverse) {return;}Collections.reverse(children);isChildrenReverse = true;}@Overridepublic boolean isChildrenReverse() {return isChildrenReverse;}@Overridepublic ArrayList<AstNode> getChildren() {reverseChildren();return children;}@Overridepublic void setAttribute(NodeKey key, Object value) {if (key == NodeKey.TEXT) {name = (String) value;}put(key, value);}@Overridepublic Object getAttribute(NodeKey key) {return get(key);}@Overridepublic 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();}@Overridepublic 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/

转载于:https://www.cnblogs.com/secoding/p/11379216.html

从零写一个编译器(九):语义分析之构造抽象语法树(AST)相关推荐

  1. 从零写一个编译器(十三):代码生成之遍历AST

    项目的完整代码在 C2j-Compiler 前言 在上一篇完成对JVM指令的生成,下面就可以真正进入代码生成部分了.通常现代编译器都是先把生成IR,再经过代码优化等等,最后才编译成目标平台代码.但是时 ...

  2. 从零写一个编译器(完结):总结和系列索引

    前言 这个系列算作我自己的学习笔记,到现在已经有十三篇了,加上这篇一共十四篇.一步一步的从词法分析到语法分析.语义分析,再到代码生成,准备在这一篇做一个总结收尾和一个这个系列以前文章的索引. (另外, ...

  3. 从零写一个编译器(十):编译前传之直接解释执行

    项目的完整代码在 C2j-Compiler 前言 这一篇不看也不会影响后面代码生成部分 现在经过词法分析语法分析语义分析,终于可以进入最核心的部分了.前面那部分可以称作编译器的前端,代码生成代码优化都 ...

  4. 从零写一个编译器(一):输入系统和词法分析

    项目的完整代码在 C2j-Compiler 前言 从半抄半改的完成一个把C语言编译到Java字节码到现在也有些时间,一直想写一个系列来回顾整理一下写一个编译器的过程,也算是学习笔记吧.就从今天开始动笔 ...

  5. 从零写一个编译器(三):语法分析之几个基础数据结构

    项目的完整代码在 C2j-Compiler 写在前面 这个系列算作为我自己在学习写一个编译器的过程的一些记录,算法之类的都没有记录原理性的东西,想知道原理的在龙书里都写得非常清楚,但是我自己一开始是不 ...

  6. 从零写一个编译器(二):语法分析之前置知识

    项目的完整代码在 C2j-Compiler 前言 在之前完成了词法分析之后,得到了Token流,那么接下来就是实现语法分析器来输入Token流得到抽象语法树 (Abstract Syntax Tree ...

  7. 抽象语法树 c语言,一个简单的例子看懂抽象语法树的魔力

    在计算机科学中,抽象语法树(Abstract Syntax Tree,AST),或简称语法树(Syntax tree),是源代码语法结构的一种抽象表示.它以树状的形式表现编程语言的语法结构,树上的每个 ...

  8. 从零写一个编译器(五):语法分析之自动机的缺陷和改进

    项目的完整代码在 C2j-Compiler 前言 在上一篇,已经成功的构建了有限状态自动机,但是这个自动机还存在两个问题: 无法处理shift/reduce矛盾 状态节点太多,导致自动机过大,效率较低 ...

  9. 从零写一个编译器(七):语义分析之符号表的数据结构

    项目的完整代码在 C2j-Compiler 前言 有关符号表的文件都在symboltable包里 前面我们通过完成一个LALR(1)有限状态自动机和一个reduce信息来构建了一个语法解析表,正式完成 ...

最新文章

  1. airtest web 录制滑块_Airtest 录屏总结,这不是一个简单的 recording!
  2. UA MATH566 用Basu定理证明统计量不完备
  3. 手握价值70万录用书的程序员提离职,领导:你已升职成功,还走?
  4. ORA-02287: sequence number not allowed here
  5. python生成器和迭代器区别_生成器、迭代器的区别?
  6. 2、IDEA以新窗口的形式打开多个项目
  7. 【OpenCV 例程200篇】91. 高斯噪声、瑞利噪声、爱尔兰噪声
  8. sublime3 常用功能总结
  9. token会被截取吗_做抖音搬运其他网站视频可以吗?
  10. 数学建模(NO.10 典型相关分析)
  11. RabbitMQ之交换机总结(图文并茂讲解)
  12. 马哥linux架构班笔记,51CTO博客-专业IT技术博客创作平台-技术成就梦想
  13. (转载)基于联盟链区块链的九个方面对比
  14. python笔记2 - 函数,表达式,语句
  15. 图灵奖得主Bengio:深度学习不会被取代,我想让AI会推理、计划和想象
  16. 接口测试——postman文件夹介绍以及批量执行用例
  17. JavaScript中递归函数
  18. 蓝鲸智云-腾讯给广大运维工作者的福利
  19. 【UnityShader】光线追踪体积光
  20. 新版汇编语言程序设计【课后习题答案】

热门文章

  1. 【03】把 Elasticsearch 当数据库使:简单指标
  2. 菲波拉契数列(传统兔子问题)
  3. Linux内核启动流程分析(一)【转】
  4. 微软 .net 你更新这么快IDE vs2015 、语法糖 6.0、framework、‘吹得这么牛,然并用...
  5. 网络攻防第十一周作业
  6. Maven 项目的 org.junit.Test 获取不到(转载)
  7. v-bind单向绑定与v-model双向绑定
  8. DataGrip按某列大小对数据集进行排序
  9. 【机器学习】LR与最大熵模型的关系
  10. 工作单元php,PHP面向对象中的工作单元是什么