首页 > 编程语言 >java实现C语言编译器:实现有参数的函数调用

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

时间:2023-06-14 11:01:06浏览次数:42  
标签:java 函数 int Symbol 函数调用 作用域 编译器 参数 变量


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

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

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_LIST SEMI

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

private void takeActionForReduce(int productNum) {
...
case CGrammarInitializer.OptSpecifier_ExtDeclList_Semi_TO_ExtDef:
        case CGrammarInitializer.TypeNT_VarDecl_TO_ParamDeclaration:
        case CGrammarInitializer.Specifiers_DeclList_Semi_TO_Def:
            Symbol symbol = (Symbol)attributeForParentNode;
            TypeLink specifier = (TypeLink)(valueStack.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参数数值设置正确后,函数体就能正确执行了

函数体的执行在上一节我们曾经讨论过,再次不再讨论。请通过参看视频获得更详细的代码讲解和调试演示过程,以便增加理解。

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

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


标签:java,函数,int,Symbol,函数调用,作用域,编译器,参数,变量
From: https://blog.51cto.com/u_16160261/6476247

相关文章

  • 拼多多接口|api接口数据采集获取商品详情数据源代码Java演示
    ​拼多多提供了商品API,可以通过该API获取拼多多所有商品的详细信息,具体步骤如下: 申请开放平台接入。注册获取apikey和apisecret,调用API时需提供。调用拼多多API,获取商品详情。请求参数:参数说明通用参数说明version:API版本key:调用key,测试key:test_api_......
  • 书写高质量JavaScript代码的要义(The Essentials of Writing High Quality JavaScript)
    原文:TheEssentialsofWritingHighQualityJavaScript才华横溢的StoyanStefanov,在他写的由O’Reilly初版的新书《JavaScriptPatterns》(JavaScript模式)中,我想要是为我们的读者贡献其摘要,那会是件很美妙的事情。具体一点就是编写高质量JavaScript的一些要素,例如避免全局变量,使......
  • Java_memcached-release 安装 原理 实例
     Java_memcached-release安装原理实例  一、了解和使用使用安装memcached在这一块已经有车了,就不再造了。一个日本君写的:长野雅广memcached-全面剖析.pdfheiyeluren(黑夜路人) Memcached-原理和使用详解.pdf  二、javamemcached客启端的调用   2.1下载客户端jar包......
  • javascript现代编程系列教程之二——IIFE
    IIFE(ImmediatelyInvokedFunctionExpression,立即执行函数表达式)是一个在定义后立即执行的JavaScript函数。它具有以下特点:是一个匿名函数:通常情况下,IIFE是一个没有名字的函数,称为匿名函数。立即执行:这个函数在声明后立即被调用并执行,而无需手动调用。创建局部作用域:它创建......
  • javascript现代编程系列教程之一:区块作用域对VAR不起作用的问题
    在JavaScript中,使用var声明的变量具有函数作用域,而不是块级作用域。这意味着在一个函数内部,使用var声明的变量在整个函数范围内都是可见的,包括嵌套的块(如if语句、for循环等)。为了避免区块对var不起作用的问题,你可以采用以下方法:使用let和const代替var:从ECMAScript2015(ES6)开始,引......
  • 深入剖析创建Java虚拟机的实现方法
    经过前文《深入剖析java.c文件中JavaMain方法中InitializeJVM的实现》的分析,找到了创建Java虚拟机具体实现的方法Threads::create_vm((JavaVMInitArgs*)args,&can_try_again)。该方法的实现在src\hotspot\share\runtime\threads.cpp文件,我去掉了部分英文注释和宏条件代码,代码更......
  • JavaScript 全局对象参考手册 encodeURIComponent() 函数
    JavaScriptencodeURIComponent()函数JavaScript全局对象参考手册定义和用法encodeURIComponent()函数可把字符串作为URI组件进行编码。语法encodeURIComponent(URIstring)  参数描述URIstring必需。一个字符串,含有URI组件或其他要编码的文本。返回值URIstring的副......
  • JavaScript 全局对象参考手册 eval() 函数
    JavaScripteval()函数JavaScript全局对象参考手册定义和用法eval()函数可计算某个字符串,并执行其中的的JavaScript代码。语法eval(string)  参数描述string必需。要计算的字符串,其中含有要计算的JavaScript表达式或要执行的语句。返回值通过计算string得到的值(如果......
  • Java多线程与静态方法
    Java多线程与静态方法在多线程中使用静态方法会发生什么事?也就是说多线程访问同一个类的static静态方法会发生什么事?是否会发生线程安全问题? publicclassTest{publicstaticvoidoperation(){//...dosomething}} 事实证明只要在静态函数中没有处理多......
  • Redis入门 – Jedis存储Java对象 - (Java序列化为byte数组方式)
     Redis入门–Jedis存储Java对象-(Java序列化为byte数组方式) 在Jedis开发中,我们很多时候希望直接把一个对象放到Redis中,然后在需要的时候取出来。Redis的key和value都支持二进制安全的字符串,存储Java对象不是问题,下面我们看一下如何来实现。 1要存储的对象现在写一个很土的J......