最近寫了一點兒 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 智能提示,只需要分析器就夠了,似乎寫編譯器有一些超出了,不過,既然都寫了,就試着先把這個完成吧。
下面是源代碼和運行截圖: