この記事はOpal Advent Calendar 2016の23日目の投稿として書いています。
今日はNative
の実装について書こうと思います。
その前にNative
のつかいかたです。
require 'native'
%x{
var foo = new Object();
foo.bar = function() {
return 'bar';
};
foo.baz = 'baz';
}
n = Native(`foo`)
puts n.bar
puts n.baz
こんなプログラムを実行してみましょう。n.bar
もn.baz
もそれぞれ文字列を返します。
Rubyのこれらの呼び出しはすべてメソッド呼び出しになります。
一方、JavaScriptではプロパティに関数だったり、文字列だったりをアサインすることができます。
Nativeは関数であれば関数を実行した結果を返すし、文字列などの値であればその値を返すようになっています。
それでは、これらがどのように実装されているか見てみましょう。
Native
はstdlib/native.rbに定義されています。
まずはNative()
の定義をみてみます。
def Native(obj)
if `#{obj} == null`
nil
elsif native?(obj)
Native::Object.new(obj)
else
obj
end
end
native
であればNative::Object
を返します。native?
はどのように判定しているのでしょうか?
def native?(value)
`value == null || !value.$$class`
end
null
の場合もしくは、$$class
が定義されていない場合はnative
であると判定しています。
$$class
はRubyのクラスを表す変数です。この定義があればRubyのクラスから生成されたオブジェクトと言えます。
Native::Object
のメソッド呼び出しはmethod_missing
でnativeの呼び出しに変換されます。
def method_missing(mid, *args, &block)
%x{
if (mid.charAt(mid.length - 1) === '=') {
return #{self[mid.slice(0, mid.length - 1)] = args[0]};
}
else {
return #{::Native.call(@native, mid, *args, &block)};
}
}
end
メソッド名の最後が=
だったらプロパティへの代入になります。
Native::Object#[]
はJSのプロパティを返します。
def [](key)
%x{
var prop = #@native[key];
if (prop instanceof Function) {
return prop;
}
else {
return #{::Native.call(@native, key)}
}
}
end
メソッド名が=
で終らない場合は、Native#call
が呼ばれます。
def self.call(obj, key, *args, &block)
%x{
var prop = #{obj}[#{key}];
if (prop instanceof Function) {
var converted = new Array(args.length);
for (var i = 0, length = args.length; i < length; i++) {
var item = args[i],
conv = #{try_convert(`item`)};
converted[i] = conv === nil ? item : conv;
}
if (block !== nil) {
converted.push(block);
}
return #{Native(`prop.apply(#{obj}, converted)`)};
}
else {
return #{Native(`prop`)};
}
}
end
Native#call
の中身はプロパティが関数の場合は引数をnativeに変換して関数を呼び出します。
関数の場合は関数の呼び出し結果、それ以外ならプロパティそのものをNative::Object
でラップして返します。
引数の変換はto_n
メソッドでおこなわれます。
def self.try_convert(value)
%x{
if (#{native?(value)}) {
return #{value};
}
else if (#{value.respond_to? :to_n}) {
return #{value.to_n};
}
else {
return nil;
}
}
end
to_n
メソッドはnative.rbのなかでいくつか定義されています。
たとえば、Numeric
なら
class Numeric
def to_n
`self.valueOf()`
end
end
valueOf()
でプリミティブを返すようになっています。
native.rbにはほかにもさまざまなクラスにto_n
の定義をしています。Array``Hash
などもあります。
もちろん、Native::Object
自身もto_n
メソッドをもちます。
しかし、to_n
が定義されていないクラスに関しては、nativeへの変換は行なわれませんので注意が必要です。
逆にto_n
を定義すればnativeのメソッドに引数として渡すことができるようになります。
Native
の実装についてみてきましたがいかがでしょうか。次回はJS
の実装を見てみましょう。
では、Happy hacking!