Settings

Theme

Opal - Ruby to Javascript compiler

opalrb.org

130 points by cap10morgan 13 years ago · 53 comments

Reader

DanielRibeiro 13 years ago

Cool idea. Not the first attempt though:

http://hotruby.yukoba.jp/

https://github.com/whitequark/coldruby

http://rb2js.rubyforge.org/

http://www.ntecs.de/blog/articles/2007/01/08/rubyjs-javascri...

https://github.com/jessesielaff/red

http://www.playmycode.com/docs/quby

https://github.com/mattknox/8ball

Unfortunately, none of them seem to be to handle the mor dynamic features of ruby, like method_missing, which diminishes the possibility of reusing existing ruby code in the browser.

dyscrete 13 years ago

It's a great idea, but it seems ridiculous how even simple arithmetic gets translated to an unreadable mess of javascript.

puts 4 + 2 + 5 + (19 + 3 * 4) - 8 / 10

translates to:

(function() { var __opal = Opal, self = __opal.top, __scope = __opal, nil = __opal.nil, __breaker = __opal.breaker, __slice = __opal.slice; var __a, __b, __c, __d, __e, __f, __g, __h; return self.$puts((__a = (__c = (__e = (__g = 4, __h = 2, typeof(__g) === 'number' ? __g + __h : __g['$+'](__h)), __f = 5, typeof(__e) === 'number' ? __e + __f : __e['$+'](__f)), __d = (__e = 19, __f = (__g = 3, __h = 4, typeof(__g) === 'number' ? __g * __h : __g['$*'](__h)), typeof(__e) === 'number' ? __e + __f : __e['$+'](__f)), typeof(__c) === 'number' ? __c + __d : __c['$+'](__d)), __b = (__c = 8, __d = 10, typeof(__c) === 'number' ? __c / __d : __c['$/'](__d)), typeof(__a) === 'number' ? __a - __b : __a['$-'](__b))) })();

  • Scriptor 13 years ago

    It's really not terribly complicated to figure out what it does, here's the formatted version for adding two numbers

        (function() {
              var __opal = Opal, self = __opal.top, __scope = __opal, nil = __opal.nil, __breaker = __opal.breaker, __slice = __opal.slice;
              var __a, __b;
              return self.$puts((__a = 1, __b = 2, typeof(__a) === 'number' ? __a + __b : __a['$+'](__b)))
        })();
    
    The first line just declares some Opal-specific variables (__scope, nil, __breaker, etc.), which I guess are created whether or not they're actually used. The second line defines the temporary variables used to store the numbers. Then, depending on the type, it either actually adds them or use its own '$+' function, which I guess it adds to the prototype for all objects.

    In terms of having to ever debug something like that, I agree that it'd probably be a huge pain.

    • scott_s 13 years ago

      I agree that it's reasonable. Consider this C program:

        int main()
        {
          return 4 + 2 + 5 + (19 + 3 * 4) - 8 / 10;
        }
      
      Becomes the following assembly:

          .section    __TEXT,__text,regular,pure_instructions
          .globl  _main
          .align  4, 0x90
        _main:
        Leh_func_begin1:
          pushq   %rbp
        Ltmp0:
          movq    %rsp, %rbp
        Ltmp1:
          movl    $42, -8(%rbp)
          movl    -8(%rbp), %eax
          movl    %eax, -4(%rbp)
          movl    -4(%rbp), %eax
          popq    %rbp
          ret
        Leh_func_end1:
      
      Yes, that's going from "high level" to "low level," but much of the same concepts exist. You have to set up a bunch of stuff unrelated to the computation first, which in the Ruby-to-JavaScript case means setting up the runtime system. In the C-to-assembly case, it means mucking around with the stack. Then the actual computation may not be the most optimal thing, because it was generated by a general framework which can handle any arbitrary computation. Reducing it to something more reasonable looking is an optimization.
  • btilly 13 years ago

    In Ruby 2100 is an exact integer. In JavaScript, it is promoted to a poor floating point approximation. If you want to try to preserve Ruby's numeric semantics, you can't use native math.

    • omaranto 13 years ago

      I think IEEE doubles can represent up to 53 bit integers exactly. (Someone here will know if that's true.)

      • btilly 13 years ago

        Gah, the website ruined what I was typing. I meant 2 to the power of 100. Which is a 100 bit integer, and certainly not correctly calculated in JavaScript.

        • dbaupp 13 years ago

          While your general point is correct, the details aren't. Powers of 2 are represented exactly in floating point (for any number, only the 53 most significant bits can be stored). So 2^100 + 1 is an example of something that's non-representable in JS.

          • btilly 13 years ago

            We could have a philosophical debate about what number a finite precision representation represents when you exceed its precision. But on a practical level, if you print it, what do you get? An exact integer, or a floating point representation?

  • lucian1900 13 years ago

    It makes sense, considering JS's silly coercion rules and overflow, which don't match Ruby's.

  • zem 13 years ago

    the problem is that ruby lets you redefine + anywhere you like, and return values of any type. so you need the most general translation of that expression into a series of method dispatches.

doktrin 13 years ago

Being relatively new to cross-language compilation, how are one language's class APIs typically translated into another?

For example :

   [1,2,3].shuffle 
[turns into] =>

   (function() {
     var __opal = Opal, self = __opal.top, __scope = __opal, nil = __opal.nil, __breaker = __opal.breaker, __slice = __opal.slice;
  
     return [1, 2, 3].$shuffle()
   })();
Of course, since JS arrays do not have a "shuffle" method, the output reads :

   TypeError: Object 1,2,3 has no method '$shuffle'
Is the answer to simply use class/object methods that are present in both languages?
  • mossity 13 years ago

    Could probably also include something like Sugar.js or Underscore.js that tries to bring a lot of the missing syntactic sugar to JS.

Xcelerate 13 years ago

I really like this idea of compiling code to Javascript. Brenden Eich makes some good points for it in his series of slides (starting here: http://brendaneich.github.com/Strange-Loop-2012/#/21).

I'm working on a little programming language myself that compiles to Javascript. In most languages that do this, I wish there was better information on the mapping between the domain language and its Javascript codomain. I think this would be a good open source project idea: a tool to assist in the compilation of various languages to Javascript. Something to the effect of how source lines in C can be printed above their generated assembly. Anyone want to give it a shot?

  • spacemanaki 13 years ago

    I think what you've described would be mostly accomplished by source maps: http://www.html5rocks.com/en/tutorials/developertools/source...

  • slurgfest 13 years ago

    Why is it not possible to engineer a basic VM to do this job from the start, rather than retrofitting Javascript to act as a bytecode?

    • adrusi 13 years ago

      implementing a vm in javascript would be noticeably slower than compiling directly to javascript. Dart has its own vm, which will eventually be bundled with chrome since it's a google-backed project, but for backwards compatibility it still needs a dart source to javascript source compiler.

jhrobert 13 years ago

They lost me at the "nil" section.

RubyScript is what we need => efficient code, à la coffeescript, with a Ruby syntax.

There is some impedance mismatch between ruby & javascript semantics. Trying to hide this fact has a cost in terms of performance.

I would love to have some ruby syntax as long as it does not compromize the speed of my code.

There is a trend these days where syntax and semantics are becoming orthogonal, that's nice.

Hence GoScript, PascalScript, PrologScript, CppScript, SmallScript...

Thanks to sourcemaps the issue of the generated code readability has disappeared. Only the performance matters from now on.

  • SilasX 13 years ago

    >RubyScript is what we need => efficient code, à la coffeescript, with a Ruby syntax.

    Why not make a Ruby -> Coffeescript compiler, since they're a lot closer in the first place?

vjeux 13 years ago

Unfortunately their hash table uses Javascript objects. Therefore it does not behave exactly like Ruby.

Eg:

  # Opal
  h = Hash.new
  h['0'] = 1 
  h[0] = 2
  print h
  # {"0"=>2}

  # Ruby
  h = Hash.new
  h['0'] = 1 
  h[0] = 2
  print h
  # {"0"=>1, 0=>2}
It is very hard to reproduce those low level specifications without rebuilding everything from scratch sadly :(
1qaz2wsx3edc 13 years ago

Yup, that's some good brainfuck:

      (function() {
        var __opal = Opal, self = __opal.top, __scope = __opal, nil = __opal.nil, __breaker = __opal.breaker, __slice = __opal.slice, __klass = __opal.klass;
        var adam = nil, __a, __b;
        (__b = [1, 2, 3, 4], __b.$each._p = (__a = function(a) {

    
          if (a == null) a = nil;

          return this.$puts(a)
        }, __a._s = self, __a), __b.$each());
        (function(__base, __super){
          // line 5, (file), class Foo
          function Foo() {};
          Foo = __klass(__base, __super, "Foo", Foo);

          var Foo_prototype = Foo.prototype, __scope = Foo._scope;

          return Foo_prototype.$name = function() {
      
            if (this.name == null) this.name = nil;

            return this.name
          }, 
          Foo_prototype['$name='] = function(val) {
      
            return this.name = val
          }, nil
        })(self, null);
        adam = __scope.Foo.$new();
        adam['$name=']("Adam Beynon");
        return self.$puts(adam.$name());
      })();
  • ghayes 13 years ago

    If you look at this rationally, it's actually pretty sane. It's just very simple JavaScript OOP inline with translated code. Even more beautiful is the output of modules:

         module Run
           def run
             puts "I am running!"
           end
         end
    
         module Jump
           def jump
             puts "I am jumping!"
           end
         end
    
         class Foo
           include Run
           include Jump
         end
    
         mario = Foo.new
         mario.run
         mario.jump
    
    
    translates to:

         (function() {
           var __opal = Opal, self = __opal.top, __scope = __opal, nil = __opal.nil, __breaker = __opal.breaker, __slice = __opal.slice, __module = __opal.module, __klass = __opal.klass;
      
         	// Define Mario
         	var mario = nil;
      
         	// Javascript "Run" class
         	(function(__base){
             // line 2, (file), module Run
             function Run() {};
             Run = __module(__base, "Run", Run);
             var Run_prototype = Run.prototype, __scope = Run._scope;
    
             Run_prototype.$run = function() {
          
               return this.$puts("I am running!");
             }
                 ;Run._donate(["$run"]);
           })(self);
    
         	// Javascript "Jump" class
           (function(__base){
             // line 8, (file), module Jump
             function Jump() {};
             Jump = __module(__base, "Jump", Jump);
             var Jump_prototype = Jump.prototype, __scope = Jump._scope;
    
             Jump_prototype.$jump = function() {
          
               return this.$puts("I am jumping!");
             }
                 ;Jump._donate(["$jump"]);
           })(self);
    
         	// JavaScript "Foo" class
           (function(__base, __super){
             // line 14, (file), class Foo
             function Foo() {};
             Foo = __klass(__base, __super, "Foo", Foo);
    
             var Foo_prototype = Foo.prototype, __scope = Foo._scope;
    
         	 // $include Run / Jump
             Foo.$include(__scope.Run);
             return Foo.$include(__scope.Jump);
           })(self, null);
    
         	// The ruby executing code translation
           mario = __scope.Foo.$new();
           mario.$run();
           return mario.$jump();
         })();
    • sic1 13 years ago

      While we are posting contrived examples. What was ever wrong with this?

          var Foo = function() {};
      
          Foo.prototype.jump = function() {
              return "I jump."
          };
      
          Foo.prototype.run = function() {
              return "I run."
          };
      
          var mario = new Foo();
          mario.run();
          mario.jump();
      
      I can't imagine that opal code running nearly as fast as javascript. And if we are waving around compiled js - coffeescript does a much better job of being readable. You just black-boxed the whole thing. I like to know what is happening with my js.

      Cool for the rubyist in you i guess. I just kinda go o_0

jonny_eh 13 years ago

While very impressive it makes me nervous. The JS it outputs just in the basic tutorial looks extremely complex, I'd hate to debug that code! Compare with CoffeeScript, where the output is quite easy to follow, and therefore debug.

  • wtetzner 13 years ago

    The difference is that CoffeeScript was designed to compile to JavaScript, so it can adopt JavaScript semantics.

    Ruby doesn't have the same semantics as JavaScript, so you can't have a 1-to-1 mapping of language constructs.

    • jonny_eh 13 years ago

      Oh I get that. I'm sure they did as good as job as possible, but it still makes me nervous.

      As nice a language as Ruby is, I don't think it's worth the trade-off. I'll stick with CoffeeScript since it's a nice compromise between Ruby and JavaScript.

  • denysonique 13 years ago

    OpalScript is going to solve this problem

lexy0202 13 years ago

Can you interact with the dom inside opal?

sambuna 13 years ago

I certainly see future for this!

jblock 13 years ago

Source maps? Source maps.

5vforest 13 years ago

What the use case for something like this? Am I missing something?

  • Sanddancer 13 years ago

    Using the language you're comfortable with client-side. Though what this really shows, once again, is that we really need a good, open, virtual machine specification for client-side byte code so we aren't stuck with the same javascript hammer for all problems.

    • MatthewPhillips 13 years ago

      ? Doesn't this show that we aren't stuck with the same hammer? Opal is a different hammer.

      • rrouse 13 years ago

        Not really. You're still using Javascript in the end.

        • ville 13 years ago

          How is that a bad thing? Source code VMs are easier to secure and as the tooling improves, you don't need to look a the generated JS anymore to debug, it becomes "byte code".

    • rcthompson 13 years ago

      Wasn't that the original goal of Java, like, 20 years ago?

      • Sanddancer 13 years ago

        It was, but the big issue there was that sun decided to keep all the keys of the castle to themselves. Hence the desire for an open bytecode format.

    • iso8859-1 13 years ago

      Native Client? Or PNaCl if you insist on extreme portability.

  • threedaymonk 13 years ago

    If you can perform computation on the client, you can avoid some round trips to the server. Of course, you can't trust the client, so you still need to perform (some of) the same computations on the server.

    If you can use the same code for both client and server, then it opens up the possibility of sending chunks of logic over the wire.

    Here's a trivial example: a website uses Markdown for comment formatting. You can improve the feedback cycle, especially for those who aren't overly familiar with Markdown, by rendering a preview on the client side. However, this means that you need two Markdown parsers, one in your server-side language, and one in JavaScript. That's a potential duplication of effort. Someone has already written a JS Markdown parser, but it might not have exactly the same behaviour as your server implementation. So why not just write the code once and have it work on both sides?

    I have a feeling that there are a lot more interesting things that you can do with split computation, but I haven't seen many examples yet. I think they'll come.

  • mikesea 13 years ago

    Rewriting your entire Rails app in Node so that it goes faster!

  • astrodust 13 years ago

    Porting existing code without having to re-implement. Keeping a single reference implementation of a particular method.

goggles99 13 years ago

Can't those standards committees just do something right for once and create a standard for a vm that will run in the browser (a la Dartium)? Stop this monopoly and oppression of innovation!!!

Keyboard Shortcuts

j
Next item
k
Previous item
o / Enter
Open selected item
?
Show this help
Esc
Close modal / clear selection