1. 程式人生 > >自制指令碼語言(10) 抽象語法樹AST與三地址線性IR

自制指令碼語言(10) 抽象語法樹AST與三地址線性IR

摘要:介紹了YF language程式語言的AST及IR表示

    根據前面定義的語法,基本上AST就已經被決定了。因為語法每reduce一次,要麼組建一個新AST,要麼在已有的AST上新增資料。這裡照慣例採用了訪問者模式,將Code Generator設為訪問者,遍歷全部AST。遍歷過程中,在每個AST結點呼叫3個過程:genSymbolTable( ), checkType( ), genIRCode( ),分別是生成符號表,檢查型別,生成IR。因為在第一遍生成符號表的時候,很可能引用到的符號是後文出現的未知的,比如後面定義class A,前面是不能判斷A是否合理的型別。我並不想用一趟生成符號表和檢查型別,太複雜,而兩趟完成比較容易實現。

    符號表,我的設計是分為變量表、型別表、函式表三種,目的是省略了一個判斷過程,其實全部放進一個表也行。我將符號表放置在AST內,這樣的好處是利於定義可用範圍,因為Code Generator訪問進入AST後可以在裡面查詢符號,省略了用額外的指標將符號表與某個AST相關聯來確定訪問範圍。checkType過程中查詢符號,如果本層AST查詢不到,那麼就向上查詢,這個訪問鏈都在Code Generator的維護中。

    IR是三地址碼,也就是說,一條三地址碼包含4個元素,大部分情況下,第一個是操作符,第二個是返回地址,第三、四個是操作物件的地址。IR對應的虛擬機器模型有無限的暫存器,所以,臨時變數用“%”+數字,既代表一個新的暫存器,也為將來升級為SSA形式做好準備,到時只需少量修改和加上phi函式就可以。IR中,保留了全部的型別資訊,即IR採用了高階形式而非偏底層,這樣也是為了將來便於新增更多的靜態分析和優化的功能。下面介紹各條IR。

    1,賦值語句 

mov,type obj src

type是引用型別,而非真實型別。obj和src分別代表源地址和目標地址。這裡往往都是"%"+數字的臨時地址。如果是有名稱的變數而非臨時變數,則用"$"+名稱作為地址。

    2,控制流,if/else/while

if bool_val goto1 goto2    while bool_val goto1 goto2

bool_val是布林型別的臨時變數。if句是根據bool_val的值,轉移到goto1或goto2。這兩個數字代表轉移後的IR行號。while句同理。 

    3,包內型別

PkgType type_val type_name package

type_val是一個臨時地址,代表指向package.type_name的型別。

    4,陣列型別

ArrType type_element element_type dim

element_type是元素型別,dim是維數。type_element指向一個數組型別。陣列作為一種內建型別,由元素型別和維數組合而成。

    5,引數化型別

GnrcType type_val type_name generic_arguments

得到一個臨時地址type_val,指向type_name<generic_arguments>型別。generic_arguments是多個型別名用“,”連線起來的字串,也就是泛型引數。

    6,函式定義

記錄入函式表

    7,類定義

記錄入型別表

    8,介面定義

記錄入型別表

    9,新陣列定義

newArr type vadim_val

val指向新陣列頭。type是元素的型別,dim_val是多個數字用“,”連線的形式,表示多維陣列的大小。虛擬機器會開闢dim1*dim2*dim...大小的連續空間,首地址為rst_val。

    10,物件初始化

newObj  type val args_val

type是新物件的型別,往往是class類,val是其返回地址,argrs_val是多個用“,”連線的引數名稱。

    11,布林運算

AND val bool_1 bool_2OR val bool_1 bool_2

    12,比較運算

LT/GT/LE/GE/EQ/NE val bool_1 bool_2

    13,加減乘除

add_i/sub_i/mul_i/div_i val opd1 opd2    add_d/sub_d/mul_d/div_d val opd1 opd2

字尾_i、_d分別代表整數與浮點數。

    14,自加自減

inc_1 val null nulldec_1 val null null

區分前後綴++的情況。後++多一條mov語句,將原值返回到一個臨時變數。

    15,型別轉換

cast type1->ref_type2 obj src

type1->ref_type2是兩個型別名用“->”連線,表示前後型別。同時記錄兩種型別,因為有可能src是個立即數,那麼虛擬機器要分析詞法來得到這個立即數。

    16,陣列取址

getArray val pre_val dim_val

    17,物件屬性取址

getField val pre_val var_name getClass val pre_val null

前者對應a.b的情況,後者對應A.class的情況,這也是為實現反射做好準備。

    18,方法/函式取址

getFunc ptr_function scope function_name:type_signature

prt_function指向返回的函式。scope表示查詢函式的範圍,有兩種特殊情況,this和global。global代表呼叫的是全域性函式而非方法,this表示查詢caller本身的方法。function_name:type_signature是一個“:”符號連線函式名和引數型別簽名,用於在函式表中查詢,因為過載函式是通過型別簽名來區分的。

    19,方法/函式呼叫

pushGArgs ptr_func gnrc_args null 

泛型函式的型別引數入棧

pushFArgs ptr_func arg_lst null

函式的實參入棧

pushThis ptr_func scope

如果函式的caller實際上是隱藏的this引數,那麼添上這一句

invoke ptr_func rst_val rst_type

最終呼叫prt_func地址指向的函式。rst-val是返回的臨時變數,rst_type是返回的臨時變數型別。

完成。

原始碼地址:https://github.com/nklofy/Compiler