OpalのJSはどのように実装されているのか

この記事は[Opal Advent Calendar 2016](http://qiita.com/advent-calendar/2016/opal)の24日目の投稿として書いています。

今日はクリスマスイブですね。昨日、予告したとおり今日はJSの実装について書きます。
例によって使いかたからです。

```ruby
require 'js'

%x{
  function Foo() {
    this.bar = function() {
      return 'bar';
    };
    this.baz = 'baz';
  };
}

foo = JS.new(`Foo`)
puts foo.JS.bar
puts foo.JS[:baz]
```

昨日のサンプルと似ていますね。どちらもJavaScriptのコードを呼びだすためのAPIということがわかります。
`Native`はRubyのオブジェクトでラップしてRubyのオブジェクトとして扱えるようにしたものですが、`JS`はJavaScriptのオブジェクトを直接操作するためにつくられています。

```ruby
foo = JS.new(`Foo`)
```

この呼び出しで返ってくるオブジェクト(`foo`)はRubyのオブジェクトではなく、JavaScript側のオブジェクトとして返ってきます。
つまり

```ruby
foo = `new Foo()`
```

と書くのと等価です。(後で見ますが実際には`apply`をつかって呼び出しています。)

`foo`はJavaScriptのオブジェクトなので、Rubyのメソッドとして`bar`を呼ぶことができません。
(`Native`では`method_missing`を利用して`bar`を呼ぶとJavaScriptの`bar()`関数が呼び出されました。)
そこで、RubyのなかでJavaScriptの`bar()`関数を呼ぶには、

```ruby
foo.JS.bar
```

のように`JS`というメソッド(?)を介して呼びだします。
このコードは

```ruby
`foo.bar()`
```
と等価になります。

それでは、*stdlib/js.rb*のコードを見てみましょう。
`new`の実装はこのようになっています。

```ruby
  if `typeof Function.prototype.bind == 'function'`
    def new(func, *args, &block)
      args.insert(0, `this`)
      args << block if block
      `new (#{func}.bind.apply(#{func}, #{args}))()`
    end
  else
    def new(func, *args, &block)
      args << block if block
      f = `function(){return func.apply(this, args)}`
      f.JS[:prototype] = func.JS[:prototype]
      `new f()`
    end
  end
```

`bind`が定義されているかどうかで分岐していますが、基本的にはコンストラクタを`apply`して`new`するという感じです。
`new`の他にも、`delete`,`in`,`instanceof`,`typeof`が定義されていますが、単純にJavaScriptのコードを呼ぶだけになっています。
また、`call`というメソッドも用意されていて、グローバルな関数を呼び出すことができます。

```ruby
  def call(func, *args, &block)
    g = global
    args << block if block
    g.JS[func].JS.apply(g, args)
  end
  alias method_missing call
```

これは`method_missing`にaliasされているので、明示的に`call`しなくても、`JS.parseInt()`のように呼びだすこともできます。

このような`JS`モジュールに定義されるメソッドは*stdlib/js.rb*に定義されていますが、`foo.JS.bar`のようなものはどのように実装されているのでしょうか。
実は*stdlib/js.rb*にはこの実装はありません。ためしに`require 'js'`をしなくてもこれらのコードはちゃんと実行できます。
ではどこにあるのでしょうか?

先程、

```ruby
foo.JS.bar
```

は

```ruby
`foo.bar()`
```

と等価と書きましたが、実際にコンパイルされたコードをみるとまったく同じにコンパイルされています。
ということは`JS`というのはどこに行ってしまったのでしょう?

答えは`Opal：：Rewriters::DotJsSyntax`というクラスにあります。(*lib/opal/rewriters/dot_js_syntax.rb*)

```ruby:lib/opal/rewriters/dot_js_syntax.rb
      def on_send(node)
        recv, meth, *args = *node
        if recv && recv.type == :send
          recv_of_recv, meth_of_recv, _ = *recv
          if meth_of_recv == :JS
            case meth
            when :[]
              if args.size != 1
                raise SyntaxError, ".JS[:property] syntax supports only one argument"
              end
              property = args.first

              node = to_js_attr_call(recv_of_recv, property)
            when :[]=
              if args.size != 2
                raise SyntaxError, '.JS[:property]= syntax supports only two arguments'
              end

              property, value = *args
              node = to_js_attr_assign_call(recv_of_recv, property, value)
            else
              node = to_native_js_call(recv_of_recv, meth, args)
            end
            super(node)
          else
            super
          end
        else
          super
        end
      end
```

Opalのコンパイラは`recv.JS`というコードに対して書き替えをおこなうようです。`JS`の後に続く文字列によって、プロパティへのアクセスあるいはアサイン、メソッドコールの3パターンに分れます。
それぞれ、`to_js_attr_call`,`to_js_attr_assign_call`,`to_native_js_call`を呼びだしています。

たとえば、`to_js_attr_call`だったら次のように定義されています。

```ruby
      def to_js_attr_call(recv, property)
        s(:jsattr, recv, property)
      end
```

Opalのパーサーはs式を模した構文木を作るので、`s`というメソッドの呼びだしになっています。
ここでは、`:jsattr`というノードをつくります。
このノードがコードを生成するのは、*lib/opal/nodes/call_special.rb*に定義されています。

```ruby:lib/opal/nodes/call_special.rb
    # recvr.JS[:prop]
    # => recvr.prop
    class JsAttrNode < Base
      handle :jsattr
      children :recvr, :property

      def compile
        push recv(recvr), '[', expr(property), ']'
      end
    end
```

`JS`という一見同じ機能に見えたものは二つの機能によって実現されていました。

* `JS`モジュールによる、JavaScriptのグローバルな関数や演算子の利用
* `recv.JS`のようなメソッド呼び出しのnativeコードへの書きかえ

この記事を書きはじめたときにはイブだったのですが、とうとう日付が変ってしまいました。
メリークリスマス！！！