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 智能提示,只需要分析器就够了,似乎写编译器有一些超出了,不过,既然都写了,就试着先把这个完成吧。

 

  下面是源代码和运行截图:

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