你們好,很久沒看到喵了,是否是想我了。前端
這是本喵看到的一篇好文章,因此忍不住想要拿過來。c++
知乎上有一種說法是「編譯器、圖形學、操做系統是程序員的三大浪漫」。程序員
先無論這個說法是對是錯,咱們假設一個程序員在國內互聯網公司寫代碼,業餘時間不看相關書籍。那麼三年以後,他的這些知識會比在校時損耗多少?函數
很顯然,損耗的比例確定很是高,畢竟國內互聯網公司平常開發工做中,程序員基本不多接觸這三塊知識。大部分程序員工做幾年後對編譯原理相關的概念只能生理上起反應,腦海裏很難再串聯起相關概念了。工具
編譯原理的概念有讓人看到就頭痛的特質,學校裏要死記硬背,考試過了恨不得趕忙全忘掉,相信很多同窗如今看到下面概念還會以爲蛋疼:學習
非肯定性有限自動機/肯定性有限自動機flex
四元式序列優化
上下文無關文法/BNFspa
終結符/非終結符操作系統
LL(1)/LR(1)
特設語法制導轉換
局部優化
其實本喵在學習這門課的時候也很煩腦,畢竟這是號稱最難學的一門學科之一。本喵是在大四的時候學的這門課,通過半個學期死命的看書,也仍是懵懵懂懂,不怎麼明白。但好在考試的時候過了。
什麼是編譯器?
廣義的編譯器能夠指任意把一種語言代碼轉爲另外一種語言代碼的程序
作編譯器實際上都須要作什麼?
編譯器是一整套工具鏈,從前端的詞法分析、語法分析,到中間表示生成、檢查、分析、優化,再到代碼生成。
若是是編譯器從業者,大部分時間在作中間這塊;若是是業餘愛好者,大部分時間在作前端和代碼生成。
先肯定源語言:
這是一門看起來像lisp的四則運算語言,四個雙目運算符分別是「add」「sub」「mul」「div」。
多項四則運算能夠這樣寫:
(mul (sub 5 (add 1 2)) 4
再來肯定目標語言:
一樣是一門四則運算語言,可是看起來可讀性更強,對應的四個雙目運算符分別是「+」「-」「*」「/」。
上面源語言的例子編譯完後應該是這樣:
((5 - (1 + 2)) * 4)
最後肯定咱們寫編譯器要用的語言:
喵選擇Haskell,有兩個緣由,一是寫Haskell有大名鼎鼎的ParseC,寫Parser很是方便;二是Haskell的代數數據類型的定義自己就是AST。
ParseC的全稱是Parser組合子。Parser,抽象理解就是一個輸入爲字符串輸出爲類型T的值的函數。ParseC庫實現了大量基礎Parser和Parser組合子,Parser組合子能夠將庫自帶的基礎Parser和用戶定義的Parser隨意組合成新的更強大的Parser。
舉個例子,你實現了一個Parser,功能是根據輸入文本返回解析到的標識符名稱。ParseC庫實現了一個名叫many的parser組合子,跟你本身的Parser組合起來就產生了一個新的Parser:能夠根據輸入文本返回解析到的標識符名稱list。
爲何要用ParseC呢?由於用ParseC定義Parser具備PEG(解析表達式文法,原理不細講,不影響接下來學習)的全部好處,同時還不用再學習語言以外的知識(好比用flex和bison前要先學習這二者本身的「DSL」)。
固然,其餘語言也有相似的庫,好比c++有boost::spirit,Java/C#/F#/JS有Haskell的ParseC的工業級實現。這些語言跟Haskell的區別無非在於要寫一些額外的邏輯把Parser的解析結果轉成AST。
若是沒有接觸過Haskell的話也不要緊,接下來的示例代碼都很是declarative,很是self-descriptive,請放心食用。
接下來就開始寫代碼了,首先咱們要定義AST的結構,目的是爲了能用這個結構描述一切源語言表達式。
簡單分析一下源語言,咱們能夠直接得出表達式這個概念的遞歸定義:一個表達式要麼是一個字面值,要麼是一個雙目運算符和兩個表達式的求值結果。
而後是字面值這個概念的遞歸定義:一個字面值要麼是一個整型值,要麼是一個浮點型值。
在Haskell裏面這兩個定義寫成下面這樣:
跟前面的文字定義對應一下:
表達式Exp,要麼是一個字面值表達式ConstExp,由一個Val組成;要麼是一個雙目運算表達式BinOpExp,由一個操做符和兩個Exp組成。
值Val,要麼是一個整型值IntVal,由一個Integer組成;要麼是一個浮點型值FloatVal,由一個Float組成。
接下來開始寫Parser。流程是先爲AST中的每一個節點類型寫一個parser,而後再把這些parser組合起來造成能parse出整棵AST的parser。
咱們先給本身定個小目標,好比先實現一個int_parser。
p_int是能從文本中Parse出Integer的Parser定義。而p_int_val改造了p_int,定義了能從文本中Parse出IntVal的Parser。
而後咱們把int和float的parser組合起來成爲一個val_parser。
listplus能夠簡單理解爲並,在具體實現上會作回溯。
同理,咱們先分別實現ConstExp的parser和BinOpExp的parser,再把二者組合爲exp_parser。
到目前爲止,咱們的parser部分就完工了。
對Haskell有興趣的同窗,能夠安裝下ghci,是haskell的REPL,而後加載剛纔寫好的Parser.hs,在命令行裏試一下
能夠看到輸出結果。稍微排版下,輸出結果變成了咱們熟悉的樹形結構,Op爲「mul」的BinOpExp就是樹的根節點。整個輸出就是一棵AST。
有了這棵AST,咱們就能夠開始作後續的代碼生成了。
CodeGenerator的主體是把Exp轉換成目標語言代碼的函數:
利用模式匹配這個語言特性實現多態既容易又優雅。
最後再套個殼,好比讀源文件,寫目標文件,整個編譯器就大功告成了
好了,到了和你們說再見的時候了。若是有興趣能夠去:http://mp.weixin.qq.com/s?__biz=MzIwNDU2MTI4NQ==&mid=2247483679&idx=1&sn=8df4b40386fb6182051f4926ab043636#rd這個網址看看。