最近写了一点儿 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 智能提示,只需要分析器就够了,似乎写编译器有一些超出了,不过,既然都写了,就试着先把这个完成吧。
下面是源代码和运行截图: