エンジニアのソフトウェア的愛情

または私は如何にして心配するのを止めてプログラムを・愛する・ようになったか

手探りでおぼえるmruby その1:クラスを定義する、メソッドを定義する

mrubyをためしています。びっくりするぐらい情報が少ないので、ためしたことおぼえたことをまとめる意味で、ブログに書きつつmrubyの核心に這いよっていきたいと思います。


しかしなんですね。あの名を知ってからおよそ四半世紀、「這いよる混沌」の名をこんなにも耳にする時代が来るとは思ってませんでした。ルルイエは南太平洋にあるのでなく、北太平洋の西端の島国のことだったんじゃないかと思う今日この頃。


閑話休題


エントリを書く中で断定的な言葉づかいをしているところがありますが、手探り状態ですので、間違い、勘違い、メモリリークaccess violation等々の欠陥が潜んでいる可能性があります。その点はご容赦ください。


またコードの全体はGitHubに登録してあります。

準備

まず指定したRubyスクリプトファイルを読み込んで実行するプログラムを書きます。

#include <cstdio>

#include <mruby.h>
#include <mruby/compile.h>
#include <mruby/proc.h>

int exec(int argc, char* argv[])
{
    mrb_state* mrb = mrb_open();

    if(mrb == 0)
    {
        printf("cannot open mruby\n");
        return -2;
    }

    for(int i = 0; i < argc; ++i)
    {
        FILE* fp = std::fopen(argv[i], "r");
        if(fp == 0)
        {
            printf("cannot open file \"%s\"\n", argv[i]);
            continue;
        }

        mrb_parser_state* parser       = mrb_parse_file(mrb, fp);
        int               byte_code    = mrb_generate_code(mrb, parser->tree);
        mrb_value         return_value = mrb_run( mrb,
                                                  mrb_proc_new(mrb, mrb->irep[byte_code]),
                                                  mrb_top_self(mrb)
                                                );
        if(mrb->exc) // if exception occurred ...
        {
            mrb_p(mrb, return_value);
            mrb->exc = 0;
        }

        std::fclose(fp);
    }

    mrb_close(mrb);

    return 0;
}

int main(int argc, char* argv[])
{
    if(argc == 1)
    {
        printf("no ruby file\n");
        return -1;
    }
    else
    {
        return exec(argc - 1, argv + 1);
    }
}


実行。

$ cat hello.rb 
print "Hello mruby world!\n"
$ ./sono001-1 hello.rb 
Hello mruby world!

クラスを定義する

クラスを定義するにはmrb_define_class関数を使います。第1引数にmrb_stateのオブジェクト、第2引数にクラス名、第3引数にスーパークラスを指定します。mrb_stateobject_classRubyObjectに相当するようですが、コードを見てみた限りではNULLを指定したばあいも自動的にObjectスーパークラスに設定してくれるみたいです。


先ほどのコードに1行追加。

    // クラスを定義する
    RClass* my_class = mrb_define_class(mrb, "MyClass", mrb->object_class);


実行。

$ cat my_class.rb 
my_class = MyClass.new
p my_class
$ ./sono001-2 my_class.rb 
#


クラスが定義されているのがわかります。

メソッドを定義する


メソッドを定義するにはmrb_define_class関数の戻り値を使います。
メソッドを定義する関数はmrb_define_method関数です。第1引数にmrb_stateのオブジェクト、第2引数にクラスをあらわすmrb_define_class関数の戻り値、第3引数にメソッド名、第4引数にメソッドを実装したC/C++の関数のポインタ、第5引数にメソッドの引数の数を指定します。
メソッドの引数の指定はARGS_NONEARGS_REQARGS_OPTというマクロを使用します。ここでは引数を使用しないのでARGS_NONEを指定しています。あとで引数を利用するばあいについて書きますが、引数の指定方法についてここでまとめておきます。

記述 意味
ARGS_NONE() 引数なし
ARGS_REQ(1) 引数1つ(必須)
ARGS_OPT(1) 引数1つ(オプション、つまり引数を指定しなくてもよい)
ARGS_REQ(1) | ARGS_OPT(1) 引数1つ、あるいは、引数2つ(1つは必須、1つはオプション)
    // クラスを定義する
    RClass* my_class = mrb_define_class(mrb, "MyClass", mrb->object_class);

    // メソッドを定義する
    mrb_define_method(mrb, my_class, "print_name", my_class_print_name, ARGS_NONE());


C/C++で定義するメソッドの実体は第1引数にmrb_stateのオブジェクト、第2引数にmrb_valueのオブジェクト、戻り値にmrb_valueのオブジェクトを指定した関数になります。このうち第2引数はselfをあらわす値に、戻り値はメソッドの戻り値をあらわす値になります。ここでは戻り値にはselfをそのまま返しています。

mrb_value my_class_print_name(mrb_state* mrb, mrb_value self)
{
    printf("class:MyClass, method:my_class_print_name\n");
    return self;
}


実行。

$ cat my_class_my_method.rb 
my_class = MyClass.new
my_class.print_name
$ ./sono001-3 my_class_my_method.rb 
class:MyClass, method:my_class_print_name


戻り値としてselfが返っていることをメソッドチェインで確かめます。

$ cat my_class_my_method.rb 
my_class = MyClass.new
my_class.print_name.print_name
$ ./sono001-3 my_class_my_method.rb 
class:MyClass, method:my_class_print_name
class:MyClass, method:my_class_print_name

メソッドを定義する、引数つきで

メソッドに引数を指定してみます。
メソッドの定義をしているコードを次のように変更します。必須の引数を1つとる指定です。

    // メソッドを定義する(引数の個数1)
    mrb_define_method(mrb, my_class, "print_name", my_class_print_name, ARGS_REQ(1));


メソッドの実体です。引数はmrb_stateのオブジェクトからmrb_get_args関数を使って取り出します。この関数はstd::scanf関数に似ていて、フォーマット文字列で取り出す引数の型を指定し、可変長の変数の並びで引数を受け取ります。
フォーマット文字列はmrb_get_args関数のコメントに詳細が記載されています(このエントリを書いている時点でこれが一番くわしいドキュメント、たぶん)。

フォーマット文字 受け取るオブジェクトの種類 C/C++での型
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 -


このうちオプションの|は必須引数とオプション引数の区切りをあらわします。例えばフォーマットに"i|S"と書くと必須の整数型とオプションの文字列型の意味になります。"|S"と書けばオプションの文字列型のみ(必須の引数はなし)の意味になります。

mrb_value my_class_print_name(mrb_state* mrb, mrb_value self)
{
    mrb_int n;
    mrb_get_args(mrb, "i", &n); // 引数の取り出し(整数型)
    printf("class:MyClass, method:my_class_print_name(%d)\n", n);
    return self;
}


実行。

$ cat my_class_my_method_with_arg.rb 
my_class = MyClass.new
my_class.print_name(10)
$ ./sono001-4 my_class_my_method_with_arg.rb 
class:MyClass, method:my_class_print_name(10)

メソッドを定義する、引数つきで、オプションの


オプションの引数を追加します。

    // メソッドを定義する(引数の個数、必須1、オプション1)
    mrb_define_method(mrb, my_class, "print_name", my_class_print_name, ARGS_REQ(1) | ARGS_OPT(1));


mrb_get_args関数は戻り値に受け取った引数の個数を返すので、オプションの引数が指定されたかどうかを判断することができます。

mrb_value my_class_print_name(mrb_state* mrb, mrb_value self)
{
    mrb_int   n;
    mrb_value s;
    int       argc = mrb_get_args(mrb, "i|S", &n, &s); // 引数の取り出し、整数型(必須)、文字列型(オプション)
    if(argc == 1)
    {
        printf("first argument is \"%d\"\n", n);
    }
    if(argc == 2)
    {
        printf("first argument is \"%d\", second argument is \"%s\"\n", n, RSTRING_PTR(s));
    }
    return self;
}


実行。

$ cat my_class_my_method_with_args.rb 
my_class = MyClass.new
my_class.print_name(10)
my_class.print_name(10, "hoge")
$ ./sono001-5 my_class_my_method_with_args.rb 
first argument is "10"
first argument is "10", second argument is "hoge"

メソッドを定義する、戻り値つきで

メソッドの戻り値の型はmrb_valueです。これにはRubyで使える値をいろいろと格納できる、ようです。ここでは文字列型の値として使っています。その他のばあいについては調査中。

    mrb_define_method(mrb, my_class, "print_name", my_class_print_name, ARGS_REQ(2));
mrb_value my_class_print_name(mrb_state* mrb, mrb_value self)
{
    mrb_int   n;
    mrb_value s;
    mrb_get_args(mrb, "iS", &n, &s);

    mrb_value result = mrb_str_dup(mrb, s); // 文字列の複製
    for(int i = 1; i < n; ++i)
    {
        mrb_str_concat(mrb, result, s); // 文字列の連結(末尾へ追加)
    }

    return result;
}


ここでは、整数値と文字列を受け取って、文字列を整数値の回数だけ繰り返したものを戻り値として返しています(数値が0以下のばあいを考慮してないことに、いま気がつきました。ご容赦)。
また文字列を扱うためにmruby/string.hファイルをインクルードしています。


実行。

$ cat my_class_my_method_with_result.rb 
my_class = MyClass.new
print my_class.print_name(10, "hoge\n")
$ ./sono001-6 my_class_my_method_with_result.rb 
hoge
hoge
hoge
hoge
hoge
hoge
hoge
hoge
hoge
hoge


次回につづく。

いつか読むはずっと読まない:旧き印

読み物ではありませんが。

衝動買い。

戦闘になったら、大抵のばあい失敗するか死亡するかのどちらかになるとか、理不尽なルールが素敵過ぎますw。

エルダーサイン 完全日本語版

エルダーサイン 完全日本語版