[[mrubyを読む]] #contents *はじめに [#wd417a91] 最近、mrubyのAPIを使うサンプルを書こうとしているのですがよい題材が思いつきません。mrubyを使う際にまず知りたいのはmrubyと自分の対象領域のソフトウェアをつなげることだと思うので、今回はmruby本体中の生きたサンプルであるTimeクラスの実装をAPI利用の観点で読んでいきたいと思います。 *クラスの定義 [#c6da9ce0] というわけで、src/time.cのmrb_init_time()を見ていきましょう。 #code(C){{ struct RClass *tc; /* ISO 15.2.19.2 */ tc = mrb_define_class(mrb, "Time", mrb->object_class); MRB_SET_INSTANCE_TT(tc, MRB_TT_DATA); mrb_include_module(mrb, tc, mrb_class_get(mrb, "Comparable")); }} クラスを定義するにはmrb_define_class()を利用します。第2引数にクラス名、第3引数にスーパークラスを指定します。次のMRB_SET_INSTANCE_TTはインスタンスタイプの設定です。個人的にはクラスオブジェクトなのでMRB_TT_DATAを指定するのは間違っているような気がするのですが((まつもとさんによると「MRB_SET_INSTANCE_TTはクラスに対してそのインスタンスの「型」を指定するから、これでいいんです」とのことだそうです))MRB_TT_DATAを指定します。 クラスにモジュールをインクルードする場合はmrb_include_module()を利用します。第2引数で指定したクラスに第3引数のモジュールをインクルードします。モジュール(クラス)を取得したい場合はmrb_class_get()を使って取得を行います。 *メソッドの定義 [#p70f1446] ではメソッドの定義に進みます。 #code(C){{ mrb_define_class_method(mrb, tc, "at", mrb_time_at, ARGS_ANY()); /* 15.2.19.6.1 */ mrb_define_class_method(mrb, tc, "gm", mrb_time_gm, ARGS_REQ(1)|ARGS_OPT(6)); /* 15.2.19.6.2 */ mrb_define_class_method(mrb, tc, "local", mrb_time_local, ARGS_REQ(1)|ARGS_OPT(6)); /* 15.2.19.6.3 */ mrb_define_class_method(mrb, tc, "mktime", mrb_time_local, ARGS_REQ(1)|ARGS_OPT(6));/* 15.2.19.6.4 */ mrb_define_class_method(mrb, tc, "now", mrb_time_now, ARGS_NONE()); /* 15.2.19.6.5 */ mrb_define_class_method(mrb, tc, "utc", mrb_time_gm, ARGS_REQ(1)|ARGS_OPT(6)); /* 15.2.19.6.6 */ }} クラスメソッドの定義はmrb_define_class_method()で行います。第2引数で指定したクラスに第3引数のメソッドが定義されます。第4引数にはメソッドが呼ばれたときに呼び出される関数を指定します。 第5引数はメソッドの引数情報で、 :ARGS_ANY()|任意の引数を取る :ARGS_REQ(1)|ARGS_OPT(6)|1つの引数は必須でオプション引数が6つある :ARGS_NONE()|引数なし という意味になります。((じつは、2012/7/22現在この情報は使われてないのですが、将来使われるかもしれないしソース読む人への情報提供の意味もあるのでちゃんと書きましょう)) 次にインスタンスメソッドの定義です。利用するAPIがmrb_define_method()になる以外はクラスメソッドの定義と同じです。 #code(C){{ mrb_define_method(mrb, tc, "==" , mrb_time_eq , ARGS_REQ(1)); mrb_define_method(mrb, tc, "<=>" , mrb_time_cmp , ARGS_REQ(1)); /* 15.2.19.7.1 */ (以下省略) }} *インスタンスの生成 [#r0632cad] 次はインスタンスの生成です。Time.nowの実装であるmrb_time_now()を見てみます。 #code(C){{ static mrb_value mrb_time_now(mrb_state *mrb, mrb_value self) { return mrb_time_wrap(mrb, mrb_class_ptr(self), current_mrb_time(mrb)); } }} mrubyでのメソッドを実装する関数のシグネチャについてはまた後で説明しますが第2引数でselfが渡されてきます。クラスメソッドなので今のselfはTimeクラスです。mrb_class_ptr()に書けることでRClass構造体へのポインタを取得できます。 currrent_mrb_time()は実際にはgettimeofday()があるかどうかで#ifdefされていますが長くなるのでgettimeofday()がない場合の部分は省略します。 #code(C){{ static struct mrb_time* current_mrb_time(mrb_state *mrb) { struct mrb_time *tm; tm = mrb_malloc(mrb, sizeof(*tm)); { struct timeval tv; gettimeofday(&tv, NULL); tm->sec = tv.tv_sec; tm->usec = tv.tv_usec; } tm->timezone = MRB_TIMEZONE_LOCAL; mrb_time_update_datetime(tm); return tm; } }} メモリ確保はmrb_malloc()を使います。 #code(C){{ static mrb_value mrb_time_wrap(mrb_state *mrb, struct RClass *tc, struct mrb_time *tm) { return mrb_obj_value(Data_Wrap_Struct(mrb, tc, &mrb_time_type, tm)); } }} インスタンスの生成はData_Wrap_Structマクロを使います。mrb_time_typeはラップする構造体の情報です。 #code(C){{ static struct mrb_data_type mrb_time_type = { "Time", mrb_time_free }; }} というわけで構造体名とGCで解放されるときに使われるfree関数を指定します。 *メソッドの実装 [#a9a00313] 続いてメソッド実装を見てみましょう。Time#<=>(other)の実装であるmrb_time_cmp()を見ます。 #code(C){{ static mrb_value mrb_time_cmp(mrb_state *mrb, mrb_value self) { mrb_value other; struct mrb_time *tm1, *tm2; mrb_get_args(mrb, "o", &other); tm1 = mrb_get_datatype(mrb, self, &mrb_time_type); tm2 = mrb_get_datatype(mrb, other, &mrb_time_type); if (!tm1 || !tm2) return mrb_nil_value(); if (tm1->sec > tm2->sec) { return mrb_fixnum_value(1); } else if (tm1->sec < tm2->sec) { return mrb_fixnum_value(-1); } /* tm1->sec == tm2->sec */ if (tm1->usec > tm2->usec) { return mrb_fixnum_value(1); } else if (tm1->usec < tm2->usec) { return mrb_fixnum_value(-1); } return mrb_fixnum_value(0); } }} まずは先ほど前振りしたメソッド実装関数のシグネチャについて説明します。メソッド実装関数は「selfを指すmrb_valueを受け取り、メソッドの処理結果をmrb_valueで返す」というシグネチャになっています。 **メソッド引数の取得 [#ud5b6457] メソッドの引数を取得する場合はmrb_get_args()を使用します。第2引数で引数の情報、第3引数以降で実際の引き数値を受け取る変数へのポインタを指定します。指定する引数情報はsrc/class.cのmrb_get_args()が実装されている箇所に書かれています。 #code(C){{ /* retrieve arguments from mrb_state. mrb_get_args(mrb, format, ...) returns number of arguments parsed. fortmat specifiers: o: Object [mrb_value] S: String [mrb_value] A: Array [mrb_value] H: Hash [mrb_value] s: String [char*,int] z: String [char*] a: Array [mrb_value*,int] f: Float [mrb_float] i: Integer [mrb_int] n: Symbol [mrb_sym] &: Block [mrb_value] *: rest argument [mrb_value*,int] |: optional */ }} 引数情報中に'|'を含めるとオプション引数が実現できます。オプション引数の例としてTime.localの実装であるmrb_time_local()を載せます。 #code(C){{ static mrb_value mrb_time_local(mrb_state *mrb, mrb_value self) { mrb_int ayear = 0, amonth = 1, aday = 1, ahour = 0, amin = 0, asec = 0, ausec = 0; mrb_get_args(mrb, "i|iiiiii", &ayear, &amonth, &aday, &ahour, &amin, &asec, &ausec); return mrb_time_wrap(mrb, mrb_class_ptr(self), time_mktime(mrb, ayear, amonth, aday, ahour, amin, asec, ausec, MRB_TIMEZONE_LOCAL)); } }} **C構造体の取得 [#gbbcacd1] オブジェクトからC構造体を取り出すにはmrb_get_datatype()を使用します。 #code(C){{ tm1 = mrb_get_datatype(mrb, self, &mrb_time_type); tm2 = mrb_get_datatype(mrb, other, &mrb_time_type); if (!tm1 || !tm2) return mrb_nil_value(); }} 上記のように第2引数に構造体を取り出すオブジェクト、第3引数にインスタンス生成時に指定した構造体の情報を渡します。オブジェクトが指定した構造体をラップしたものでない場合はNULLが返るのでちゃんとNULLチェックを行うようにしてください。 ところで、voidポインタってキャストしなくても怒られないんだっけ?warningレベルを上げたら怒られそうな気がする。 ところで、voidポインタってキャストしなくても怒られないんだっけ?warningレベルを上げたら怒られそうな気がする。((C++だと怒られる(コンパイルできない?)のか2012/7/30にキャストするように修正されました)) **戻り値の返却 [#lbf1acab] メソッドの戻り値はmrb_value型を返します。 #code(C){{ if (tm1->sec > tm2->sec) { return mrb_fixnum_value(1); } else if (tm1->sec < tm2->sec) { return mrb_fixnum_value(-1); } /* tm1->sec == tm2->sec */ if (tm1->usec > tm2->usec) { return mrb_fixnum_value(1); } else if (tm1->usec < tm2->usec) { return mrb_fixnum_value(-1); } return mrb_fixnum_value(0); }} 整数をmrb_valueにするにはmrb_fixnum_value()を使用します。それ以外の型については以下のまとめを参照してください。 *APIまとめ [#s859e253] 最後にクラス定義・実装に利用するAPIをまとめておきます。一部、上で紹介していないAPIも知っていると便利なので載せておきます。 :struct RClass * mrb_class_get(mrb_state *mrb, const char *name);|クラスを取得する。第2引数に取得したいクラス名を指定する :struct RClass *mrb_define_module(mrb_state *mrb, const char *name);|モジュールを定義する。第2引数にモジュール名を指定する :struct RClass *mrb_define_class(mrb_state *mrb, const char *name, struct RClass *super);|クラスを定義する。第2引数にクラス名、第3引数にスーパークラスを指定する :struct RClass * mrb_define_class_under(mrb_state *mrb, struct RClass *outer, const char *name, struct RClass *super);|指定モジュール以下にクラスを定義する。第2引数で指定したモジュール以下にクラスが定義される :void mrb_include_module(mrb_state *mrb, struct RClass *c, struct RClass *m);|モジュールをインクルードする。第2引数で指定したクラスに第3引数で指定したモジュールをインクルード :void mrb_define_const(mrb_state *mrb, struct RClass *mod, const char *name, mrb_value v);|クラス(モジュール)に定数を定義する。第2引数のクラスに第3引数の名前で第4引数の値を定義する :void mrb_define_method(mrb_state *mrb, struct RClass *c, const char *name, mrb_func_t func, int aspec);|インスタンスメソッドを定義する。第2引数のクラスに第3引数の名前のインスタンスメソッドが定義される。メソッドが呼び出されると第4引数で指定した関数が呼び出される。第5引数にはメソッドの引数情報を指定する :void mrb_define_class_method(mrb_state *mrb, struct RClass *c, const char *name, mrb_func_t func, int aspec);|クラスメソッドを定義する。引数の意味はmrb_define_method()と同じ void :void mrb_define_module_function(mrb_state *mrb, struct RClass *c, const char *name, mrb_func_t func, int aspec);|モジュール関数を定義する。引数の意味はmrb_define_method()と同じ :struct RClass *mrb_class_ptr(mrb_object klass);|mrb_objectからRClassへのポインタを取り出す :struct RData* Data_Wrap_Struct(mrb_state *mrb, struct RClass *klass, const struct mrb_data_type *type, void *ptr);|Cの構造体をラップしたオブジェクトを生成する :mrb_value mrb_obj_value(void *p);|オブジェクト構造体へのポインタをオブジェクトに変換する :int mrb_get_args(mrb_state *mrb, const char *format, ...);|メソッドに渡された引数を取得する。第2引数で引数の情報、第3引数以降で実際の引き数値を受け取る変数へのポインタを指定する。関数の戻り値はメソッドに渡された引数の数が返る :void *mrb_get_datatype(mrb_state *mrb, mrb_value obj, const struct mrb_data_type *type);|ラップされているC構造体を取得する。第2引数に対象オブジェクト、第3引数に構造体情報を指定する。対象オブジェクトが期待する構造体をラップしていない場合はNULLが返る :mrb_value mrb_fixnum_value(mrb_int i);|整数をオブジェクトに変換する :mrb_value mrb_float_value(mrb_float f);|浮動小数点数をオブジェクトに変換する :mrb_value mrb_true_value(void);|trueを表すオブジェクトを返す :mrb_value mrb_false_value(void);|falseを表すオブジェクトを返す :mrb_value mrb_nil_value(void);|nilを表すオブジェクトを返す *おわりに [#vc40a353] 今回はmruby APIのサンプルということでAPI実装の中身にはあまり突っ込まずにTimeクラスの実装を眺めてみました。この記事を参考にいろんなソフトにmrubyが組み込まれたら幸いです。それではみなさんもよいコードリーディングとコードライティングを。