ruby.wasmでクエリ文字列を扱おうとしたら - @ledsun blogで
JS.global.URLSearchParams.newと書きたい
と書きました。 ためしに実装してみました。
class JS::Object def method_missing(sym, *args, &block) if sym == :new # new で呼び出されたら、コンストラクタとして呼び出す。 JS.eval("return #{self.to_construct(args)}") elsif self[sym].typeof == "function" # 関数として定義されていたら、関数として呼び出す。 self.call(sym, *args, &block) else super end end # When call new, received self is like a 'function URLSearchParams() { [native code] }' # so, we need to convert it to 'new URLSearchParams()' def to_construct(args) "new #{constructor_name}(#{to_js_argument_string(args)})" end def constructor_name self.to_s.match(/function\s+([^(]+)/)[1].strip end # When call new, received argument is like a '["?phrase=%E3%81%82%E3%81%84%E3%81%86"]" # But converting string strips double quotes, so we need to add double quotes. def to_js_argument_string(args) "#{args.map do to_string_with_quote _1 end.join(', ')}" end # Convert to string with double quotes. # Support Ruby String and JavaScript String both. def to_string_with_quote(var) var.is_a?(String) || var.typeof == "string" ? "\"#{var}\"" : var.to_s end end
想像していたより大変でした。これで
JS.global[:URLSearchParams].new(JS.global[:location][:search])
と書けます。 実際にやっているのはJavaScriptで
eval('return new URLSearchParams("?phrase=あいうえお"')
を実行しています。 つまりJavaScriptのコンストラクターを呼び出す文を作って実行しています。
つまづいた点
newメソッドのレシーバーは関数オブジェクト
self
がfunction URLSearchParams() { [native code] }
のような関数オブジェクトです。
ここから関数の名称を正規表現で取得しています。
ユーザーが作成した関数や無名関数などこのパターンにあてはまらないコンストラクター関数もありそうに思います。
未確認です。
引数をJavaScriptの文字列に展開するときに引用符が消える
"return new URLSearchParams(#{args.join(", "))"
で引数を展開すると、引数の価が文字列だったときに"
が消えてJavaScriptのパースエラーになります。
そこで引数の型が文字列のときだけ”
でくくるto_string_with_quote
関数を通しています。
またRubyの文字列とJavaScriptの文字列を両方判定するためにvar.is_a?(String) || var.typeof == "string"
で判定しています。