Kentaro Kuribayashi's blog

Software Engineering, Management, Books, and Daily Journal.

Cに組み込んだ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);
}