龙空技术网

Java实现C语言编译器:实现有参数的函数调用

余生做酒长醉不忧 173

前言:

而今朋友们对“cjava调用”大致比较关切,看官们都需要了解一些“cjava调用”的相关内容。那么小编同时在网摘上收集了一些关于“cjava调用””的相关文章,希望兄弟们能喜欢,朋友们一起来了解一下吧!

上一节,我们实现了没哟汆熟传递的函数调用,本节,我们看看如何实现有参数的函数调用。

有参数的函数调用要比无参数的函数调用复杂得多,一个难题在于,我们需要确定参数变量的作用域,例如下面的代码:

int a;void f(int a,int b){	int c;  c=a+b;}

在代码里,有两个同名变量都称为a,显然这两个变量的作用域不同,同名的存在并不矛盾,当两个变量存在符号表时,由于名字相同,要实现参数传递,必须确定参数的值传递给正确的变量a,如果传递错了,那么整个程序运行的逻辑就混乱了。

因此,要实现有参数的函数调用,首要问题是确保把数值传递给对应作用域相应的变量,那么如何确定变量的作用域呢,根据我们的代码实现,每个变量都对应一个Symbol对象,我们在该对象里面添加一个字符串,,用来表明该对象的作用域,如果变量是全局变量,那么它的作用域字符串内容为“global”,如果变量是某个函数的参数,或是定义在函数体内,那么该变量的作用域字符串就可以用该函数来命名。

以上面的代码为例,第一个变量a,作用域是global,第二个变量a作用域范围是f,也就是它对应的函数名字,我们看看代码是如何实现这个功能

一条变量定义的代码语句,例如:

int a;

对应的语法表达式为:

ext_def->opt_specifiers ext_decl SEMI

因此,当语法解析器读入一条语句,然后解析上面的语句时,我们就知道,代码正在定义一个变量,此时就可以设置该变量的作用域字符串了,代码如下:

private void takeActionForReduce(int productionNum){  case CGrammarInitializer.TypeNt_VarDecl_To_ParamDeclaration:  case CGrammarInitializer.Specifiers_DeclList_Semi_To_Def:  	Symbol symbol =(symbol)attributeForParentNode;  	TypleLink specifier =(TypeLink)(value.get(valueStack.size()-3))    typeSystem.addSpecifierToDeclaration(specifier, symbol);    typeSystem.addSymbolsToTable(symbol, symbolScope);}

我们早在语法解析是讲解过上面代码,当C语言定义一个变量时,上面对应的case代码会被执行,进而为生成的变量对应的Symbol对象,在把Symbol对象加入符号表时,需要多添加一个变量,就是symbolScope,这是一个字符串全局变量,用来代表当前变量所在的作用域,它的初始化方式如下:

public class LRStateTableParser {    private Lexer lexer;    int    lexerInput = 0;    int    nestingLevel = 0;    int    enumVal = 0;    String text = "";    public static final String GLOBAL_SCOPE = "global";    public String symbolScope = GLOBAL_SCOPE;    ...    }

它一开始时就被初始化为”global”,因此,我们的解析器在遇到变量声明时,先把当前变量设置为global的,如果在后面的解析中,发现该变量并不是全局变量,那么在后面再修改该变量的作用范围。例如函数调用:

void f(int a, int b) {    int c;    c = a + b;}

两个参数a,b,在第一次解析时,根据前面的case代码,会先为这两个变量的作用域字符串设置为”global”,然后继续解析,等到解析器解析完整个函数头,也就是当解释器根据以下表达式进行递归时:

FUNCT_DECL -> NEW_NAME LP VAR_LIST RP

FUNCT_DECL -> NEW_NAME LP RP

此时,我们可以得知,当前解析器解析的代码是函数头部定义,这个时候,我们将改变symbolScope的内容,把它变成当前解析函数的函数名,这样,接下来遇到变量声明时,它对应的作用域字符串就会变成当前的函数名。

前面我们说过,参数定义时,解析器首先会把它的作用域设置为global,即使它是函数的参数,现在我们解析到函数头定义了,这时候是把原来参数的作用域范围更改为对应函数的正确时机,因此代码如下:

private void takeActionForReduce(int productNum) {   ....case CGrammarInitializer.NewName_LP_VarList_RP_TO_FunctDecl:            setFunctionSymbol(true);            Symbol argList = (Symbol)valueStack.get(valueStack.size() - 2);            ((Symbol)attributeForParentNode).args = argList;            typeSystem.addSymbolsToTable((Symbol)attributeForParentNode, symbolScope);            //遇到函数定义,变量的scope名称要改为函数名,并把函数参数的scope改为函数名            symbolScope = ((Symbol)attributeForParentNode).getName();            Symbol sym = argList;            while (sym != null) {                sym.addScope(symbolScope);                sym = sym.getNextSymbol();            }            break;        case CGrammarInitializer.NewName_LP_RP_TO_FunctDecl:            setFunctionSymbol(false);            typeSystem.addSymbolsToTable((Symbol)attributeForParentNode, symbolScope);            //遇到函数定义,变量的scope名称要改为函数名            symbolScope = ((Symbol)attributeForParentNode).getName();            break;   ....}

从上面代码我们可以看到,在进入到函数头的解析时,解释器会把symbolScope设置为函数名,如果当前的case 等于CGrammarInitializer.NewName_LP_VarList_RP_TO_FunctDecl时,通过argList变量获得函数参数对应的输入参数变量链表,这个链表的建立,我们在前面章节中已经详细解释过。然后通过参数链表变量每个参数,把参数的作用域字符串改为对应的函数名。

等到函数全部解析完毕后,变量的作用域就得重新转变为global, 根据前一节内容,我们知道,函数定义解析完毕对应的语法表达式为:

EXT_DEF -> OPT_SPECIFIERS FUNCT_DECL COMPOUND_STMT

当解释器根据上面的表达式进行递归时,我们知道,当前状态是函数解析结束,因此,我们在这时就需要把symbolScope的内容,重新改为global.代码如下:

private void takeActionForReduce(int productNum) {        switch(productNum) {        ...        case CGrammarInitializer.OptSpecifiers_FunctDecl_CompoundStmt_TO_ExtDef:            symbol = (Symbol)valueStack.get(valueStack.size() - 2);            specifier = (TypeLink)(valueStack.get(valueStack.size() - 3));            typeSystem.addSpecifierToDeclaration(specifier, symbol);            //函数定义结束后,接下来的变量作用范围应该改为global            symbolScope = GLOBAL_SCOPE;            break;        ...        }

接下来,我们看看函数是如何传递的。上一节,我们知道,没有参数输入的函数调用对应的语法表达式是:

UNARY -> UNARY LP RP

那么如果,函数调用有参数的话,其对应的语法表达式是:

UNARY -> UNARY LP ARGS RP

ARGS -> NO_COMMA_EXPR

ARGS -> NO_COMMA_EXPR COMMA ARGS

由此,我们需要构造一个ARGS对应的节点,代码如下:

public ICodeNode buildCodeTree(int production, String text) {        ICodeNode node = null;        Symbol symbol = null;        switch (production) {        ...        case CGrammarInitializer.NoCommaExpr_TO_Args:            node = ICodeFactory.createICodeNode(CTokenType.ARGS);            node.addChild(codeNodeStack.pop());            break;        case CGrammarInitializer.NoCommaExpr_Comma_Args_TO_Args:            node = ICodeFactory.createICodeNode(CTokenType.ARGS);            node.addChild(codeNodeStack.pop());            node.addChild(codeNodeStack.pop());            break;        ...        }

由此,我们还需要构造一个ARGS节点对应的Executor对象,以便实现参数解析,代码如下:

package backend;import java.util.ArrayList;import frontend.CGrammarInitializer;public class ArgsExecutor extends BaseExecutor {    @Override    public Object Execute(ICodeNode root) {        int production = (Integer)root.getAttribute(ICodeKey.PRODUCTION);        ArrayList<Object> argList = new ArrayList<Object>();        ICodeNode child ;        switch (production) {        case CGrammarInitializer.NoCommaExpr_TO_Args:            child = (ICodeNode)executeChild(root, 0);            int val = (Integer)child.getAttribute(ICodeKey.VALUE);            argList.add(val);            break;        case CGrammarInitializer.NoCommaExpr_Comma_Args_TO_Args:            child = executeChild(root, 0);            val = (Integer)child.getAttribute(ICodeKey.VALUE);            argList.add(val);            child = (ICodeNode)executeChild(root, 1);            ArrayList<Object> list = (ArrayList<Object>)child.getAttribute(ICodeKey.VALUE);            argList.addAll(list);            break;        }        root.setAttribute(ICodeKey.VALUE, argList);        return root;    }}

对应函数调用,例如f(1,2,3) ,ArgsExecutor 的作用是构造一个参数队列:

3 -> 2 -> 1, 然后把这个队列交给函数的执行对象,也就是ExtDefExecutor.

ArgsExecutor 先通过子执行子节点,把数字字符串读取成对应的数值,然后再把这些数值加入一个队列中返回。

同时UnaryExecutor也要做相应变化,它需要让ArgsExecutor执行后,获取参数的数值列表,以便传递给ExtDefExecutor,对应的代码改动如下:

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:        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);            }            break;        ...        }

executeChildren(root);首先让孩子节点先执行,由于ARGS是UNARY的孩子节点,所以executeChildren会让ArgsExecutor先执行,这样就可以获取参数数值列表。然后通过ARGS节点得到参数列表,也就是执行下面语句得到参数列表:

ICodeNode argsNode = root.getChildren().get(1);

ArrayList<Object> argList = (ArrayList<Object>)argsNode.getAttribute(ICodeKey.VALUE);

有了列表之后,怎么把列表传递给函数执行体呢,是通过一个单子对象存储的:

package backend;import java.util.ArrayList;public class FunctionArgumentList {    private static FunctionArgumentList argumentList = null;    private ArrayList<Object> funcArgList = new ArrayList<Object>();    public static FunctionArgumentList getFunctionArgumentList() {        if (argumentList == null) {            argumentList = new FunctionArgumentList();        }        return argumentList;    }    public void setFuncArgList(ArrayList<Object> list) {        funcArgList = list;    }    public ArrayList<Object> getFuncArgList() {        return funcArgList;    }    private FunctionArgumentList() {}}

UnaryExecutor将获得的列表放入FunctionArgumentList对象,然后从根据要调用的函数名,从函数哈希表中找到函数执行树的头结点,接着再通过ExtDefExecutor去执行函数体内的语句。

有了参数列表,接下来要做的是把参数列表对应的数值传递给参数,这样函数运行时才能获得输入的数值,数值传递是由FunctDeclExecutor实现的,代码如下:

public class FunctDeclExecutor extends BaseExecutor {    private ArrayList<Object> argsList = null;    private ICodeNode currentNode;    @Override    public Object Execute(ICodeNode root) {    switch (production) {    ...    case  CGrammarInitializer.NewName_LP_VarList_RP_TO_FunctDecl:            symbol = (Symbol)root.getAttribute(ICodeKey.SYMBOL);            //获得参数列表            Symbol args = symbol.getArgList();            initArgumentList(args);            if (args == null || argsList == null || argsList.isEmpty()) {                //如果参数为空,那就是解析错误                System.err.println("Execute function with arg list but arg list is null");                System.exit(1);            }            break;    ...    }private void initArgumentList(Symbol args) {        if (args == null) {            return;        }        argsList = FunctionArgumentList.getFunctionArgumentList().getFuncArgList();        Collections.reverse(argsList);        Symbol eachSym = args;        int count = 0;        while (eachSym != null) {            IValueSetter setter = (IValueSetter)eachSym;            try {                /*                 * 将每个输入参数设置为对应值并加入符号表                 */                setter.setValue(argsList.get(count));                count++;            } catch (Exception e) {                // TODO Auto-generated catch block                e.printStackTrace();            }            eachSym = eachSym.getNextSymbol();        }    }}

它先通过FunctionArgumentList获得参数数值列表,然后找到对应的参数symbol对象列表,逐个把传入数值设置到参数对应的symbol中,当symbol参数数值设置正确后,函数体就能正确执行了

标签: #cjava调用