rhinoで言語の境界をとっぱらう

以前jrubyでjavascriptを動かすみたいなことを試したんだけど、現状のプロジェクトを進めるうちにどんどんその夢が広がりんぐなので、またその方向を押し進める試みをば。

最近だと大体どの言語環境でもJSONを解釈するlibはあるけど、JSONにfunctionを忍ばせてあるのまでは解釈ができないわけですよ。というわけでJSONに隠れて潜むfunctionもメソッドとして使いましょうというネタ。

・・たぶんわたすのグルに「なんでjruby!!??」といわれそうなんだけど、いまgroovyのExpandoを細かく見ているので練習がてらに近々groovyに書き直す予定。がんばれオレ!!

以下ソース。

#!/opt/jruby/bin/jruby
require 'java'
include_class "org.mozilla.javascript.Context"
include_class "org.mozilla.javascript.ScriptableObject"

class Javascript
  def initialize(script)
    @ctx = Context::enter
    @parent_scope = @ctx.initStandardObjects
    @script = "var jsObj="+script+";" ##TODO:無名jsonをムリヤリにグローバルスコープに追加。ださい。
    @ctx.evaluateString(@parent_scope,@script,nil,0,nil)
    @current_scope = @parent_scope.get("jsObj",@parent_scope)
    begin
      yield self if block_given?
    rescue
      puts "error"
    ensure
      Context::exit
    end
  end
  
  def method_missing(name,*arg)
    js_find(name.to_s,arg)
  end
  
  private
  def js_find(name,args=nil)   
    evl = @current_scope.get(name,@current_scope)
    args = args.to_java unless args.nil?
  
    ##callがdefinedならば実行できるやつ curryは・・・ムリっぽい
    if evl.respond_to?("call")
      ret = evl.call(@ctx, @parent_scope, @current_scope, args)
      ret
    
    else
      ##Array
      if evl.instance_of? Java::OrgMozillaJavascript::NativeArray
        ret = []
        evl.length.times{|i|ret << evl.get(i,evl.getParentScope)}
        ret
      
      ##Object Hash??
      elsif evl.instance_of? Java::OrgMozillaJavascript::NativeObject
        ##TODO
      
      ##IntegerもFloatで!!??
      else
        evl 
      end
    end
  end
end

こんなクラスをjruby側で定義してやって、こんなふうに使う。

data = File.open(__FILE__).read.gsub(/.*__END__/m,"")
  
class Person < Javascript
  require 'date'
  def name
    self.lastName.upcase+", "+self.firstNam
  end
    
  def address
    self.city+", "+self.state+", "+self.country
  end
    
  def age
    monthnames = {}
    Date::MONTHNAMES.each_with_index{|v,i| monthnames[v] = i+1}
      
    today = DateTime.now
    birth_date = DateTime.new(self.birthYear.to_i,monthnames[self.birthMonth],self.birthDay.gsub(/[^0-9]/,"").to_i)
    today.year - birth_date.year
  end
end
  
who = Person.new(data)
##from js
puts who.firstName
puts who.lastName
puts who.fullName
puts who.birth
  
##from ruby
puts who.name
puts who.address
puts who.age

__END__
{
  firstName:'Leroy',
  lastName:'Hutson',
  country:'U.S.A.',
  state:'New Jersey',
  city:'Newark',
  birthYear:'1945',
  birthMonth:'June',
  birthDay:'4th',
  profile:'Replaced Curtis Mayfield as lead singer for The Impressions in 1970 when Curtis Mayfield wanted to concentrate even more on his Curtom label and solo career.',
  roomMate:'Donny Hathaway',
  fullName:function(){return this.firstName+" "+this.lastName},
  birth:function(){return this.birthDay+", "+this.birthMonth+", "+this.birthYear}
}

javascript側のオブジェクトはmethod_missing経由でrhino解釈で戻って、当たり前だけどselfで参照できるからクラスメソッドとして定義したメソッドでどうとでもいじくれるというもの。ある程度の処理ならばクライアントサイドとサーバサイドでおんなじ処理ができますよ的な。