D Parser 之前:寫一個簡單的虛擬機

  最近寫了一點兒 D 程序,除了感覺標準庫太差之外,沒有一個好的 IDE 也是一個很頭疼的事,特別是沒有智能提示,每次調用一個函數什麼的,都要查文檔或者直接看源代碼,實在是太費勁了。

 

  所以決定自己嘗試寫一個支持智能提示的 D 的 IDE。因爲 SharpDelelop 比較小,而且它對 C# 的支持也做到了智能提示、窗體編輯器等等,所以決定用它作爲主框架,除了智能提示,也許還能加入 DFL 的窗體編輯之類的功能(Entice 做的窗體編輯已經不錯了,只是沒有事件支持)。目前,已經完成了語法加亮,代碼摺疊(目前和 notepad++ 一樣只是通過大括號匹配來做的),下一步,就是智能提示了,而智能提示就牽涉到語法分析。

 

  找了幾個分析器生成器,試用之後,覺得 Grammatica 還不錯,生成的代碼比較清晰,調試起來也比較方便。照它的例子寫了一個四則運算的分析器,還不錯。

 

  看了一下 D 的語法詳細列表,那也不是一般的複雜。所以,決定先寫一個簡單的語言的分析器、編譯器和虛擬機練練手。今天先把虛擬機做了出來。

 

  這種語言的語法非常簡單,姑且稱之爲 Z 語言吧(還沒有細化):

 

聲明語句: int x;        // 只支持 int
賦值語句: x = 1;
條件語句: if(x > 1) { ... } else { ... }
跳轉語句: goto lable
標籤語句: :lable
輸出語句: write(x);        // 只支持 int
註釋語句: // ... <eol>

 

  而虛擬機部分,參照 x86 asm,定義如下:

寄存器:        EAX, EBX, ESP, EIP        // EAX,EBX操作數,ESP堆棧指針,EIP指令指針
內存:          200000B,0B~99999B爲堆棧,100000B~199999B爲程序
指令:
      爲EAX賦值:                       set eax, 1                // 01 01 00 00 00
        將當前堆棧地址變量複製到eax:     mov eax, *esp             // 02
        爲EAX賦值:                       set ebx, 1                // 03 01 00 00 00
        將當前堆棧地址變量複製到ebx:     mov ebx, *esp             // 04
        爲當前堆棧地址變量賦值eax:       mov *esp, eax             // 05
        爲當前堆棧地址變量賦值ebx:       mov *esp, ebx             // 06
        esp 加運算:                      add esp, 1                // 07 01 00 00 00
        eax 加運算:                      add eax, ebx              // 08
        eax大於ebx?結果放eax:           gt                        // 11
        eax大於等於ebx?結果放eax:       gteq                      // 12
        eax等於ebx?結果放eax:           eq                        // 13
        eax bool not:                    not                       // 14
        eax 爲非 0 跳轉(相對):         if eax jmp {sp}           // 21 {sp}
        無條件跳轉(相對):              jmb {sp}                  // 22 {sp}
        輸出 eax:                        out                       // 31
        結束:                            over                      // ff

  

   另外,虛擬機需要能顯示寄存器值,顯示當前堆棧頂值,顯示輸出。支持單步執行。

 

  再寫一段小程序,用來驗證虛擬機的運行情況,因爲只支持 int,所以計算 1 到 100 的和是一個比較合適的小代碼段, C 的代碼如下:

int n = 0;
for(int i=1; i<=100; i++)
{
    n += i;
}
write(n);

  Z 語言不支持 for 循環,所以,相應的 Z 代碼大體如下:

int n = 0;
int i = 1;
:next
if(i > 100) { goto end; }
n += i;
i++;
goto next;
:end
write(n);

  

  而根據上面定義的指令集,其相應的彙編代碼如下:

 

// esp n, esp+4 i;
// int n = 0;
set eax, 0                               // 01 00 00 00 00
mov *esp, eax                            // 05
// int i = 1;
add esp, 4                               // 07 04 00 00 00
set eax, 1                               // 01 01 00 00 00
mov *esp, eax                            // 05
// :next
// if(i > 100) { goto end; }
mov eax, *esp                            // 02
set ebx, 100                             // 03 64 00 00 00
gt                                       // 11
if eax jmb <end>                         // 21 1B 00 00 00
// n += i;
mov ebx *esp                             // 04
add esp, -4                              // 07 FC FF FF FF
 mov eax *esp                             // 02
add eax, ebx                             // 08
mov *esp, eax                            // 05
add esp, 4                               // 07 04 00 00 00
// i++;
mov eax, *esp                            // 02
set ebx, 1                               // 03 01 00 00 00
add eax, ebx                             // 08
mov *esp, eax                            // 05
// goto next;
jmb <next>                               // 22 D9 FF FF FF
// :end
// write(n);
add esp, -4                              // 07 FC FF FF FF
mov eax, *esp                            // 02
out                                      // 31
over                                     // FF


  虛擬機的代碼不算複雜,VM 類擁有 eax, ebx, esp, eip 等屬性,然後有一個函數 Step 提供執行一條指令的功能,在 Step 中,使用一個 switch 來處理不同的指令。之後,運行程序,把上面的彙編代碼的字節序列寫入 add.bin 文件中,用虛擬機加載,運行,得到結果:5050。

 

  在把 Z 轉換到彙編的過程中,發現寫編譯器的話,對於寄存器的使用,是一個很需要考慮的問題,而對於 D 智能提示,只需要分析器就夠了,似乎寫編譯器有一些超出了,不過,既然都寫了,就試着先把這個完成吧。

 

  下面是源代碼和運行截圖:

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章