项目的完整代码在 C2j-Compiler前端
这一篇不看也不会影响后面代码生成部分java
如今通过词法分析语法分析语义分析,终于能够进入最核心的部分了。前面那部分能够称做编译器的前端,代码生成代码优化都是属于编译器后端,现在有关编译器的工做岗位主要都是对后端的研究。固然如今写的这个编译器由于水平有限,并无优化部分。node
在进行代码生成部分以前,咱们先来根据AST来直接解释执行,其实就是对AST的遍历。现代解释器通常都是生成一个比较低级的指令而后跑在虚拟机上,可是简单起见咱们就直接根据AST解释执行的解释器。(本来这部分是不想写的,是能够直接写代码生成的)git
此次的文件在interpreter包里,此次涉及到的文件比较多,就不列举了github
在开始说解释器的部分前咱们看一下,认真观察以前在构造符号表对赋初值的推导式的处理是有问题的,可是问题不大,只要稍微改动一下后端
在github源代码的部分已经改了,改动以下:数组
case SyntaxProductionInit.VarDecl_Equal_Initializer_TO_Decl: attributeForParentNode = (Symbol) valueStack.get(valueStack.size() - 3); ((Symbol) attributeForParentNode).value = initialValue; break; case SyntaxProductionInit.Expr_TO_Initializer: initialValue = (Integer) valueStack.get(valueStack.size() - 1); System.out.println(initialValue); break;
其实就是一个拿到赋的初值放到Symbol的value里ide
先看一下这篇完成以后解释执行的效果函数
void swap(int arr[10], int i, int j) { int temp; temp = arr[i]; arr[i] = arr[j]; arr[j] = temp; } void quickSort(int a[10], int p, int r) { int x; int i; i = p - 1; int j; int t; int v; v = r - 1; if (p < r) { x = a[r]; for (j = p; j <= v; j++) { if (a[j] <= x) { i++; swap(a, i, j); } } v = i + 1; swap(a, v, r); t = v - 1; quickSort(a, p, t); t = v + 1; quickSort(a, t, r); } } void main () { int a[10]; int i; int t; printf("Array before quicksort:"); for(i = 0; i < 10; i++) { t = (10 - i); a[i] = t; printf("value of a[%d] is %d", i, a[i]); } quickSort(a, 0, 9); printf("Array after quicksort:"); for (i = 0; i < 10; i++) { printf("value of a[%d] is %d", i, a[i]); } }
全部可以执行结点的类都要实现这个接口,因此以此来达到遍历AST来执行代码oop
解释器的启动在Interpreter类里,它也实现了Executor接口
Interpreter类的execute传入的参数就是整棵抽象语法树的头节点了,ExecutorFactory的getExecutor则是根据当前结点的TokenType返回一个能够解释当前节点的类,而其它执行节点的类都继承了BaseExecutor
@Override public Object execute(AstNode root) { if (root == null) { return null; } ExecutorFactory factory = ExecutorFactory.getInstance(); Executor executor = factory.getExecutor(root); executor.execute(root); return root; }
BaseExecutor的两个主要方法就是执行它的子节点,而且能够指定执行哪一个子节点。能够先忽略Brocaster,这些是用来实现执行节点类以前的通信的,如今尚未用。reverseChildren是用来对节点的反转,由于在建立的AST的过程因为堆栈的缘由,因此节点顺序的相反的。continueExecute是标志位,后面可能会执行到设置它的节点来结束运行
protected void executeChildren(AstNode root) { ExecutorFactory factory = ExecutorFactory.getInstance(); root.reverseChildren(); int i = 0; while (i < root.getChildren().size()) { if (!continueExecute) { break; } AstNode child = root.getChildren().get(i); executorBrocaster.brocastBeforeExecution(child); Executor executor = factory.getExecutor(child); if (executor != null) { executor.execute(child); } else { System.err.println("Not suitable Generate found, node is: " + child.toString()); } executorBrocaster.brocastAfterExecution(child); i++; } } protected AstNode executeChild(AstNode root, int childIdx) { root.reverseChildren(); AstNode child; ExecutorFactory factory = ExecutorFactory.getInstance(); child = (AstNode)root.getChildren().get(childIdx); Executor executor = factory.getExecutor(child); AstNode res = (AstNode)executor.execute(child); return res; }
咱们能够知道一个C语言的源文件通常都是一些函数定义和一个main的函数来启动,因此在AstBuilder里返回给Interpreter的节点就是从main开始的
public AstNode getSyntaxTreeRoot() { AstNode mainNode = funcMap.get("main"); return mainNode; }
用来执行函数的Executor是ExtDefExecutor
@Override public Object execute(AstNode root) { this.root = root; int production = (Integer) root.getAttribute(NodeKey.PRODUCTION); switch (production) { case SyntaxProductionInit.OptSpecifiers_FunctDecl_CompoundStmt_TO_ExtDef: AstNode child = root.getChildren().get(0); funcName = (String) child.getAttribute(NodeKey.TEXT); root.setAttribute(NodeKey.TEXT, funcName); saveArgs(); executeChild(root, 0); executeChild(root, 1); Object returnVal = getReturnObj(); clearReturnObj(); if (returnVal != null) { root.setAttribute(NodeKey.VALUE, returnVal); } isContinueExecution(true); restoreArgs(); break; default: break; } return root; }
执行函数会先执行它的括号的前部分也就是标识符和参数那部分,对参数进行初始化,函数的传递的参数用单独一个类FunctionArgumentList来表示
@Override public Object execute(AstNode root) { int production = (Integer) root.getAttribute(NodeKey.PRODUCTION); Symbol symbol; currentNode = root; switch (production) { case SyntaxProductionInit.NewName_LP_RP_TO_FunctDecl: root.reverseChildren(); copyChild(root, root.getChildren().get(0)); break; case SyntaxProductionInit.NewName_LP_VarList_RP_TO_FunctDecl: symbol = (Symbol) root.getAttribute(NodeKey.SYMBOL); Symbol args = symbol.getArgList(); initArgumentList(args); if (args == null || argsList == null || argsList.isEmpty()) { System.err.println("generate function with arg list but arg list is null"); System.exit(1); } break; default: break; } return root; }
执行语句的部分就开始对树的遍历执行,可是咱们来看一下这个节点的推导式
COMPOUND_STMT-> LC LOCAL_DEFS STMT_LIST RC
在构建AST的时候咱们并无构建LOCAL_DEFS,而且在以前符号表也没有进行处理,因此咱们直接执行第0个节点就能够了
@Override public Object execute(AstNode root) { return executeChild(root, 0); }
下面看UnaryNodeExecutor,UnaryNodeExecutor应该是全部Executor最复杂的之一了,其实对于节点执行,先执行子节点,而且向上传递执行结果的值。
只说其中的几个
这个就是对指针的操做了,本质是对内存分配的一个模拟,再设置实现ValueSetter的DirectMemValueSetter,让它的父节点能够经过这个节点的setter对指针指向进行赋值
ValueSetter是一个能够对变量进行赋值的接口,数组、指针、简单的变量都有各自的valueSetter
case SyntaxProductionInit.Start_Unary_TO_Unary: child = root.getChildren().get(0); int addr = (Integer) child.getAttribute(NodeKey.VALUE); symbol = (Symbol) child.getAttribute(NodeKey.SYMBOL); MemoryHeap memHeap = MemoryHeap.getInstance(); Map.Entry<Integer, byte[]> entry = memHeap.getMem(addr); int offset = addr - entry.getKey(); if (entry != null) { byte[] memByte = entry.getValue(); root.setAttribute(NodeKey.VALUE, memByte[offset]); } DirectMemValueSetter directMemSetter = new DirectMemValueSetter(addr); root.setAttribute(NodeKey.SYMBOL, directMemSetter); break;
这是执行数组或者是指针的操做,对于数组和指针的操做会在节点中的Symbol里设置一个能够进行赋值的接口:ArrayValueSetter、PointerValueSetter,逻辑都不是很复杂。对于指针的操做实际上是对于内存地址分配的一个模拟。
case SyntaxProductionInit.Unary_LB_Expr_RB_TO_Unary: child = root.getChildren().get(0); symbol = (Symbol) child.getAttribute(NodeKey.SYMBOL); child = root.getChildren().get(1); int index = (Integer) child.getAttribute(NodeKey.VALUE); try { Declarator declarator = symbol.getDeclarator(Declarator.ARRAY); if (declarator != null) { Object val = declarator.getElement(index); root.setAttribute(NodeKey.VALUE, val); ArrayValueSetter setter = new ArrayValueSetter(symbol, index); root.setAttribute(NodeKey.SYMBOL, setter); root.setAttribute(NodeKey.TEXT, symbol.getName()); } Declarator pointer = symbol.getDeclarator(Declarator.POINTER); if (pointer != null) { setPointerValue(root, symbol, index); PointerValueSetter pv = new PointerValueSetter(symbol, index); root.setAttribute(NodeKey.SYMBOL, pv); root.setAttribute(NodeKey.TEXT, symbol.getName()); } } catch (Exception e) { System.err.println(e.getMessage()); e.printStackTrace(); System.exit(1); } break;
函数调用也是属于一元操做,对于函数调用有两种状况:一种是自定义的函数,还有一种是解释器提供的函数
case SyntaxProductionInit.Unary_LP_RP_TO_Unary: case SyntaxProductionInit.Unary_LP_ARGS_RP_TO_Unary: String funcName = (String) root.getChildren().get(0).getAttribute(NodeKey.TEXT); if (production == SyntaxProductionInit.Unary_LP_ARGS_RP_TO_Unary) { AstNode argsNode = root.getChildren().get(1); ArrayList<Object> argList = (ArrayList<Object>) argsNode.getAttribute(NodeKey.VALUE); ArrayList<Object> symList = (ArrayList<Object>) argsNode.getAttribute(NodeKey.SYMBOL); FunctionArgumentList.getInstance().setFuncArgList(argList); FunctionArgumentList.getInstance().setFuncArgSymbolList(symList); } AstNode func = AstBuilder.getInstance().getFunctionNodeByName(funcName); if (func != null) { Executor executor = ExecutorFactory.getInstance().getExecutor(func); executor.execute(func); Object returnVal = func.getAttribute(NodeKey.VALUE); if (returnVal != null) { ConsoleDebugColor.outlnPurple("function call with name " + funcName + " has return value that is " + returnVal.toString()); root.setAttribute(NodeKey.VALUE, returnVal); } } else { ClibCall libCall = ClibCall.getInstance(); if (libCall.isApiCall(funcName)) { Object obj = libCall.invokeApi(funcName); root.setAttribute(NodeKey.VALUE, obj); } } break;
逻辑语句处理无非就是根据节点值判断该执行哪些节点
代码逻辑和语句的逻辑是同样,好比对于
for(i = 0; i < 5; i++){}
就会先执行i = 0部分,在执行{}和i++部分,而后再判断条件是否符合
case SyntaxProductionInit.FOR_OptExpr_Test_EndOptExpr_Statement_TO_Statement: executeChild(root, 0); while (isLoopContinute(root, LoopType.FOR)) { //execute statement in for body executeChild(root, 3); //execute EndOptExpr executeChild(root, 2); } break; case SyntaxProductionInit.While_LP_Test_Rp_TO_Statement: while (isLoopContinute(root, LoopType.WHILE)) { executeChild(root, 1); } break;
if语句就是先执行判断部分,再根据判断的结果来决定是否执行{}块
@Override public Object execute(AstNode root) { AstNode res = executeChild(root, 0); Integer val = (Integer)res.getAttribute(NodeKey.VALUE); copyChild(root, res); if (val != null && val != 0) { executeChild(root, 1); } return root; }
这一篇写的很乱,一是解释器部分仍是蛮大的,想在一篇以内写完比较难。因此省略了不少东西。但其实对于解释器实现部分对于AST的遍历才比较涉及编译原理部分,其它的主要是逻辑实现
对于解释器部分,由于没有采用虚拟机那样的实现,而是直接对AST的遍历。因此对AST的遍历是关键,主要在于遍历到该执行的子节点部分,而后处理逻辑,再把信息经过子节点传递到父节点部分。