實(shí)現(xiàn)簡易的C語言編譯器(part 9)

? ? ? ? 這一部分,我們研究語義分析中剩下的的流程和類型檢查。

6.2 流程檢查

? ? ? ? 還是以我們前面舉例使用的那段源代碼作為例子,經(jīng)過聲明檢查的錯(cuò)誤提醒,可以改成:

int main()
{
    int a = 1;
    return;
}

很重要的一步。但是,這段代碼仍然是有語法錯(cuò)誤的,因?yàn)闆]有返回值。這個(gè)錯(cuò)誤將會(huì)很快檢查出來。
? ? ? ? 我們還是遍歷那棵抽象語法樹,與聲明檢查重點(diǎn)遍歷變量不同,流程檢查將重點(diǎn)遍歷語句。這一點(diǎn)是非常直觀,也是容易理解的。仿照聲明檢查的方法,我們?cè)俣x一個(gè)遍歷的類,然后定義將會(huì)用到的節(jié)點(diǎn)函數(shù)。

class FlowControlVisitor(NodeVisitor):
    def visit_AST(self, node):
        node.has_return = False

    def visit_TranslationUnit(self, node):
        self.visit_list(node.nodes)

    def visit_FunctionDefn(self, node):
        node.body.visit(self)
        if node.return_type != VOID and not node.body.has_return:
            raise Exception("Function doesn't return through all branches." )

    def visit_CompoundStatement(self, node):
        node.statements.visit(self)
        node.has_return = node.statements.has_return

    def visit_StatementsList(self, node):
        node.has_return = False
        for stmt in node.nodes:
            stmt.visit(self)

    def visit_ReturnStatement(self, node):
        node.has_return = True

為了實(shí)現(xiàn)返回值檢查,我們?yōu)檫@些節(jié)點(diǎn)定義了has_return的屬性。這個(gè)屬性從節(jié)點(diǎn)FunctionDefn逐漸滲透到模板節(jié)點(diǎn),只在遇到節(jié)點(diǎn)ReturnStatement時(shí)才返回真,這樣,當(dāng)再次回溯到FunctionDefn節(jié)點(diǎn)時(shí),就可以做出判斷,返回值檢查也就實(shí)現(xiàn)了。
? ? ? ? 此外,在流程檢查中,還有一類錯(cuò)誤需要關(guān)注:關(guān)鍵字breakcontinue只能在循環(huán)語句中才能使用。這部分內(nèi)容,我們重點(diǎn)關(guān)注循環(huán)節(jié)點(diǎn)及其相關(guān)的continue, break節(jié)點(diǎn):

    def __init__(self):
        self.in_loop = False

    def visit_ForLoop(self, node):
        self.in_loop = True
        node.stmt.visit(self)

    def visit_BreakStatement(self, node):
        if not self.in_loop:
            raise Exception('Break statement outside of loop.')

    def visit_ContinueStatement(self, node):
        if not self.in_loop:
            raise Exception('Continue statement outside of loop.')

通過引入成員變量in_loop,進(jìn)入函數(shù)體時(shí)初始化為假,而只在循環(huán)節(jié)點(diǎn)入口才設(shè)置in_loop為真,之后再遍歷到returnbreak節(jié)點(diǎn),語法就是正確的,否則語法錯(cuò)誤。但是,對(duì)于下面這種情況:

int main()      // in_loop = false  0
{
    int k = 0;
    for (int j = 0; j < 10; j = j + 1) {    // in_loop = true  1
        for (int i = 0; i < 10; i = i + 1) {    // in_loop = true  2
            if (i > 5)    
               break;
        }
        int i = 0;
        if (i < 5) {    // in_loop = true  1
            i = i + 1;
            continue;
        }
    }
    if (k < 5) {    // in_loop = false  0
        continue;
    }
}

continue出現(xiàn)在結(jié)構(gòu)體外,但是由于第一個(gè)循環(huán)體改變了in_loop的屬性而沒有復(fù)原,使得對(duì)continue的檢查將出現(xiàn)錯(cuò)誤。此外,也不能簡單地將其重置為假,因?yàn)檠h(huán)是可以嵌套的,否則將改變in_loop原來的正確狀態(tài)。因此,采用棧結(jié)構(gòu)對(duì)變量進(jìn)行保存:

    def visit_ForLoop(self, node):
        super_in_loop = self.in_loop
        self.in_loop = True
        node.stmt.visit(self)
        self.in_loop = super_in_loop

super_in_loop來暫時(shí)保存當(dāng)前的in_loop屬性,當(dāng)遍歷離開循環(huán)節(jié)點(diǎn)時(shí),又恢復(fù)到原來的狀態(tài)。
? ? ? ? 最后,合并has_returnin_loop的使用,就實(shí)現(xiàn)了流程檢查。

6.3 類型檢查

? ? ? ? 我們先來看下面這段C代碼:

int *a;
int b;
a = *b; // error 1
b = a; // error 2
a = &5; // error 3

這段代碼,我們列出了三個(gè)語法錯(cuò)誤,分別是:

  • error 1: b不是指針類型
  • error 2: 將指針類型轉(zhuǎn)換到int類型
  • error 3: 對(duì)右值取地址

這些就是類型檢查將要做的事情。我們?cè)谇懊婊ㄙM(fèi)很大力氣定義的那些類型節(jié)點(diǎn),它們將在這里進(jìn)行排上用場。同樣地,再定義一個(gè)遍歷類:

class TypeCheckVisitor(NodeVisitor):
    pass

在定義節(jié)點(diǎn)函數(shù)之前,我們先定義一個(gè)葉子函數(shù),也就是輔助判斷函數(shù),來幫助我們比較兩個(gè)變量的類型:

    def compare_types(self, from_type, to_type):
        conflict = False
        from_str = from_type.to_str()
        to_str = to_type.to_str()
        if from_str != to_str:
            if from_str == 'char':
                if to_str == 'int':
                    pass
                else:
                    conflict = True
            else:
                conflict = True

        if conflict:
            raise Exception(f"Cannot convert from {from_str} to {to_str}.")

通過函數(shù)to_str()(在類型節(jié)點(diǎn)中添加為成員函數(shù))獲取將要比較的兩個(gè)type的具體類型,我們?cè)试Scharint的轉(zhuǎn)換,但對(duì)于其它情況,將會(huì)拋出異常。
? ? ? ? 接下來,我們定義所關(guān)心的節(jié)點(diǎn)函數(shù):

    def visit_AST(self, node):
        pass

    def visit_AddrOf(self, node):
        node.expr.visit(self)
        if not node.expr.is_lvalue():
            raise Exception(f"Address-of (&) target has no address!")

    def visit_Pointer(self, node):
        node.expr.visit(self)
        if node.expr.type.to_str() == 'pointer':
            node.set_lvalue()
        else:
            raise Exception(f"Pointer dereference (*) target is not a pointer!")

    def visit_BinOp(self, node):
        node.left.visit(self)
        node.right.visit(self)
        if node.op == '=':
            if not node.left.is_lvalue():
                raise Exception(f"{node.left.expr} is an invalid lvalue: not an address!.")

        self.compare_types(node.right.type, node.left.type)

在前面,我們通過派生出具體的類型來羅列單目運(yùn)算符的表達(dá)式,這里就具有實(shí)際意義了。因?yàn)?,我們可可以單?dú)創(chuàng)建節(jié)點(diǎn)函數(shù)進(jìn)行處理。在BinOp中,通過調(diào)用compare_types,我們解決了error 2;通過定義節(jié)點(diǎn)Pointer的函數(shù),我們解決了error 1;而對(duì)于error 3,可用上面代碼中的兩個(gè)函數(shù)set_lvalue()is_lvalue()來輔助判斷。
? ? ? ? 那么,那些值是左值呢?我們需要回到最開始聲明檢查中定義的那個(gè)節(jié)點(diǎn)函數(shù)里去判斷:聲明過的變量,我們將其加入_symbols中,如果后面使用的變量就在這個(gè)表中,那么這個(gè)變量就認(rèn)為是左值,因?yàn)樗环峙淞舜鎯?chǔ)空間。具體細(xì)節(jié),我們將在生成匯編代碼中進(jìn)一步說明。這樣,修改那里的代碼為:

   def visit_Id(self, node):
        symbol = self.current_scope.lookup(node.expr)
        if symbol is not None:
            node.set_lvalue()
        else:
            comment = f"Identifier '{node.expr}' not found."
            raise Exception(comment)

這樣,就和TypeCheckVisitor關(guān)聯(lián)上了。當(dāng)然,還有其它地方需要進(jìn)行類型比較的地方,比如ReturnStatement節(jié)點(diǎn):

    def visit_FunctionDefn(self, node):
        self.curr_func = node
        node.body.visit(self)

    def visit_ReturnStatement(self, node):
        node.expr.visit(self)
        return_type = self.curr_func.return_type
        self.compare_types(node.expr.type, return_type)

我們同樣增加了一個(gè)成員變量curr_func,使得我們能夠在ReturnStatement節(jié)點(diǎn)中拿返回值的類型與函數(shù)定義的返回值類型進(jìn)行比較,從而發(fā)現(xiàn)錯(cuò)誤。
? ? ? ?
? ? ? ? 語義分析的內(nèi)容基本上就到此為止。這里,我們只是簡單地覆蓋了C語言中非常常見的語法錯(cuò)誤。但是,通過熟悉了遍歷方式,定義節(jié)點(diǎn)函數(shù)訪問節(jié)點(diǎn)進(jìn)行節(jié)點(diǎn)內(nèi)容分析,大家可以觸類旁通,通過不斷添加和完善節(jié)點(diǎn)函數(shù),繼續(xù)在這棵抽象語法樹上進(jìn)行更多的語法檢查。

實(shí)現(xiàn)簡易的C語言編譯器(part 0)
實(shí)現(xiàn)簡易的C語言編譯器(part 1)
實(shí)現(xiàn)簡易的C語言編譯器(part 2)
實(shí)現(xiàn)簡易的C語言編譯器(part 3)
實(shí)現(xiàn)簡易的C語言編譯器(part 4)
實(shí)現(xiàn)簡易的C語言編譯器(part 5)
實(shí)現(xiàn)簡易的C語言編譯器(part 6)
實(shí)現(xiàn)簡易的C語言編譯器(part 7)
實(shí)現(xiàn)簡易的C語言編譯器(part 8)
實(shí)現(xiàn)簡易的C語言編譯器(part 10)
實(shí)現(xiàn)簡易的C語言編譯器(part 11)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容