Sup bros. I need to post in this thing more often. Yesterday, someone tipped over my scooter again. I’m getting kind of tired of that.
Anyway, its time for me to write about this. RKelly is pretty much dead. For the past few months, John and I have been working on RKelly’s replacement called Johnson. Basically we’re now putting a ruby wrapper around Mozilla’s Spidermonkey. The project is coming along quite nicely. Ruby objects can be passed in to javascript land, and javascript objects can be passed back in to ruby land.
For example, we can define an alert function in our javascript context:
require 'johnson'
ctx = Johnson::Context.new
ctx['alert'] = lambda { |x| puts x }
ctx.evaluate('alert("Hello world!");')
Johnson::Context#evaluate will also return the last statement evaluated. We can evaluate an expression, and manipulate that expression in ruby land. For example, I’ll create an object in javascript, return it to ruby land, then access a property of the javascript object:
require 'johnson'
ctx = Johnson::Context.new
obj = ctx.evaluate('var foo = { x: "hello world" }; foo')
puts obj.x # => 'hello world'
We can even do the reverse by stuffing ruby objects in to the context:
A = Struct.new(:foo)
ctx = Johnson::Context.new
ctx['alert'] = lambda { |x| puts x }
ctx['a'] = A.new("bar")
ctx.evaluate('alert(a.foo);') # => 'bar'
But it gets better. We added a top level variable called “Ruby” that lets you access constants and globals from Ruby land. We can rewrite the previous example completely in javascript:
ctx = Johnson::Context.new
ctx.evaluate("var x = new (new Ruby.Struct(Johnson.symbolize('foo')));")
ctx.evaluate("x.foo = 'bar'")
puts ctx.evaluate('x').foo # => 'bar'
puts ctx.evaluate('x').class # => #<Class:0x49714>
Since the ‘Ruby’ constant delegates to Object, you can access any constant. Including ones you’ve defined yourself. We could, for example, look up a bunch of User records through rails:
ctx = Johnson::Context.new
ctx['alert'] = lambda { |x| puts x }
ctx.evaluate(<<-END
for(var user in Ruby.User.find(Johnson.symbolize('all'))) {
alert(user.first_name());
}
END
)
You might be wondering what this Johnson.symbolize business is about. Since Javascript doesn’t have a concept of a symbol, we’ve created a helper to “mark” a string as a symbol and pass it back in to ruby land.
To conclude this update about my Johnson, I’d like to show off an interactive shell for Johnson (thanks to Brohuda Katz). Johnson has an interactive shell that lets you try things out in javascript land or ruby land, and let you quickly switch between the two. Typing ‘js’ will put in you the javascript shell, ‘rb’ will switch you to the ruby shell. In the ruby shell, you can use the ‘cx’ variable to get ahold of you javascript context:
$ ruby -I lib bin/johnson
js> var x = { foo: 'bar', hello: function() { return 'world' } };
=> nil
js> rb
rb> cx['x'].foo
=> "bar"
rb> cx['x'].hello()
=> "world"
rb>
~~~
We aren't quite ready for a release yet, but if you'd like to play around with Johnson, you can pull it down from github here. Just run 'rake', and you should have it compiled and running!
My next Johnson related post will be about Javascript parse trees and Javascript code generation.