今天抓到了一个隐藏了3个月的bug。这个bug以前一直没有被找到,因为以前写的用于测试脚本的代码都没有出现类成员函数使用非全局的外部对象的情况。Vampire.Kiss用我的Vczh Free Script 2.0代替PHP开发了一个网站,过程中也向我提了不少要求。其中有一个就是想在脚本中加入namespace。其实这是相当合理的,只是我没想到脚本第一次应用就会被用来开发库。因此今晚就加上了namespace。
实际上在目前的结构中添加namespace并不复杂,因为namespace也可以用
闭包来模拟。其实闭包不仅仅是函数,而是一段带了上下文的指令表。因为namespace本身也是用于控制符号在上下文中解释方法工具,因此使用闭包来做也就是十分合适的了。想到以前是用闭包模拟class的时候,曾经实现了一个把一堆环境链接到上下文中的指令。类的继承实际上也是控制符号在类成员函数的符号在上下文解释方法的工具,因此我使用了如下方法来让闭包可以顺利地模拟class的继承:
其中粗线代表环境与符号表的链接,细线代表环境链表。上图表示了A继承出B,B再继承出C的时候,构造一个C的实例所建造的实际内存结构。这样的话每一个类都可以访问到自己、父类以及自己所在的上下文。父类与子类所在的上下文是互不干涉的。
于是我就想到using Namespace;的时候,实际上可以把Namespace插入到当前的环境中(虽然后来证实这样做是错的)。于是就改了编译器,写了一个namespace来跑一跑。结果在多层namespace而且夹杂着许多using的测试用例下挂了。于是我就去看了代码,发现其中的一个小错误。实际上在创建C的时候,需要创建B和A。这个时候C向上搜索(搜索base对象),得知C上面一共有B和A两个对象。这么做的原因是Vczh Free Script 2.0是动态语言。于是我把C指向B,B指向A,A指向C的内容,于是造成了一个bug:
这样我们就可以看到实际上C.base和C.base.base都已经脱离了B和A的上下文了。为了验证这个事情,我写了一段小脚本来测试一下:
1 GetA=func(value)
2 {
3 return class()
4 {
5 print=func()
6 {
7 writeln(value);
8 }
9 };
10 };
11
12 class(GetA(10))
13 {
14 }.new().print();
因为由于bug的缘故子类的基类对象不能正确的访问到基类所在的上下文,因此writeln(value)的时候必然会因为找不到value而发生错误。经过测试,的确发生了。于是确定了错误,然后改正。
不过话说回来,这个bug是因为using的错误实现而发现的,倒是有些运气的成分。实际上using的时候应该把环境包复制一份,然后把整条都插入当前环境中。而且因为环境包是引用符号表的,所以实际上符号并没有被复制,被复制的是
符号查找规则。今天除了namespace以外,还往Vczh Free Script 2.0中加入了操作符重载和虚拟成员向。
1 VectorSpace=namespace
2 {
3 Vector=class()
4 {
5 local X=0;
6 local Y=0;
7
8 local __get__=func(name)
9 {
10 if(name=="length")
11 {
12 return sqrt(X*X+Y*Y);
13 }
14 else
15 throw("找不到"++name++"。");
16 };
17
18 local __set__=func(name,value)
19 {
20 if(name=="length")
21 {
22 len=sqrt(X*X+Y*Y);
23 X=X*value/len;
24 Y=Y*value/len;
25 }
26 else
27 throw("找不到"++name++"。");
28 };
29
30 local __add__=func({Vector}a,{Vector}b)
31 {
32 return Vector.new(a.X+b.X,a.Y+b.Y);
33 };
34
35 local __sub__=func({Vector}a,{Vector}b)
36 {
37 return Vector.new(a.X-b.X,a.Y-b.Y);
38 };
39
40 local tostr=func()
41 {
42 return "("++X++","++Y++")";
43 };
44
45 local constructor=func(x,y)
46 {
47 X=x;
48 Y=y;
49 };
50 };
51 };
52
53 v1=VectorSpace.Vector.new(3,4);
54 writeln(v1.length);
55 v1.length=10;
56 writeln(v1.tostr());
57
58 using VectorSpace;
59
60 v2=Vector.new(-1,1);
61 writeln((v1+v2).tostr());
62 writeln((v1-v2).tostr());
输出:
1 5.0
2 (6.0,8.0)
3 (5.0,9.0)
4 (7.0,7.0)
我们可以看到加号、减号以及虚拟的length成员是如何添加进去的。虽然这么看起来虚拟成员比较奇怪,不过实际上这个东西可以用来实现现在很多语言中流行的属性,或者可以用来封装一个XML的解析器以便可以直接使用名字访问节点(譬如Document.Book1.Author,虽然Book1和Author都是XML文件中的对象,但是通过虚拟成员就可以免去原本需要的Document.Get("Book1").Get("Author")的这种麻烦的代码)等等。
目前Vczh Free Script还欠缺的语法已经很少了,等这些东西加上去以后,就可以慢慢做库了。等这个阶段完成之后就发布一个dll出来。
posted on 2008-05-11 10:07
陈梓瀚(vczh) 阅读(1832)
评论(5) 编辑 收藏 引用 所属分类:
Vczh Free Script