跳轉到內容

鸚鵡虛擬機器/Squaak 教程/窺探編譯器內部

來自華夏公益教科書,開放的書籍,開放的世界

在第一集中,我們介紹了 Parrot 編譯器工具,並使用 Parrot 發行版提供的 shell 指令碼生成了一種非常簡單的語言。我們還宣佈了 Squaak,一種專門為本教程開發的簡單程式語言。Squaak 將作為案例研究,展示 PCT 如何作為一組非常有效的工具來實現 Parrot 的語言。Squaak 的特性列表已經列出。如果你感覺幸運,你甚至可以嘗試完成上一集末尾的練習。

在本集中,我們將更深入地瞭解生成的編譯器。我們將檢視編譯過程的不同階段,並展示基於 PCT 的編譯器中正在發生的事情。

深入瞭解

[編輯 | 編輯原始碼]

還記得我們之前是如何呼叫編譯器的嗎?我們可以傳遞一個檔案,或者在不使用命令列引數的情況下呼叫編譯器,在這種情況下,編譯器將進入互動模式。考慮第一種情況,傳遞檔案 test.sq,就像我們之前做的那樣

   $ parrot squaak.pbc test.sq

當像這樣呼叫編譯器時,檔案 test.sq 會被編譯,生成的程式碼(位元組碼)會立即被 Parrot 執行。你可能會好奇,這是如何工作的呢?指令碼的解釋是透過一系列轉換完成的,從指令碼源開始,最終以 Parrot 可執行的格式結束。使用 PCT(基於 HLL::Compiler 類)構建的編譯器可以接受一個 target 選項,以顯示其中一箇中間表示。此選項可以具有以下值,對應於 HLLCompiler 物件的四個預設編譯階段

  • --target=parse
  • --target=past
  • --target=post
  • --target=pir

這是一個使用 target 選項設定為 “parse” 的示例,它將列印輸入的解析樹到標準輸出

   $ parrot squaak.pbc --target=parse test.sq

在互動模式下,輸入此內容

   say 42;

將列印此解析樹(不帶行號)

    1 "parse" => PMC 'Regex;Match' => "say 42;\n" @ 0 {
    2     <statementlist> => PMC 'Regex;Match' => "say 42;\n" @ 0 {
    3         <statement> => ResizablePMCArray (size:1) [
    4             PMC 'Regex;Match' => "say 42" @ 0 {
    5                 <statement_control> => PMC 'Regex;Match' => "say 42" @ 0 {
    6                     <EXPR> => ResizablePMCArray (size:1) [
    7                         PMC 'Regex;Match' => "42" @ 4 {
    8                             <integer> => PMC 'Regex;Match' => "42" @ 4 {
    9                                 <decint> => PMC 'Regex;Match' => "42" @ 4
   10                                 <VALUE> => \parse[0][0]
   11                             }
   12                         }
   13                     ]
   14                     <sym> => PMC 'Regex;Match' => "say" @ 0
   15                 }
   16             }
   17         ]
   18     }
   19 }

當更改 target 選項的值時,輸出會變成輸入的不同表示。現在何不嘗試一下?

因此,一個 HLL::Compiler 物件有四個編譯階段:解析、構建鸚鵡抽象語法樹 (PAST)、構建鸚鵡操作碼語法樹 (POST)、生成鸚鵡中間表示 (PIR)。編譯完成後,生成的 PIR 會立即執行。

如果你的編譯器需要額外的階段,你可以將它們新增到你的 HLL::Compiler 物件中。對於 Squaak,我們不需要這樣做,但有關詳細資訊,請檢視 compilers/pct/src/PCT/HLLCompiler.pir。

我們現在將更詳細地討論每個編譯階段。前兩個階段,解析輸入和構建 PAST 是同時執行的。因此,我們將一起討論它們。

解析階段:匹配物件和 PAST 結構

[編輯 | 編輯原始碼]

在解析階段,使用 Perl 6 的擴充套件正則表示式(稱為規則,有關詳細資訊,請參閱 概要 5)分析輸入。當一個規則匹配某個輸入字串時,就會建立一個所謂的匹配物件。匹配物件是一個組合的陣列和雜湊表,這意味著它可以透過整數和字串進行索引。由於規則通常由其他(子)規則組成,因此很容易檢索匹配的某個部分。例如,此規則

   rule if_statement {
       'if' <expression> 'then' <statement> 'end'
       {*}
   }

有兩個子規則:expression 和 statement。if_statement 規則的匹配物件表示從 if 到 end 的整個字串。當您只對 expression 或 statement 部分感興趣時,您可以透過子規則的名稱(在本例中分別為 expression 和 statement)索引匹配物件來檢索它。

在解析階段,PAST 會被構建。有一組小的 PAST 節點型別,例如,PAST::Var 用於表示變數(識別符號,例如 print),PAST::Val 用於表示文字值(例如,“hello” 和 42),等等。稍後我們將更詳細地討論各種 PAST 節點。

現在,你可能想知道,PAST 的構建究竟是在哪個點發生的?這就是上面顯示的 if_statement 規則中“if”字串下面的特殊符號 {*} 的作用。這些特殊標記指示應該呼叫解析操作。這樣的解析操作只是一個方法,它具有與它所在的規則相同的名稱(在本例中為:if_statement)。因此,在解析階段,會執行多個解析操作,每個操作都構建一個表示輸入字串的總 PAST 的一部分。稍後將對此進行更多解釋。

鸚鵡抽象語法樹只是同一個輸入字串(被編譯的程式)的不同表示。它是一個方便的資料結構,可以將其轉換為不同的東西(例如可執行的 Parrot 程式碼),但也用於進行各種分析,例如編譯時型別檢查。

PAST 到 POST

[編輯 | 編輯原始碼]

在構建 PAST 的解析階段之後,HLL::Compiler 將此 PAST 轉換為稱為 鸚鵡操作碼語法樹 (POST) 的東西。POST 表示也是一個樹結構,但這些節點處於更低的抽象級別。例如,在 PAST 級別上,有一個節點型別用於表示 while 語句(構建為 PAST::Op.new( :pasttype('while') ))。while 語句的模板通常包含多個標籤和跳轉指令。在 POST 級別上,相同的 while 語句由一組節點表示,每個節點表示一個指令或一個標籤。因此,將 POST 轉換為可執行的東西比從 PAST 級別進行轉換要容易得多。

通常,作為 PCT 的使用者,您不需要了解 POST 節點的詳細資訊,這就是我們不會詳細討論它的原因。使用 target 選項檢視 POST 的樣子。

POST 到 PIR

[編輯 | 編輯原始碼]

在第四(也是最後)階段,POST 被轉換為鸚鵡中間表示 (PIR)。如前所述,將 POST 轉換為可執行的東西相當簡單,因為 POST 節點已經表示單獨的指令和標籤。同樣,正常使用 PCT 不需要您瞭解此轉換的任何細節。

現在帶來好訊息...

[編輯 | 編輯原始碼]

我們已經建立了基於 PCT 的編譯器的通用資料流,它包含四個階段:

  1. 原始碼到解析樹
  2. 解析樹到 PAST
  3. PAST 到 POST
  4. POST 到 PIR

我們注意到前兩個階段在解析階段完成。現在,當您閱讀本教程時,您可能對使用 PCT 為 Parrot 實現您喜歡的語言感興趣。我們已經看到語言語法是用 Perl 6 規則表達的。那麼其他轉換呢?

嗯,在本節的前面,我們提到了“解析操作”這個術語,這些操作建立 PAST 節點。在您為每個語法規則編寫了解析操作後,您就完成了!

你說什麼?

[編輯 | 編輯原始碼]

沒錯。一旦您正確構建了 PAST,您的編譯器就可以生成可執行的 PIR 程式碼,這意味著您剛剛為 Parrot 實現了自己的第一種語言。當然,您仍然需要實現任何特定於語言的庫,但這無關緊要。

基於 PCT 的編譯器已經知道如何將 PAST 轉換為 POST,以及如何將 POST 轉換為 PIR。這些轉換階段已由 PCT 提供。

接下來呢?

[編輯 | 編輯原始碼]

在本節中,我們更深入地瞭解了基於 PCT 的編譯器的內部機制。我們討論了四個編譯階段,它們將輸入字串(根據您的定義,可能是程式或指令碼)轉換為 PAST、POST,最後轉換為可執行的 PIR 程式碼。接下來的部分是“有趣的部分”:我們將為 Parrot 實現 Squaak。我們將逐步實現解析器和解析操作。最後,我們將演示 John Conway 的“生命遊戲”在 Parrot 上執行,該遊戲是用 Squaak 實現的。

從下一節開始,練習會更有趣。現在,瀏覽編譯器的原始檔,看看您是否理解 Grammar.pg 中的語法規則與 Actions.pm 中的方法之間的關係會很有幫助。

實驗本節中描述的 --target 選項也很有用。如果您不瞭解 PIR,現在是做一些準備的時候了。關於 PIR 的資訊很多,請參閱參考資料部分以獲取詳細資訊。同時,如果您有任何建議、問題等,請隨時留言。

參考資料

[編輯 | 編輯原始碼]
  1. PIR 語言規範:docs/pdds/pdd19_pir.pod
  2. PIR 書籍:docs/book/pir
華夏公益教科書