Take it to the limit one more time 13

Posted by Aaron Patterson on April 23, 2008

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.

Trackbacks

Use this link to trackback from your own site.

Comments

Leave a response

  1. Michael Neumann Wed, 23 Apr 2008 14:32:28 PDT

    Great stuff! I am considering to use it for RubyJS (Ruby to Javascript compiler and runtime), mainly for testing.

  2. Mark Holton Wed, 23 Apr 2008 14:45:58 PDT

    …this is really sweet… can’t wait to mess with this, and hear more about it.

  3. Aaron Patterson Wed, 23 Apr 2008 16:48:07 PDT

    Thanks guys! I’m having a great time working on the project. I think that soon it will be good enough to start integrating in to Ruby Mechanize.

  4. aaron's mistress Wed, 23 Apr 2008 17:43:49 PDT

    wait - where’s the parser tree? LIAR.

  5. Bug Wed, 23 Apr 2008 22:55:35 PDT

    Surprise! Recognition! Awe! Acknowledgement! Appreciation! Obnoxiousness!

  6. Johnson | АяксЛайн.ру Thu, 24 Apr 2008 15:37:20 PDT

    [...] Источник: Блог Аарона Паттерсона [...]

  7. My GoRuCo 2008 highlights « Invisible Blocks Mon, 28 Apr 2008 15:37:01 PDT

    [...] Johnson is a ruby gem that executes JavaScript code. (It’s a successor to RKelly, which did the same thing.) I don’t know why I think this is so cool. Most people agreed the main use case for something like this is testing, but it seems to me there might be neater tricks to play. We’ll see how I feel after playing with it for a while. [...]

  8. Wes Garrison Sun, 25 May 2008 13:17:45 PDT

    I love my Stella, too!

    Found your website via the mechanize docs and I’ll be adding you to my ruby feeds, so keep the posts coming!

  9. markus Tue, 03 Jun 2008 11:23:30 PDT

    Does this work?

    ctx = Johnson::Context.new
    ctx << “test”

    and << would be an alias to evaluate?

    Right now typing .evaluate seems to be a bit cumbersome

  10. weepy Tue, 24 Jun 2008 02:27:10 PDT

    Is it possible to make the ‘Ruby’ global optional.

    I am interested in using Johnson to run user defined javascript - so Obviously I have concerns about security.

    weepy ;…(

  11. Aaron Patterson Tue, 24 Jun 2008 06:18:48 PDT

    Yes, it is quite possible. We have it on by default, but it would only take minor changes to implement.

  12. Quickredfox Fri, 19 Sep 2008 11:57:08 PDT

    Should be mentioned that a day after posting this, Runtime became the new Context

    http://github.com/jbarnette/johnson/commit/ae3584f2d4e47d82c063c89631bcd8b23acfd9db

    ;)

  13. Quickredfox Fri, 19 Sep 2008 11:57:34 PDT

    Okie sorry, it was a couple days later….

Comments

Check Spelling
Activate Spell Check while Typing