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

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

6.2 流程檢查

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

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

很重要的一步把还。但是,這段代碼仍然是有語法錯(cuò)誤的茸俭,因?yàn)闆]有返回值吊履。這個(gè)錯(cuò)誤將會很快檢查出來。
? ? ? ? 我們還是遍歷那棵抽象語法樹调鬓,與聲明檢查重點(diǎn)遍歷變量不同艇炎,流程檢查將重點(diǎn)遍歷語句。這一點(diǎn)是非常直觀袖迎,也是容易理解的冕臭。仿照聲明檢查的方法,我們再定義一個(gè)遍歷的類燕锥,然后定義將會用到的節(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ò)誤宋列。但是昭抒,對于下面這種情況:

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ù)原炼杖,使得對continue的檢查將出現(xiàn)錯(cuò)誤灭返。此外,也不能簡單地將其重置為假嘹叫,因?yàn)檠h(huán)是可以嵌套的婆殿,否則將改變in_loop原來的正確狀態(tài)。因此罩扇,采用棧結(jié)構(gòu)對變量進(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: 對右值取地址

這些就是類型檢查將要做的事情捞高。我們在前面花費(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的具體類型冗尤,我們允許charint的轉(zhuǎn)換,但對于其它情況胀溺,將會拋出異常裂七。
? ? ? ? 接下來,我們定義所關(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澎埠;而對于error 3虽缕,可用上面代碼中的兩個(gè)函數(shù)set_lvalue()is_lvalue()來輔助判斷。
? ? ? ? 那么蒲稳,那些值是左值呢氮趋?我們需要回到最開始聲明檢查中定義的那個(gè)節(jié)點(diǎn)函數(shù)里去判斷:聲明過的變量,我們將其加入_symbols中江耀,如果后面使用的變量就在這個(gè)表中剩胁,那么這個(gè)變量就認(rèn)為是左值,因?yàn)樗环峙淞舜鎯臻g祥国。具體細(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)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市疫赎,隨后出現(xiàn)的幾起案子盛撑,更是在濱河造成了極大的恐慌,老刑警劉巖捧搞,帶你破解...
    沈念sama閱讀 206,839評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件抵卫,死亡現(xiàn)場離奇詭異狮荔,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)介粘,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評論 2 382
  • 文/潘曉璐 我一進(jìn)店門殖氏,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人姻采,你說我怎么就攤上這事雅采。” “怎么了慨亲?”我有些...
    開封第一講書人閱讀 153,116評論 0 344
  • 文/不壞的土叔 我叫張陵婚瓜,是天一觀的道長。 經(jīng)常有香客問我刑棵,道長巴刻,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,371評論 1 279
  • 正文 為了忘掉前任蛉签,我火速辦了婚禮胡陪,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘正蛙。我一直安慰自己督弓,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,384評論 5 374
  • 文/花漫 我一把揭開白布乒验。 她就那樣靜靜地躺著愚隧,像睡著了一般。 火紅的嫁衣襯著肌膚如雪锻全。 梳的紋絲不亂的頭發(fā)上狂塘,一...
    開封第一講書人閱讀 49,111評論 1 285
  • 那天,我揣著相機(jī)與錄音鳄厌,去河邊找鬼荞胡。 笑死,一個(gè)胖子當(dāng)著我的面吹牛了嚎,可吹牛的內(nèi)容都是我干的泪漂。 我是一名探鬼主播,決...
    沈念sama閱讀 38,416評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼歪泳,長吁一口氣:“原來是場噩夢啊……” “哼萝勤!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起呐伞,我...
    開封第一講書人閱讀 37,053評論 0 259
  • 序言:老撾萬榮一對情侶失蹤敌卓,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后伶氢,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體趟径,經(jīng)...
    沈念sama閱讀 43,558評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡瘪吏,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,007評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了蜗巧。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片掌眠。...
    茶點(diǎn)故事閱讀 38,117評論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖幕屹,靈堂內(nèi)的尸體忽然破棺而出扇救,到底是詐尸還是另有隱情,我是刑警寧澤香嗓,帶...
    沈念sama閱讀 33,756評論 4 324
  • 正文 年R本政府宣布,位于F島的核電站装畅,受9級特大地震影響靠娱,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜掠兄,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,324評論 3 307
  • 文/蒙蒙 一像云、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧蚂夕,春花似錦迅诬、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,315評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至等脂,卻和暖如春俏蛮,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背上遥。 一陣腳步聲響...
    開封第一講書人閱讀 31,539評論 1 262
  • 我被黑心中介騙來泰國打工搏屑, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人粉楚。 一個(gè)月前我還...
    沈念sama閱讀 45,578評論 2 355
  • 正文 我出身青樓辣恋,卻偏偏與公主長得像,于是被迫代替她去往敵國和親模软。 傳聞我的和親對象是個(gè)殘疾皇子伟骨,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,877評論 2 345

推薦閱讀更多精彩內(nèi)容