Cに組み込んだmrubyのコードのエラーハンドリングについて

「ミドルウェアにmrubyを組み込む方法」についてまとめた - Kentaro Kuribayashi's blogの続き的な話題。Cのプログラムに組み込んだmrubyのコードを評価した時に、そのコードがなんらかのエラーを起こした時にどうするか。 ### mrubyのコードを評価するAPI まず、mrubyのコード

「ミドルウェアにmrubyを組み込む方法」についてまとめた - Kentaro Kuribayashi's blogの続き的な話題。Cのプログラムに組み込んだmrubyのコードを評価した時に、そのコードがなんらかのエラーを起こした時にどうするか。

mrubyのコードを評価するAPI

まず、mrubyのコードを評価するAPIには、コンテキスト(実行するコードのファイル、行などを示す情報)をわたさないものとわたすものとの2種類があります。

  • コンテキストをわたさないもの
    • 例: mrb_value mrb_load_string(mrb_state *mrb, const char *s)
  • コンテキストをわたさすもの
    • 例: mrb_value mrb_load_string_cxt(mrb_state *mrb, const char *s, mrbc_context *c)

とりあえず、まずはmrubyのコードを評価したいだけならどっちを使ってもOKです。しかし、入力されるmrubyのコードには、正常に終了する場合の他、エラーで終了する場合もあるため、それらのハンドリングをする必要があります。

コンテキストをわたさないAPIを使うと、上記のエラーのうち、コンパイルエラー時にSEGVになってしまうので(仕様というか、API利用時の制限?)、コンテキストをわたすものを使うのがよいでしょう。

エラーが発生したらどうするか

mrubyのコードを評価した結果が上記のようなエラーとなった場合、以下の値が帰ってきます。

  • コンパイル時のエラー: nilmrb_nil_value()の返す値)
  • 実行時のエラー: undefmrb_undef_value()の返す値)

また、mrb->excにエラーの内容が代入されます。どっちであってもなんらかのエラーを示す型の値(=mrb_value型の値)として扱えればいいということであれば、以下のようなコードを書けばいいことになります。

  mrb_value value;
mrb_state *mrb = mrb_open();
mrbc_context *cxt = mrbc_context_new(mrb);
value = mrb_load_string_cxt(mrb, code, cxt);
// undef: compile error
// nil:   runtime error
if (mrb->exc != 0 && (mrb_nil_p(value) || mrb_undef_p(value))) {
value = mrb_obj_value(mrb->exc);
}

こうしておけば、エラーで終了することなく、変数valueになんらかのオブジェクトを示すmrb_value型の値として、正常時・異常時どちらであっても返り値を記録しておくことができます。

まとめ

以上を、まとめると、以下のようなコードになるでしょう。その上で、得られたmrb_value型の値を使っていくには、ミドルウェアにmrubyを組み込む方法の17ページ目以降を参照してください。

ちなみに、下記コードのtypeがどういう値を取るかは、include/mruby/value.hを参照のこと。

#include "mruby.h"
#include "mruby/compile.h"
#include "mruby/string.h"
int main(int argc, char **argv) {
char *code = argv[1];           // 本エントリの趣旨に関係ないので適当に処理
mrb_state *mrb = mrb_open();
mrbc_context *cxt = mrbc_context_new(mrb);
mrb_value value = mrb_load_string_cxt(mrb, code, cxt);
// 返り値が:
//   undef: compile error
//   nil:   runtime error
if (mrb->exc != 0 && (mrb_undef_p(value) || mrb_nil_p(value))) {
value = mrb_obj_value(mrb->exc);
}
enum mrb_vtype type = mrb_type(value);
printf("type: %d, Inspect: %s\n", type, mrb_str_to_cstr(mrb, mrb_inspect(mrb, value)));
mrb->exc = 0;                   // このコードでは不要だが、`mrb`を使いまわす場合は戻しておく
mrbc_context_free(mrb, cxt);
mrb_close(mrb);
}