首页 > 编程语言 >java开发C编译器:结构体的解析和执行

java开发C编译器:结构体的解析和执行

时间:2023-06-14 11:06:03浏览次数:40  
标签:java 变量 Symbol struct v1 编译器 解析 symbol 结构


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

结构体是C语言中,最为复杂的原生数据结构,它把多种原生结构结合在一起,形成一个有特点含义的数据结构,要实现一个完整的C语言编译器或解释器,就必须要拥有对结构体的解析能力,本节,我们在当前解释器的基础上,增加结构体的解释执行能力,完成本节后,我们的解释器可以解析执行下面代码:

void main() {

struct TAG {
int v1;
int v2;
char v3;
} tag;

struct TAG  myTag;
struct TAG  herTag;
myTag.v1 = 1;
herTag.v1 = 2;

printf("set filed v1 of struct myTag to value : %d, and v1 of herTag to value : %d", myTag.v1, herTag.v1); 

}

我们先回忆一下结构体的语法表达式:

struct_specifier -> STRUCT OPT_TAG LC def_list RC
                    | STRUCT tag;

我们对比下具体的结构体定义和语法表达式的对应关系:

struct TAG {
int v1;
int v2;
char v3;
} tag;

上面定义中struct 是关键字,对应语法表达式中的STRUCT终结符,TAG 是结构体定义名,对应表达式中的OPT_TAG; int v1;int v2; char v3; 这三个变量定义对应于def_list,.

另一个与结构体相关的语法表达式是:

unary -> unary STRUCTOP NAME

上面表达式用来说明对结构体某个值域的引用,例如语句myTag.v1就可以对应上面的语句,STRUCTOP是终结符,他对应的文本字符为”.”, 或 “->”.

在前面的课程我们详细说明过,当解释器解析到结构体的定义时,它先给结构体变量构建一个symbol对象,该symbol对象的修饰符,也就是specifier含有一个结构体叫StructDefine, StructDefine 会为结构体中的每一个变量创建一个Symbol对象,然后把这些对象串联成一个队列,仍然以上面的结构体定义为例,我们的解释器解析后,形成如下结构:
(图一)

java开发C编译器:结构体的解析和执行_c语言


当我们定义一个结构体变量时,例如语句struct TAG myTag; 任何有关变量声明的语句经过一系列递归后,最后对应的语法表达式为:

def -> specifiers decl_list SEMI;

当解释器解析代码是,递归到上面的表达式时,解释器要判断一下,当前声明的变量是否是结构体,如果是的话,那么必须为当前结构体变量赋值一份结构体内部的变量所对应的Symbol队列,也就是说,当解释器解析到语句 struct TAG myTag;时,会把上图的结构再复制一份:

(图二)

java开发C编译器:结构体的解析和执行_java_02

这样一来,对结构体某个变量的值域的读写,直接转换成对某个变量Symbol的读写就可以了,例如代码中的语句:

myTag.v1 = 1;

这相当与把数值1写入到上图中最下面v1所对应的Symbol对象即可。

我们看看相应的代码实现,第一步就是,当解析到结构体的变量声明时,把结构体定义的符号表数据结构复制一份,也就是从图1到图2的过程:

public class LRStateTableParser {
....
    private void takeActionForReduce(int productNum) {

        switch(productNum) {
        ....
        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);

            handleStructVariable(symbol);
            break;
        ....
        }
....
}

private void handleStructVariable(Symbol symbol) {
       //先看看变量是否属于struct类型
       boolean isStruct = false;
       TypeLink typeLink = symbol.typeLinkBegin;
       Specifier specifier = null;
       while (typeLink != null) {
           if (typeLink.isDeclarator == false) { 
               specifier = (Specifier)typeLink.getTypeObject();
               if (specifier.getType() == Specifier.STRUCTURE) {
                   isStruct = true;
                   break;
               }
           }

           typeLink = typeLink.toNext();
       }

       if (isStruct == true) {
           //把结构体定义中的每个变量拷贝一份,存储到当前的symbol中
           StructDefine structDefine = specifier.getStructObj();
           Symbol copy = null, headCopy = null, original = structDefine.getFields();
           while (original != null) {
               if (copy != null) {
                  Symbol sym = original.copy();
                  copy.setNextSymbol(sym);
                  copy = sym;
               } else {
                   copy = original.copy();
                   headCopy = copy;
               }

               original = original.getNextSymbol();
           }

           symbol.setArgList(headCopy);
       }
   }

handleStructVariable 这个函数的作用就是把图一中的结构复制一遍,实现从图一到图二的转换。这样一来,当声明同一个结构体类型的不同变量时,就像我们的示例代码中,声明了两个结构体变量,分别是myTag,herTag, 那么对应v1的Symbol对象就有两份,对不同的v1赋值,实际上是把数值赋值到不同的Symbol对象中。

我们再看看对结构体变量的读写,例如语句:
myTag.v1 = 1;

当执行上面语句时,解释器先获得要读写的结构体变量对应域的名称,上面给定代码,要赋值的域的名称是”v1”, 然后在符号表中,找到变量名myTag对应的Symbol对象,然后找到Specifer,进而找到StructDefine对象,在该对象中,找到结构体里面各个变量所对应的Symbol队列,然后利用域的名称字符串“v1”,在队列中找到独有的Symbol对象,最后把数值1写入到该Symbol对象中。

相应代码如下:

public class UnaryNodeExecutor extends BaseExecutor{

    @Override
    public Object Execute(ICodeNode root) {
    ....
     case CGrammarInitializer.Unary_StructOP_Name_TO_Unary:
            child = root.getChildren().get(0);
            String fieldName = (String)root.getAttribute(ICodeKey.TEXT);
            symbol = (Symbol)child.getAttribute(ICodeKey.SYMBOL);

            Symbol args = symbol.getArgList();
            while (args != null) {
                if (args.getName().equals(fieldName)) {
                    break;
                }

                args = args.getNextSymbol();
            }

            if (args == null) {
                System.err.println("access a filed not in struct object!");
                System.exit(1);
            }

            root.setAttribute(ICodeKey.SYMBOL, args);
            root.setAttribute(ICodeKey.VALUE, args.getValue());
            break;
    ....
    }
}

如果通过结构体对应成员的名字字符串,在StructDefine中的Symbol队列中找不到给定名字的Symbol对象,这表明程序要访问结构体定义中不存在的变量,从而我们的程序就会因此种异常而退出。

本节内容较为复杂,请参看视频获得更详实的讲解和代码调试演示。

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

java开发C编译器:结构体的解析和执行_c语言_03


标签:java,变量,Symbol,struct,v1,编译器,解析,symbol,结构
From: https://blog.51cto.com/u_16160261/6476214

相关文章

  • 用java做操作系统内核:软盘读写
    在前两节,我们将一段代码通过软盘加载到了系统内存中,并指示cpu执行加入到内存的代码,事实上,操作系统内核加载也是这么做的。只不过我们加载的代码,最大只能512byte,一个操作系统内核,少说也要几百兆,由此,系统内核不可能直接从软盘读入系统内存。通常的做法是,被加载进内存的512Byte程......
  • java开发系统内核:caps 按键处理
    更详细的讲解和代码调试演示过程,请参看视频LinuxkernelHacker,从零构建自己的内核上一节,我们成功实现了对shift按键的处理,这一节,我们看看如何处理caps按键,当该键按下时,输入系统的字符在大小写间切换。由于我们系统启动后,默认输入是大写字符,完成本节后,我们把系统的默认字符改成......
  • java开发系统内核:像Linux一样使用中断实现内核API
    我们当前提供的内核API有个问题,就是每次使用时,需要计算API函数在内核中的位置,一旦内核代码改变,API接口的位置也会改变,同时调用API的应用程序也必须跟着改变,显然这种限制是不可接受的。为了突破当前缺陷,我们必须想出新的API提供办法。常用的做法是,仿照Linux将API当做一个中断调用,由......
  • java开发系统内核:使用C语言开发系统应用程序
    更详细的讲解和代码调试演示过程,请参看视频用java开发C语言编译器更详细的讲解和代码调试演示过程,请参看视频如何进入google,算法面试技能全面提升指南如果你对机器学习感兴趣,请参看一下链接:机器学习:神经网络导论更详细的讲解和代码调试演示过程,请参看视频LinuxkernelHacker,......
  • java开发C语言编译器:JVM 的基本操作指令介绍及其程序运行原理
    更详细的讲解和代码调试演示过程,请参看视频用java开发C语言编译器更详细的讲解和代码调试演示过程,请参看视频如何进入google,算法面试技能全面提升指南如果你对机器学习感兴趣,请参看一下链接:机器学习:神经网络导论更详细的讲解和代码调试演示过程,请参看视频LinuxkernelHacker,......
  • 编译原理动手实操,用java实现一个简易编译器-语法解析
    语法和解析树:举个例子看看,语法解析的过程。句子:“我看到刘德华唱歌”。在计算机里,怎么用程序解析它呢。从语法上看,句子的组成是由主语,动词,和谓语从句组成,主语是“我”,动词是“看见”,谓语从句是”刘德华唱歌“。因此一个句子可以分解成主语+动词+谓语从句:句子-->主语+动词+谓语......
  • java开发系统内核:进程的挂起和恢复
    有了进程的自动调度后,接下来的任务在于,如何将空闲进程挂起,空闲进程往往是那些没有具体任务需要处理的进程,因此,如果继续让其运行的话,那么必然会耗费宝贵的CPU资源,如果能让它先挂起,等到它需要执行具体任务时,再把它调度到前台,那才是一种合理的进程管理机制。我们实现的进程调度,是依赖......
  • java开发C语言编译器: return 语句的解释和执行
    在C语言程序中,很多函数并不是执行全部语句后,才从最底部返回的,大多数情况下,当某些条件成立时就可以通过return语句立即返回,而无需执行接下来的代码,本节,我们继续增强java开发的C语言解释器功能,使其能够处理return语句,完成本节代码后,我们的C语言解释器能够正常解析和执行下面的代码:in......
  • java开发系统内核:自动化进程切换
    我们已经通过时钟中断完成了两个进程间的相互切换。但当前实现有很大的缺陷,例如我们只能在两个指定的进程间切换,如果要想增添新的进程,那么,没增加一个进程,按照当前模式,我们只能再增加相应代码,这显然是不可接受的。因此,这节,我们希望完成进程的切换机制,使得有新进程时,我们无需改动代码......
  • java开发系统内核:实现进程自动切换,再现Linus当年辉煌一刻
    Linux操作系统内核于1991年10月5日被LinusBenedictTorvalds所开发,从此后,世界软件史揭开了新的帷幕,我们现在很多伟大的软件项目,都构建在Linux的基础之上,不说用于支撑谷歌,阿里,百度等巨头业务的后台大型服务器,现在风靡世界的安卓操作系统,也是构建在Linux之上的,可以说,没有当年Linux......