Ruby to Javascript in 100 LOC, is it possible ?

The goal of this tutorial is to show you how, with Ruby, we can turn this:


if ($(a).value == a) {
  doThis(true, maybe);
  andThis($(a).innerHTML);
}
var MyClass = Class.create();
MyClass.prototype = {
  myMethod: function(arg1, arg2) {
    cool();
  },
  anotherOne: function() {
    say(hi);
  },
};

Into Ruby code, with a 100 lines Ruby script.

Go grab a beer / coffee / Guru / water / Asian pear / Pad Thai and get ready !


# First we build a base Page class that will handle the common statement and hold the output.
# This is pretty simple, as you can see, we only output text.
class Page
  def initialize
    @output = 
  end

  def if(condition)
    self << "if (#{condition}) {"
    yield self
    self << "}"
  end

  def def(method, *args)
    self << "#{method}: function(#{args * , }) {"
    yield self
    self << "},"
  end

  def class(name, &block)
    self << "var #{name} = Class.create();"
    self << "#{name}.prototype = {"
    yield self
    self << "};"
  end

  def <<(output)
    @output << output << "\n"
  end

  def to_s
    @output
  end
end

page = Page.new

# Using those methods looks pretty cool
# but we still need to write some js code.
page.if "$(’a').value == ‘a’" do
  page << "doThis(true, ‘maybe’);"
  page << "andThis($(’a').innerHTML);"
end
page.class :MyClass do
  page.def myMethod, :arg1, :arg2 do
    page << "cool();"
  end
  page.def anotherOne do
    page << "say(’hi’);"
  end
end

puts page

# Outputs
if ($(a).value == a) {
doThis(true, maybe);
andThis($(a).innerHTML);
}
var MyClass = Class.create();
MyClass.prototype = {
myMethod: function(arg1, arg2) {
cool();
},
anotherOne: function() {
say(hi);
},
};

# Iteration 2 - Make the outputed code beautiful

# Lets add some helper methods to all the objects
# so we can make beautiful Ruby code output as
# beautiful Javascript.
class Object
  # This method will allow to use Ruby naming convention
  # to declare Javascript methods.
  # Converting my_method to myMethod
  def to_js_method
    to_s.gsub(/_(\w)/) { $1.upcase }
  end

  # This one will convert an object to it’s Javascript
  # equivalent. This is pretty basic here. Wrap string in ‘
  # or outputing if any other type.
  def to_js_arg
    self.is_a?(String) ? "#{self}" : self
  end
end

class Page
  def initialize
    @output = 
    @indent = 0
  end

  # We make the outputed code a little more pretty
  # by indenting it! See the indented method bellow.
  def if(condition)
    self << "if (#{condition}) {"
    indented { yield self }
    self << "}"
  end  # If an unexisting method is called, output it
  # as a Javascript method call.
  def method_missing(method, *args)
    self << "#{method.to_js_method}(#{args.collect { |arg| arg.to_js_arg } * , });"
  end

  def <<(output)
    @output <<    * @indent << output << "\n"
  end

  private
    def indented
      @indent += 1
      yield
      @indent -= 1
    end
end

page = Page.new

page.if "$(’a').value == ‘a’" do
  # As you can see, we use the method_missing magic
  # to create method calls
  page.do_this true, maybe
  page.and_this "$(’a').innerHTML"
end
page.class :MyClass do
  page.def :my_method, :arg1, :arg2 do
    page.cool
  end
  page.def :another_one do
    page.say hi
  end
end

puts page

# Nice camelized method names, clearly indented = sweet!
if ($(a).value == a) {
  doThis(true, maybe);
  andThis($(a).innerHTML);
}
var MyClass = Class.create();
MyClass.prototype = {
  myMethod: function(arg1, arg2) {
    cool();
  },
  anotherOne: function() {
    say(hi);
  },
};

# Iteration 3 - converting Ruby expressions to Javascript

# Now lets take care of that if statement.
# In Ruby, even operators are methods. We can take advantage of this
# and override those and output what we want!

# This module will override all comparison operators
# and output a string representation of the expression.
# So calling a == ‘a’ will return the string "a == ‘a’"
module JSComparable
  def self.included(base)
    base.class_eval do
      %w(== < > <= >=).each do |operator|
        define_method operator do |other|
          [self.to_js_arg, operator, other.to_js_arg] *  
        end
      end
    end
  end
end

# We need to defined a simple class in which we will
# override those operators.
class Element
  attr_reader :id
  include JSComparable

  def initialize(id)
    @id = id
  end

  def to_s
    "$(’#{id}‘)"
  end
end

# Since we’re lazy, we’d like to type E[:a] ratter then Element.new(:a)
module E
  def self.[](id)
    Element.new id
  end
end

# All this allows us to rewrite this:
page.if "$(’a') == ‘a’" do
# into this:
page.if E['a'] == a do
# Pure Ruby, how cool ?

###############
# Iteration 4 #
###############
# Now one line is still pretty annoying:
#   page.and_this "$(’a').innerHTML"
# To turn this into Ruby code we can use a little
# method_missing magic again.

# We’ll create a small class that will handle
# the calls to methods on element.
# When rendered, parent.method is outputed.
class MethodProxy
  include JSComparable

  def initialize(parent, name)
    @parent = parent
    @name = name
  end

  def to_s
    [@parent, @name.to_js_method] * .
  end
end

# Add a simple hook into Element
# so any unknow method calls are rendered as
# Javascript method calls.
class Elementdef method_missing(method, *args)
    MethodProxy.new(self, method)
  end
end

# All this allows us to come to the almost perfect:
page.if E['a'].value == a do
  page.do_this true, maybe
  page.and_this E[:a].innerHTML
end
page.class :MyClass do
  page.def :my_method, :arg1, :arg2 do
    page.cool
  end
  page.def :another_one do
    page.say hi
  end
end

# Iteration 5 - Final touch, making this a real language !

# The ‘page.’ thing is pretty annoying I know!
# What’s the simpless thing we could do to remove this ?

page = Page.new

code = File.read(ARGV[0])
code.gsub! /(if|class|def) (.*)$/, page.\1 \2 do
eval code

puts page

# Now place this in a seperate file:
if E[:a].value == a
  page.do_this(true, maybe)
  page.and_this(E[:a].innerHTML)
end

class :MyClass
  def :my_method, :arg1, :arg2
    page.cool
  end
  def :another_one
    page.say(hi)
  end
end

# And run with: ruby js.rb javascript.rjs
# and you’ll get:

if ($(a).value == a) {
  doThis(true, maybe);
  andThis($(a).innerHTML);
}
var MyClass = Class.create();
MyClass.prototype = {
  myMethod: function(arg1, arg2) {
    cool();
  },
  anotherOne: function() {
    say(hi);
  },
};

If you’re serious about this, I’d suggest you take a look at ParseTree which allows you to explore the parse tree of some Ruby code.

I hope this helps anyone understand better some of Ruby meta-programming capability, beauty and power!

Ruby, I love you!

Amen

Get the full script here (rename it to js.rb)

6 Responses to “Ruby to Javascript in 100 LOC, is it possible ?”


  1. 1 Treblid September 14, 2007 at 3:17 am

    This is converting Ruby code to Javascript code using a Ruby program right?

    So can you convert Ruby on Rails into Rhino on Rails?

    Another thourght - can the program convert itself, run the result on Rhino and still work?

    A sort of “eat your own dog food”.

  2. 2 macournoyer September 14, 2007 at 9:27 am

    That’s right Treblid, it converts Ruby code to JS using a Ruby script

    But it would require a lot more work to convert a RoR app. I haven’t implemented the whole js language here and I beleive Rails and Rhino are not mapped one to one.

    What do you mean by “convert itself”? That sounds like black magic, end of the world kind of stuff! I’m scared!

  3. 3 Treblid September 14, 2007 at 10:14 am

    I think the answer is no as you “haven’t implemented the whole js language” (should that be Ruby language?). Now that would be REALLY impressive…

    I’m not that familiar with Ruby but say you run it as follows,
    foo bar.rb
    and this produces bar.js, then try
    foo foo.rb
    to get foo.js
    Then in theory you would have a javascript program that converts Ruby to javascript. To run the javascript program you would need Rhino (from Mozilla it runs JS on the java Virtual Machine).

    If it was this capable, as Rails is written in Ruby, you could in theory create a JS Framework by converting Rails’s Ruby programs.

  4. 4 macournoyer September 14, 2007 at 10:52 am

    With JRuby, Ruby runs on the Java VM.

    I don’t know if it supports Rails already but it will in a near future I’m sure.

  5. 5 James Golick September 17, 2007 at 9:44 am

    With prototype trying so hard to bring rubyishness to javascript, I almost think ruby-to-js conversion would be possible.

    Before rails, I was working with Google Web Toolkit quite a bit. With a code converter, a library like that might be possible on top of rails.

    Anybody want to give it a try with me?

  6. 6 macournoyer September 17, 2007 at 10:25 am

    I think the problem with code converter is that you always end up needing to fix that tiny little details that requires you to fall down to the actual language. And I don’t find Javascript that bad, I fact, I like javascript! Specially with prototype!

    But what would be cool is adding if statement to Rails RJS template, now that would be awesome!

    page.if page[:block].visible do
    page[:block].update ‘This is visible’
    end

    Thx for the comment James!

Leave a Reply




RefactorMyCode.comrefactorer Recommend Me

Top Clicks

  • None
Top Blogues page counter Add to Technorati Favorites

Blog Stats

  • 87,300 hits