Take it to the limit one more time 15

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>
[/sourcecode]
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.

Pointcut Your Javascript 1

Posted by Aaron Patterson on January 24, 2008

Sorry for all of the RKelly updates, but thats what I’ve been doing in my free time. I mean, besides J-School. But I don’t think anyone wants to read my poor Japanese!

I wanted to make my ASTs easily searchable, so I added an AOP style interface to my AST. The AST will now let you pointcut your javascript and give you back a list of all of the points it finds. Take this javascript for example:

try {
  Element.update('another_id',"blah blah blah");
  Element.update(10,"blah blah blah");
} catch (e) { }

Lets say you wanted to find every place that Element.update() was called with 2 arguments, and mess with those arguments. You can pointcut the ast, then modify the arguments by giving the AST a pattern to match on like so:

parser = RKelly::Parser.new
ast = parser.parse(DATA.read)
ast.pointcut("Element.update(Object, Object)").matches.each do |m|
  m.arguments.value.each { |x|
    x.value = rand(20)
  }
end

puts ast.to_ecma

But lets say you only want to update the call that was made with a number as the first parameter. No problem! Just change your pattern to use a number, like so:

parser = RKelly::Parser.new
ast = parser.parse(DATA.read)
ast.pointcut("Element.update(Number, Object)").matches.each do |m|
  m.arguments.value.each { |x|
    x.value = rand(20)
  }
end

puts ast.to_ecma

You can even get more specific and match the arguments exactly. For example, matching just the function call where the first argument is ‘another_id’:

parser = RKelly::Parser.new
ast = parser.parse(DATA.read)
ast.pointcut("Element.update('another_id', Object)").matches.each do |m|
  m.arguments.value.each { |x|
    x.value = rand(20)
  }
end

Maybe you don’t care the update is being called on an Element, but you want to match all places that update is being called on something. The pointcut will match on node type too, so this is a perfectly valid pattern:

parser = RKelly::Parser.new
ast = parser.parse(DATA.read)
ast.pointcut("ResolveNode.update('another_id', Object)").matches.each do |m|
  m.arguments.value.each { |x|
    x.value = rand(20)
  }
end

Hopefully you get the picture. This feature isn’t full tested yet, but I think I might do my first RKelly release after I finish testing.

Clean it up! RKelly::Nodes::Node#to_ecma

Posted by Aaron Patterson on January 23, 2008

Last night at Nerd Club, I decided to add a “to_ecma” method to RKelly. This means you can turn your ECMA AST back in to ECMAScript, with the added bonus of properly indenting your code. For example:

require 'rkelly'

parser = RKelly::Parser.new
puts parser.parse(DATA.read).to_ecma

__END__
function yo(a,b,a) { this.be.some(); if(nasty) { code();} }

will output this:

function yo(a, b, a){
  this.be.some();
  if(nasty) {
    code();
  }
}

Now you should be able to modify your AST and generate javascript. Next I want to add finders to easily find nodes in the tree.

パンツないな興奮 – Javascript stuff 1

Posted by Aaron Patterson on January 19, 2008

While writing RKelly (a pure ruby javascript interpreter), I’ve run across weird cases in the ECMA spec that I didn’t know about before. For example, when coercing an object into a number, first the “valueOf” property is checked. That seemed to make sense to me. But then the spec says that if there is no “valueOf” property, to check the “toString” property and try to coerce that in to a number.

So, this bit of code:

var x = new Object;
x.valueOf = function() { return 11; };
x++;

is equivalent to this bit of code:

var x = new Object;
x.stringValue = function() { return "11"; }
x++;

That is some underpants-free excitement.

Happy New Year! (RKelly Progress report) 3

Posted by Aaron Patterson on January 03, 2008

I’ve just started getting the runtime working with RKelly. Its working well enough at this point that I was able to execute my earlier Fibonacci example. I’ve added a method to the runtime that allows you to define ruby functions that may be called from inside javascript. For example, the alert function in the following example is defined in ruby and delegates to puts.

runtime = RKelly::Runtime.new

runtime.define_function(:alert) do |*args|
  puts(*args)
end

runtime.execute(<<END
function f(n) {
  var s = 0;
  if(n == 0) return(s);
  if(n == 1) {
    s += 1;
    return(s);
  } else {
    return(f(n - 1) + f(n - 2));
  }
}
alert(f(20));
END
)

Here is the execution time with ruby 1.8.6 on my machine:

[aaron@mac-mini rkelly]$ time ~/.multiruby/install/1.8.6-p111/bin/ruby -I lib test.rb
6765

real 0m54.332s
user 0m53.913s
sys 0m0.336s
[aaron@mac-mini rkelly]$
[/sourcecode]

Same code, same machine, but with ruby 1.9.0:


[aaron@mac-mini rkelly]$ time ~/.multiruby/install/1.9.0-0/bin/ruby -I lib test.rb
6765

real 0m20.863s
user 0m20.678s
sys 0m0.142s
[aaron@mac-mini rkelly]$
[/sourcecode]

I need to get loops working next!

More ECMA Awesomeness 1

Posted by Aaron Patterson on May 22, 2007

I’ve been adding more tender loving javascript features to RKelly (my Javascript to Ruby converter). You can now call to_ruby on an RKelly object and get back your Javascript as Ruby. For example:

puts RKelly.process(<<END
function c() {
  alert('asdfasdf');
}
var a = {};
foo['b'] = c;
END
).to_ruby

And that will out put the following ruby code:

def c
  alert("asdfasdf")
end
a = lambda do
  s = OpenStruct.new
  return s
end.call
class << foo
  def b
    alert("asdfasdf")
  end
end

Implicit object declaration is now supported too:

puts RKelly.process(<<END
var s = {
  x: function () { alert("blh"); },
  y: "foo"
};
END
).to_ruby

Which will output this:

s = lambda do
  s = OpenStruct.new
  class << s
    def x
      alert("blh")
    end
  end
  s["y"] = "foo"
  return s
end.call

One thing I’ve found while implementing RKelly is that javascript tends to pass around function pointers a lot. I will probably have to convert RKelly to declare lambdas for all functions instead of actual functions. The problem is that I’ll need to get it so the lambdas will be executed in the context of the object, but I’m sure that will be pretty easy.