你是隻被動學習這篇文章中的材料還是主動練習它?我真的希望你能夠主動練習它:
記得孔子所說的嗎?

4b311b.png)
在之前的文章中你已經學習到了怎樣利用算術表達式中的加減操作符進行解析(識别)并且翻譯它們,比方”7-3+2-1“。你還學會了文法圖并且知道它們是怎樣被使用辨識一個程式設計語言中的文法的。
今天你将要學習怎樣利用在算術表達式中的任何數量的乘除操作符進行解析和翻譯,比方”74、23“。這篇文章中的中的除操作符指的是整除,是以如果表達式是”9/4“之類的,那麼結果将會是整數:2。
今天我将談論相當多的另一個廣泛使用的符号,這些是用來規定一個程式設計語言的文法的。它叫做上下文無關文法(簡稱文法)或者 bnf(巴科斯範式形式)。這篇文章的目的是我将不是使用純粹的 bnf 符号而是更多的使用像 ebnf 這樣的修飾了的符号。
這裡是使用文法的幾個原因:
1.一種文法以簡明的方式規定了其文法規則。不像文法圖那樣,文法本身是很濃縮的。你将看到我在之後的文章會更多地使用文法。
2.一種文法能充當大量的文檔。
3.一種文法是一個好的起點即使是你手動從你的手稿中寫文法分析器。通常是你隻要将文法通過一組簡單的規則轉換成代碼。
4.這裡有一組工具,稱為文法分析産生器,它能夠接受文法作為輸入并且基于此文法自動産生文法分析器。我将在接下來的系列中談談這些工具。
現在,讓我們談談文法機制方面的問題吧?
這是一種文法描述的算術表達式像“74/23”這樣(這隻是可由文法産生的衆多表達式中的一種):
一種文法包含一系列的規則,也稱作産品。以下是我們的文法的兩個規則:
一個規則包含一個非終端,稱為産品的 head(頭) 或者 left-hand side(左手端),一個冒号和一系列的終端和/或者非終端,稱為産品的 body(體) 或者 right-hand side(右手端):
在上面我所展示的文法中,像 mul,div 和 inteher 這樣的标記稱作終端,像 expr 和 facto 這樣的變量稱作非終端。通常,非終端包含一系列的終端和/或者非終端:
第一條規則的左手端的非終端符号稱之為起始符。我們的文法中的情況是,起始符是個表達式。
你可以視這一規則中的表達式為如下定義:“一個表達式可以是随意跟随着一個乘法運算符或者除法運算符的一個因子,同時這跟随着的乘法或除法運算符又可以是随意跟随着另外一個這樣因子的運算符,反過來這另外一個因子又可以是随意跟随着一個乘法或者除法運算符的一個因子,同時這跟随着的乘法或除法運算符又可以跟随着另外一個因子,如此反複。”
什麼是因子?這篇文章的目的是一個因子僅是一個整數。
我們先快速浏覽一下文法中的所有符号及它們的意義。
如果你之前與正規表達式打過交道,那麼這些符号 |,(),和 (...)* 對于你來說應該是相當熟悉的。
一個語言的文法定義了這個語言可以支援多少種的語句。下面,我們将具體說明如何通過上面定義的文法來産生這個文法支援的語句集合:說起來這個過程也很簡單,就是從起始符号 expr 開始,不斷地将非終結符号使用一個規則的主題進行替換,直到整個表達式中隻包含終結符号。通過不斷重複這樣的過程得到的語句集合就形成了這個文法定義的語言。
對于一個具體的算術表達式,如果無法通過文法定義推到出來,那麼就意味着這個文法定義不支援這種算術表達式,那麼對應的文法分析器在看到這個算術表達式的時候就會産生一個文法錯誤的提示。
讓我們舉一些具體的日子,來看看我們前面定義的文法可以産生哪些語句。
但我第一次接觸到文法以及相關術語的時候,我就想下面途中所畫的那樣,覺得頭暈眼花,無所适從:
從我的經驗來看,現在的你應該也是一頭霧水,而不會像下面圖中的人一樣說我全部都搞懂了:
我着實花了一段時間才搞清楚這些标記,他們是如何用來描述文法的,以及在分析器和文法分析其中是如何使用它們的,但是我要說從長遠來說,你現在所花的時間是覺得值得的,因為它們是如此廣泛地應用在編譯器實踐中(類似的分析工具也可以應用在編譯器外的其他地方)。是以為什麼不在現在就搞清楚這些東西呢?:)
下面,讓我們一起看看如何從文法圖産生正确的代碼。
下面我們列出了将一個文法表示翻譯成代碼的基本步驟,根據這些步驟,你可以友善地将一個文法表示翻譯解釋器代碼:
根據上面的步驟,對于我們前面定義的文法表示,我們可以得到下面的這張圖所示的翻譯過程:
下面就讓我們來真正實作我們的分析器吧。
在我們目前定義的文法中有 2 個規則定義: 一個規則叫 expr,另外一個規則叫 factor。讓我們從最簡單的規則 factor 開始. 根據我們上面講的翻譯規則,我們需要建立一個方法叫做 factor(規則1),同時根據規則 4,這個方法隻有一個對 eat 方法的調用(eat(integer)):
看到了吧,根據翻譯規則,把這條文法規則翻譯成代碼就是這麼簡單
繼續!
規則 expr 根據規則 1 被翻譯成一個叫做 expr 的方法。根據翻譯規則 1,2 和 3,這個方法的執行将包括:1)調用 factor 方法;2)(...)*被翻譯成一個 while 循環;3)(mul | div)這個被翻譯成一個 if..else 語句。把這些東西合在一起就變成了下面的代碼:
現在請花一點時間閱讀上面的翻譯過程,確定你完全了解了這部分的内容。
我把以上的代碼放在一個叫做 parser.py 的檔案中 ( 你可以直接從 github 下載下傳這個檔案),這個檔案包含了詞法分析器代碼,但是沒有包含解釋器(是以不會得到最終的計算結果)。但你運作這個程式的時候,程式将會顯示一個"calc>"提示,你可以在這個提示之後輸入算術表達式,程式将告訴你這個表達式是否合法。
下圖顯示的是我在我自己的機器上運作這個程式的情況:
(強烈建議讀者在本地嘗試一下這個代碼)
這裡我忍不住要再強調一次我們的文法圖,下面是我們的 expr 規則的文法圖:
現在是時候來實作我們的算術表達式解釋器了。下面是完整的源代碼,它實作了一個可以運作合法算術表達式的電腦。如果你認真閱讀了代碼,你會發現現在我把詞法分析器變成了一個單獨的類 --- lexer 類,而且解釋器也修改成接受 lexer 執行個體來進行詞法分析。:
你可以從 github 直接下載下傳這個檔案,然後在你自己的機器上進行測試。
這是在我的筆記本上運作的示例:
我知道你迫不及待了:)下面是今天新的練習:
請嘗試回答下面的問題來測試你對今天的内容了解了多少,下圖是今天的課程所使用的文法圖,在你回到下面的問題的時候,可以參考下圖獲得文法資訊:
什麼叫做上下文無關文法?
本文中的文法包含多少規則定義,以及這些規則可以産生多少語句?
什麼叫終結符? (請找出上圖中所有的終結符)
什麼叫非終結符? (請找出上圖中所有的非終結符)
什麼叫規則頭? (找出上圖所有的規則頭)
什麼叫規則體? (找出上圖所有的規則體)
那個符号是上圖所示文法的起始符号?
這篇文章包含了大量的理論知識,再次恭喜你通讀了這篇文章,對此我深深以你為榮。
過段時間我會再釋出一些關于解釋器的新的文章,請保持你的求知欲,并且嘗試完成本站所留的作業,這些作業能夠讓你更好地掌握本章所學的内容。