從零寫一個編譯器(十):編譯前傳之直接解釋執行

項目的完整代碼在 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]);
    }
}

Executor接口

全部可以執行結點的類都要實現這個接口,因此以此來達到遍歷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;
}

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

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

  1. 若是是自定義函數,就找到這個函數的頭節點,從這個頭節點開始執行
  2. 若是是解釋器提供的函數,就交由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 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語句

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

相關文章
相關標籤/搜索