項目的完整代碼在 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的遍歷是關鍵,主要在於遍歷到該執行的子節點部分,而後處理邏輯,再把信息經過子節點傳遞到父節點部分。