java開發C語言編譯器:為C語言提供API呼叫

tyler_download發表於2017-01-10

更詳細的講解和程式碼除錯演示過程,請參看視訊
用java開發C語言編譯器

我們第一次使用C語言開發程式時,往往是在控制檯上列印一句”Hello World”,實現列印語句功能的函式是printf, 這個函式是有C語言的連結庫提供的,開發者可以直接呼叫,類似於這種無需自己實現,直接可以呼叫的函式,我們都稱為庫函式,或是API, 本節,我們要為當前構建的虛擬機器提供C語言庫函式,我們要給直譯器提供一種函式呼叫機制,這些函式無需程式自己實現,而是由我們的直譯器提供的,C語言程式直接呼叫即可,這節,我們要實現的一個庫函式就是printf. 完成本節程式碼後,我們的直譯器能解釋執行下面程式:

void main() {
printf("a is %d:",1);
}

printf函式是我們直譯器提供給程式碼的,有了庫函式,程式的開發便能高效很多。

庫函式機制的實現由新構造的類ClibCall,我們先看看它的實現程式碼:

package backend;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.Set;

public class ClibCall {
    private Set<String> apiSet;

    private ClibCall() {
        apiSet = new HashSet<String>();
        apiSet.add("printf");
    }
    private static ClibCall instance = null;

    public static ClibCall getInstance() {

        if (instance == null) {
            instance = new ClibCall();
        }

        return instance;
    }

    public boolean isAPICall(String funcName) {
        return apiSet.contains(funcName);
    }

    public Object invokeAPI(String funcName) {
        switch (funcName) {

        case "printf":
            return handlePrintfCall();

        default:
            return null;
        }
    }

    private Object handlePrintfCall() {
        ArrayList<Object> argsList = FunctionArgumentList.getFunctionArgumentList().getFuncArgList(false);
        String argStr = (String)argsList.get(0);
        String formatStr = "";

        int i = 0;
        int argCount = 1;
        while (i < argStr.length()) {
            if (argStr.charAt(i) == '%' && i+1 < argStr.length() && 
                    argStr.charAt(i+1) == 'd') {
                i += 2;
                formatStr += argsList.get(argCount);
                argCount++;
            } else {
                formatStr += argStr.charAt(i);
                i++;
            }
        }

        System.out.println(formatStr);

        return null;
    }
}

ClibCall 包含了一個集合類叫apiSet, 其用於儲存庫函式的函式名,當程式碼中有函式呼叫時,直譯器拿到被調函式的名字,提交給ClibCall, 該類會在apiSet中查詢是否含有與給定名字相同的字串,如果有,那意味著該函式屬於庫函式。

由於目前我們只實現了一個庫函式printf, 因此初始化時,我們把字串”printf”加入到該集合中。 isAPICall 傳入的是函式名,如果函式名包含在apiSet裡面,那麼返回true, 表明他是庫函式呼叫。

invokeAPICall 用來執行給定的庫函式,傳入引數是庫函式的名稱。在裡面,直譯器根據不同的庫函式名稱,去實現不同的庫函式。handlePrintfCall用於實現printf呼叫,首先,它獲得輸入引數,第一個輸入引數是要顯示到控制檯上的字串,在字串中,可能會含有格式化字元,當前我們實現的printf可接受的格式化字元是%d. 在printf的模擬實現中,我們遍歷每一個字元,當遇到%d時,我們從引數列表中獲得對應的數值,然後把數值替換%d格式符,最後通過println把格式化後的字串列印到控制檯上。

有了庫函式呼叫後,每當直譯器解析到函式呼叫是,需要確定當前呼叫的函式是程式碼自己實現的,還是庫函式提供的,這個機制的實現在UnaryExecutor中,程式碼如下:

public class UnaryNodeExecutor extends BaseExecutor{

    @Override
    public Object Execute(ICodeNode root) {
        executeChildren(root);

        int production = (Integer)root.getAttribute(ICodeKey.PRODUCTION); 
        String text ;
        Symbol symbol;
        Object value;
        ICodeNode child;

        switch (production) {
        ....
        case CGrammarInitializer.Unary_LP_ARGS_RP_TO_Unary:
            //先獲得函式名
            String funcName = (String)root.getChildren().get(0).getAttribute(ICodeKey.TEXT);
            if (production == CGrammarInitializer.Unary_LP_ARGS_RP_TO_Unary) {
                ICodeNode argsNode = root.getChildren().get(1);
                ArrayList<Object> argList = (ArrayList<Object>)argsNode.getAttribute(ICodeKey.VALUE);
                FunctionArgumentList.getFunctionArgumentList().setFuncArgList(argList); 
            }

            //找到函式執行樹頭節點
            ICodeNode func = CodeTreeBuilder.getCodeTreeBuilder().getFunctionNodeByName(funcName);
            if (func != null) {
                Executor executor = ExecutorFactory.getExecutorFactory().getExecutor(func);
                executor.Execute(func);
                Object returnVal = func.getAttribute(ICodeKey.VALUE);
                if (returnVal != null) {
                    System.out.println("function call with name " + funcName + " has return value that is " + returnVal.toString());
                    root.setAttribute(ICodeKey.VALUE, returnVal);
                }
            } else {
                ClibCall libCall = ClibCall.getInstance();
                if (libCall.isAPICall(funcName)) {
                    Object obj = libCall.invokeAPI(funcName);
                    root.setAttribute(ICodeKey.VALUE, obj);
                } 
            }
        ....
        }

當直譯器解析函式呼叫時,它現在函式呼叫表中,查詢給定函式的語法執行樹頭結點,如果找不到的話,那麼直譯器知道,這個函式是庫函式,於是呼叫ClibCall來處理,它先通過isAPICall來判斷,給定函式是否是庫函式,如果是的話,則呼叫invokeAPI來執行庫函式提供的功能。

有了ClibCall, 以後我們想要新增新的庫函式時,只要修改ClibCall的實現即可,基於現在的架構基礎上,我們今後可以快速的實現更多庫函式,從而讓我們的直譯器越來越強大!

更加具體的程式碼解釋和除錯過程請參看視訊。

更多技術資訊,包括作業系統,編譯器,面試演算法,機器學習,人工智慧,請關照我的公眾號:
這裡寫圖片描述

相關文章