如何寫乙個直譯器 1 編譯原理

2022-02-26 20:36:22 字數 1920 閱讀 4675

最近在看dsl的東西,對於外部dsl,寫乙個直譯器是必不可少的。我試圖歸納一下我學到的,以寫乙個直譯器為目標,講一下如果來實現乙個可用的直譯器。乙個直譯器通常可以分為一下幾個階段:

詞法分析(lexer)

語法分析(parser, bnf, cfg, ast)

語義分析(ast的處理, annotated ast)

目標語言生成(stack-based)

這裡的直譯器不包括目標語言的執行和執行時環境,如果需要類似於python/ruby的解析執行器的話,還需要bytecode-compiler, bytecode-interpreter和runtime。我們這裡只談解釋的部分,不談執行的部分。作為dsl我們大多都有宿主語言,通常也就我們生成的目標語言,有關執行這部分,我們交給目標語言去解決。換個說法,我們只討論乙個完整解釋執行器的前端。

當前編譯技術已經得到極大發展,對於詞法分析和語法分析,我們可以借助成熟的工具或庫很快的完成,極大簡化了寫直譯器的難度。對於乙個直譯器來說我們需要自己處理的部分基本上集中於語義的分析,即ast的處理和目標語言的生成。其實這裡的ast的處理結果相當於乙個與執行環境無關的中間語言表示,而目標語言的生成就是針對目標語言的特性,將annotated ast轉換為目標語言以方便在目標語言的執行環境中執行。

繼續扯扯淡,lisp語言的語法基本上就是直接寫ast,直接用ast的表示法來表示程式,所以lisp語法是最貼近於編譯器的中間表示,開始可能根本就不是給一般人用的,誰知道這麼多人喜歡用。lisp之所以叫list processer那是因為lisp直接處理ast的資料結構表示即nested list,所以才叫list processor,一點都沒有謙虛的意思。在資料結構裡,表示樹這種資料結構的,最簡單直接的辦法就是nested list(通過linked-list了來實現)。而且這種結構利於遞迴遍歷,方便stack-based執行或者**的生成。

詞法分析是將源**轉換成tokens,這些tokens有些事關鍵字,有些是變數,有些是字面量,有些是語法結構,等等。比如:

var s = 5;

if(s > 0)],這些tokens是有順序的,但是沒有具體的語法和語義。我們的詞法分析器就是將源**作為輸入,輸出就是這個tokens的序列。

詞法分析中我們會遇到一些問題,比如當我們看到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,這個就是沒有意義的,這個我們需要在語義處理階段解決。

待續…

如何寫乙個Stack?

1.棧是陣列 2.先進後出 3.出棧 4.入棧 手寫乙個雙向鍊錶 棧 public class stackpopandpush public stackpopandpush int lens 返回元素個數 public intsize 返回陣列長度,容量,棧資料長 private intcapaci...

如何寫乙個鍊錶

有的時候,處於記憶體中的資料並不是連續的。那麼這時候,我們就需要在 資料結構中新增乙個屬性,這個屬性會記錄下面乙個資料的位址。有了這個位址之後,所有的資料就像一條鍊子一樣串起來了,那麼這個位址屬性就起到了穿線鏈結的作用。相比較普通的線性結構,鍊錶結構的優勢是什麼呢?我們可以總結一下 1 單個節點建立...

如何寫乙個Vue元件

寫的是以.vue結尾的單檔案元件的寫法,是基於webpack構建的專案。template 模板 js 邏輯 css 樣式 每個元件都有屬於自己的模板,js和樣式。如果將乙個頁面比喻成一間房子的話,元件就是房子裡的客廳 臥室 廚房 廁所。如果把廚房單獨拿出來的話,元件又可以是刀 油煙機.等等。就是說頁...