從零開始實(shí)現(xiàn) Lua 解釋器之語法分析2

警告??:這將是一個又臭又長的系列教程骇塘,教程結(jié)束的時(shí)候蘸朋,你將擁有一個除了性能差勁核无、擴(kuò)展性差、標(biāo)準(zhǔn)庫不完善之外藕坯,其他方面都和官方相差無幾的 Lua 語言解釋器团南。說白了,這個系列的教程實(shí)現(xiàn)的是一個玩具語言炼彪,僅供學(xué)習(xí)吐根,無實(shí)用性。請謹(jǐn)慎 Follow霹购,請謹(jǐn)慎 Follow佑惠,請謹(jǐn)慎 Follow。

這是本系列教程的第六篇齐疙,如果你沒有看過之前的文章膜楷,請從頭觀看。

前言

語法分析算是 SLua 解釋器中相對復(fù)雜的一個模塊了贞奋,為了避免篇幅過長赌厅,我們將分成多篇來講解,本文是第二部分轿塔。

上一節(jié)說到了 parseStatement 的實(shí)現(xiàn)特愿,現(xiàn)在我們就來分析一下 SLua 中到底有幾種類型的 Statement:

  • Do Statament,這個語句的作用是將一個 Block 封裝成一個單獨(dú)的語句勾缭。類似于 C 語言中將一段代碼放在一對大括號中揍障,主要用于控制局部變量的作用域。
  • While Statement
  • If Statement
  • Local Statement俩由,局部變量聲明語句毒嫡。
  • Other Statement,全局變量聲明及變量賦值語句幻梯,沒錯兜畸,SLua 中的全局變量聲明語句和變量賦值語句的形式是一樣的努释。

解析 Do Statement

Do Statement 的解析代碼如下:

func (p *Parser) parseDoStatement() syntax.SyntaxTree {
    p.nextToken()
    assert(p.currentToken.Category == scanner.TokenDo, "not a do statement")
    block := p.parseBlock()
    if p.nextToken().Category != scanner.TokenEnd {
        panic(&Error{
            module: p.module,
            token:  p.currentToken,
            str:    "expect 'end' for 'do' statement",
        })
    }
    return &syntax.DoStatement{Block: block}
}

首先,我們使用 assert 函數(shù)來確保當(dāng)前 Token 的類型為 TokenDo咬摇;然后調(diào)用 parseBlock 函數(shù)來解析 Do Statement 內(nèi)部的 Block伐蒂;緊接著,在解析完成后肛鹏,我們檢查下一個 Token 的類型是否為 TokenEnd逸邦,如果不是,則拋出錯誤龄坪。

怎么樣昭雌,很簡單吧。其他的語句的解析也跟這個類似健田,就像流水線一樣烛卧,一步接一步,直到解析完成妓局,將各部分的結(jié)果組裝起來总放,得到我們需要的東西。

解析 While Statement

在解析之前好爬,我們先來看一下之前在介紹抽象語法樹時(shí)提到過的 WhileStatement 的定義:

type WhileStatement struct {
    Exp   SyntaxTree
    Block SyntaxTree
}

可以看到局雄,While Statement 包含兩部分,Exp 為用于判斷循環(huán)是否繼續(xù)的表達(dá)式存炮,Block 則是循環(huán)體炬搭。解析代碼如下:

func (p *Parser) parseWhileStatement() syntax.SyntaxTree {
    p.nextToken()
    assert(p.currentToken.Category == scanner.TokenWhile,
        "not a while statement")
    exp := p.parseExp()
    if p.nextToken().Category != scanner.TokenDo {
        panic(&Error{
            module: p.module,
            token:  p.currentToken,
            str:    "expect 'do' for 'while' statement",
        })
    }
    block := p.parseBlock()
    if p.nextToken().Category != scanner.TokenEnd {
        panic(&Error{
            module: p.module,
            token:  p.currentToken,
            str:    "expect 'end' for 'while' statement",
        })
    }
    return &syntax.WhileStatement {
        Exp:   exp,
        Block: block,
    }
}

首先,與解析 Do Statement 相同穆桂,首先我們通過調(diào)用 assert 保證當(dāng)前 Token 的類型為 TokenWhile宫盔;然后調(diào)用 parseExp 解析緊接著 TokenWhile 后面的循環(huán)表達(dá)式;正常情況下享完,表達(dá)式后面會跟著 TokenDo灼芭,所以,如果不是的話般又,我們就需要報(bào)告語法錯誤然后退出程序彼绷;緊跟在 TokenDo 之后的是一個做為函數(shù)體的 Block,我們調(diào)用之前定義好的 parseBlock 函數(shù)來解析它茴迁;最后寄悯,我們需要判斷緊跟在 Block 之后的是否為 TokenEnd,如果不是堕义,則需要報(bào)告語法錯誤热某。

parseExp 函數(shù)的實(shí)現(xiàn)是整個語法分析最復(fù)雜的部分,我會在后面的文章中詳細(xì)地講解它,目前只需要知道它用于解析表達(dá)式就行了昔馋。

解析 If Statement

相比于上面介紹的 Do Statement 和 While Statement,If Statement 的結(jié)構(gòu)較為復(fù)雜糖耸,它可以包含零到多個 Elseif 分支和零或一個 Else 分支秘遏,所以將整個解析過程放在一個函數(shù)中無疑會大大增加復(fù)雜度,所以我們將解析函數(shù)拆分成了多個函數(shù)嘉竟。

我們在第四篇文章中介紹過 If Statement 的結(jié)構(gòu):

IfStatement AST

由上圖可以看到邦危,If Statement 包含三個部分:Exp、True Branch舍扰、False Branch倦蚪,其中 True Branch 為表達(dá)式求值為真的時(shí)候執(zhí)行的 Block,而 FalseBranch 可以是 Elseif Statement 或 Else Statement 或 nil边苹。Elseif Statement 的構(gòu)成與 If Statement 相同陵且,Else Statement 則只包含一個 Block。

下面先來看一下 If Statement 的實(shí)現(xiàn):

func (p *Parser) parseIfStatement() syntax.SyntaxTree {
    p.nextToken()
    assert(p.currentToken.Category == scanner.TokenIf, "not a if statement")
    exp := p.parseExp()
    if p.nextToken().Category != scanner.TokenThen {
        panic(&Error{
            module: p.module,
            token:  p.currentToken,
            str:    "expect 'then' for 'if' statement",
        })
    }
    trueBranch := p.parseBlock()
    falseBranch := p.parseFalseBranchStatement()
    return &syntax.IfStatement{
        Exp:         exp,
        TrueBranch:  trueBranch,
        FalseBranch: falseBranch,
    }
}

首先个束,自然是判斷當(dāng)前的 Token 類型是否為 TokenIf慕购;然后調(diào)用 parseExp 解析緊隨其后的表達(dá)式;然后判斷下一個 Token 的類型是否為 TokenThen茬底,如果不是沪悲,則報(bào)告語法錯誤;然后我們調(diào)用 parseBlock 來解析 True Branch阱表,調(diào)用 parseFalseBranchStatement 解析 False Branch殿如。

上面說過,F(xiàn)alse Branch 有三種情況最爬,容易想到涉馁,parseFalseBranchStatement 方法會根據(jù)下一個 Token 的類型是 TokenElseif、TokenElse 還是 TokenEnd 來執(zhí)行不同的動作烂叔,下面就來看一下具體的實(shí)現(xiàn):

func (p *Parser) parseFalseBranchStatement() syntax.SyntaxTree {
    if p.lookAhead().Category == scanner.TokenElseif {
        return p.parseElseifStatement()
    } else if p.lookAhead().Category == scanner.TokenElse {
        return p.parseElseStatement()
    } else if p.lookAhead().Category == scanner.TokenEnd {
        p.nextToken()
    } else {
        panic(&Error{
            module: p.module,
            token:  p.lookAheadToken,
            str:    "expect 'end' for 'if' statement",
        })
    }
    return nil
}

parseFalseBranchStatement 方法的實(shí)現(xiàn)很容易懂谨胞,這里就不再詳細(xì)解釋了。因?yàn)?Elseif Statement 與 If Statement 的形式完全相同蒜鸡,所以解析它的方法的實(shí)現(xiàn)也基本相同胯努,而 Else Statement 則只是簡單地調(diào)用一個 parseBlock 方法就可以了,也不再細(xì)說逢防,這兩個方法的實(shí)現(xiàn)在下面:

func (p *Parser) parseElseifStatement() syntax.SyntaxTree {
    p.nextToken()
    assert(p.currentToken.Category == scanner.TokenElseif,
        "not a 'elseif' statement")
    exp := p.parseExp()
    if p.nextToken().Category != scanner.TokenThen {
        panic(&Error{
            module: p.module,
            token:  p.currentToken,
            str:    "expect 'then' for 'elseif' statement",
        })
    }
    trueBranch := p.parseBlock()
    falseBranch := p.parseFalseBranchStatement()
    return &syntax.ElseifStatement{
        Exp:         exp,
        TrueBranch:  trueBranch,
        FalseBranch: falseBranch,
    }
}

func (p *Parser) parseElseStatement() syntax.SyntaxTree {
    p.nextToken()
    assert(p.currentToken.Category == scanner.TokenElse,
        "not a 'else' statement")
    block := p.parseBlock()
    if p.nextToken().Category != scanner.TokenEnd {
        panic(&Error{
            module: p.module,
            token:  p.currentToken,
            str:    "expect 'end' for 'else' statement",
        })
    }
    return &syntax.ElseStatement{Block: block}
}

本節(jié)就先說到這里叶沛,在下一節(jié)中,我們將繼續(xù)講解解析 Local Statement 和 Other Statement 的方法實(shí)現(xiàn)忘朝。

獲取源代碼


代碼已托管到 GitHub 上:SLua灰署,每一個階段的代碼我都會創(chuàng)建一個 release,你可以直接下載作為參照。雖然提供了源代碼溉箕,但并不建議直接復(fù)制粘貼晦墙,因?yàn)檫@樣學(xué)到的知識會很容易忘記。

如果你覺得這篇教程有幫助肴茄,請不要吝嗇給文章點(diǎn)個喜歡晌畅,我會更加有動力將這個系列寫下去。關(guān)注一下簡書和 GitHub 可以在第一時(shí)間獲得更新提醒哦寡痰。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末抗楔,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子拦坠,更是在濱河造成了極大的恐慌连躏,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,402評論 6 499
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件贞滨,死亡現(xiàn)場離奇詭異入热,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)疲迂,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,377評論 3 392
  • 文/潘曉璐 我一進(jìn)店門才顿,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人尤蒿,你說我怎么就攤上這事郑气。” “怎么了腰池?”我有些...
    開封第一講書人閱讀 162,483評論 0 353
  • 文/不壞的土叔 我叫張陵尾组,是天一觀的道長。 經(jīng)常有香客問我示弓,道長讳侨,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,165評論 1 292
  • 正文 為了忘掉前任奏属,我火速辦了婚禮跨跨,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘囱皿。我一直安慰自己勇婴,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,176評論 6 388
  • 文/花漫 我一把揭開白布嘱腥。 她就那樣靜靜地躺著耕渴,像睡著了一般。 火紅的嫁衣襯著肌膚如雪齿兔。 梳的紋絲不亂的頭發(fā)上橱脸,一...
    開封第一講書人閱讀 51,146評論 1 297
  • 那天础米,我揣著相機(jī)與錄音,去河邊找鬼添诉。 笑死屁桑,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的吻商。 我是一名探鬼主播掏颊,決...
    沈念sama閱讀 40,032評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼艾帐!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起盆偿,我...
    開封第一講書人閱讀 38,896評論 0 274
  • 序言:老撾萬榮一對情侶失蹤柒爸,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后事扭,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體捎稚,經(jīng)...
    沈念sama閱讀 45,311評論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,536評論 2 332
  • 正文 我和宋清朗相戀三年求橄,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了今野。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,696評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡罐农,死狀恐怖条霜,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情涵亏,我是刑警寧澤宰睡,帶...
    沈念sama閱讀 35,413評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站气筋,受9級特大地震影響拆内,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜宠默,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,008評論 3 325
  • 文/蒙蒙 一麸恍、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧搀矫,春花似錦抹沪、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至冰垄,卻和暖如春蹬癌,著一層夾襖步出監(jiān)牢的瞬間权她,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,815評論 1 269
  • 我被黑心中介騙來泰國打工逝薪, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留隅要,地道東北人。 一個月前我還...
    沈念sama閱讀 47,698評論 2 368
  • 正文 我出身青樓董济,卻偏偏與公主長得像步清,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子虏肾,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,592評論 2 353

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

  • 警告??:這將是一個又臭又長的系列教程廓啊,教程結(jié)束的時(shí)候,你將擁有一個除了性能差勁封豪、擴(kuò)展性差谴轮、標(biāo)準(zhǔn)庫不完善之外,其他...
    每天一道編程題閱讀 4,578評論 0 8
  • 警告??:這將是一個又臭又長的系列教程吹埠,教程結(jié)束的時(shí)候第步,你將擁有一個除了性能差勁、擴(kuò)展性差缘琅、標(biāo)準(zhǔn)庫不完善之外粘都,其他...
    每天一道編程題閱讀 2,522評論 2 7
  • 警告??:這將是一個又臭又長的系列教程,教程結(jié)束的時(shí)候刷袍,你將擁有一個除了性能差勁翩隧、擴(kuò)展性差、標(biāo)準(zhǔn)庫不完善之外做个,其他...
    每天一道編程題閱讀 9,192評論 3 14
  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語法鸽心,類相關(guān)的語法,內(nèi)部類的語法居暖,繼承相關(guān)的語法顽频,異常的語法,線程的語...
    子非魚_t_閱讀 31,622評論 18 399
  • 【今日話題】閱讀完這篇文章太闺,你有什么收獲糯景? 1.我們經(jīng)常講,細(xì)節(jié)決定成敗省骂,做好每一件小事蟀淮,就是你進(jìn)步和成長的機(jī)會。...
    黎落清音閱讀 359評論 0 3