在《D Parser 之前:寫一個簡單的虛擬機》裏,其中計算 1 到 100 之和的程序 add.bin,是使用十六進制編輯器直接編輯出來的。虛擬機制作完後,考慮了一下,如果直接寫 Z 的編譯器,難度還是不小,所以決定,先寫一個彙編語言的編譯器,實現從彙編代碼到機器代碼的編譯工作。
大體來說,彙編編譯基本上是一條一條對照生成,不過,行號的需求使得其中多了一些複雜性,另外,我還決定加入註釋的支持。所以,這也是一個比較好的機會實踐一下分析器生成器的使用。
彙編語言部分做了少量修改,over 改爲 end,行號改爲加 @ 前綴,完成的 Grammatica 的分析文件如下:
%header%
GRAMMARTYPE = "LL"
DESCRIPTION = "A asm grammar for zvm."
AUTHOR = "Lephone Liang"
VERSION = "1.0"
DATE = "7 January 2008"
LICENSE = "."
COPYRIGHT = "Copyright (c) 2008 Lephone. All rights reserved."
%tokens%
EAX = "eax"
EBX = "ebx"
ESP = "esp"
EIP = "eip"
SET = "set"
MOV = "mov"
TJMP = "jmp"
ADD = "add"
TGT = "gt"
TGTEQ = "gteq"
TEQ = "eq"
TNOT = "not"
IF = "if"
TOUT = "out"
TEND = "end"
POINT = "*"
COMMA = ","
NUMBER = <<(-)?([0-9])+>>
LABEL = <<@[a-z]+>>
COMMENT = <<;[^\n\r]*[\r\n]>> %ignore%
WHITESPACE = <<[ \t\n\r]+>> %ignore%
%productions%
Expression = Atom [Expression];
Atom
= SetEax
| MovEax8Esp
| SetEbx
| MovEbx8Esp
| Mov8EspEax
| Mov8EspEbx
| AddEsp
| AddEaxEbx
| Gt
| Gteq
| Eq
| Not
| IfEaxJmp
| Jmp
| Out
| End
| LineLabel ;
SetEax = SET EAX COMMA NUMBER;
MovEax8Esp = MOV EAX COMMA POINT ESP;
SetEbx = SET EBX COMMA NUMBER;
MovEbx8Esp = MOV EBX COMMA POINT ESP;
Mov8EspEax = MOV POINT ESP COMMA EAX;
Mov8EspEbx = MOV POINT ESP COMMA EBX;
AddEsp = ADD ESP COMMA NUMBER;
AddEaxEbx = ADD EAX COMMA EBX;
Gt = TGT;
Gteq = TGTEQ;
Eq = TEQ;
Not = TNOT;
IfEaxJmp = IF EAX TJMP LABEL;
Jmp = TJMP LABEL;
Out = TOUT EAX;
End = TEND;
LineLabel = LABEL;
生成代碼後,加入新建的 ZasmC 工程,參照 Grammatica 的例子調試了一會兒,增加一些處理代碼後,編譯器可以正常工作了。用它編譯上一次的的 1 到 100 和的彙編代碼,發現幾個彙編代碼的格式錯誤 後,編譯成功,加載入虛擬機,運行得到結果:5050。
還想再寫一個程序驗證一下,Fibonacci 序列是一個不錯的例子,於是編寫 d 的原型如下:
import std.stdio;
static void main(char[][] args)
{
int i=0;
int a=1;
write(a);
int b=1;
write(b);
int t;
next:
t = a + b;
write(t);
a = b;
b = t;
i++;
if(i<10) goto next;
}
void write(int n)
{
writefln("%d", n);
}
改寫爲彙編代碼如下:
; 斐波那契
; esp i, esp+4 a, esp+8 b, esp+12 t
; int i=0;
set eax, 0
mov *esp, eax
; int a=1;
; write(a);
set eax, 1
add esp, 4 ; a
mov *esp, eax
out eax
; int b=1;
; write(b);
add esp, 4 ; b
mov *esp, eax
out eax
add esp, -8 ; i
; int t;
@next
; t = a + b;
; write(t);
add esp, 4 ; a
mov eax, *esp
add esp, 4 ; b
mov ebx, *esp
add eax, ebx
add esp, 4 ; t
mov *esp, eax
out eax
; a = b;
; b = t;
add esp, -4 ; b
mov eax, *esp
add esp, -4 ; a
mov *esp, eax
add esp, 8 ; t
mov eax, *esp
add esp, -4 ; b
mov *esp, eax
; i++;
add esp, -8 ; i
mov eax, *esp
set ebx, 1
add eax, ebx
mov *esp, eax
set ebx, 10 ; 循環次數
gteq
not
if eax jmp @next
end
用 ZasmC 編譯,生成 Fibonacci.bin,加載到虛擬機,第一次運行錯誤,後來發現是 d 轉匯編的時候的疏忽,修正彙編代碼後,編譯,加載運行,得到正確的結果。
下一步就是寫 Z 的編譯器了,這一步可能要花比較長的時間,準備把 Z 編譯成彙編代碼,然後再用這個彙編編譯器編譯成機器代碼,這樣,Z 編譯器就不需要處理行號問題了。
下面是虛擬機和彙編編譯器的源代碼,以及運行 Fibonacci 的截圖: