Opal:Ruby to Javascript Compiler

RubyのコードをJavaScriptに変換するOpalというソフトウェアがある。Opal自身はRubyで書かれていて、gemでインストールできる。これを使ってRubyコードをJavaScriptに変換してWebサイトにアップロードすると、手元でRubyで書いたコードがブラウザ上で動作してしまう、という話である。
Opalはすでにかなり開発が進んでいて、組み込みライブラリや標準ライブラリが実装されている。RailsSinatraとの連携、jQueryのバインダなどもあり、Opal用ゲームライブラリまである。
逆に欠点はと言うと、変換する都合で直接JavaScriptを書くよりも遅いとか、文字列の破壊的変更ができないとか、そんな感じ。文字列の破壊的変更はそもそもあんまりやらないので基本的には違和感はあまり無い。

opal-parser.js

OpalはRubyで書かれたソフトウェアなので、Opal自身をOpalでJavaScript化することができる。たぶんそれで作られたのではないかと思うもののひとつにopal-parser.jsというものがあり、こいつをサイトに読み込んでおくと、scriptタグでtype="text/ruby"と指定することでHTML上に直接Rubyのコードを書けるようになる。実行時変換である。おそらくeval用に存在するんじゃないかと。
んで、お試しでこいつを使ってサンプルを作ってみた。JavaScriptに変換したコードを公開するとインパクトが無いが、opal-parser.jsでHTML埋め込みRubyコードを実行するとインパクトがかなりある。
opalサンプル

サンプル解説

つってもyharaさんの記事のサンプルとかそこで使われてるyeahのコードを参考にしているので偉そうなことは言えないのだが。
とりあえずさっきのサンプルを動かしてもらえると、カーソルキーで画像が動くのがわかるだろう。んで、ソースを表示すると下のほうにRubyっぽいコードが書いてあるわけだ。ここに書いてあるコードはDXRubyの超簡易互換インターフェイスの定義で、一番下のonload do〜endの中を見てもらうとメインのコードはDXRuby用サンプルそのままであることがわかる。ヤバそうな感じが漂ってきた。
では順番に行ってみよう。Opalの雰囲気とヤバさが伝われば今回の記事の価値もあろうというものだ。

Imageクラス
class Image
  def self.load(filename)
    image = Native(`new Image()`)
    image.src = filename
    image
  end
end

``で括るとその中はJavaScriptとして実行される。Rubyの中にJavaScriptを埋め込めるという時点ですでに何かがおかしい。んで、Nativeメソッドに渡すとJavaScriptのオブジェクトがRubyオブジェクトに変換されて、それ用の関数をRubyから呼べるようになる。たぶんこれを使うとJavaScript用ゲームライブラリなどもそのまま使えるようになるので、pixiとかがたぶんそのまま使える。
ということで、このloadメソッドが返すのはDXRuby互換ImageではなくJavaScriptのImageである。Imageをクラスにしている意味が無いが、まあ、本気で互換ライブラリを作るならここは変える必要があるだろう。

Inputモジュール

ポイントはこの部分。

  %x{
    window.addEventListener('keydown', function(event) {
      #{@tmp_pressed_keys[`event.keyCode`] = true}
    });

    window.addEventListener('keyup', function(event) {
      #{@tmp_pressed_keys[`event.keyCode`] = nil}
    });
  }

%x{}はJavaScript埋め込みである。addEventListenerでキーボードイベントを受け取り、JavaScriptのfunctionを呼んでいるわけなのだが、その中に記述されている#{}はRubyを埋め込む記法であり、つまりfunctionの内部はRubyである。さらにその中に``でJavaScriptが埋め込まれている。どこまでいけるんだこれ。functionの引数eventが一番内側のJavaScriptで使われているところも注目だ。

onload

Windowモジュールはどうでもいいので省略して、メインの部分。

def onload(&block)
  `window.onload = block;`
end

onload do
  image = Image.load("../img/logo.png")
  x = y = 100
  Window.loop do
    x += Input.x * 5
    y += Input.y * 5
    Window.draw(x, y, image)
  end
end

onloadメソッドは渡されたブロックをJavaScriptのwindow.onloadにセットす・・・る・・・?
Ruby側のローカル変数をJavaScriptで直接参照できるというところもすごいが、RubyのブロックをそのままJavaScriptに渡すことができるというのもすごい。これが可能なのはRubyのブロックがJavaScriptのfunctionに変換されているからだろう。
ちなみにWindow.loopはブロックをrequestAnimationFrameでコールバックさせるように作ってあるので、実際にはループしない。このへんはJavaScriptの流儀に沿って作らないといけないので、自前でウィンドウを作ったりするDXRubyとは同様にはできない。見た目だけ合わせた。

おしまい

かつてDXRubyがブラウザで動いたらいいのになあ、なんていう甘い妄想をしていたもんだが、現実のほうが先に行っていたようだ。JavaScriptの言語トランスレータ技術は凄まじい勢いで進歩している。
当然、WebSocketなんかを使ってサーバとのやりとりなんかもできるだろうし、その手のライブラリを使うこともできるはずで、一気に世界が広がりそうな可能性を感じた。
つってもWeb関連は全くの素人(避けてたし)で、まともに動作する何かを作れるとすればおそらくずいぶん先になるんじゃないかと思う。