這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
原文在此。
————翻譯分隔線————
編譯器(6)-標識符
第一部分:介紹
第二部分:編譯、轉譯和解釋
第三部分:編譯器設計概覽
第四部分:語言設計概述
第五部分:Calc 1 語言規格說明書
在本文中,我們終於可以開始沉浸在代碼中去了!
標識符
在之前的內容裡,我們已經討論了文法和需要掃描的標識符的集合。我們定義了運算式、數字和運算子。同時還明確期望有成對的括弧。還應當讓解析器知道,掃描器什麼時候到達檔案結尾。
在開始掃描之前,為了讓掃描器能夠工作之前,需要將代碼中的標識符格式化。在編譯器所涉及的所有階段都會用到標識符。如果我們希望開發出像 Go 的 fmt 或 vet 這樣的程式,還可能需要複用標識符。
這是第一部分的代碼:https://github.com/rthornton128/calc/blob/calc1/token/token.go
那些常量開始的時候看起來會挺有趣的。有一些小寫字母開頭的、非匯出的標識符混合在大寫字母開頭、匯出的之間。非匯出標識符可以為我們編寫工具函數提供協助,並且允許在不修改其他任何代碼的情況下對語言進行擴充。
https://github.com/rthornton128/calc/blob/calc1/token/token.go#L36
接下來將標識符(Token)映射到字串。還可以使用一個字串的數組,不過我沒這麼做。現在這樣寫查詢函數(Lookup)比較容易。
https://github.com/rthornton128/calc/blob/calc1/token/token.go#L50
其餘的部分是工具函數。你可以在 IsLiteral 和 IsOperator 中看到我們的非匯出常量派上用場的地方。不論要添加多少新的運算子或文法符號,都無須對這些函數進行修改。方便啊!
https://github.com/rthornton128/calc/blob/calc1/token/token.go#L58
Lookup、String 和 Valid 在建置錯誤資訊的時候提供協助。
位置
這個檔案可能需要你花點時間來思考。我將試著慢慢解釋給你聽。
在掃描的時候,從流中獲得第一個字元開始,從上往下、從左往右的的進行。第一個字元的位移量是零。
相比而言,當使用者希望知道彙報出來的錯誤是發生在哪行哪列的時候,第一個字元應當在第一行,第一列。因此,需要將字元的位置資訊翻譯為對終端使用者有意義的資訊。
位置(Pos)是字元的位移量加上檔案的基數。如果基數是一,字串的位移是零,這個字串的 Pos 是一。
位置為零是非法的,因為這意味著檔案之外的地方。同樣的,如果一個位置大於檔案的基數加上檔案的長度,那麼也是非法的。
為什麼要考慮這麼複雜的事情呢?好吧,當你需要解析多個檔案的時候,在沒有很多支援的情況下,要確定錯誤資訊是從哪個檔案中產生的是一件很麻煩的事情。Pos 使事情變得簡單。在後面的文章中會有更多關於此的介紹。
Position 類型嚴格用於錯誤報表。它允許我們輸出清晰的關於哪行、哪列,以及哪個檔案發生了錯誤的資訊。在這個階段,我們只需要處理單獨的一個檔案,但是將來我們會對這段代碼感激不禁。
File
嚴格意義上說,對於編寫一個編譯器,File 是完全沒有必要的,不過為了清晰的錯誤訊息和生死攸關的匯入功能,我還是覺得有它比較好。Go 這方面的工作做得不錯,不過其他一些編譯器就不一定了。例如,GNU C 編譯器,通常都很討厭。不過這幾年它還是改進了不少。
這些代碼提供了一個一些將要提供的內容的架構。如果哪天我們需要處理多於一個檔案時,我們才會用到它。
核心思想本質來說是為了錯誤報表,並且與位置碼緊密相連。再次提示,由於我們只有一個檔案(基數為一),或者說開始的位置是一。它不可能更小,不過將來可能會更大一些。因此現在不要去糾結它。
每次,掃描器檢測到一個新行的字元,就需要將這個位置添加到檔案的行列表中。這樣,Position 函數就可以進行計算錯誤是在哪裡發生的,並且報告這個位置。
總結
關於標識符所涵蓋的內容大約就這麼多。如我所承諾的,不會對相關的代碼進行太多的解釋。
我建議經常回顧一下這些代碼。一旦你明白了它們是怎麼協同工作的,它看起來將會有意義得多。這個庫將在編譯器中大量使用,因此我們將會不斷的提到它。