java開發C語言編譯器:實現庫函數調用

更詳細的講解和代碼調試演示過程,請參看視頻
用java開發C語言編譯器vue


咱們第一次使用C語言開發程序時,每每是在控制檯上打印一句」Hello World」,實現打印語句功能的函數是printf, 這個函數是有C語言的連接庫提供的,開發者能夠直接調用,相似於這種無需本身實現,直接能夠調用的函數,咱們都稱爲庫函數,或是API, 本節,咱們要爲當前構建的虛擬機提供C語言庫函數,咱們要給解釋器提供一種函數調用機制,這些函數無需程序本身實現,而是由咱們的解釋器提供的,C語言程序直接調用便可,這節,咱們要實現的一個庫函數就是printf. 完成本節代碼後,咱們的解釋器能解釋執行下面程序:java

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

printf函數是咱們解釋器提供給代碼的,有了庫函數,程序的開發便能高效不少。api

庫函數機制的實現由新構造的類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中查找是否含有與給定名字相同的字符串,若是有,那意味着該函數屬於庫函數。markdown

因爲目前咱們只實現了一個庫函數printf, 所以初始化時,咱們把字符串」printf」加入到該集合中。 isAPICall 傳入的是函數名,若是函數名包含在apiSet裏面,那麼返回true, 代表他是庫函數調用。架構

invokeAPICall 用來執行給定的庫函數,傳入參數是庫函數的名稱。在裏面,解釋器根據不一樣的庫函數名稱,去實現不一樣的庫函數。handlePrintfCall用於實現printf調用,首先,它得到輸入參數,第一個輸入參數是要顯示到控制檯上的字符串,在字符串中,可能會含有格式化字符,當前咱們實現的printf可接受的格式化字符是%d. 在printf的模擬實現中,咱們遍歷每個字符,當遇到%d時,咱們從參數列表中得到對應的數值,而後把數值替換%d格式符,最後經過println把格式化後的字符串打印到控制檯上。app

有了庫函數調用後,每當解釋器解析到函數調用是,須要肯定當前調用的函數是代碼本身實現的,仍是庫函數提供的,這個機制的實如今UnaryExecutor中,代碼以下:ide

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的實現便可,基於如今的架構基礎上,咱們從此能夠快速的實現更多庫函數,從而讓咱們的解釋器愈來愈強大!ui

更加具體的代碼解釋和調試過程請參看視頻。


本文分享自微信公衆號 - Coding迪斯尼(gh_c9f933e7765d)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。

相關文章
相關標籤/搜索