[符號表]靜態地址管理符號表接口及使用原則

    進入語義分析的首要任務是組織和管理變量符號。這一點有多種選擇,一種是類C/C++語言那樣的靜態處理,即將變量映射爲地址,運行時內存中根據地址取得變量值,這樣運行效率無論是從內存使用量和訪問速度上都會較高;而另一種就是可反射的符號表,即在運行時根據變量名在符號表中取得變量值,這樣做的典型是Python語言,雖然這樣效率較低,不過好處是方便ORM等反射機制的實現。不過既然現在Jerry並沒有意向弄得那麼複雜,因此就採用前一種。

    首先,對於這樣的符號表,基本的操作包括:

    - 聲明變量,將變量存入符號表

    - 取得變量類型

    - 取得變量首地址

    - 對於數組,取得變量某一維的大小

第一個是必須的毫無疑問。不過有一個問題,那就是調用該函數,傳入的參數是什麼。是一個DeclarationNode嗎?當然這是可以的,但是這樣就違反了數據封裝的原則,即在設計該函數時必須對DeclarationNode的內部結構有了解,這加大了難度。所以可以考慮其參數全部爲基本數據類型。包括其它函數也是這樣,在取得變量信息時全部使用基本數據類型,而不用VariableNode之類的東西。

void regVar(char* ident, AcceptType type, int nrDim, int* dims);
AcceptType getVarType(char* ident);
int getVarAddr(char* ident);
int getVarNrDim(char* ident);
int getVarDimSize(char* ident, int index);

其中函數的作用分別是

    regVar        在符號表中註冊一個變量,給出標識符、類型及維度信息

    getVarType    根據標識符獲取變量類型

    getVarAddr    根據標識符獲取變量首地址

    getVarNrDim   根據標識符獲取變量的維度數

    getVarDimSize 根據標識符和某一維的大小

當然,第一個函數就沒什麼好說的了。第二個函數獲取類型是爲了做靜態上下文類型判斷與轉型,如 i 是一個整型變量而 j 是一個實型變量,如有一條語句爲

    i = j;

那麼在運行時會將先將 j 的值放在棧頂,然後轉換成整型,再賦給 i 所在的地址。getVarAddr則是用來獲取變量在內存中的首地址,對於數組變量也是這樣,數組變址則要靠最後一個函數。getVarNrDim函數用以獲取變量的維度,在訪問變量時,首先要對其維度數量進行匹配(因爲Jerry沒有考慮支持數組,所以引用時的變量維度數量必須跟聲明時一樣)。現在來看最後一個函數。當對一個數組變量進行合法引用時,比如

    i[1][2][x]

這時爲了獲取這個變量的地址,該怎麼辦呢?注意到最後一維的下標是個變量,這也就意味着無法通過靜態計算來獲得,必須動態綁定。過程是這樣的,首先查詢 i 在聲明時各維度大小,假設是d0d1d2i 的首地址是a0,那麼首先計算 i[1][2] 的地址:

    a0d1 * d2 * 1 + d2 * 2

在運行時再將這個地址與 x 的值相加,就得到了該變量的值,至於取得d0d1d2這些信息,就得靠函數getVarDimSize了。(注意:以上運算並不正確,因爲地址偏移並未考慮到每個該類型變量佔用的字節數,必須在運算時乘算該大小因子。)爲了避免每次在獲取數組高維度的偏移時都要重複計算(如剛纔例子中最高維度的偏移是d1 * d2),可以在調用regVar時,讓符號表中存放的數組維度信息就是偏移,而不是每個維度的大小。

 

當然符號表管理並不是這樣就好了,以上這些接口是相當理想化的接口,它無法處理兩件事:

    - 出錯

    - 內層變量覆蓋外層變量

爲了對付出錯,那麼就得讓函數有所“表示”,最基本的方法就是返回錯誤值,那麼原來的返回值呢?答案是,將其地址作爲參數傳入,這是一種基本的策略,C語言庫函數中的fgets也是這麼幹的,我們可以效仿之。至於花括號內外層這檔子事兒,就必須採用使用多個符號表,並附加一個符號表棧來實現。一開始該棧裏面有一個符號表,當到達基本塊開頭(也就是遇到正花括號)時,壓入一個新的符號表進棧,直到基本塊結束纔將其彈出棧,當要獲取變量信息時,依次從棧頂向下逐個對符號表進行查詢,直到找到該變量,或者到達棧底的樓下(這時報錯)。不過關於符號表棧這東西,並不由符號表本身來考慮,應該放在語義分析的控制部分或者中間插入一個變量管理層進行。對於符號表,現在比較合理的接口形式是(struct SymbolTable表示符號表結構體)

/* 符號表操作過程中可能的錯誤 */
/* const.h */
typedef enum {
    SymTab_OK = 0, // 無錯誤
    SymTab_MultiDef, // 重複定義(在同一基本塊內, 內外層則會覆蓋)
    SymTab_NotDef, // 找不到定義
    SymTab_SizeExceeded, // 大小超限, 由於聲明的變量太多了或數組太大了
    SymTab_ArrDimOutOfRange // 數組維度訪問越界, 請求維度大於維度數量
} SymbolTableError;

/* 新建一個符號表 */
struct SymbolTable* newSymbolTable(int base);
/* 讓符號表退休 */
void finalizeSymbolTable(struct SymbolTable* table);

/* 參數名爲 ret 的都是返回值,現在它們由地址傳入,函數內部將返回值寫入地址 */
SymbolTableError regVar(struct SymbolTable* table, char* ident,
                        AcceptType type, int nrDim, int* dims);
SymbolTableError getVarType(struct SymbolTable* table, char* ident,
                            AcceptType* ret);
SymbolTableError getVarAddr(struct SymbolTable* table, char* ident,
                            int* ret);
SymbolTableError getVarNrDim(struct SymbolTable* table, char* ident,
                             int* ret);
SymbolTableError getVarDimSize(struct SymbolTable* table, char* ident,
                               int* ret, int index);

不需要考慮符號表的繼承(這難道還面向對象?),所以這些函數就這麼靜態弄,不用變成成員函數來搞。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章