介绍下XRuby项目


XRuby是什么?它是一个编译器。与其它编译器一样,它完成的工作是将一种格式的语言转换成另一种。与大多数编译器不同的是,它是将Ruby的代码(.rb)转换成Java的bytecode(.class)。

Xruby是一群中国开发者维护的项目,它的目的如上所述。它的主页是http://code.google.com/p/xruby/。与JRuby不同,JRuby一开始是想使用java写ruby解析器,性能上是个大问题,当然现在也走上了编译这条路。而XRuby是第一个实现这种想法的人。

我翻译下了《XRuby Hacking Guide》,这篇文章是XRuby的入门指南。

介绍

这篇文章是为了帮助用户/开发者理解xruby的内部结构而写的。

如何编译ruby?

怎么将ruby编译成java字节码呢?首先,你不必成为一名字节码方面的专家来考虑这个问题,Java的字节码是对原生机器语言的较高层次的抽象,非常类似于java源代码。你可以简化这个问题为:如何用java表示一段ruby程序?

这两门语言有很多的相同之处:ruby是一门OO语言,它有类、方法、变量等,java也是如此。这是否意味着我们可以将一个ruby类类比为java类,ruby方法作为java方法?可是,除了这些相同之处外,它们之间有足够的不同点让你打消这个主意:首先,ruby是一门动态类型语言,因此一个方法可以接受不同类型的参数,而在java中,参数类型是方法签名(signature)的一部分。其次,在ruby中,方法可以从一个类中动态地添加和移除;但是目前的JVM并不支持这样的行为。值的注意的上述的这些问题也许会在将来的JVM版本中解决,请参考Gilad Bracha's work at JSR 292.

第一个办法是我们自己维护一个类型系统,这正是xruby目前采用的办法(Ruby.net好像也是如此)。从JVM的角度看,一个ruby类只是一个Object,这个Object中包含着代表方法等的其他object。我们将在后面更多讨论这点。

另一个办法是动态地编译(ruby)源代码,在运行时获得类型信息,将源代码编译成高效的代码(字节码?)是可能的。(一些方法由于duct typeing的特性将被编译成好几个版本)

我们将比较这两个办法,

实例

通过一个例子来了解xruby:
 
 
<!----> def say_hello_three_times
    
3 . times  {puts  ' hello ' }
end

say_hello_three_times

将上面的代码存为test.rb,使用xruby编译(下载的xruby解压后运行build.bat或者build.sh生成xruby-0.1.3.jar):
 
 
<!----> java  - jar xruby - 0.1 . 3 . jar  - c test . rb

可以看到生成了一个test.jar文件,执行下面的命令来运行这个程序:
 
 
<!----> java  - jar test . jar

当然,你将看到下面的输出:

hello
hello
hello
如果你查看test.jar文件,你将看到以下3个class文件:
  • test/BLOCK$1.class
  • test/say_hello_three_times$0.class
  • test/main.class
    这些class文件等价于下面这段java程序:
      
      
    <!----> // test/main.class
    public   class  main
        
    implements  RubyProgram
    {

        
    public  main()
        {
        }

        
    public   static   void  main(String args[])
        {
            RubyRuntime.init(args);
            (
    new  main()).run();
            RubyRuntime.fini();
        }

        
    public  RubyValue run()
        {
            RubyRuntime.ObjectClass.defineMethod(
    " say_hello_three_times " new  say_hello_three_times._cls0());
            
    return  RubyRuntime.callMethod(ObjectFactory.topLevelSelfValue,  null null " say_hello_three_times " );
        }
    }


    // say_hello_three_times$0.class
    class  say_hello_three_times$ 0   extends  RubyMethod
    {

        
    protected  RubyValue run(RubyValue rubyvalue, RubyArray arrayvalue, RubyBlock rubyblock)
        {
            
    return  RubyRuntime.callPublicMethod(ObjectFactory.createFixnum( 3 ),  null new  BLOCK._cls1(),  " times " );
        }

        
    public  say_hello_three_times$ 0 ()
        {
            
    super ( 0 false );
        }
    }


    // test/BLOCK$1.class
    class  BLOCK$ 1   extends  RubyBlock
    {

        
    protected  RubyValue run(RubyValue rubyvalue, RubyArray arrayvalue)
        {
            RubyArray arrayvalue1 
    =   new  RubyArray( 1 );
            arrayvalue1.add(ObjectFactory.createString(
    " hello " ));
            
    return  RubyRuntime.callMethod(rubyvalue, arrayvalue1,  null " puts " );
        }

        
    public  BLOCK$ 1 ()
        {
            
    super ( 0 false );
        }
    }

    在main类中:首先在"Object"类中定义了一个私有的方法"say_hello_three_times",然后通过no parameter, no block和一个top level "self"作为接收者的方式调用这个方法。

    "say_hello_three_times$0"类表示say_hello_three_times方法的实现(参考command模式)。在代码中,我们可以看到Fixnum"3"(接收者)调用了"timer"这个方法,仍然没有parameter,但是有一个block被传进去(方法)。

    BLOCK$1类则表示传进"3.times"方法中的block,代码中是"puts 'hello'"的实现。

    源码结构

     

    • com.xruby.compiler.parser 提供了一个compiler前端(parser and tree parser)。 Parser转换ruby脚本成AST (Abstract Syntax Tree),然后 tree parser将AST转换为内部结构(internal structure)。
      编译器前端使用 Antlr 作为语法分析器的生成器.实践证明,将这个前端分为两部分可以带来好处:parser 和 tree parser;其中 parser 解析脚本,而tree parser生成内部结构(internal structure)。
    • com.xruby.compiler.codedom 定义了描述ruby脚本结构的内部结构(internal structure)。内部结构作为前端和后端的接口,对于xruby是非常重要的。
    • com.xruby.compiler.codegen 实现了编译器的后端(代码生成)。后端将前端生成的内部结构转换为java字节码。代码生成是通过ASM实现的,ASM简化了对字节码的操作。
    • com.xruby.runtime 实现了xruby运行时(runtime),它维护着运行ruby脚本必需的类型系统, com.xruby.runtime.lang 描述了ruby类型的运行时结构,一些ruby内建标准库实现在 com.xruby.runtime.builtin.

     

    内建库

    通往xuby hacking之路最简便的办法就是学习 'com.xruby.runtime.builtin'包的源代码。

    下面是来自Fixnum::+方法实现的代码片段:

      
      
    <!----> class  Fixnum_operator_plus  extends  RubyMethod {
        
    public  Fixnum_operator_plus() {
            
    super ( 1 );
        }

        
    protected  RubyValue run(RubyValue receiver, RubyArray args, RubyBlock block) {
            RubyFixnum value1 
    =  (RubyFixnum)receiver.getValue();
            RubyFixnum value2 
    =  (RubyFixnum)args.get( 0 ).getValue();
            
    return  ObjectFactory.createFixnum(value1.intValue()  +  value2.intValue());
        }
    }


    RubyClass c 
    =  RubyRuntime.GlobalScope.defineNewClass( " Fixnum " , RubyRuntime.IntegerClass);
    c.defineMethod(
    " + " new  Fixnum_operator_plus());

    XRuby的语法解析器

    Xruby的解析器使用 Antlr 作为解析器的生成器。 这是目前相比于c ruby唯一另类的ruby语法。

    对于大部分编程语言来说,词法分析(lexing)和语法解析是两个不同的步骤:首先词法分析器将输入的字符组织成单词(token),然后解析器将单词组织成句法单元。但是在ruby(和perl语言)中,词法分析器和语法解析器是紧紧地耦合在一起的:有时候词法分析器需要从语法分析器中获取上下文信息。

    疑难解决

    作为Xruby的开发者,我们的任何改变都可能导致编译器出错并且生成有问题的字节码。当这种情况发生时,我们可以依赖3样工具:javap,ASM和你所喜欢的java反编译器(比如jad

    如果生成的class文件格式正确但是运行结果不是预期的,我们可以简单地使用反编译工具将字节码转换成可读的java源代码,以便查找错误。

    如果你遇到是一个verifier error,大部分的反编译器都不能正常工作(jad在这种情况也许会crash掉)。我们不得不使用javap来研读字节码。多数情况下,JVM class验证器(verifier)给出的信息没什么用处,但是我们可以通过ASM更快地找到错误发生点。(see ASM FAQ: Why do I get the [xxx] verifier error?).  

  • 你可能感兴趣的:(jvm,脚本,OO,Ruby,jruby)