はじめに
- 1.3.0の時点でのコードを対象としています (https://github.com/mruby/mruby/tree/1.3.0)
- 筆者のC言語の経験は高専の授業で数年触った程度です
Array#size
1. それっぽいファイルを読む
size
なんていう識別子をgrepすると死ぬのが見えているので、まずはそれっぽいファイルを探っていきます。
まずは、src/array.c
といういかにもそれっぽいファイルがあるので、その中をsize
でgrepしてそれっぽい行を探します。
mrb_define_method(mrb, a, "rindex", mrb_ary_rindex_m, MRB_ARGS_REQ(1)); /* 15.2.12.5.26 */
mrb_define_method(mrb, a, "shift", mrb_ary_shift, MRB_ARGS_NONE()); /* 15.2.12.5.27 */
mrb_define_method(mrb, a, "size", mrb_ary_size, MRB_ARGS_NONE()); /* 15.2.12.5.28 */
mrb_define_method(mrb, a, "slice", mrb_ary_aget, MRB_ARGS_ANY()); /* 15.2.12.5.29 */
mrb_define_method(mrb, a, "unshift", mrb_ary_unshift_m, MRB_ARGS_ANY()); /* 15.2.12.5.30 */
mrb_define_method(mrb, a, "prepend", mrb_ary_unshift_m, MRB_ARGS_ANY());
いかにもそれっぽいですね。mrb_define_method
という関数(?)でsize
を定義しているような雰囲気を感じます。
2. mrb_define_method
とは?
さて、mrb_define_method
とは一体何者でしょうか。これは普通にgrepしても良さそうな気がします。
$ git status
HEAD detached at 1.3.0
nothing to commit, working tree clean
$ git grep mruby_define_method | head
include/mruby.h: * mrb_define_method(mrb, mrb->kernel_module, "example_method", example_method, MRB_ARGS_NONE());
include/mruby.h:MRB_API void mrb_define_method(mrb_state *mrb, struct RClass *cla, const char *name, mrb_func_t func, mrb_aspec aspec);
include/mruby.h: * mrb_define_method(mrb, example_class_a, "example_method", mrb_example_method, MRB_ARGS_NONE());
include/mruby.h: * mrb_define_method(mrb, example_class, "example_method", exampleMethod, MRB_ARGS_NONE());
include/mruby/class.h:MRB_API void mrb_define_method_raw(mrb_state*, struct RClass*, mrb_sym, struct RProc *);
include/mruby/class.h:MRB_API void mrb_define_method_id(mrb_state *mrb, struct RClass *c, mrb_sym mid, mrb_func_t func, mrb_aspec aspec);
mrbgems/mruby-array-ext/src/array.c: mrb_define_method(mrb, a, "assoc", mrb_ary_assoc, MRB_ARGS_REQ(1));
mrbgems/mruby-array-ext/src/array.c: mrb_define_method(mrb, a, "at", mrb_ary_at, MRB_ARGS_REQ(1));
mrbgems/mruby-array-ext/src/array.c: mrb_define_method(mrb, a, "rassoc", mrb_ary_rassoc, MRB_ARGS_REQ(1));
mrbgems/mruby-array-ext/src/array.c: mrb_define_method(mrb, a, "values_at", mrb_ary_values_at, MRB_ARGS_ANY());
よく見てみると、include/mruby.h
に書かれているmrb_define_method
には、先頭に型の定義(void
)が付いていていかにもという感じがします。というわけで、include/mruby.h
の中身を見に行きます。
/**
* Defines a global function in ruby.
*
* If you're creating a gem it may look something like this
*
* Example:
*
* !!!c
* mrb_value example_method(mrb_state* mrb, mrb_value self)
* {
* puts("Executing example command!");
* return self;
* }
*
* void mrb_example_gem_init(mrb_state* mrb)
* {
* mrb_define_method(mrb, mrb->kernel_module, "example_method", example_method, MRB_ARGS_NONE());
* }
*
* @param [mrb_state *] mrb The MRuby state reference.
* @param [struct RClass *] cla The class pointer where the method will be defined.
* @param [const char *] name The name of the method being defined.
* @param [mrb_func_t] func The function pointer to the method definition.
* @param [mrb_aspec] aspec The method parameters declaration.
*/
MRB_API void mrb_define_method(mrb_state *mrb, struct RClass *cla, const char *name, mrb_func_t func, mrb_aspec aspec);
なるほど、mrb
とRClass
とname
とfunc
とaspec
を受けとっていますね……なんのこっちゃ。
落ち着いてYARDを読むと……
引数 | 正体 |
---|---|
mrb_state *mrb |
mrubyの状態への参照 |
struct RClass *cla |
そのメソッドが定義されるクラスへの参照 |
const char *name |
定義されるメソッドの名前 |
mrb_func_t func |
メソッド定義への関数ポインタ |
mrb_aspec aspec |
メソッド引数の宣言 |
ということのようですね。
ということは、Array#size
の本体は、先ほどのコードで第4引数に渡されているmrb_ary_size
っぽいことがわかります。
mrb_define_method(mrb, a, "size", mrb_ary_size, MRB_ARGS_NONE());
mrb_define_method
の本体は一旦置いておいて、mrb_ary_size
のほうを見に行きましょう。
3. mrb_ary_size
とは?
$ git status
HEAD detached at 1.3.0
nothing to commit, working tree clean
$ git grep mrb_ary_size
src/array.c:mrb_ary_size(mrb_state *mrb, mrb_value self)
src/array.c: mrb_define_method(mrb, a, "length", mrb_ary_size, MRB_ARGS_NONE()); /* 15.2.12.5.19 */
src/array.c: mrb_define_method(mrb, a, "size", mrb_ary_size, MRB_ARGS_NONE()); /* 15.2.12.5.28 */
mrb_ary_size
の本体は、src/array.c
にありそうです。
static mrb_value
mrb_ary_size(mrb_state *mrb, mrb_value self)
{
struct RArray *a = mrb_ary_ptr(self);
return mrb_fixnum_value(a->len);
}
はい、よくわかりません。
第1引数のmrb_state *mrb
はmrb_define_method
にも渡していたので、mrb_define_method
経由で渡されるような気がします。第2引数は自分自身でしょうか。とりあえず今は深追いせずに、関数本体の処理を見てみます。
まずstruct RArray *a = mrb_ary_ptr(self)
ですが、どうやらself
からarrayのポインタを取得してRArray
型の構造体(のポインタ)に格納しているようです。
次にreturn mrb_fixnum_value(a->len);
ですが、先程の構造体のlen
を取得してmrubyのfixnumに変換して返しているように見えます。
それぞれ見ていきましょう。
4. struct RArray
の正体とmrb_ary_ptr
struct RArray
というくらいですから、定義はinclude/mruby/array.h
でしょう。見に行ってみます。
struct RArray {
MRB_OBJECT_HEADER;
mrb_int len;
union {
mrb_int capa;
mrb_shared_array *shared;
} aux;
mrb_value *ptr;
};
#define mrb_ary_ptr(v) ((struct RArray*)(mrb_ptr(v)))
なんとその1行下にmrb_ary_ptr
のマクロ定義もありました。(syntax highlightがおかしいような……)
RArray
構造体にはlen
フィールドがあり、mrb_ary_ptr
はmrb_ptr
の結果をRArray
のポインタに変換するマクロだ、ということがわかります。
len
フィールドにいつ値が格納されるのか、とかは一旦無視して、次はmrb_fixnum_value
を見に行きます。
5. mrb_fixnum_value
の正体
$ git status
HEAD detached at 1.3.0
nothing to commit, working tree clean
$ git grep mrb_fixnum_value | head
include/mruby.h: * mrb_define_const(mrb, mrb->kernel_module, "AGE", mrb_fixnum_value(22));
include/mruby.h: * mrb_funcall(mrb, obj, "method_name", 1, mrb_fixnum_value(i));
include/mruby/value.h:MRB_INLINE mrb_value mrb_fixnum_value(mrb_int i)
mrbgems/mruby-array-ext/src/array.c: mrb_fixnum_value(i)
mrbgems/mruby-array-ext/src/array.c: mrb_fixnum_value(i),
mrbgems/mruby-array-ext/src/array.c: mrb_fixnum_value(RARRAY_LEN(v))
mrbgems/mruby-compiler/core/codegen.c: str = mrb_format(mrb, "$%S", mrb_fixnum_value((mrb_int)(intptr_t)tree));
mrbgems/mruby-compiler/core/codegen.c: int off = new_lit(s, mrb_fixnum_value(i));
mrbgems/mruby-compiler/core/codegen.c: int off = new_lit(s, mrb_fixnum_value(i));
mrbgems/mruby-compiler/core/parse.y: mrb_bug(p->mrb, "Internal error in backref_error() : n=>car == %S", mrb_fixnum_value(c));
定義はinclude/mruby/value.h
にありそうですね。見に行きましょう。
/*
* Returns a fixnum in Ruby.
*/
MRB_INLINE mrb_value mrb_fixnum_value(mrb_int i)
{
mrb_value v;
SET_INT_VALUE(v, i);
return v;
}
はい、そうですか……。ちなみにSET_INT_VALUE
の定義を見に行こうとしてgrepしたのですが、深淵を見そうになったので一旦ここまでで止まります。
まとめ
「mrubyにおけるArray#size
の実装はsrc/array.c
にあるmrb_ary_size
である。mrb_ary_size
では、RArray
構造体のlen
フィールドをmrubyのfixnumに変換して返している」
今後の課題
-
mrb_define_method
の本体 -
SET_INT_VALUE
の正体 - その他いろいろ