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

时间:2022-03-17 01:04:42

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

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

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

void main() {
    f();
}

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

EXT_DEF -> OPT_SPECIFIERS FUNCT_DECL COMPOUND_STMT
FUNCT_DECL -> UNARY LP RP

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

根据表达式,解释器会构造如下执行树:
java实现C语言解释器:无参数传递的函数调用的解释和执行

同理,对于main函数,我们也有同样的执行树:
java实现C语言解释器:无参数传递的函数调用的解释和执行

在主函数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被调用的效果。

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

更多技术信息,包括操作系统,编译器,面试算法,机器学习,人工智能,请关照我的公众号:
java实现C语言解释器:无参数传递的函数调用的解释和执行