java实现C语言解释器:无参数传递的函数调用的解释和执行

来源:互联网 发布:软件开发包 编辑:程序博客网 时间:2024/04/30 15:39

具体的代码讲解和调试演示过程,请参看视频:
用java开发C语言编译器

本节,我们看看,如何实现C语言中的函数调用,举个具体例子,在完成本节的代码后,我们的解释器功能进一步增强,使得它能解释执行下面的C语言代码:

void f() {    int a;    a = 1;}void main() {    f();}

我们先看看函数定义的语法表达式:

EXT_DEF -> OPT_SPECIFIERS FUNCT_DECL COMPOUND_STMTFUNCT_DECL -> UNARY LP RP

对应于函数f的定义 ,其中 OPT_SPECIFIERS 对应的是关键字void, FUNCT_DECL对应的是 f(), 最后COMPOUND_STMT对应的是:
{
int a;
a = 1;
}

根据表达式,解释器会构造如下执行树:
这里写图片描述

同理,对于main函数,我们也有同样的执行树:
这里写图片描述

在主函数main 中,实现了对函数f的调用,函数调用对应的语法如下:
UNARY -> UNARY LP RP
NUARY -> NAME

其中NAME 对应的是被掉函数名 f, LP RP 对应左右括号。

我们看看构造执行树的代码对此进行的相应改动,CodeTreeBuilder.java:

public class CodeTreeBuilder {private String functionName;    private HashMap<String, ICodeNode> funcMap = new HashMap<String , ICodeNode>();  public ICodeNode buildCodeTree(int production, String text) {        ICodeNode node = null;        Symbol symbol = null;        switch (production) {        ...        case CGrammarInitializer.NewName_LP_RP_TO_FunctDecl:        case CGrammarInitializer.NewName_LP_VarList_RP_TO_FunctDecl:            node = ICodeFactory.createICodeNode(CTokenType.FUNCT_DECL);            node.addChild(codeNodeStack.pop());            child =  node.getChildren().get(0);            functionName = (String)child.getAttribute(ICodeKey.TEXT);            symbol = assignSymbolToNode(node, functionName);            break;        case CGrammarInitializer.NewName_TO_VarDecl:            //我们暂时不处理变量声明语句            codeNodeStack.pop();            break;        case CGrammarInitializer.NAME_TO_NewName:            node = ICodeFactory.createICodeNode(CTokenType.NEW_NAME);            node.setAttribute(ICodeKey.TEXT, text);            break;        case CGrammarInitializer.OptSpecifiers_FunctDecl_CompoundStmt_TO_ExtDef:            node = ICodeFactory.createICodeNode(CTokenType.EXT_DEF);            node.addChild(codeNodeStack.pop());            node.addChild(codeNodeStack.pop());            funcMap.put(functionName, node);            break;    ....    }}

由于我们现在是基于函数调用来解释C语言代码的,因此,当我们将一个函数的执行树构造出来后,需要把在执行树的头节点加入一个哈希表,表的关键字用的就是函数名,当某个函数被调用时,解释器会从该表中,通过被调用函数的名字,找到该函数对应的执行树的头节点,然后再根据该执行树进行解释执行相应节点,这样,我们就能实现函数调用了。

我们再看看函数的执行部分,我们根据执行树,增加了若干个Executor,对应于函数定义执行树的头节点,我们增加了ExtDefExecutor,代码如下:

package backend;import frontend.CGrammarInitializer;public class ExtDefExecutor extends BaseExecutor {    @Override    public Object Execute(ICodeNode root) {        int production = (Integer)root.getAttribute(ICodeKey.PRODUCTION);        switch (production) {        case CGrammarInitializer.OptSpecifiers_FunctDecl_CompoundStmt_TO_ExtDef:            executeChild(root, 0);            ICodeNode child = root.getChildren().get(0);             String funcName = (String)child.getAttribute(ICodeKey.TEXT);            root.setAttribute(ICodeKey.TEXT, funcName);            executeChild(root, 1);            child = root.getChildren().get(1);            Object returnVal = child.getAttribute(ICodeKey.VALUE);            if (returnVal != null) {                root.setAttribute(ICodeKey.VALUE, returnVal);            }            break;        }        return root;    }}

它的逻辑简单,由于它先执行FUNCT_DECL节点,然后在执行函数体,也就是大括号包住的部分。

我们再看看FUNCT_DECL节点对应的Executor:

public class FunctDeclExecutor extends BaseExecutor {    @Override    public Object Execute(ICodeNode root) {        int production = (Integer)root.getAttribute(ICodeKey.PRODUCTION);        switch (production) {        case CGrammarInitializer.NewName_LP_RP_TO_FunctDecl:            root.reverseChildren();            copyChild(root, root.getChildren().get(0));            break;        }        return root;    }}

该节点执行时,只是单纯的拷贝子节点信息,后面我们实现由参数传递的函数调用时,该节点将发挥重要作用。

由于函数调用对应的语法是:
UNARY -> UNARY LP RP
因此,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_RP_TO_Unary:            //先获得函数名            String funcName = (String)root.getChildren().get(0).getAttribute(ICodeKey.TEXT);            //找到函数执行树头节点            ICodeNode func = CodeTreeBuilder.getCodeTreeBuilder().getFunctionNodeByName(funcName);            if (func != null) {                Executor executor = ExecutorFactory.getExecutorFactory().getExecutor(func);                executor.Execute(func);            }            break;        ...        }

它首先找到要调用的函数名,利用函数名在函数哈希表中找到对应的执行树的头节点,根据头结点构造函数的执行节点ExtDefExecutor,然后调用该Executor的execute接口,于是ExtDefExecutor便开始变量函数f的执行树,在对应节点执行相应操作,从而实现函数f被调用的效果。

具体的代码讲解和调试演示请参看视频。

更多技术信息,包括操作系统,编译器,面试算法,机器学习,人工智能,请关照我的公众号:
这里写图片描述

0 0