日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

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

發布時間:2023/12/20 编程问答 34 豆豆
生活随笔 收集整理的這篇文章主要介紹了 从零写一个编译器(十):编译前传之直接解释执行 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

項目的完整代碼在 C2j-Compiler

前言

這一篇不看也不會影響后面代碼生成部分

現在經過詞法分析語法分析語義分析,終于可以進入最核心的部分了。前面那部分可以稱作編譯器的前端,代碼生成代碼優化都是屬于編譯器后端,如今有關編譯器的工作崗位主要都是對后端的研究。當然現在寫的這個編譯器因為水平有限,并沒有優化部分。

在進行代碼生成部分之前,我們先來根據AST來直接解釋執行,其實就是對AST的遍歷。現代解釋器一般都是生成一個比較低級的指令然后跑在虛擬機上,但是簡單起見我們就直接根據AST解釋執行的解釋器。(原本這部分是不想寫的,是可以直接寫代碼生成的)

這次的文件在interpreter包里,這次涉及到的文件比較多,就不列舉了

一個小問題

在開始說解釋器的部分前我們看一下,認真觀察之前在構造符號表對賦初值的推導式的處理是有問題的,但是問題不大,只要稍微改動一下

在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里

示例

先看一下這篇完成之后解釋執行的效果

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]);} }

Executor接口

所有能夠執行結點的類都要實現這個接口,所以以此來達到遍歷AST來執行代碼

解釋器的啟動在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; }

執行函數ExtDefExecutor

用來執行函數的Executor是ExtDefExecutor

  • 在進入execute會先執行FunctDecl節點,再執行CompoundStmt節點
  • saveArgs和restoreArgs屬于保護當前的環境,就是進入其它作用域的時候保證這個符號不變修改,不比如當作參數傳遞的時候
  • returnVal也是屬于由其它節點設置的屬性
  • root.setAttribute的作用就是對節點設置屬性,把值往上傳遞
@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; }

函數定義 FunctDeclExecutor

執行函數會先執行它的括號的前部分也就是標識符和參數那部分,對參數進行初始化,函數的傳遞的參數用單獨一個類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; }

執行語句部分 CompoundStmtExecutor

執行語句的部分就開始對樹的遍歷執行,但是我們來看一下這個節點的推導式

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;
  • 函數調用

函數調用也是屬于一元操作,對于函數調用有兩種情況:一種是自定義的函數,還有一種是解釋器提供的函數

  • 如果是自定義函數,就找到這個函數的頭節點,從這個頭節點開始執行
  • 如果是解釋器提供的函數,就交由ClibCall處理,比如printf就是屬于庫函數
  • 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、WHILE語句

    代碼邏輯和語句的邏輯是一樣,比如對于

    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 bodyexecuteChild(root, 3);//execute EndOptExprexecuteChild(root, 2); } break;case SyntaxProductionInit.While_LP_Test_Rp_TO_Statement: while (isLoopContinute(root, LoopType.WHILE)) {executeChild(root, 1); } break;
    • IF語句

    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的遍歷是關鍵,主要在于遍歷到該執行的子節點部分,然后處理邏輯,再把信息通過子節點傳遞到父節點部分。

    轉載于:https://www.cnblogs.com/secoding/p/11382017.html

    總結

    以上是生活随笔為你收集整理的从零写一个编译器(十):编译前传之直接解释执行的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。