實(shí)現(xiàn)簡(jiǎn)易的C語(yǔ)言編譯器(part 8)

? ? ? ? 繞來(lái)繞去绰上,千辛萬(wàn)苦泡垃,我們終于創(chuàng)建了抽象語(yǔ)法樹析珊,完成了對(duì)整個(gè)源代碼結(jié)構(gòu)性的分析,似乎可以喘一口氣了蔑穴。但是唾琼,對(duì)于下面的代碼:

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

可以得到下面的抽象語(yǔ)法樹圖示:

圖6-1. 抽象語(yǔ)法樹

但并不代表代碼已經(jīng)沒有語(yǔ)法錯(cuò)誤了。很明顯澎剥,因?yàn)檫@里并沒有定義變量a锡溯,而且函數(shù)也沒有返回值。針對(duì)這樣的情況哑姚,還需要對(duì)代碼進(jìn)行進(jìn)一步的檢查祭饭。
? ? ? ? 在進(jìn)行檢查之前,我們先研究如何遍歷這棵樹叙量〕回顧一下,我們使用AST派生出多種節(jié)點(diǎn)來(lái)存儲(chǔ)各種不同的表達(dá)式绞佩、聲明和語(yǔ)句寺鸥。比如存儲(chǔ)函數(shù)定義的節(jié)點(diǎn)FunctionDefn

class FunctionDefn(AST):
    """A node representing a function definition"""
    def __init__(self, declaration, body):
        self.name = declaration.name
        self.type = declaration.type
        self.return_type = declaration.type.child
        self.body = body

是由多個(gè)其它類型的節(jié)點(diǎn)組成,并且相互之間存在聯(lián)系品山。對(duì)訪問(wèn)函數(shù)體而言胆建,其實(shí)就是訪問(wèn)CompoundStatement節(jié)點(diǎn)。而該節(jié)點(diǎn)則由更多的聲明節(jié)點(diǎn)Declaration和具體的語(yǔ)句節(jié)點(diǎn)如IfStatement組成肘交。因此笆载,可以從根節(jié)點(diǎn)出發(fā),按照節(jié)點(diǎn)的具體類型進(jìn)行逐次地遍歷涯呻×棺ぃ基于這種方法,我們引入python中的屬性函數(shù)getattr()复罐,進(jìn)行如下設(shè)計(jì):

  • 修改模板節(jié)點(diǎn)
class AST(object):
    ...

    def visit(self, visitor):
        return self.dig_visit(self.__class__, visitor)

    def dig_visit(self, node, visitor):
        """appending the class' name with 'visit_'"""
        method_name = 'visit_' + node.__name__
        visitor_method = getattr(visitor, method_name, None)
        if visitor_method is None:
            # call the class's superclass
            bases = node.__bases__
            last = None
            for child in bases:
                last = self.dig_visit(child, visitor)
            return last
        else:
            return visitor_method(self)

我們將從當(dāng)前節(jié)點(diǎn)出發(fā)涝登,遍歷所有用visit_前綴和具體節(jié)點(diǎn)類名組成的定義在visitor中的函數(shù),稱之為節(jié)點(diǎn)函數(shù)效诅,如果沒有對(duì)應(yīng)的實(shí)現(xiàn)胀滚,則會(huì)往父類方向遍歷咳短,直到模板節(jié)點(diǎn)AST為止,因?yàn)槲覀儠?huì)定義一個(gè)模板節(jié)點(diǎn)的函數(shù)蛛淋,以阻止遍歷往更基類的方向進(jìn)行咙好。
? ? ? ? 有了上面遍歷抽象語(yǔ)法樹的方式,我們將語(yǔ)義分析分成聲明檢查褐荷、流程檢查和類型檢查三個(gè)部分進(jìn)行分別檢查勾效。

6.1 聲明檢查

? ? ? ? 簡(jiǎn)單地說(shuō),就是看變量使用之前是否已經(jīng)被聲明叛甫。由于C語(yǔ)言允許在不同作用域定義同名變量层宫。因此,這部分檢查還需要考慮作用域其监,其一般用大括號(hào)對(duì){}劃分萌腿。我們先定義一個(gè)這樣的作用域類:

class ScopedSymbolTable:
    """scoped symbol table for look up"""
    def __init__(self, enclosing_scope=None):
        self._symbols = {}
        self.enclosing_scope = enclosing_scope

    # insert symbols
    def insert(self, name, value):
        """add symbol with the given value"""
        if name in self._symbols:
            if not self._symbols[name].type == value.type:
                comment = f"Variable '{name}' has declared before."
                raise Exception(comment)
       
        self._symbols[name] = value

    # look up symbols
    def lookup(self, name):
        symbol = self._symbols.get(name)

        if symbol is not None:
            return symbol

        # recursively go up the chain and lookup the name
        if self.enclosing_scope is not None:
            return self.enclosing_scope.lookup(name)
        else:
            return None

其中,symbols用來(lái)存儲(chǔ)所有聲明的變量抖苦,而每一個(gè)作用域enclosing_scope都會(huì)有自己的_symbols毁菱。整個(gè)類型檢查的邏輯就是:變量在聲明的時(shí)候,會(huì)被插入到當(dāng)前作用域的_symbols中锌历。當(dāng)某個(gè)變量被使用的時(shí)候贮庞,則用lookup進(jìn)行查找,如果不能在當(dāng)前或者更上一層的作用域范圍內(nèi)找到究西,則可以判斷該變量沒有被聲明窗慎。那么,剩下的任務(wù)就是遍歷抽象語(yǔ)法樹卤材,找到可能存在變量聲明和使用的地方遮斥。
? ? ? ? 接下來(lái),我們開始遍歷抽象語(yǔ)法樹扇丛。首先定義如下的類:

class NodeVisitor(object):
    def visit(self, node):
        return node.visit(self)

    def visit_list(self, _list):
        """like NodeList in parser"""
        last = None
        for child in _list:
            last = child.visit(self)
        return last

class SymbolTableVisitor(NodeVisitor):
    def __init__(self):
        self.current_scope = None

    def push_table(self, node):
        """push the current_scope into stack and create a child scope"""
        self.current_scope = ScopedSymbolTable(
            enclosing_scope=self.current_scope 
        )

    def pop_table(self):
        """restore the parent scope"""
        self.current_scope = self.current_scope.enclosing_scope

基類NodeVisitor的引入有助于我們調(diào)用getattr()獲取當(dāng)前的visit_函數(shù)术吗。同時(shí),我們使用pushpop方法來(lái)保護(hù)當(dāng)前父作用域,同時(shí)創(chuàng)建出新的子作用域。例如涯竟,CompoundStatement節(jié)點(diǎn)中會(huì)引入大括號(hào)规惰,從而將引入新的作用域,因此訪問(wèn)這個(gè)節(jié)點(diǎn)函數(shù)時(shí)堤器,我們需要先將當(dāng)前作用域壓入棧昆庇,創(chuàng)建新的作用域,然后訪問(wèn)組成它的節(jié)點(diǎn)闸溃,訪問(wèn)完畢再?gòu)臈V袕棾龈缸饔糜蛘海@樣就有效地保護(hù)了不同作用域聲明的變量拱撵。
? ? ? ? 考慮這部分最開頭的源代碼,我們?cè)?code>SymbolTableVisitor中定義所關(guān)心的可能會(huì)包含變量的節(jié)點(diǎn)函數(shù):

    def visit_AST(self, node):
        pass

    """root"""
    def visit_TranslationUnit(self, node):
        """the entry of the file"""
        self.current_scope = ScopedSymbolTable(
            enclosing_scope=self.current_scope 
        )
        self.visit_NodeList(node)
        node.scope_symbol = self.current_scope

    """for all list derived from NodeList, eg. DeclarationList."""
    def visit_NodeList(self, node):
        self.visit_list(node.nodes)

    """expressions"""
    def visit_BinOp(self, node):
        node.left.visit(self)
        node.right.visit(self)

    """functions"""
    def visit_FunctionDefn(self, node):
        self.add_symbol(node)
        self.push_table(node)
        node.body.visit(self)
        self.pop_table()

    """statements"""
    def visit_CompoundStatement(self, node):
        self.push_table(node)
        node.declarations.visit(self)
        node.statements.visit(self)
        self.pop_table()

    def visit_ExpressionStatement(self, node):
        node.expr.visit(self)

    def visit_ReturnStatement(self, node):
        node.expr.visit(self)

上面函數(shù)名前綴為visit_的即為節(jié)點(diǎn)函數(shù)表蝙。從visit_TranslationUnit進(jìn)入拴测,通過(guò)visit函數(shù),我們會(huì)遍歷以下的節(jié)點(diǎn)函數(shù):

visit_TranslationUnit -> visit_FunctionDefn->visit_CompoundStatement\
->visit_ExpressionStatement->visit_BinOp->visit_AST

那么府蛇,我們?nèi)绾闻袛嘧兞渴欠袷褂弥耙呀?jīng)聲明了呢集索?

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

由于所有的變量都用節(jié)點(diǎn)Id存儲(chǔ),訪問(wèn)BinOp之后將訪問(wèn)Id汇跨,從而檢查出對(duì)應(yīng)的問(wèn)題务荆。如果對(duì)源代碼進(jìn)行修改如果,在最上面添加:

int a;

那么穷遂,我們遍歷到聲明節(jié)點(diǎn)函匕,并通過(guò)visit_Declaration插入變量:

  """declarations"""
    def visit_Declaration(self, node):
        self.current_scope.insert(node.name, node)

當(dāng)再次經(jīng)由BinOp訪問(wèn)Id時(shí),就能夠找到對(duì)應(yīng)的已經(jīng)聲明的變量了蚪黑。

? ? ? ? 但是盅惜,我們研究的這段源代碼中還有一個(gè)問(wèn)題尚未解決:返回值檢查。這部分內(nèi)容忌穿,將在下一部分的流程檢查中進(jìn)行酷窥。

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市伴网,隨后出現(xiàn)的幾起案子蓬推,更是在濱河造成了極大的恐慌,老刑警劉巖澡腾,帶你破解...
    沈念sama閱讀 216,651評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件沸伏,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡动分,警方通過(guò)查閱死者的電腦和手機(jī)毅糟,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,468評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)澜公,“玉大人姆另,你說(shuō)我怎么就攤上這事》厍” “怎么了迹辐?”我有些...
    開封第一講書人閱讀 162,931評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)甚侣。 經(jīng)常有香客問(wèn)我明吩,道長(zhǎng),這世上最難降的妖魔是什么殷费? 我笑而不...
    開封第一講書人閱讀 58,218評(píng)論 1 292
  • 正文 為了忘掉前任印荔,我火速辦了婚禮低葫,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘仍律。我一直安慰自己嘿悬,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,234評(píng)論 6 388
  • 文/花漫 我一把揭開白布水泉。 她就那樣靜靜地躺著善涨,像睡著了一般。 火紅的嫁衣襯著肌膚如雪茶行。 梳的紋絲不亂的頭發(fā)上躯概,一...
    開封第一講書人閱讀 51,198評(píng)論 1 299
  • 那天,我揣著相機(jī)與錄音畔师,去河邊找鬼娶靡。 笑死,一個(gè)胖子當(dāng)著我的面吹牛看锉,可吹牛的內(nèi)容都是我干的姿锭。 我是一名探鬼主播,決...
    沈念sama閱讀 40,084評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼伯铣,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼呻此!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起腔寡,我...
    開封第一講書人閱讀 38,926評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤焚鲜,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后放前,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體忿磅,經(jīng)...
    沈念sama閱讀 45,341評(píng)論 1 311
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,563評(píng)論 2 333
  • 正文 我和宋清朗相戀三年凭语,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了葱她。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,731評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡似扔,死狀恐怖吨些,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情炒辉,我是刑警寧澤豪墅,帶...
    沈念sama閱讀 35,430評(píng)論 5 343
  • 正文 年R本政府宣布,位于F島的核電站辆脸,受9級(jí)特大地震影響但校,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜啡氢,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,036評(píng)論 3 326
  • 文/蒙蒙 一状囱、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧倘是,春花似錦亭枷、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,676評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至瘤睹,卻和暖如春升敲,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背轰传。 一陣腳步聲響...
    開封第一講書人閱讀 32,829評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工驴党, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人获茬。 一個(gè)月前我還...
    沈念sama閱讀 47,743評(píng)論 2 368
  • 正文 我出身青樓港庄,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親恕曲。 傳聞我的和親對(duì)象是個(gè)殘疾皇子鹏氧,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,629評(píng)論 2 354

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

  • 第3章 基本概念 3.1 語(yǔ)法 3.2 關(guān)鍵字和保留字 3.3 變量 3.4 數(shù)據(jù)類型 5種簡(jiǎn)單數(shù)據(jù)類型:Unde...
    RickCole閱讀 5,124評(píng)論 0 21
  • Swift1> Swift和OC的區(qū)別1.1> Swift沒有地址/指針的概念1.2> 泛型1.3> 類型嚴(yán)謹(jǐn) 對(duì)...
    cosWriter閱讀 11,097評(píng)論 1 32
  • 函數(shù)和對(duì)象 1、函數(shù) 1.1 函數(shù)概述 函數(shù)對(duì)于任何一門語(yǔ)言來(lái)說(shuō)都是核心的概念佩谣。通過(guò)函數(shù)可以封裝任意多條語(yǔ)句把还,而且...
    道無(wú)虛閱讀 4,560評(píng)論 0 5
  • JavaScript語(yǔ)言精粹 前言 約定:=> 表示參考相關(guān)文章或書籍; JS是JavaScript的縮寫。 本書...
    微笑的AK47閱讀 580評(píng)論 0 3
  • 淅淅瀝瀝的小雨一直下個(gè)不停茸俭,泥濘的鄉(xiāng)間小路上吊履,一個(gè)老人深一腳淺一腳的走著。雨天路滑瓣履,老人又瘸著一條腿率翅,所以經(jīng)常摔倒...
    麥田守望者lzu閱讀 158評(píng)論 0 2