mrubyを読む

はじめに

最近、mrubyのAPIを使うサンプルを書こうとしているのですがよい題材が思いつきません。mrubyを使う際にまず知りたいのはmrubyと自分の対象領域のソフトウェアをつなげることだと思うので、今回はmruby本体中の生きたサンプルであるTimeクラスの実装をAPI利用の観点で読んでいきたいと思います。

クラスの定義

というわけで、src/time.cのmrb_init_time()を見ていきましょう。

Everything is expanded.Everything is shortened.
  1
  2
  3
  4
  5
 
 
 
 
 
  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を指定するのは間違っているような気がするのですが*1MRB_TT_DATAを指定します。

クラスにモジュールをインクルードする場合はmrb_include_module()を利用します。第2引数で指定したクラスに第3引数のモジュールをインクルードします。モジュール(クラス)を取得したい場合はmrb_class_get()を使って取得を行います。

メソッドの定義

ではメソッドの定義に進みます。

Everything is expanded.Everything is shortened.
  1
  2
  3
  4
  5
  6
 
 
 
 
 
 
  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()
引数なし

という意味になります。*2

次にインスタンスメソッドの定義です。利用するAPIがmrb_define_method()になる以外はクラスメソッドの定義と同じです。

Everything is expanded.Everything is shortened.
  1
  2
  3
 
 
 
  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 */
  (以下省略)

インスタンスの生成

次はインスタンスの生成です。Time.nowの実装であるmrb_time_now()を見てみます。

Everything is expanded.Everything is shortened.
  1
  2
  3
  4
  5
 
 
-
|
!
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()がない場合の部分は省略します。

Everything is expanded.Everything is shortened.
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 
 
-
|
|
|
-
|
|
|
|
|
!
|
|
|
|
!
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()を使います。

Everything is expanded.Everything is shortened.
  1
  2
  3
  4
  5
 
 
-
|
!
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はラップする構造体の情報です。

Everything is expanded.Everything is shortened.
  1
 
static struct mrb_data_type mrb_time_type = { "Time", mrb_time_free };

というわけで構造体名とGCで解放されるときに使われるfree関数を指定します。

メソッドの実装

続いてメソッド実装を見てみましょう。Time#<=>(other)の実装であるmrb_time_cmp()を見ます。

Everything is expanded.Everything is shortened.
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 
 
-
|
|
|
|
|
|
|
-
|
!
-
|
!
|
-
|
!
-
|
!
|
!
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で返す」というシグネチャになっています。

メソッド引数の取得

メソッドの引数を取得する場合はmrb_get_args()を使用します。第2引数で引数の情報、第3引数以降で実際の引き数値を受け取る変数へのポインタを指定します。指定する引数情報はsrc/class.cのmrb_get_args()が実装されている箇所に書かれています。

Everything is expanded.Everything is shortened.
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
!
/*
  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()を載せます。

Everything is expanded.Everything is shortened.
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 
 
-
|
|
|
|
|
|
!
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構造体の取得

オブジェクトからC構造体を取り出すにはmrb_get_datatype()を使用します。

Everything is expanded.Everything is shortened.
  1
  2
  3
 
 
 
  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レベルを上げたら怒られそうな気がする。*3

戻り値の返却

メソッドの戻り値はmrb_value型を返します。

Everything is expanded.Everything is shortened.
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
-
|
!
-
|
!
 
-
|
!
-
|
!
 
  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まとめ

最後にクラス定義・実装に利用する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を表すオブジェクトを返す

おわりに

今回はmruby APIのサンプルということでAPI実装の中身にはあまり突っ込まずにTimeクラスの実装を眺めてみました。この記事を参考にいろんなソフトにmrubyが組み込まれたら幸いです。それではみなさんもよいコードリーディングとコードライティングを。


*1 まつもとさんによると「MRB_SET_INSTANCE_TTはクラスに対してそのインスタンスの「型」を指定するから、これでいいんです」とのことだそうです
*2 じつは、2012/7/22現在この情報は使われてないのですが、将来使われるかもしれないしソース読む人への情報提供の意味もあるのでちゃんと書きましょう
*3 C++だと怒られる(コンパイルできない?)のか2012/7/30にキャストするように修正されました

トップ   編集 凍結 差分 バックアップ 添付 複製 名前変更 リロード   新規 一覧 単語検索 最終更新   ヘルプ   最終更新のRSS
Last-modified: 2012-07-30 (月) 20:51:03 (4288d)