• 从头学习compiler系列2——COOL语言学习1


            
        Cool语言是一个面向对象的语言。虽然比较小型,但也包含了许多现代语言的特性,如对象、强类型和自动化内存管理。如果你之前熟悉c++、java等面向对象语言,那么会很容易上手。
        学一门新语言,最急迫想知道的就是,如何编写“hello world”程序,下面就来尝个鲜。
        class Main inherits IO {
           main(): SELF_TYPE {
            out_string("Hello, World.\n")
           };
        };
        打眼一看,外形很像java,其实意思也很相近了。下面我来细细详谈。全文大部分翻译cool语言手册。
     

    1,类结构 classes

        一个Cool程序是由许多的类组成,每个类的定义必须在一个源文件里。但是多个类可以定义在一个源文件中。类的形式:
        class <type> [ inherits <type> ] {
            <feature_list>
        };
        <type>代表类名,[]里的内容是可选项。{}内是feature集合。所有类名都是全局可见的。类名是以大写字母开头的字符串。类不能重复定义(不能有两个类名字相同)。
        “hello world”例子,就只有一个类,名字是Main。
        

    2,features

        feature也就是我们说的函数和属性的统称。一个类是由数个features组成。
     

        2.1 函数 methods

        函数结构如下:
        <id> ( <id> : <type> , ... , <id> : <type>) : <type> { <expr> }
        <id>是标示符,也就是名字。<type>是类型。<expr>是表达式。()里面是函数的形参定义。例如:
        add ( a   :  Int, b : Int ) : Int {
            a + b
        }
        add是函数名,a和b是参数名字,":"后跟着的是参数的类型。") :"后跟着的是返回类型。{}里面就是函数内容,也就是表达式组成。表达式后面会讲到。
        类函数是外部可见的,也就是外部可以调用类的任何函数。        
        函数参数可以有1个或更多,也可以没有,并且参数的名字不能相同。函数的返回值必须明确给出。如果参数的名字和类属性名字相同,那么就会隐藏掉类属性。也就是说在这个函数里,参数和类属性名字相同,那么同名用的都是参数。

        2.2 属性 attributes

        属性结构如下:
        <id> : <type> [ <- <expr> ];
        <id>是属性名,<type>是属性类型。[]是可选的,属性的初始化。也就是把<expr>表达式的值赋给<id>。例如:
        class A {
            x : Int <- 1;
        }
        类A有一个属性为Int型的x,初始化为1。
        初始化操作是在新建立一个类的对象的时候进行。在一个类内,属性初始化的顺序就是定义的顺序。类属性是局部变量,是类私有的。外部不能直接访问类的属性,只能通过函数去修改值。这也体现了现代OO语言的封装性。
        所有的属性,如果没有指定初始化,那么默认初始为void。void类似于c语言里面的NULL,或者java里的null。不过在Cool语言里没有void的定义。唯一一个创建void值得方法就是在某个类里建立一个变量,除了Int,String,Bool类,并让这个void初始化,或者保存while循环的结果。有一个表达式isvoid expr,用来判断expr的值是不是void(会在之后表达式里讲到)。
        基础类Int,Bool和String的属性定义和初始化是特殊的,会在后面基础类里讲到。
     

    3,继承 inherits

        继承的结构如下:
        class C inherits P { ... }
        类C是类P的子类,类P是类C的父类。
        类C除了自己定义的features,还继承了所有父类P的所有features。如果子类父类定义了相同的函数,那么子类函数有更高的优先级。子类父类不定定义相同名字的属性。
        有一个特殊的类Object(后面基础类会讲到)。如果一个类没有继承任何类,那么会默认的继承类Object。子类最多只能继承一个父类,这也叫做“单继承”。根据类的父子关系,可以定义一个集成体系图。这个图不能包含环。例如:类C继承类P,类P就不能继承类C。如果类C继承类P,那么类P必须在程序中有定义。因为是单继承,所以形成的图是一个树形结构,根节点就是类Object。
        新对象建立,所有的基类的属性也都要初始化操作,基类优先,逐步向外。
        为了确保类型安全,继承的函数有一些限制。规则很简单:如果子类C从父类P继承了函数f,那么类C必须覆盖类P中f的定义,参数和返回类型必须保持一致。
        class P {
            f(): Int { 1 };
        };
        class C inherits P {
            f(): String { "1" };
        };
        如果p是动态类型类P的对象,那么p.f() + 1的值就是2。但是如果p赋值为类C的对象,那么结果为string + Int,也就会出现问题。也就是如果子类随意的覆盖父类的函数,那么就不是对父类函数行为的扩展,继承也就没有作用。
     

    4,类型 types

        在Cool语言里,每一个类名也是一个类型。SELF_TYPE类型用在比较特殊的场合。
        类型声明通过x : C的方式。x是变量,C是类型。在使用一个变量前,这个变量必须声明过。
        在Cool里有一个基本规则。如果类C继承类P,那么用到P的值地方,也可以用C的值代替。
        如果类C的值可以代替类P的值,那么我们说C从属于P,或者表示为C《 P(可以认为类C是继承树的位置更低)。就像上面所说的那样,从属关系根据的是继承树。
        定义,A,C和P都是类型:
            A 《 A
            如果 C继承P,那么C《 P
            如果A《 C,并且C 《 P,那么A 《 P
            因为Object是继承树的根节点,所有对于所有类型A,都有A 《 Object
        

       4.1 SELF_TYPE

            SELF_TYPE就是self变量的类型。在继承的时候很好用,尤其是当避免写类的具体类型。举个例子:
            class Silly {
                copy() : SELF_TYPE { self };
            };
            class Sally inherits Silly { };
            class Main {
                x : Sally <- (new Sally).copy();
                main() : Sally { x };
            }; 
            Silly的copy函数返回值的SELF_TYPE,那就意味着返回的是类调用者的类型。所以子类Sally调用copy函数,返回的也是Sally,而不是Silly。
            我们可以注意到,SELF_TYPE类型并不是固定的,是根据相关类哪里出现。
            还有一个用法,就是: new SELF_TYPE,可以作为函数的返回类型,或者let变量的类型声明,或者属性的声明。除此之外,其它的用法是错误的。
     

        4.2 类型检查 Type Checking

            Cool的类型检查系统保证在编译期通过的程序,不会在运行时候出现类型错误。程序员需要提供变量的类型,然后类型检查器就会据此在程序用到的地方来推断类型。
            在编译期,类型检查器给变量定义类型,我们叫它表达式的静态类型;在运行期一个表达式的计算的类型值,称为动态类型。
            有这种区别是因为,在编译器,类型检查器没有在运行期时的动态数据,所以一般区别这两种类型。然而我们关心的是静态类型,可以保证程序运行时正确性。
     

    5,表达式 expressions

        5.1 常量 Constants

            最简单的表达式就是常量。布尔常量是true或false。整型常量是无符号的字符串,如:0,123,007. 字符串常量是被两个双引号包围的字符序列,例如:"this is a string." 字符串之多只能有1024个字符长度。
            这三个常量分别对应三个基础类:Bool,Int,String。

        5.2 标示符 Identifiers

            局部变量的名字,函数的参数名字,self,和类的属性都是标示符。self标示符可以被调用,但是不能给self赋值,或者在let、case或者函数参数里绑定它。也不允许把类属性命名为self。
            局部变量和函数参数都有词法控件约束。类属性的作用域在那个类或继承的类里,尽管它可能被同名局部变量锁隐藏。绑定的标示符作用在声明的最内层空间。一个例外就是self,永远表示的每个类。

        5.3 分配 Assignment

            形式结构如下:
            <id> <- <expr>
            expr表达式的静态类型必须从属于id的声明类型。分配表达式的值就是expr的值,分配表达式的静态类型就是expr的静态类型。

        5.4 函数调用 Dispatch

            在Cool里,有三种形式的函数调用。之间的区别取决于如何选择调用函数。最常见的形式是:
            <expr>.<id>(<expr>, ... ,<expr>)
            考虑一个一般的形式 e0.f(e1, ... ,en). 要计算整个表达式的值,先要计算参数的值。先从e1开始,从左到右直到en计算出值。下一步计算e0的值,查看e0属于哪个类C(e0值如果是void,那么就会产生一个运行时错误)。最后,调用类C的函数f,并且e0的值绑定到函数f的self变量,实参绑定到形参里。Dispatch表达式的值,就是函数的返回值。
            函数调用的类型检查包括很多步。假设e0的静态类型是A。那么A必须有一个函数f,这个f的参数和调用的参数数量必须相同,并且每个参数对应的静态类型也必须符合。
            如果函数返回类型B,并且B是一个类名。那么Dispatch表达式的静态类型就是B。相反,如果函数f返回类型为SELF_TYPE,那么Dispatch的静态类型就是A。因为函数f里的self变量绑定的是类A,返回SELF_TYPE类型,所以我们推断返回类型的结果就是类A。
            其它的两种调用形式:
                <id>(<expr>, ... ,<expr>)
                <expr>@<type>.id(<expr>, ... ,<expr>)
                前一种形式是 self.<id>(<expr>, ... ,<expr>)的简写形式。
            后一种形式提供了一个方法,用来调用父类的函数。如果子类覆盖了父类的函数,那么子类里只能调用子类定义的函数,父类函数因为隐藏掉了。这种形式指定了类型,所以就可以调用继承关系任意类的函数。例如:e @B.f()调用类B的函数f。但是e的类型必须从属于类B,也就是说必须是类B或B的子类。

        5.5 条件 Conditionals

            条件的形式如下:
            if <expr> then <expr> else <expr> fi
            一个标准的条件语法。先判断第一个expr的值,如果是true,那么就计算then后expr的值;如果是false,那么就计算else后expr的值,fi表示条件语句结束。条件语句的值,就是所属分支的值。
            第一个expr的静态类型必须是类型Bool,各分支没有限制。至于条件表达式的类型,我们先定义一个符号“U",让A,B,D为出了SELF_TYPE的任意一个类型。那么有:
                A U B = C (C类型是A和B共同从属的最小类型,也就是A、B相同的最小父类,小的意思是子类比父类小)
                A U A = A
                A U B = B U A
                SELF_TYPE U A = D U A  (假如SELF_TYPE指定类型D)
            T代表为真的表达式的静态类型,F代表为假的表达式的静态类型。那么整个条件表达式的静态类型为T U F。
     

        5.6 循环 Loops

            循环的形式如下:
            while <expr> loop <expr> pool
            先计算第一个expr的值,如果类型是true,那么计算第二个expr值,然后回到第一个expr计算;如果false,那么整个循环表达式返回void。第一个expr的静态类型必须是Bool。循环表达式的静态类型是类Object。

        5.7 代码块 Blocks

            代码块的形式如下:
            { <expr>; .. <expr>;}
            表达式的计算顺序是从左到右。每一个代码块必须至少有一个表达式;代码块的值就是最后一个表达式的值。代码块的静态类型是最后一个表达式的静态类型。
            在Cool里,很容易把";”号用错。分号是表达式的终结符,不是表达式的分隔符。详细例子以后遇见了会介绍。

        5.8 Let 表达式

            let表达式形式如下:
            let <id1> : <type1> [ <- <expr1> ], ... , <idn> : <typen> [ <- <exprn> ] in <expr>
            初始化的表达式如<expr1>是可选择的。如果有,先计算<expr1>的值,绑定到<id1>;然后在计算<expr2>,绑定到<id2>。如此顺序直到<idn>。接下来进行let的主体,in后面的<expr>的计算。let的值也就是主体表达式的值。
            <id1>到<idn>只有在let的主体里才是可见。并且当m > k对于<idm>,<idk>是可见的。
            如果let定义的标示符名字重复,那么前面定义的会被隐藏掉。每个let必须至少声明一个标示符。
            每个初始化表达式的类型必须从属于标示符声明的类型。主体表达式的类型就是let的类型。

        5.9 分支 Case

            case形式如下:
            case <expr0> of
                <id1> : <type1> => <expr1>;
                . . .
                <idn> : <typen> => <exprn>;
            esac
            case提供了运行时的检测。首先,计算<expr0>的动态类型为C(如果类型为void,那么会报运行时错误)。下一步,找出符合条件的分支。当C 《 <typek>,那么就选择这个类型的分支。<idk>绑定<expr0>的值,然后计算<exprk>。如果没有一个分支符合,那么产生一个运行时错误。每个case必须至少包含一个分支。
            设Ti为<expri>的静态类型,那么T1 U T2 U ... U Tn就是case的静态类型。分支里声明的<idk>,作用域在<exprk>里,并且隐藏了外部同名的标示符。
            case没有特殊的结构代表“default”或“otherwise”的分支。有一个相同的效果是包含分支:
                x : Object => ...
                因为每个类型都有 《 Object。
            case提供程序员一个进行运行时类型检测的方法,之前的都是类型检测器推断的保守的静态类型。一个典型的应用场合:程序员写一个表达式e,并检测e是否有静态类型P。然而事实上这个程序员可能知道e的动态类型总是C(C 《 P)。如下写法可以捕获这个信息:
                case e of x : C => ...
            在这个分支里,x绑定了更具体的静态类型C的e表达式的值。

        5.10 New 表达式

            new的形式如下:
            new <type>
            根据<type>申请一个新的类。如果类型是SELF_TYPE,那么会根据当前空间self绑定的对象类型来建立。new的静态类型为<type>。

        5.11 Isvoid

            isvoid的形式如下:
            isvoid expr
            如果expr值为true,那么isvoid为true;如果expr值为not void,那么isvoid为false。
     

    6,基础类 basic classes

        Object

            Obejct类是继承树的根节点类。定义了以下函数:
                abort() : Object
                type_name() : String
                copy() : SELF_TYPE
            abort()函数会终止程序,并携带错误信息。type_name函数返回类的名字。copy函数是对自身的一个浅拷贝(浅拷贝就是拷贝自身,不进行指针指向的内存拷贝)。

        IO

            提供了一些简单的输入输出操作函数:
                out_stirng( x : String ) : SELF_TYPE
                out_int( x : Int) : SELF_TYPE
                in_string() : String
                in_int() : Int
            out_string和out_int函数打印参数并返回self。in_string函数从标准输入读入一个字符串,直到有一个换行符。in_int函数读入一个整型,直到一个空格。任何在整型之后的字符,直到并包括下个换行符,那么这个整型会被丢掉。
            一个类可以继承IO来用IO的函数。不能重新定义IO类。

        Int

            Int类是提供整型。这个类没有函数。默认初始化时0(不是void)。不能继承或重定义Int。

        String

            String类提供字符串。定义了以下函数:
                length() : Int
                concat( s : String) : String
                substr( i : Int, l : Int) : String
            length函数返回self参数的长度。concat函数连接self和s字符串。substr函数返回self参数从位置i到l的字串(从0 开始计算)。如果超界了,会产生一个运行时错误。
            默认的初始化为""(不是void)。不能继承或重定义String。

        Bool

            Bool类提供true和false。默认的初始化是false(不是void)。不能继承或重定义Bool。
     

    7,Main Class

        每个程序必须有一个类Main,并且Main里必须有一个没有参数的函数main。main函数必须在Main里定义(不能通过继承其它类)。程序通过(new Main).main()来执行。

    8,注释 Comments

        Cool里有两种注释。
        一种是以"--"开头,直到行尾都被注释掉。
        一种注释以"(*"和"*)"包围着。
     
    —— Pinkman



  • 相关阅读:
    在日本被禁止的コンプガチャ設計
    Starling常见问题解决办法
    Flixel引擎学习笔记
    SQLSERVER中修复状态为Suspect的数据库
    T4 (Text Template Transformation Toolkit)实现简单实体代码生成
    创建Linking Server in SQL SERVER 2008
    Linq to Sql 与Linq to Entities 生成的SQL Script与分页实现
    Linq to Entity 的T4 模板生成代码
    在VisualStudio2008 SP1中调试.net framework 源代码
    使用HttpModules实现Asp.net离线应用程序
  • 原文地址:https://www.cnblogs.com/pinkman/p/2954902.html
Copyright © 2020-2023  润新知