编译器开发系列--Ocelot语言3.类型名称的消解

“类型名称的消解”即类型的消解。类型名称由TypeRef 对象表示,类型由Type 对象表示。类型名称的消解就是将TypeRef 对象转换为Type 对象。

TypeResolver 类的处理仅仅是遍历抽象语法树,发现TypeRef 的话就从叶子节点开始将其转换为Type 类型。类型和变量的不同之处在于没有作用域的嵌套(作用域唯一),因此没
有必要使用栈。
【TypeRef 对象和Type 对象的对应关系保存在TypeTable 对象中。】

其中Type为类型的定义。struct point { int x; int y; }; 是类型的定义。

TypeRef为类型的名称。struct point 是类型的名称,之所以特意将Type 类和TypeRef 类分开,是因为在类型定义之前就可以编写用到了该类型的代码。也就是说,可以编写如下所示的代码,C 语言中是不可以编写这样的代码的:

	struct s var;
	struct s {
		int memb;
	};

类型名称的消解入口:

    /*入口
     *
     */
    // #@@range/resolveProgram{
    public void resolve(AST ast) {
    	/*
    	 * 首先调用defineTypes 方法,根据代码中定义的类型生成Type 对象,并保存到
			TypeTable 对象中。通过import 导入的类型定义也在这里处理。
    	 */
        defineTypes(ast.types());
        /*类型和抽象语法树的遍历.
         * 但defineTypes 方法不处理结构体成员的类型等TypeRef 对象。将抽象语法树中已有
			的TypeRef 转换成Type 的处理将在下面的foreach 语句中执行。如果这两部分处理不分开进
			行的话,在处理递归的类型定义时程序会陷入死循环。

			ast.types()--源文件内外的类型定义
         */
        // #@@range/resolveProgram_core{
        for (TypeDefinition t : ast.types()) {
            t.accept(this);
        }
        /*
         * 第2 个foreach 语句将使用import 从文件外部读入的定义、全局变量以及函数等所有剩余
			的TypeRef 转换为Type。

			ast.entities()--用import 导入的变量和函数的声明,以及源文件内的变量和函数的定义
         */
        for (Entity e : ast.entities()) {
            e.accept(this);
        }
        /*
         * 上面两个for循环遍历在源文件内外定义的所有类型、变量、函数,将其中所包含的TypeRef 对象
			全部转换为Type 对象。
         */
        // #@@}
    }

首先对ast.types(),即StructNode(结构体定义)、UnionNode(联合体定义)、TypedefNode(用户类型定义)执行defineTypes:

    /*类型的声明.
     * defineTypes 是将类型定义添加到TypeTable 对象的方法
     */
    // #@@range/defineTypes{
    private void defineTypes(List<TypeDefinition> deftypes) {
    	/*
    	 * 使用foreach 语句将deftypes 中的TypeDefinition 对象逐个取出, 将def.
			typeRef() 和def.definingType() 关联成对, 用typeTable.put 方法添加到
			typeTable 中。def.typeRef() 返回的是该TypeDefinition 对象要定义的类型的
			TypeRef(类型名称)。def.definingType() 返回的是该TypeDefinition 对象要定义的
			Type(类型)。
    	 */
        for (TypeDefinition def : deftypes) {
        	/*
        	 * 但如果typeTable.isDefined() 为true 的话,说明这个TypeRef 已经存在,这种情
				况下取消添加处理并输出错误消息。
        	 */
            if (typeTable.isDefined(def.typeRef())) {
                error(def, "duplicated type definition: " + def.typeRef());
            }
            else {
            	/*
            	 * TypeDefinition 类是抽象类, 实际生成的实例是TypeDefinition 的子类
					StructNode、UnionNode、TypedefNode。StructNode 表示结构体的定义,UnionNode
					表示联合体的定义,TypedefNode 表示typedef 语句。
					StructNode#definingType:
					public Type definingType() {
						return new StructType(name(), members(), location());
					}

					调用TypeTable#put 方法将生成的StrcutType 对
					象添加到TypeTable 对象中。TypeTable 对象的内部保存有HashMap 对象, 因此
					TypeTable#put 方法只需简单地调用HashMap#put 即可。
            	 */
                typeTable.put(def.typeRef(), def.definingType());
            }
        }
    }

把上面三种类型的名称和类型都保存在typeTable中,注意typeTable初始化的时候已经自动把所有基本类型都put进去了。然后第一个for循环的三个visit方法:

    // #@@range/StructNode{
    public Void visit(StructNode struct) {
        resolveCompositeType(struct);
        return null;
    }
    // #@@}

    // #@@range/UnionNode{
    public Void visit(UnionNode union) {
        resolveCompositeType(union);
        return null;
    }
    // #@@}

    // #@@range/TypedefNode{
    public Void visit(TypedefNode typedef) {
        bindType(typedef.typeNode());
        bindType(typedef.realTypeNode());
        return null;
    }
    // #@@}

接着:

    public void resolveCompositeType(CompositeTypeDefinition def) {
        CompositeType ct = (CompositeType)typeTable.get(def.typeNode().typeRef());
        if (ct == null) {
            throw new Error("cannot intern struct/union: " + def.name());
        }
        for (Slot s : ct.members()) {
            bindType(s.typeNode());
        }
    }

    /*
     * 首先,用TypeNode#isResolved 方法检查是否已经完成了转换,如果已经完成,则即
		刻使用return 结束处理。如果还未转换,用n.typeRef() 从TypeNode 中取出TypeRef,
		再用typeTable.get 转换为Type 对象, 然后将此Type 对象用n.setType 设置到
		TypeNode 中。
     */
    // #@@range/bindType{
    private void bindType(TypeNode n) {
        if (n.isResolved()) return;
        n.setType(typeTable.get(n.typeRef()));
    }

也很简单,resolveCompositeType是针对每种类型的成员的类型检查,关键的类是TypeNode,从它里面获取TypeRef(类型的名称),再通过类型的名称从typeTable获取已有的类型的定义。然后获取到当前类型的所有的成员变量,再将这个成员变量的类型的名称和定义通过bindType方法绑定起来。typeTable实际上是起到一个中转站的作用。

第二个for循环是将除了上面三种类型的所有剩余的TypeRef 转换为Type。比如:

    /*
     * 变量定义的类型消解.
     */
    // #@@range/DefinedVariable{
    public Void visit(DefinedVariable var) {
    	/*
    	 * TypeRef 对象基本上都存放在TypeNode 对象中。TypeNode 是成对地保存TypeRef 和
			Type 的对象,其目的在于简化TypeResolver 类的代码。
    	 */
        bindType(var.typeNode());
        if (var.hasInitializer()) {
            visitExpr(var.initializer());
        }
        return null;
    }

还有重要的函数类型:

    /*
     * 函数定义的类型消解.
     */
    // #@@range/DefinedFunction{
    public Void visit(DefinedFunction func) {
    	/*
    	 * 在函数定义中,如下这些地方存在TypeRef。
			1. 返回值的类型
			2. 形参的类型
			3. 函数体的代码中
    	 */
        resolveFunctionHeader(func);
        visitStmt(func.body());
        return null;
    }

    private void resolveFunctionHeader(Function func) {
    	/*
    	 * resolveFunctionHeader 方法的第1 行用于处理返回值的类型。func.typeNode()
			返回保存有返回值类型的TypeNode 对象,再调用bindType 方法将返回值的类型从
			TypeRef 转换为Type。
    	 */
        bindType(func.typeNode());
        /*
         * resolveFunctionHeader 方法从第2 行开始都是对形参进行的处理。用foreach 语句
         * 对func.parameters() 进行遍历,取出表示形参的Parameter 对象。然后用param.
			typeNode() 取出Parameter 对象中的TypeNode 对象,将TypeRef 转换为Type。
         */
        for (Parameter param : func.parameters()) {
            // arrays must be converted to pointers in a function parameter.
        	/*
        	 * 只有在将形参的TypeRef 转换为Type 时使用了TypeTable 类的getParamType 方法。
				它和通常的get 方法的区别在于数组的TypeRef 会被转换为指针的Type。C 语言(C?)中形
				参类型是数组的情况下完全等同于指针类型,因此在此处统一成为指针类型。
        	 */
            Type t = typeTable.getParamType(param.typeNode().typeRef());
            param.typeNode().setType(t);
        }
    }

首先调用resolveFunctionHeader方法,里面第一行是绑定函数的返回类型,然后一个for循环绑定函数的所有形参类型。然后再调用visitStmt(func.body());绑定函数体的所有类型:

    public Void visit(BlockNode node) {
        for (DefinedVariable var : node.variables()) {
            var.accept(this);
        }
        visitStmts(node.stmts());
        return null;
    }
时间: 2024-08-07 00:18:56

编译器开发系列--Ocelot语言3.类型名称的消解的相关文章

编译器开发系列--Ocelot语言2.变量引用的消解

"变量引用的消解"是指确定具体指向哪个变量.例如变量"i"可能是全局变量i,也可能是静态变量i,还可能是局部变量i.通过这个过程来消除这样的不确定性,确定所引用的到底是哪个变量. 为了消除这样的不确定性,我们需要将所有的变量和它们的定义关联起来,这样的处理称为"变量引用的消解".具体来说,就是为抽象语法树中所有表示引用变量的VariableNode 对象添加该变量的定义(Variable 对象)的信息. LocalResolver就是用来处理变量

编译器开发系列--Ocelot语言1.抽象语法树

从今天开始研究开发自己的编程语言Ocelot,从<自制编译器>出发,然后再自己不断完善功能并优化. 编译器前端简单,就不深入研究了,直接用现成的一款工具叫JavaCC,它可以生成抽象语法树,抽象语法树是生成中间代码的关键,而中间代码又是生成后端代码的关键. 整个编译器代码采用java语言编写,主要功能是对JavaCC生成的抽象语法树进行语义分析.优化,最后生成优化后的汇编代码,然后再用汇编器对汇编代码汇编生成机器码,最后再用命令链接生成Linux可执行文件,就可以直接在Linux上运行了. 整

编译器开发系列--Ocelot语言5.表达式的有效性检查

本篇将对"1=3""&5"这样无法求值的不正确的表达式进行检查. 将检查如下这些问题.●为无法赋值的表达式赋值(例:1 = 2 + 2)●使用非法的函数名调用函数(例:"string"("%d\n", i))●操作数非法的数组引用(例:1[0])●操作数非法的成员引用(例:1.memb)●操作数非法的指针间接引用(例:1->memb)●对非指针的对象取值(例:*1)●对非左值的表达式取地址 具体例子以及问题的检测

IOS开发系列--C语言之构造类型

概述 在第一节中我们就提到C语言的构造类型,分为:数组.结构体.枚举.共用体,当然前面数组的内容已经说了很多了,这一节将会重点说一下其他三种类型. 结构体 枚举 共用体 结构体 数组中存储的是一系列相同的数据类型,那么如果想让一个变量存储不同的数据类型就要使用结构体,结构体定义类似于C++.C#.Java等高级语言中类的定义,但事实上它们又有着很大的区别.结构体是一种类型,并非一个变量,只是这种类型可以由其他C语言基本类型共同组成. // // main.c // ConstructedType

IOS开发系列--C语言之指针

概览 指针是C语言的精髓,但是很多初学者往往对于指针的概念并不深刻,以至于学完之后随着时间的推移越来越模糊,感觉指针难以掌握,本文通过简单的例子试图将指针解释清楚,今天的重点有几个方面: 什么是指针 数组和指针 函数指针 什么是指针 存放变量地址的变量我们称之为"指针变量",简单的说变量p中存储的是变量a的地址,那么p就可以称为是指针变量,或者说p指向a.当我们访问a变量的时候其实是程序先根据a取得a对应的地址,再到这个地址对应的存储空间中拿到a的值,这种方式我们称之为"直接

IOS开发系列--C语言之生存储方式和作用域

概述 基本上每种语言都要讨论这个话题,C语言也不例外,因为只有你完全了解每个变量或函数存储方式.作用范围和销毁时间才可能正确的使用这门语言.今天将着重介绍C语言中变量作用范围.存储方式.生命周期.作用域和可访问性. 变量作用范围 存储方式 可访问性 变量作用范围 在C语言中变量从作用范围包括全局变量和局部变量.全局变量在定义之后所有的函数中均可以使用,只要前面的代码修改了,那么后面的代码中再使用就是修改后的值:局部变量的作用范围一般在一个函数内部(通常在一对大括号{}内),外面的程序无法访问它,

IOS开发系列--C语言之预处理

概述 大家都知道一个C程序的运行包括编译和链接两个阶段,其实在编译之前预处理器首先要进行预处理操作,将处理完产生的一个新的源文件进行编译.由于预处理指令是在编译之前就进行了,因此很多时候它要比在程序运行时进行操作效率高.在C语言中包括三类预处理指令,今天将一一介绍: 宏定义 条件编译 文件包含 宏定义 对于程序中经常用到的一些常量或者简短的函数我们通常使用宏定义来处理,这样做的好处是对于程序中所有的配置我们可以统一在宏定义中进行管理,而且由于宏定义是在程序编译之前进行替换相比定义成全局变量或函数

SharePoint 2013 图文开发系列之创建内容类型

原文:SharePoint 2013 图文开发系列之创建内容类型 SharePoint内容类型,是很有特色的,就好比发布新闻,同在一张列表里,可以由内容类型区分图片新闻.文字新闻等,顾名思义,就是在一张列表里发布不同类型的项目. 1.添加新项目,选择SharePoint 2013 空项目,如下图: 2.选择调试网站和解决方案类型,如下图: 3.添加新项,选择内容类型,如下图: 4.选择继承的父内容类型,如下图: 5.修改内容类型的Feature文件,如下图: 6.查看内容类型的xml文件,如下图

iOS开发系列--C语言之基础知识

概览 当前移动开发的趋势已经势不可挡,这个系列希望浅谈一下个人对IOS开发的一些见解,这个IOS系列计划从几个角度去说IOS开发: C语言 OC基础 IOS开发(iphone/ipad) Swift 这么看下去还有大量的内容需要持续补充,但是今天我们从最基础的C语言开始,C语言部分我将分成几个章节去说,今天我们简单看一下C的一些基础知识,更高级的内容我将放到后面的文章中. 今天基础知识分为以下几点内容(注意:循环.条件语句在此不再赘述): Hello World 运行过程 数据类型 运算符 常用