OpalのStringクラスはどのような実装になっているのか
これはOpal Advent Calendar 2016の11日目の記事です。
OpalのAdvent Calendarは今年が初めてということで、Opalのソースを少し読んでみたいと思います。今回はStringクラスです。
string.rb
Opalの組み込みクラスのソースはopal/corelib以下にあります。
https://github.com/opal/opal/blob/7310b27a1c6bec135610e5df5f20753eb349965e/opal/corelib/string.rb
拡張子的にはRubyで書かれていそうですが、中身を見ると、バッククオート記法を使ってJavaScriptがたくさん埋め込まれていることが分かります。バッククオートはRubyでは外部コマンド実行ですが、Opalでは外部コマンドを実行することは無いので、代わりに「生のJavaScriptを記述する」という用途に置き換えられています。
Stringクラスの場合、self
で実体となるJavaScript文字列を取得できます。ということで、例えばString#lengthメソッドは以下のような定義になっています。JavaScriptのlengthを呼び出すだけです。簡単ですね。
def length
`self.length`
end
String#downcaseとかも同じように簡単に実装できています。
def downcase
`self.toLowerCase()`
end
String#empty?はこう。
def empty?
`self.length === 0`
end
String#reverseは、JSレベルで文字に分解して逆順にしてつなぎ直しているようです。
def reverse
`self.split('').reverse().join('')`
end
String#start_with?
もう少し長いのも見てみましょう。以下はstart_with?メソッドの定義です。selfが指定した文字列から始まるかどうかをチェックするメソッドですね。
"foobar".start_with?("foo") #=> true
"foobar".start_with?("bar") #=> false
指定文字列は複数渡すことができ、その場合はどれか1つでもマッチすれば真を返します。
"Pen-Pineapple-Apple-Pen".start_with?("Apple", "Pinapple", "Pen") #=> true
OpalのString#start_with?の実装は以下です。JavaScriptのfor文を使って指定文字列を順に見ていき、先頭が一致する(=indexOfが0)ものがあればtrueを返しています。
def start_with?(*prefixes)
%x{
for (var i = 0, length = prefixes.length; i < length; i++) {
var prefix = #{Opal.coerce_to(`prefixes[i]`, String, :to_str).to_s};
if (self.indexOf(prefix) === 0) {
return true;
}
}
return false;
}
end
ところで%xについては解説が必要かもしれません。%x{}
というのは%記法の一種で、バッククオートと同じ意味です(あまり知られていないですがRubyにもあります)。そのため%x{}
で囲まれた部分はJavaScriptのコードと見なされ、コンパイル後のJavaScriptにそのまま展開されます。ただしその中に#{}
があった場合は、そこだけはOpalのコードと見なしてJavaScriptへのコンパイルが行われます。上記のコードでも、真ん中あたりで#{}
を使っている箇所があります。ここでは引数に文字列以外が渡された場合を考慮して、Opal.coerce_toでto_strを呼んで文字列への変換を試みています。
ここでOpal.coerce_toの引数をよく見ると、ここでもバッククオートが使われています。ということは、Opalのソースに書かれたJavaScriptの中に埋め込まれたOpalの中にJavaScriptが埋め込まれているということになります!
今回はここまでです。時間があったらまた別のクラスも見てみたいと思います。