メイン

jsruby アーカイブ

2007年12月12日

JSRuby で DOM 操作

日々ちょっとずつ開発している JSRuby に以下の機能が追加されました。

  • Array のサポート
  • JavaScript オブジェクトを呼び出し可能に
まだ自由に JavaScript のオブジェクトを JSRuby に渡せるようにはしていないのですが ( def を実装すれば、引数で渡せるようにする予定)、window オブジェクトと document オブジェクトをそれぞれ $window, $document で参照できるようにしています。

例えば、以下のような JavaScript なんだか Ruby なんだかよくわからないコードが動きます。


$window.alert $window.prompt "hogehoge", 123


$document.getElementsByTagName("div").each {|x|
  x.style.border = "2px solid red"
} 


この実装のために、JavaScript のオブジェクトの判別とか、IE で window.alert.apply が呼び出せないのとか色々落とし穴があったんですが、都度 id:ZIGOROu さんに助けてもらっちゃいました。ZIGOROu++

2008年01月08日

Javascript で実装した Ruby インタプリタ JSRuby 0.1 リリースしました。

あけましておめでとうございます。
1月からサイボウズ・ラボのオフィスが引っ越しました。新年から気分も新しくぶりぶり仕事します。よろしくです。

というわけでちょっとずつ作っていました Javascript で実装した Ruby インタプリタの "JSRuby" を一区切り付けるべくバージョン番号を付けてリリースしました。
JSRuby Project Page (CodeRepos)
http://coderepos.org/share/wiki/JSRuby
JSRuby Test Page
http://labs.cybozu.co.jp/blog/nakatani/jsruby/jsruby_test.html

ウリはパーサまで含めて js で実装しているので、与えられた Ruby スクリプトをブラウザ上でそのまま実行できることと、任意の Javascript のオブジェクトを渡して JSRuby 上でそのまま自然に扱えるので、特に個別にラッパーなどを実装しなくても js の様々なライブラリをそのまま使える(可能性がある)ことです。

(console)

簡単なフィボナッチ数列のプログラムですが、このとおり blog の記事の中で Ruby のコードが動きます。
全て JS だけで完結しているので、この実行部分を Bookmarklet 化すれば、任意のウェブページ上の Ruby スクリプトをそのブラウザ内で実行させることが出来ます(対応している機能だけですが)。

ブックマークレット:選択したrubyコードを実行

上記ブックマークレットのリンクをブックマークツールバーなどにドラッグし、下記コードをマウスで選択後、ブックマークレットを実行してみてください。rubyコンソールが開いて実行結果が表示されます(コンソールを消すときはリロードしてやってください。後で消す機能付けますすいません……右上の [x] をクリックしてください。)。

def fz(n)
  1.upto(n){|x|puts ["FizzBuzz",x,0,"Buzz",0,0,"Fizz"][x**8%15%7]}
end
fz 20
本ブックマークレットはコマンド入力欄も用意されているのでそのまま続けてスクリプトを逐次入力&実行ができます。

もちろんこのブックマークレットは本ブログ以外でも有効です。
闘うプログラマの友「どう書くorg」に投稿されている Ruby コードで、JSRuby にて基本無変更で動くものを下記にいくつかあげておきますので、それらで試してみてもらうのもおもしろいかもしれません。

http://ja.doukaku.org/comment/2980/
関数定義しかないので、コンソールで diff [3,1,4,1,5,9,2] など入力して実行してみる。
http://ja.doukaku.org/comment/1105/
1から10000だと終わらないので、最後の1行を除いて選択、bookmarklet でコンソールを開いたら、入力欄に実行範囲を 1から200に変えたスクリプトを入れてみる。
1.upto(200){|i| puts i if double_complete_number?(i) }
http://ja.doukaku.org/comment/159/
http://ja.doukaku.org/comment/142/
ピラミッド表示。後者は関数になっているので、続けて pyramid 5 とか pyramid 9 とか入力して遊べます。


またもう一つのウリである Javascript との連携ですが、def で定義したメソッドは以下のように記述することで javascript 側から直接呼び出せます。
alert( ruby.call("fib", [8]) )
これは上のフィボナッチ数列を計算するメソッドを呼び出す例です。実際、上のフィボナッチ数列のスクリプトをもう一度実行後、このページで FireBug のコンソールを開いてこの通り叩けば fib メソッドを呼び出せますので、試してもらえると楽しいかも。
こうして呼び出す Ruby メソッドの引数に Javascript の任意のオブジェクトを渡すことで、Ruby スクリプト側ではあたかも Ruby のオブジェクトであるかのようにそのオブジェクトのメソッドを呼び出すことが出来ます。
詳細は長くなってしまうので記事を改めることにして、とりあえずここでは簡単な例をごらんください。



実行すると画面を書き換えてしまう(全てのリンクに赤枠をつけてしまう)ので、見にくければリロードしてやってください(苦笑)。
window オブジェクトと document オブジェクトはそれぞれ $window, $document というグローバル変数にて最初から渡されており、それら Javascript のオブジェクトをこのような形で扱うことが出来ます。


JSRuby はまだ実装されていない機能も多く、目標である「どう書くorgに投稿されている Ruby コードの7割(≒ require を使っていないもの)が動く」にはまだまだ道のりが長いのですが、CodeRepos にてリポジトリが公開されていて、メソッドの実装追加などは簡単に行えるようになっていますので、多くの人にコミットしてもらえると嬉しいです。

参考:JSRuby 0.1 で実装済みの機能の一覧です。
  • if ~ elsif ~ else ~ end
  • def
  • puts, p
  • Array#<<
  • Array#[], []=
  • Array#each
  • Array#inject
  • Array#join
  • Array#length
  • Array#member?
  • Array#new
  • Array#push
  • Array#reverse
  • Numeric#+, -, *, /, %, **, ==, <=>
  • Numeric#chr
  • Numeric#to_s
  • Numeric#upto
  • String#+, *, ==
  • String#[], []=
  • String#center
  • String#length
  • String#reverse
  • String#to_i
  • Range#each


以下、戯れ言。
年末に yukoba さんが YARV インタプリタな HotRuby というのを作られて、JSRuby 作りながら「やっぱ YARV コンパイラ&VM を JS で書く方が正解なのかなー。でも VM を書くのはきっと楽しそうだけど、コンパイラを書くのはなんか楽しくなさそうだよなあ」と常々思っていたので全然くやしくなんかありません。すいません嘘です。比べてないですが、おそらく実行速度では敵わないと思います。
YARV ベースの ruby for javascript もいずれは出てくるだろうと思ってはいたんですが、あと半年は先かなあと思ってのんびりしてました(苦笑)。
ちなみに YARV の仕様全然知らないで言っているので、「VM書くのは楽しそう」「コンパイラ書くのは楽しくなさそう」も嘘かもしれません。あしからず。

2008年01月15日

JSRuby から jQuery を使う ( Javascript 連携 )

JSRuby は Javascript で実装された Ruby インタプリタです。
動作イメージ&サンプルは記事「Javascript で実装した Ruby インタプリタ JSRuby 0.1 リリースしました。」を参照していただくとして、ここでは JSRuby の Javascript 連携まわりを解説します。

JSRuby は Javascript と自然な連携ができるよう、以下の機能を持っています。
  • JSRuby-Javascript 間での任意の Javascript オブジェクトの受け渡し
  • Ruby で定義されたメソッドの Javascript 側からの呼び出し
  • JSRuby 内での Javascript メソッド実行&関数オブジェクトの呼び出し、インスタンス化
このあたりの機能を整理して使いやすくしたものを JSRuby 0.1.1 としてリリースしました。最新版の取得などは CodeRepos 上の Project Page をごらんください。
JSRuby Project Page (CodeRepos)
http://coderepos.org/share/wiki/JSRuby


前回の記事では、Ruby 上のメソッドを Javascript から呼び出す場合は alert( ruby.call("fib", [8]) ) のように書くと説明しましたが、0.1.1 からは定義されたメソッド名でそのまま自然に呼び出すことが出来るようになりました。

<script type="text/ruby">
  def fib(n)
    if n<2 then n else fib(n-1)+fib(n-2) end
  end
</script>

<script type="text/javascript">
  // JSRuby インタプリタの取得
  var ruby = new RubyEngine.Interpreter();

  // type="text/ruby" を実行 ( fib メソッドの定義。 ruby.fib() が使えるように )
  ruby.exec(RubyEngine.Util.getRubyScript());

  alert( ruby.fib(10) );  // => 55
</script>

このように JSRuby 側で def fib すれば、その関数が ruby.fib() で Javascript 側から呼び出せます。
もちろん ruby.fib は普通の関数オブジェクトですので、setTimeout に与えたり、apply したりとかもできます。
ただし JSRuby インタプリタのオブジェクトがもともと持つプロパティ名と重複している場合( exec, run など)は、従来通り ruby.call(メソッド名, [引数列]) で呼び出してください。


上の例では ruby.fib() の引数として通常の整数を与えましたが、ここに任意の Javascript オブジェクトを与えることが出来ます。
通常の「 Javascript なオブジェクト」(document とか)を与えてまるで Ruby のオブジェクトのようにメソッド呼び出しなどが出来るのはもちろんですが、コンストラクタを渡して JSRuby 側で new してそのインスタンスを返すなんてこともできます。

<script type="text/ruby">
  def newinstance(f)
    f.new 2
  end
</script>

<script type="text/javascript">
  var ruby = new RubyEngine.Interpreter();
  ruby.exec(RubyEngine.Util.getRubyScript());

  function Foo(n) = { this.bar = n * 2; }
  Foo.prototype.getbar = function() { return this.bar; }

  alert( ruby.newinstance(Foo).getbar() );     // => 4
</script>

またRuby にはいわゆる「関数オブジェクト」にあたるものがないのですが( Proc をそう呼ぶのはなー)、JSRuby では Javascript の関数オブジェクトを受け取って、ちゃんと関数オブジェクトらしく呼び出すことが出来ます。

<script type="text/ruby">
  def callfunc(fnc)
    fnc 2
  end
</script>

<script type="text/javascript">
  var ruby = new RubyEngine.Interpreter();
  ruby.exec(RubyEngine.Util.getRubyScript());

  alert( ruby.callfunc( function(x){return x*3;} ) );  // => 6
</script>

気をつけないといけないのは、ruby の文法では関数名を書いただけでその関数の呼び出しが実行されることです。つまり渡された関数オブジェクトをさらに別の Ruby メソッドに渡してその先で実行ということは出来ません。

  def callfunc(fnc)
    callfunc2 fnc   # × 関数そのものではなく、関数の実行結果を渡そうとする
  end
  def callfunc2(fnc)
    fnc 2
  end

jQuery などのライブラリを JSRuby で使うにはこれは不便です(やっと本題)。
そこで、JSRuby のグローバル変数にそれらのライブラリ用のオブジェクトを設定できるようにしています。

<script type="text/ruby">
def getfooter
  $jquery('div.footer')[0].innerHTML
end
</script>

<script type="text/javascript" src="jquery-*.*.*.js"></script>
<script type="text/javascript">
  var ruby = new RubyEngine.Interpreter();
  ruby.exec( RubyEngine.Util.getRubyScript() );

  // jQuery の $ をグローバル変数 $jquery で利用できるように
  ruby.put( "$jquery", $ )

  alert( ruby.getfooter() ); // => (content of footer)
</script>

<div class="footer"> (content of footer) </div>

JSRuby の $window や $document は同じように window, document オブジェクトがあらかじめそれらのグローバル変数に代入されているだけですね。


まだ他の様々な Javascript のライブラリ( prototype.js とか script.aculo.us とかとかとかとか)で検証が出来ているわけではないので、実際の運用ではまだまだ不具合の生じる可能性はあるかもしれませんが、基本的には上述のように自然な利用ができるように想定しています。

2008年01月19日

非同期 JSRuby


Javascriptで無限ループを実現する5つの方法
http://mono.kmc.gr.jp/~yhara/d/?date=20080114#p02
sleep はまだなさそうだけど、原理的には出来るはず

おお、JSRubyにも言及してくださってありがとうございます。
sleep かあ。やっぱりできると嬉しいのかな。
でも実は今の JSRuby って構文木を再帰的に解釈しながら実行していっているので、今のままだと sleep とか絶対無理なんですよねえ。
これを sleep できるようにするには、少なくとも例えばスタックマシンとかに作り変えなくちゃあならないんですが、そうするとインタプリタのコア部分はすっかり作り直しになるのはもちろん、Rubyのクラスやメソッドの実装回りも残らず全部影響を受けるし、構文木をスタックマシン用の命令列に変換するコンパイラも新しく必要に。

ちょっと今来週締切の仕事を抱えてて、そういう寄り道をしている余裕は全く全然ないんですよねえ。おもしろそうなネタではあるんですけどね。残念。


というわけでちょっとだけ作ってみました。

続きを読む "非同期 JSRuby" »

About jsruby

ブログ「nakatani @ cybozu labs」のカテゴリ「jsruby」に投稿されたすべてのエントリーのアーカイブのページです。過去のものから新しいものへ順番に並んでいます。

前のカテゴリはiVocaです。

次のカテゴリはイベントです。

他にも多くのエントリーがあります。メインページアーカイブページも見てください。