前言:
而今朋友们对“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调用