いまruby.wasmでブラウザ向けプログラミングするために
<script src="https://cdn.jsdelivr.net/npm/ruby-head-wasm-wasi@2.0.0/dist/browser.script.iife.js"></script>
を使うとプロパティを参照しようとすると次のように書きます。
require 'js' documetnt = JS.global[:document]
これを次のように書きたいです。
require 'js' documetnt = JS.global.document
次のモンキーパッチを書きました。
require 'js' class JS::Object def method_missing(sym, *args, &block) if self[sym].typeof == "function" # 関数として定義されていたら、関数として呼び出す。 self.call(sym, *args, &block) elsif sym.end_with? '=' # =で終わるメソッドはセッター self.method(:[]=).call(sym.to_s.gsub!(/=$/, ''), *args) elsif args.empty? # 引数がなければゲッター # JavaScriptのオブジェクトなので、存在しないプロパティを読んでもエラーにしない。undefiendを返す。 self.method(:[]).call(sym) else # 引数がないメソッド呼び出しは method_missing super end end end
これは https://github.com/ruby/ruby.wasm/blob/394841d142fabc2287e7f918a605c7009e545846/ext/js/lib/js.rb#L92-L98 に対するパッチです。 ビルドは必要ありません。 ブラウザで動かしたいRubyスクリプトにコピペすれば動きます。
ポイントは「プロパティ参照のとき、指定した名前のプロパティが定義されていない時にエラーにしない。」です。 JavaScriptには、プロパティの定義という概念がありません。 ですので
hoge = { fuga: undefined } hoge.fuga // => undefined
と
hoge = {} hoge.fuga // => undefined
に、区別がありません。 JS::ObjectはJavaScriptオブジェクトのラッパーなので、この動きを踏襲すると良さそうです。 そこで「プロパティ参照のとき、指定した名前のプロパティが定義されていない時にエラーにしない。」です。
しばらく使ってみて不満がないかどうか確かめてみます。
先行事例
ruby.wasm で await を使う - tmtms のメモ
module JSrb def method_missing(sym, *args, &block) sym = sym.to_s.gsub(/_([a-z])/){$1.upcase}.intern v = self.method(:[]).super_method.call(sym.intern) v = self.call(sym, *args, &block) if v.typeof == 'function' v end end
プロパティ参照の実装はこちらを参考にしました。
unloose
def method_missing(sym, *args, &block) ret = self[sym] case ret.typeof when "undefined" str = sym.to_s if str[-1] == "=" self[str.chop.to_sym] = args.first return args.first end super when "function" self.call(sym, *args.map, &block).to_rb else ret.to_rb end end
ほとんど一緒ですが、未定義プロパティを参照したときにエラーになる点が異なります。 Ruby で Processing がブラウザ上からできる p5.rb を作りました - ブログのおんがえしの実装も同じようです。