王垠:怎樣寫一個直譯器

發表於2012-08-14

來源:王垠

賣了好久關子了,說要寫一個程式語言理論的入門讀物,可是一直沒有下筆。終於狠下心來兌現一部分承諾。今天就從直譯器講起吧。

直譯器是比較深入的內容。雖然我試圖從最基本的原理講起,儘量讓這篇文章不依賴於其它的知識,但是這篇教程並不是針對函數語言程式設計的入門,所以我假設你已經學會了最基本的 Scheme 和函數語言程式設計。如果你完全不瞭解這些,可以讀一下《 SICP | 計算機程式的構造和解釋》的第一,二章。當然你也可以繼續讀這篇文章,有不懂的地方再去查資料。我在這裡也會講遞迴和模式匹配的原理。如果你已經瞭解這些東西,這裡的內容也許可以加深你的理解。

直譯器其實不是很難的東西,可是好多人都不會寫,因為在他們心目中直譯器就像一個 Python 直譯器那樣複雜。如果你想開頭就寫一個 Python 直譯器,那你多半永遠也寫不出來。你必須從最簡單的語言開始,逐步增加語言的複雜度,才能構造出正確的直譯器。這篇文章就是告訴你如何寫出一個最簡單的語言 (lambda calculus) 的直譯器,並且帶有基本的的算術功能,可以作為一個高階計算器來使用。

一般的編譯器課程往往從語法分析(parsing)開始,折騰 lex 和 yacc 等工具。Parsing 的作用其實只是把字串解碼成程式的語法樹(AST)結構。麻煩好久得到了 AST 之後,真正的困難才開始!而很多人在寫完 parser 之後就已經倒下了。鑑於這個原因,這裡我用“S-expression”來表示程式的語法樹(AST)結構。S-expression 讓我們可以直接跳過 parse 的步驟,進入關鍵的主題:語義(semantics)。

這裡用的 Scheme 實現是 Racket。為了讓程式簡潔,我使用了 Racket 的模式匹配(pattern matching)。如果你用其它的 Scheme 實現的話,恐怕要自己做一些調整。直譯器是什麼

首先我們來談一下直譯器是什麼。說白了直譯器跟計算器差不多。它們都接受一個“表示式”,輸出一個 “結果”。比如,得到 ‘(+ 1 2) 之後就輸出 3。不過直譯器的表示式要比計算器的表示式複雜一些。直譯器接受的表示式叫做“程式”,而不只是簡單的算術表示式。從本質上講,每個程式都是一臺機器的“描述”,而直譯器就是在“模擬”這臺機器的運轉,也就是在進行“計算”。所以從某種意義上講,直譯器就是計算的本質。當然,不同的直譯器就會帶來不同的計算。

需要注意的是,我們的直譯器接受的引數是一個表示式的“資料結構”,而不是一個字串。這裡我們用一種叫“S-expression”的資料結構來表示表示式。比如表示式 ‘(+ 1 2) 裡面的內容是三個符號:’+, ‘1 和 ‘2,而不是字串“(+ 1 2)”。從結構化的資料裡面提取資訊很方便,而從字串裡提取資訊很麻煩,而且容易出錯。

從廣義上講,直譯器是一個通用的概念。計算器實際上是直譯器的一種形式,只不過它處理的語言比程式的直譯器簡單很多。也許你會發現,CPU 和人腦,從本質上來講也是直譯器,因為直譯器的本質實際上是“任何用於處理語言的機器”。

遞迴定義 (recursive definition)

直譯器一般都是“遞迴程式”。之所以是遞迴的原因,在於它處理的資料結構(程式)本身是“遞迴定義”的結構。算術表示式就是一個這樣的結構,比如:'(* (+ 1 2) (* (- 9 6) 4))。每一個表示式裡面可以含有子表示式,子表示式裡面還可以有子表示式,如此無窮無盡的巢狀。看似很複雜,其實它的定義不過是:

“算術表示式”有兩種形式:

1) 一個數

2) 一個 ‘(op e1 e2) 這樣的結構(其中 e1 和 e2 是兩個“算術表示式”)

看出來哪裡在“遞迴”了嗎?我們本來在定義“算術表示式”這個概念,而它的定義裡面用到了“算術表示式”這個概念本身!這就構造了一個“迴路”,讓我們可以生成任意深度的表示式。

很多其它的資料,包括自然數,都是可以用遞迴來定義的。比如常見的對自然數的定義是:

“自然數”有兩種形式:

1) 零

2) 某個“自然數”的後繼

看到了嗎?“自然數”的定義裡面出現了它自己!這就是為什麼我們有無窮多個自然數。

所以可以說遞迴是無所不在的,甚至有人說遞迴就是自然界的終極原理。遞迴的資料總是需要遞迴的程式來處理。雖然遞迴有時候表現為另外的形式,比如迴圈(loop),但是“遞迴”這個概念比“迴圈”更廣泛一些。有很多遞迴程式不能用迴圈來表達,比如我們今天要寫的直譯器就是一個遞迴程式,它就不能用迴圈來表達。所以寫出正確的遞迴程式,對於設計任何系統都是至關重要的。其實遞迴的概念不限於程式設計。在數學證明裡面有個概念叫“歸納法”(induction),比如“數學歸納法”(mathematical induction)。其實歸納法跟遞迴完全是一回事。

我們今天的直譯器就是一個遞迴程式。它接受一個表示式,遞迴的呼叫它自己來處理各個子表示式,然後把各個遞迴的結果組合在一起,形成最後的結果。這有點像二叉樹遍歷,只不過我們的資料結構(程式)比二叉樹複雜一些。

模式匹配和遞迴:一個簡單的計算器

既然計算器是一種最簡單的直譯器,那麼我們為何不從計算器開始寫?下面就是一個計算器,它可以計算四則運算的表示式。這些表示式可以任意的巢狀,比如 ‘(* (+ 1 2) (+ 3 4))。我想從這個簡單的例子來講一下模式匹配(pattern matching) 和遞迴 (recursion) 的原理。

下面就是這個計算器的程式碼。它接受一個表示式,輸出一個數字作為結果,正如上一節所示。

(define calc
 (lambda (exp)
   (match exp                                ; 匹配表示式的兩種情況
     [(? number? x) x]                       ; 是數字,直接返回
     [(,op ,e1 ,e2)                         ; 匹配並且提取出操作符 op 和兩個運算元 e1, e2
      (let ([v1 (calc e1)]                   ; 遞迴呼叫 calc 自己,得到 e1 的值
            [v2 (calc e2)])                  ; 遞迴呼叫 calc 自己,得到 e2 的值
        (match op                            ; 分支:處理操作符 op 的 4 種情況
          ['+ (+ v1 v2)]                     ; 如果是加號,輸出結果為 (+ v1 v2)
          ['- (- v1 v2)]                     ; 如果是減號,乘號,除號,相似的處理
          ['* (* v1 v2)]
<wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr>
<wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr>

相關文章