ruby.wasmでクエリ文字列を扱おうとしたら - @ledsun blog で、JavaScriptのオブジェクトの返す真理値が真か確認するために
if searchParams.has('phrase') == JS.eval('return true;')
と書きました。
if searchParams.has('phrase').to_b
と書きたいです。
すでにto_s
メソッドが存在します。
これを参考にしたら実装出来るでしょうか?
to_sのテストコードの確認
def test_to_s assert_equal "str", JS.eval("return 'str';").to_s assert_equal "24", JS.eval("return 24;").to_s assert_equal "true", JS.eval("return true;").to_s assert_equal "null", JS.eval("return null;").to_s assert_equal "undefined", JS.eval("return undefined;").to_s end
が、あります。 つぎのようなテストコードを追加して、通れば良さそうです。
def test_to_b assert_true, JS.eval("return true;").to_b assert_false "24", JS.eval("return false;").to_b end
to_sの実装を確認
https://github.com/ruby/ruby.wasm/blob/394841d142fabc2287e7f918a605c7009e545846/ext/js/lib/js.rb をみるとto_s
メソッドの実装がありません。
Rubyでは実装されていないようです。
C拡張として実装されていそうです。
/* * call-seq: * to_s -> string * * Returns a printable version of +self+: * JS.eval("return 'str'").to_s # => "str" * JS.eval("return true").to_s # => "true" * JS.eval("return 1").to_s # => "1" * JS.eval("return null").to_s # => "null" * JS.global.to_s # => "[object global]" * * JS::Object#inspect is an alias for JS::Object#to_s. */ static VALUE _rb_js_obj_to_s(VALUE obj) { struct jsvalue *p = check_jsvalue(obj); rb_js_abi_host_string_t ret0; rb_js_abi_host_js_value_to_string(p->abi, &ret0); return rb_utf8_str_new(ret0.ptr, ret0.len); }
で、実装がありました。 また
rb_define_method(rb_cJS_Object, "to_s", _rb_js_obj_to_s, 0);
にto_s
メソッドとして呼び出せるように、していそうなところをみつけました。
どうやら同じような形式で定義して上げるとよさそうです。
_rb_js_obj_to_s
の実装を確認しなおすと、rb_js_abi_host_js_value_to_string
関数で、JavaScriptのオブジェクトから文字列に変換していそうです。
rb_js_abi_host_js_value_to_string
関数の実装を確認
https://github.com/ruby/ruby.wasm/blob/main/ext/js/bindgen/rb-js-abi-host.c#L156-L163
void rb_js_abi_host_js_value_to_string(rb_js_abi_host_js_abi_value_t value, rb_js_abi_host_string_t *ret0) { __attribute__((aligned(4))) uint8_t ret_area[8]; int32_t ptr = (int32_t) &ret_area; __wasm_import_rb_js_abi_host_js_value_to_string((value).idx, ptr); *ret0 = (rb_js_abi_host_string_t) { (char*)(*((int32_t*) (ptr + 0))), (size_t)(*((int32_t*) (ptr + 4))) }; }
にありました。 気になるのはこのファイルは関数間に行間がありません。 人間が書くファイルではなく、何らかツールで生成するファイルなのかもしれません。 このファイルのBlameを見ていたら、よさそうなコミットを見つけました。
Add `JS::Object#{to_i,to_f}` · ruby/ruby.wasm@6884a17 · GitHub
to_i
とto_f
を追加するコミットです。
to_s
ではありませんが、参考になりそうです。
コミットの中身
bindgen
というディレクトリが複数回でてくるのが気になります。
ruby.wasm/CONTRIBUTING.md at main · ruby/ruby.wasm · GitHubを見ると.wit
ファイルからコードを生成するようです。
wit-bindgenをためす
rustc
とcargo
はインストール済みです。
rake check:bindgen
を実行してみます。
build/toolchain/wit-bindgen/bin/wit-bindgen guest c --import ext/js/bindgen/rb-js-abi-host.wit --out-dir ext/js/bindgen Generating "ext/js/bindgen/rb-js-abi-host.c" Generating "ext/js/bindgen/rb-js-abi-host.h" build/toolchain/wit-bindgen/bin/wit-bindgen host js --import ext/witapi/bindgen/rb-abi-guest.wit --export ext/js/bindgen/rb-js-abi-host.wit --out-dir packages/npm-packages/ruby-wasm-wasi/src/bindgen Generating "packages/npm-packages/ruby-wasm-wasi/src/bindgen/intrinsics.js" Generating "packages/npm-packages/ruby-wasm-wasi/src/bindgen/rb-abi-guest.d.ts" Generating "packages/npm-packages/ruby-wasm-wasi/src/bindgen/rb-abi-guest.js" Generating "packages/npm-packages/ruby-wasm-wasi/src/bindgen/rb-js-abi-host.d.ts" Generating "packages/npm-packages/ruby-wasm-wasi/src/bindgen/rb-js-abi-host.js"
やはり両方のbindgen
ディレクトリの中身は自動生成されているようです。
コミットを見直す
rb-js-abi-host.wit
の
variant raw-integer { f64(float64), bignum(string), }
から
rb-js-abi-host.h
の
typedef struct { uint8_t tag; union { double f64; rb_js_abi_host_string_t bignum; } val; } rb_js_abi_host_raw_integer_t; #define RB_JS_ABI_HOST_RAW_INTEGER_F64 0 #define RB_JS_ABI_HOST_RAW_INTEGER_BIGNUM 1 void rb_js_abi_host_raw_integer_free(rb_js_abi_host_raw_integer_t *ptr);
が生成されていそうです。
構造体とそれをfree
する関数を生成してくれるようです。
rb-js-abi-host.wit
の
js-value-to-integer: func(value: js-abi-value) -> raw-integer
からは
rb-js-abi-host.c
の
__attribute__((import_module("rb-js-abi-host"), import_name("js-value-to-integer: func(value: handle<js-abi-value>) -> variant { f64(float64), bignum(string) }"))) void __wasm_import_rb_js_abi_host_js_value_to_integer(int32_t, int32_t); void rb_js_abi_host_js_value_to_integer(rb_js_abi_host_js_abi_value_t value, rb_js_abi_host_raw_integer_t *ret0) { __attribute__((aligned(8))) uint8_t ret_area[16]; int32_t ptr = (int32_t) &ret_area; __wasm_import_rb_js_abi_host_js_value_to_integer((value).idx, ptr); rb_js_abi_host_raw_integer_t variant; variant.tag = (int32_t) (*((uint8_t*) (ptr + 0))); switch ((int32_t) variant.tag) { case 0: { variant.val.f64 = *((double*) (ptr + 8)); break; } case 1: { variant.val.bignum = (rb_js_abi_host_string_t) { (char*)(*((int32_t*) (ptr + 8))), (size_t)(*((int32_t*) (ptr + 12))) }; break; } } *ret0 = variant; }
が生成されていそうです。 しかし、条件分岐まで生成されるのでしょうか?
index.ts
の
jsValueToInteger(value) { if (typeof value === "number") { return { tag: "f64", val: value }; } else if (typeof value === "bigint") { return { tag: "bignum", val: BigInt(value).toString(10) + "\0" }; } else if (typeof value === "string") { return { tag: "bignum", val: value + "\0" }; } else if (typeof value === "undefined") { return { tag: "f64", val: 0 }; } else { return { tag: "f64", val: Number(value) }; } },
に分岐があるのでこれが元のでしょうか? でも、分岐数かちがいますね。 謎です。
index.tsを変更してrake check:bindgenしてみる
たとえば次のように変更してみましょう。
jsValueToInteger(value) { if (typeof value === "number") { return { tag: "f64", val: value }; } else if (typeof value === "string") { return { tag: "bignum", val: value + "\0" }; } else if (typeof value === "bigint") { return { tag: "bignum", val: BigInt(value).toString(10) + "\0" }; } else if (typeof value === "undefined") { return { tag: "f64", val: 0 }; } else { return { tag: "f64", val: Number(value) }; } },
分岐の順番を入れ替えてました。
rake check:bindgen
をしても生成されるファイルに変更はありません。
これが元ではないようです。
rake check:bindgen
の出力をよく読みます。
build/toolchain/wit-bindgen/bin/wit-bindgen host js --import ext/witapi/bindgen/rb-abi-guest.wit --export ext/js/bindgen/rb-js-abi-host.wit --out-dir packages/npm-packages/ruby-wasm-wasi/src/bindgen Generating "packages/npm-packages/ruby-wasm-wasi/src/bindgen/intrinsics.js" Generating "packages/npm-packages/ruby-wasm-wasi/src/bindgen/rb-abi-guest.d.ts" Generating "packages/npm-packages/ruby-wasm-wasi/src/bindgen/rb-abi-guest.js" Generating "packages/npm-packages/ruby-wasm-wasi/src/bindgen/rb-js-abi-host.d.ts" Generating "packages/npm-packages/ruby-wasm-wasi/src/bindgen/rb-js-abi-host.js"
ext/witapi/bindgen/rb-abi-guest.wit
とext/js/bindgen/rb-js-abi-host.wit
が入力ファイルになっていそうです。
後者は見ました。前者はみたこがありません。
https://github.com/ruby/ruby.wasm/blob/394841d142fabc2287e7f918a605c7009e545846/ext/witapi/bindgen/rb-abi-guest.witを見た感じでは、to_i
とは関係なさそうです。
ということはext/js/bindgen/rb-js-abi-host.wit
からrb-js-abi-host.d.ts
とrb-js-abi-host.js
が生成されていそうです。
そう思ってみると、名前も対応しています。
呼び出しフローを整理してみる
- JS::Object#to_i
- _rb_js_obj_to_i
- rb_js_abi_host_js_value_to_integer
- bindgenで生成された関数を経由
- jsValueToIntegerでJavaScriptの値をハッシュに変換
bindgenで生成した関数は
rb-js-abi-host.js
は、ハッシュの値をバイト列(?)に変換rb-cs-abi-host.c
は、バイト列を構造体に変換
この中間でやりとりするバイト列や構造体は、rb-js-abi-host.wit
で定義したデータ構造から生成されているようです。
to_b実装作戦
同じ構成を取ると仮定すると
rb-js-abi-host.wit
に呼び出したいJavaScriptの関数を定義。データ型が必要であればそれも定義index.ts
に呼び出したいJavaScriptの関数を定義rake check:bindgen
を実行してbindgen関数群を生成- 生成された関数を呼び出す関数を、
js-core.js
に定義
すればよさそうです。