最近在看DSL的東西,對於外部DSL,寫一個解釋器是必不可少的。我試圖概括一下我學到的,以寫一個解釋器爲目標,講一下若是來實現一個可用的解釋器。一個解釋器一般能夠分爲一下幾個階段:前端
這裏的解釋器不包括目標語言的執行和運行時環境,若是須要相似於python/ruby的解析執行器的話,還須要bytecode-compiler, bytecode-interpreter和runtime。咱們這裏只談解釋的部分,不談執行的部分。做爲DSL咱們大多都有宿主語言,一般也就咱們生成的目標語言,有關執行這部分,咱們交給目標語言去解決。換個說法,咱們只討論一個完整解釋執行器的前端。python
當前編譯技術已經獲得極大發展,對於詞法分析和語法分析,咱們能夠藉助成熟的工具或庫很快的完成,極大簡化了寫解釋器的難度。對於一個解釋器來講咱們須要本身處理的部分基本上集中於語義的分析,即AST的處理和目標語言的生成。其實這裏的AST的處理結果至關於一個與執行環境無關的中間語言表示,而目標語言的生成就是針對目標語言的特性,將annotated AST轉換爲目標語言以方便在目標語言的運行環境中運行。ruby
繼續扯扯淡,Lisp語言的語法基本上就是直接寫AST,直接用AST的表示法來表示程序,因此Lisp語法是最貼近於編譯器的中間表示,開始可能根本就不是給通常人用的,誰知道這麼多人喜歡用。Lisp之因此叫List Processer那是由於Lisp直接處理AST的數據結構表示即Nested List,因此才叫List Processor,一點都沒有謙虛的意思。在數據結構裏,表示樹這種數據結構的,最簡單直接的辦法就是Nested List(經過Linked-List了來實現)。並且這種結構利於遞歸遍歷,方便Stack-based執行或者代碼的生成。數據結構
詞法分析是將源代碼轉換成tokens,這些tokens有些事關鍵字,有些是變量,有些是字面量,有些是語法結構,等等。好比:工具
var s = 5;flex
if(s > 0){code
print 「greater than 5」orm
}遞歸
輸出的tokens是[var, s, 5, ;, if, (, s, >, 0, ), {, print, 「greater than 5」, }],這些tokens是有順序的,可是沒有具體的語法和語義。咱們的詞法分析器就是將源代碼做爲輸入,輸出就是這個tokens的序列。token
詞法分析中咱們會遇到一些問題,好比當咱們看到whe的時候咱們不知道這個是個變量名仍是when關鍵字,這個須要看連續的4個字符才能夠知道,這個就是LL(4),LL表明從左邊依次讀取,從最左邊的w->wh-whe->when依次來推導出這個是關鍵字仍是變量。
這個咱們這裏很少講,細節能夠找本編譯器的書來看看。有不少現成的工具和類庫幫咱們完成這樣的工做,好比lex, flex, ply,等等。
語法分析是幫咱們分析這個token序列是否符合咱們的語法,而且將語法分析結果用AST表示出來。既然是檢查語法和按語法抽取出AST,那麼咱們就須要表示語法,一般咱們使用Backs-Naur Format,也就是咱們常說的BNF表示法。不過可笑的是BNF自己是個規範,不是具體的實現。也就是說一樣BNF,不一樣的實現有不一樣的表示。好比:
statement ::= statement | empty
這個表示稱爲statement的產生式,也就是statement的語法規則,一樣的有的BNF表示法是這樣表示的
statement –> statement or empty
其中::=和->, |和or,是同樣的意思。
語法分析的結果就是符合語法的AST,也能夠說是符合語法的一個實例。
語法分析階段咱們也須要處理一些問題,好比爲了解決算術的二義性,咱們須要引入優先級(precedence),和結合性(associative)。
語法分析也有現成的工具和類庫可使用好比yacc, bison, antlr, ply等等。
符合語法的源代碼不必定有意義,好比a/0,這個就是沒有意義的,這個咱們須要在語義處理階段解決。
待續…