Tenshi的函数接口

  脚本语言的函数一般都有一个C语言的函数原型,以便程序进行扩展,与C进行交互,这对于脚本语言几乎是必须的,那么,就要设计出一个合适的函数接口。在设计过程中,要考虑到以下一些问题:
(1)接口是否要一致的问题
  在同一个脚本语言中,不同类型的函数是否可以使用不同的接口。如使用三个操作数的函数与使用两个操作数的函数,或是有返回值的函数与返回值的函数。一般的脚本语言一般只能使用统一的接口,但由于Tenshi使用了JIT的技术,因此完全可以采用不同的接口,使用不同接口的好处是使得编译成的函数可以与一般的C函数相近或完全相同,这样写扩展时就非常方便了。但是接口如果不统一,同时也带来了一些复杂性,我们还必须保存函数的原型,而且动态类型的动态特性也就大为折扣。而且很多动态类型的函数参数都是不固定的,由此,我决定还是使用统一的接口。
(2)是否使用解释器引用作为参数
  我最早的设计中,函数接口就是简单的

  1. typedef void (* tns_api)(TInterpreter *)

  可以说,这个接口非常简洁,而且功能足够强大,一个函数的参数可以从这个解释器引用中获得,同时返回值也是在这个解释中设置。不过这样的设计对于写扩展的确有些麻烦,Lua的设计与此类似,不过返回值是int,用来表示返回值的个数。由于使用了JIT,我于是想用JIT来获得更高的执行效率,尽量使用系统的堆栈,而不是设置于解释器中。如果是这样,解释器引用反而在很多时候用不上,偏偏每次都得传入这个参数,有些麻烦。不过有了解释器引用参数,就可以有了很多扩展的余地,同时也容易方便地实现异常处理等。将来如果添加更多功能时就非常有用了。
(3)不定参数如何参数
  由于脚本语言的特点,一个函数的参数往往不是固定的,这样一来所有的参数必须放置在数组中传入。这样一来,就有一个问题,如何确定参数的个数?一种是像C语言的字符串那样使用一个哨位,也就是最后一个值使用空引用,另外一种是像C语言main参数那样,参数个数和参数数组都是两个不同的参数,参数的个数不是通过参数数组中的哨位隐含确定,而是直接给出。
  我选择的是后一种,虽然前者的好处有便于遍历和简化接口的作用。
(4)返回值的设置
  由于Tenshi的基本元素是一个结构体,是机器字长的两倍,这样一来如果直接使用返回值就比较麻烦。于是就干脆传一个返回值引用好了。只需设置这个值即可。
(5)参数传引用还是传值
  本来按照一般的习惯,一般函数的参数都是传值,不过感觉统一传引用更方便,这样一样要省掉很多心思,甚至左右值都可以轻松处理了。反正都统一看成引用。
  综上以上考虑,最后的方案如下: 

  1. typedef void (*tns_api)(TInterpreter *tns, //解释器引用
  2.    TObject* result, // 返回值引用
  3.    int argc, //参数个数
  4.    TObject *argv[]); //参数数组

Tenshi Comments(0) 2008年4月11日 15:13

Tenshi的类型系统<续>

  在寒假里,虽然我一行Tenshi的代码也没写,但是它的设计却一直在我脑海里不断地更新。特别是类型系统,在Lua的metatable机制的启发下,我觉得在Tenshi中完全也可以使用。一个Tenshi对象就是一个C语言中的一个结构体:TObject,一个叫法和Lua一样,其中T是前辍,在Tenshi源代码中,所有以类似TXxx是Tenshi的类型。它由两部分构成:

  1. struct TObject{
  2.  TType *type;
  3.  TValue value;
  4. };


  这与Lua的处理方法不同之处是它的类型是一个指向TType的指针,而不是一个整数,TValue是一个共用体,用于保存对象的值。而且目前为了方便起见,暂时为4个字节,与一般32位机的字长相同。TType类似于Lua的Metatable,里面保存了相应的方法:

  1. struct TType{
  2.  const char *name; // 类型名,为空表示匿名类型。
  3.  tns_api add,subtract,multiply,divide,modulus; // 五种算术运算
  4.  tns_api less,equal; // 比较函数
  5.  tns_api iterator; // 默认的迭代函数
  6.  tns_api assign; // 赋值函数 
  7.  tns_api input,output; // 输入输出
  8.  tns_api read,write; // 读写
  9.  tns_api destructor; // 用于处理一些析构操作
  10.  tns_api call; // 函数调用操作
  11.  tns_api tostring; // 转化成字符串的形式
  12.  ...
  13. };
  14.  


  例如当遇到c=a+b这样的表示式时,则是调用a的add函数,把a和b作为参数传入,然后调用c的assign函数,把c和a+b的结果代入。比较函数虽然只有<(less)和==(equal)两个函数,但是其它的比较函数都是由这两个函数衍生而来。如
a>b -> b<a
a>=b -> not a<b
a<=b -> not b<a
a!=b -> not a==b
  也就是遇到其它的比较操作,只须在编译期稍微做一些变动就可以了,而且这也比较符合逻辑,如果a<b,那么b必然大于a,即使被比较的不是数字,也应该符合这个规律。所以这样的约定可以将问题简化。
  在Tenshi中,函数式的编程思想也是存在的,因此,只要有如foo(a,b)则是调用foo本身的call函数,并且把foo,a,b三个参数传入,因为foo也是一个对象,它可以作为参数被其它函数调用,也可以作为返回值,也可以有除调用以外的任何操作如算术比较运算等等。从这点来说,TType可看作C++中的虚函数表,这套机制类似于C++的动态绑定机制。
  iterator也是重要的操作之一,它是Tenshi完成高级迭代的重要方法。如for(a in array){...}这样的for循环中,实际上就是不断地调用array的iterator函数不断改变a的取值。从而达到迭代的目的。关于Tenshi的迭代机制,下次博客里再写。
  输入输出也是极为重要的操作,在它的input函数,用于从流中读入数据,而output则用于输出数据。而read和write主要用于流的读写,Tenshi提倡使用流的机制来完成输入和输出。而且流可以是文件流,也可以是网络流,还可以是字符串流,二进制数据流等等,而它的io对象我将参考glib中的GIOChannel对象,
  一般这些字段不能为空,Tenshi在编译时不会进行非空测试。如果没有提供相应的操作可以赋其为tns_op_idle(什么都不做),或是tns_op_except(引发异常),tostring相当于Ruby中重载的to_s,destructor相当于Lua中Metatable的__gc。
  当然,以后可以通过这个机制来进行基础类型的扩展,不过现在还没有正式开始写,一切都还是设想而已,不过我自认为这是一个很好的设计,也许已经有一些脚本语言就是这样设计的。

Tenshi Comments(0) 2008年4月11日 15:11

Tenshi中关于类型系统的设计

  在几乎所有的脚本语言中,它的类型都是动态的,类型都是由运行时决定的,这样的确可以更灵活,使用起来也更方便。但是有时却带来一些在静态语言中所没有的隐患。
  在最初用C++时,我用的得动态类型,本来嘛,这是脚本语言特色之一,但是使用过一些脚本语言(如Ruby)后,发现动态类型常常出现一些运行时错误,特别严重是拼写错误,由于动态类型,这些不能算是语法上的错误,但是逻辑上出了问题。后来看到了Lua,慢慢重效率的思想抬头,决定使用静态类型,也算是Tenshi区别于其它脚本语言上的特点。
  但是解释器的运行都是执行期的,所以对于其实现来说,两者相较,动态类型可能更好做一些。而且可以享受到很多动态类型所带来的便利,例如一个如果表示一个整数没有设定,我们可以直接赋其为nil,而在C#中则得使用int?这样的可空类型。而且在函数定义时,也不须指定参数和返回值,其代码要简洁很多。
  考虑到这些便利之处,我想采取一种较为折衷的方案,默认是静态,可提高效率与一些编译期错误检查,另一方面再提供动态类型(称为Vishnu类型),以提高编程的灵活性与方便性。但这样同时提供两种类型,却同时加大了一些编程上的困难与语法设计上的复杂性,这必然又会使语言本身变成复杂。
  现在我的想法是,语义上是动态类型,但是在生成中间码时加入静态类型的优化。如假如a,b都是整数类型,则在c=a+b时,直接使用整数加法,而不是动态地判定。
  基本上,设计的思路变化为
动态类型->静态类型->动静折衷->语义为动态,编译时静态优化。

Tenshi Comments(0) 2008年4月11日 15:08

Tenshi中关于数据结构部分的设计

  在一个脚本语言中,什么动态数组啦,哈希表啦等结构可以说是必须的,而标准C中又没有提供相应的库,导致这个问题总是困扰着我。
  最初的时候,我使用的是C++来开发,所以有STL可用,在C里,就没有了这个便利。于是有两条路来选择,一是使用第三方库,第二便是自己来写。我在选择的途中有不少反复。
  首先我在SourceForge上找到了一个叫CTL的东西,名字很吓人,叫C Template Library,其实就是用宏来实现C++中的模板,但是里面只有一些最简单的结构,没有映射这类的东西,那怎么办呢?我后来专门搜了一番,有一个younglib,是国人写的,里面的库比较丰富,里面还有字符串结构。能用的地方都比较好用,但是也有一些严重的问题。那个哈希表至今是如何插入键值对我也不大清楚,它的例程里也没有相应的说明,此外一个类型的初始化函数参数太多,而它的成员往往还带有一些m_的前辍,这一点我也很不喜欢,不过其源代码是开源的,因此可以用来做参考。
  要想找一个较为通用的库,我想了想,还是找到了著名的glib,这个库更新很快,版本已到2.15,但是主版本号一直还是2,这个库的风格我很喜欢,命名很有规律,源代码也很好,它的构造很精妙,上次我在看它的GArray的实现里就发现了其用C语言来实现数据隐藏的方法,很好。我从中尝到了很多东西,这个东西在Linux里应用很广泛,它同时也是GTK的基础,GNU的主打工程之一。
  我用这个库把Tenshi完成得差不多了,可是这个程序在Windows上运行时,需要好几个动态库,这个一是很麻烦的地方,我想保持我的程序尽量小巧,Lua是最好的榜样,如果一两个动态库还好说,四五个动态库就太臃肿了。而且那个glib的哈希表不能使用栈空间的数据作为键值,一定得是静态空间或是堆空间。这样一来也不是很好用。
  于是我想还是除掉glib,自己手写数据结构。这个过程很容易产生问题,不过我花了一些时间和精力终于弄好了。但除去glib的代码很多事情都不能做了。而不久后我开始没时间了,于是Tenshi至今连个半成品都不是。
  这样,从STL->CTL->YoungLib->Glib->自己手动写,转了好几次,我还是决定自己造轮子,这样一则更熟悉数据结构,少了一些黑箱,二则解释器的技术含量也就更高了。

Tenshi Comments(0) 2008年4月11日 15:06

Tenshi脚本语言的设计

  已經有好幾個星期没寫博客了,這幾個星期裏也没什麽事。不過我的那個Tenshi語言有了很大的進展,基于glib-2.0的解釋器已經實現了表達式的堆棧處理代碼,而整體的框架也搭建得差不多了。不過還是有很多方面的問題没有解决,很多設計上還遠不穩定,中間又改了很多。

  其中一個很重要的問題就是數據結構的處理,由于C語言的標准并没有標准的容器,要想管理符號表之類的東西要麽自己寫,要麽用第三方的庫。我一直想找一個通用性强,效率又高的庫。GNU的庫本來是最好不過了,它的glib裏有我想要的所有容器,充分地滿足了我的要求,不過它的庫是都是動態庫,而且要運行我的程序,居然要四個動態庫,如在Windows中需要libglib-2.0.dll,libgmodule-2.0.dll,iconv.dll和intl.dll。雖然都不大,可是却不是我需要的,後來找了一個CTL(C Template Library),也就是用C語言的宏來實現類似C++模板類,雖然文件很小,但功能實在是太少,後來又找了個ylib,這個很不錯,還是國人的作品,作者對C++的STL很熟悉,這個庫實際上就是C語言的STL,而且還是静態庫,滿足我的要求,可是那裏對哈希表的操作我一直没搞明白,所以後來想想還是用glib,後來基于glib的我寫得差不多了,後來發現本來我想用的很多功能glib能提供的我也不用花很大力氣,還是學學人家lua,完全的Ansi C,一點廢話都没有。而glib裏面的有些容器還是不符合我的要求,其實就容器而言,最好用的還是STL,可惜那是C++的東西,既然要用C語言實現,STL還是割愛好了。用C語言來實現數據結構總是不如C++的類來得方便。

  最終,我又决定,還是自己寫,去掉glib,不過glib給我的啓示還是很大的,我後來寫的函數大多都是類似glib的命名方式,如棧的壓棧操作,我定義為tns_stack_push,這種前輟+域+操作,并且以下劃綫分割,全用小寫字母,看起來很順眼。除了命名上的啓示外,還有一個很重要的Quark處理也是引自glib。

  關于Tenshi中的模塊處理,因為在Linux和Windows中關于動態庫的加載的系統調用是不同的,最早我是准備兩個不同的文件,在Makefile裏設置,如unix裏定義為module.unix.c,在Windows裏這個文件命名為module.win.c,後來我使用了條件編譯,這様就可以寫在同一個文件裏,再後來,我用宏來統一加載動態庫的接口,如打開動態庫的操作,我這様定義

  1. #ifdef unix
  2. #define tns_module_open(name) dlopen(name,RTLD_LAZY)
  3. #elif WIN32
  4. #define tns_module_open(name) LoadLibrary(name)
  5. #endif

  感覺這種處理是最簡單和最優雅的。以前看C語言的頭文件,發現預處理特彆多,覺得很惡心,現在也覺得這様做的必要性了。

  關于語言是動態類型還是静態類型的問題,因為現在主流的脚本語言都是動態類型的,所以最早的設計就是動態的,這様棧的數據一定包含有類型信息,後來,這種設計實際上增加了很多執行期的負擔,每次執行時都要進行類型檢查,後來我還是想出了一個折衷的方案,類似boo脚本的設計,變量本身是有類型的,這個類型一旦確定,就不能再更改,一切賦予它的值則必須兼容這種類型,我覺得變量的類型變來變去事實上加大了很多出錯的幾率,這使得寫起來容易,調試起來難,而事實上調試的時間遠比寫的時間長,所以在這方面我用的是静態類型,但是類似C的静態類型本身的缺點是在執行期就完全丢掉了類型信息,有時很不便,我有一種特彆的解决方案,即Tenshi脚本的函數調用分為兩種,一種可以在編譯成中間碼時就執行,而第二種要執行時才執行,而這二者對程序員是透明的,完全由庫决定。這是Tenshi脚本的一個很大的特色。

Tenshi Comments(0) 2008年4月11日 14:56

Ackermann函数

  这是我注册is-programmer博客以来第一篇博客,我打算主要在这个博客里写一些技术性的文章。这个博客的风格很好,界面非常清爽,速度也快。网易博客虽然功能稳定,但是风格太繁,不太适合纯程序方面的文章。特别是贴代码,那个真麻烦,以前我为了贴一些把代码贴得漂亮点,花了很多功能,要么它把那些缩进的Tab键给我弄没了,要么带有高亮的风格速度超慢。弄个截图更是麻烦。后来偶尔见到了这个博客站点,感觉很对我的胃口。简约但不失强大,速度很快。

  Ackermann是我在一个专门测试各类编程语言上的网站见到过一次,后来在charlee的博客上又看到了相关的说明,原来这个函数虽然很简单,但是其算法的复杂度却很高,适于语言性能的比较。文章里专门还用Perl和C语言进行了对比,结果C语言比Perl快了280倍以上!看来得了解一下,文章还提供了一个MathWorld的链接,里面有更详细的说明,下面是Ackermann函数的定义,图是从MathWorld里弄来的。

  下面是测试所用的Perl代码:

  1. #!/usr/bin/perl
  2.  
  3. sub ackermann {
  4.   my ( $m, $n ) = @_;
  5.  
  6.   return $n + 1 if $m == 0;
  7.   return ackermann( $m - 1, 1 ) if $n == 0;
  8.   return ackermann( $m - 1, ackermann( $m, $n - 1 ) );
  9. }
  10.  
  11. print ackermann( 3, 10 ), "\n";
  下面是C代码:
  1. #include <stdio.h>
  2.  
  3. int ackermann(int m, int n);
  4.  
  5. int main() {
  6.   int result = ackermann(3, 10);
  7.   printf("%d\n", result);
  8.   return 0;
  9. }
  10.  
  11. int ackermann(int m, int n) {
  12.   if (m == 0) return n+1;
  13.   if (n == 0) return ackermann(m-1, 1);
  14.   return ackermann(m-1, ackermann(m, n-1));
  15. }
  16.  

   下面我再写一个Lua代码,纯粹测试一下is-programmer的FCKEditor 2.6的所提供的代码美化功能:

  1. #! /usr/bin/env lua
  2.  
  3. function ackermann(m,n)
  4.         if m==0 then
  5.                 return n+1
  6.         elseif n==0 then
  7.                 return ackermann(m-1,1)
  8.         else
  9.                 return ackermann(m-1,ackermann(m,n-1))
  10.         end     
  11. end
  12.  
  13. print(ackermann(3,10))

 

Perl Comments(0) 2008年4月11日 13:28