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


评论
D Parser 之前(二):汇编编译器 http://llf.javaeye.com/blog/153818
D Parser 之前(三):Z 语言编译器 http://llf.javaeye.com/blog/155151
貌似是波兰的吧,好像还有 MS Research 的资助
BSD的license,有鉴于麒麟OS,希望它可别真成了中国大学的“研究项目”
Nemerle 是大学的研究项目么?我倒没有注意,什么时候中国的大学研究项目是这种的,就好了。
我个人感觉 Nermerle 的符号用得太多,可读性不强。
玩玩 Mogre 应该不错,C++/CLI的包装,既有C++的效率,又有 .Net 的遍历性。
jmonkey 我记得去看过,截图不错,我下载了一个示例(或者开源的游戏,记不清了),测试后的印象大概如下:(不敢保证是 jmonkey 的,以前下载过挺多这种东西的)
1 是 3D 的
2 WSAD 可以以第一人称视角移动
3 贴图严重混乱
XNA Studio 我也玩过一下,比直接使用 DirectX 是简单一些,不过要形成一个可以称之为引擎的东西,所要做的东西还是很多。而且微软自己也说,XNA 是给业余作者和爱好者使用的。
至少现在来说,很难想象类似半条命2、孤岛惊魂、虚幻3、Quake3、魔兽世界或者使命召唤4之类的游戏会采用 Java/.Net 来开发。
当然,.Net 和 Java 做服务器端很多,做客户端软件很少,一是启动比较慢,二是需要客户安装运行库。
但是即使做客户端,目前可能还是 Delphi 比较多,D 的优势也不明显。
以前找过一些 Java 和 .Net 的本机编译器,一个 Hello World 也能编译出7、8M,其实把大多数 Framework 都打包进去了,做大软件可能还好。不过总给人一种还不如让用户安装运行库的感觉。Mono 我是很久没 Follow 了,听说现在 WinForm 的程序也能运行的八八九九,可能还是不错的吧。
要说 .Net 语言,其实基本上就是 C#。vb.net 也处在一个尴尬的地位,很少有人用。其实,早期的时候,如果 BOO 语言(BOO 是静态语言,却包含很多动态语言特性,比如 Method Missing)能被微软收购的话,倒是能填上这个缺,可惜现在有了 IronPython 和 IronRuby,应该不可能了。(微软并没有收购 BOO 的意向,只不过是我的希望而已)
apaged 确实是我没说清楚,这是一个很方便的解析器生成器,等于是结合了flex和yacc,但是实际上还是玩具水平。
和C++/CLI相比,也许是D比较适合吧,不过,我从.Net刚出就用.Net做开发,也差不多6、7年了,在工作中从来没有用过C++/CLI,也不觉得有这种需求。工作中确实见过一个别人用C++/CLI做的程序,因为结构太差,可读性也差,而我要维护它,所以完全被我改成C#了。
另外,C++/CLI 似乎在编译成 IL 的时候还是做了一些限制,并非所有的 C++ 特性都可以被编译成 IL。C++/CLI 因为可以混合 IL 和本机代码,所以基本上只是为了起粘合剂的作用。
比如 union 之类的结构,D 也支持,虽然我没有仔细研究过 IL,但是应该是不支持的。而如果只使用它的“单根继承,abstract/interface”之类的特性,和 C# 相比有什么不同?
dsource.org 上的 parser 项目,我下载了,代码我简单看了,确实没看懂。那两个 eclipse 插件,我都装过,试过,智能提示的功能都属于玩具级别。
另外,http://apaged.mainia.de/ 的那个,是用 D 写的 Parser 生成器,不是解析 D 的 Parser。http://seatd.mainia.de/ 那个编辑器,我也下载试了,和上面说的 eclipse 插件一样,还属于玩具级别。
其实我也是因为最近准备换工作,所以有一些时间,心血来潮想写这样一个 IDE,至于最后能不能完成,现在还不敢打保票。我写这个虚拟机、编译器,是因为它简单,而以前,我也没有写过这种程序。写了这些程序后,就能更好地理解怎么才能写出 D Parser。而如果要把 D 编译成 IL,则除了 D Parser 之外,还要写 D Compiler,那比单纯的 D Parser 就更难了,或者也可以说,只有在写出 D Parser 之后,才有可能考虑 D Compiler 的问题。另外,和 DMD 兼容性也是一个问题。
IL 我在用反射写一些程序的时候用到过,确实比 x86 汇编要直观,有了 D Parser 之后,把 D 的子集(不支持 C 接口和 C 结构,汇编,内联之类的功能等等)编译成 IL 还是可能的,另外,SharpDevelop 支持使用各种语言的 Parser 转换成中间结构,就可以提供每两种语言之间的代码的转换。所以应该可以支持 CSharp <=> D 之间的双向转换吧。当然,这种转换不见得能转换出无错的代码。
我之所以对 D 感兴趣,其实什么 C 的无缝集成,内联汇编,内置的单元测试,契约编程等等都不是重点。对我来说,本机编译才是。除了可能的速度上的提升(是否速度更快要看具体场景,另外大多数时候速度并不不是很重要),对于很多公司来说,Java 和 .Net 可以被反编译这件事,一直都是一个不大不小的问题。
关于 D,我觉得,它要抢夺 Java 或者 .Net 的阵营,基本上不可能。而作为本机编译的高级语言,做游戏开发,嵌入式设备开发是两个可能的途径。游戏开发方面,dsource 上已经有一些库,不过完成度好像都不高。而嵌入式设备开发,我去 GDC 的主页看过,他们说还没有支持,说是其它方面都没有问题,只是需要针对每一种 CPU 写垃圾回收器是现在的障碍。对于嵌入式设备的 D 编译器,也只有寄希望与 GDC 了……
dsource.org 上已经有很多个parser项目了,也有不下两个的 eclipse 插件
D 的 Parser 见过几个,不过不太会用
做虚拟机只是为了练手,“为 DMD 做一个生成 .Net IL 的后端”可要难多了,而且,老实说,把 D 编译成 .Net IL 意义不大。