更詳細的講解和代碼調試演示過程,請參看視頻
用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源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。