從頭學習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




免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM