[[mrubyを読む]]

#contents

*はじめに [#ha2d1f6f]

スクリプト解析は文字列を解析する関数とファイルを解析する関数がありますが、それらの違いは所詮、入力の違いなのでmrb_parse_string()をスクリプト解析のエントリポイントとして読み進めていきたいと思います。

*mrb_parse_nstring(src/parse.y) [#k3c722a6]

mrb_parse_string()は渡された文字列をstrlenしてmrb_parse_nstring()に渡しているだけなのでmrb_parse_nstring()を見てみましょう。

#code(C){{
parser_state*
mrb_parse_nstring(mrb_state *mrb, const char *s, size_t len)
{
  parser_state *p;

  p = mrb_parser_new(mrb);
  if (!p) return 0;
  p->s = s;
  p->send = s + len;

  mrb_parser_parse(p);
  return p;
}
}}

スクリプト解析で鍵となるのはmrb_parser_state構造体のようです。mrb_parser_stateはinclude/compile.hに書かれています。なお、上記ではparser_stateになっていますが、parser_stateはmrb_parser_stateをtypedefしたものです。

次にmrb_parser_parse()を見てみます。ちょっと長めですが全部貼り付け。

#code(C){{
void
mrb_parser_parse(parser_state *p)
{
  node *tree;

  if (setjmp(p->jmp) != 0) {
    yyerror(p, "memory allocation error");
    p->nerr++;
    p->tree = p->begin_tree = 0;
    return;
  }

  p->cmd_start = TRUE;
  p->in_def = p->in_single = FALSE;
  p->nerr = p->nwarn = 0;
  p->sterm = 0;

  yyparse(p);
  tree = p->tree;
  if (!tree) {
    if (p->begin_tree) {
      tree = p->begin_tree;
    }
    else {
      tree = new_nil(p);
    }
  }
  else {
    if ((intptr_t)tree->car == NODE_SCOPE) {
      p->locals = cons(tree->cdr->car, 0);
    }
    if (p->begin_tree) {
      tree = new_begin(p, p->begin_tree);
      append(tree, p->tree);
    }
  }
}
}}

関数を眺めると、

yyparse()を実行するとmrb_parser_state.treeに解析結果が格納される

ということがわかります。node(mrb_ast_nodeがtypedefされたもの)は上記のmrb_parser_parse()を見てもわかるとおり、LISPのリスト構造と同じになっています。((やぱり、まつもとさんはLISPが一番好きなのか、いや、なんでもありません))

#code(C){{
typedef struct mrb_ast_node {
  struct mrb_ast_node *car, *cdr;
} mrb_ast_node;
}}

CRubyのNODE構造体に比べると驚くほどシンプルです。シンプルな分は運用で回避。parse.yの前半部分にはリスト操作、およびそれを使って解析ツリーを作るための関数群が定義されています。例えばこんな感じ、

#code(C){{
// (:scope (vars..) (prog...))
static node*
new_scope(parser_state *p, node *body)
{
  return cons((node*)NODE_SCOPE, cons(p->locals->car, body));
}
}}

*解析してみる [#a48d3327]

具体的にスクリプトをNODEに変換してみましょう。対象とするスクリプトは[[Ruby1.9のスクリプト解析>Ruby1.9/スクリプト解析を読む]]で変換してみたものと同じスクリプトを使います((余談ですが、2012/5/29時点でKernel#randが実装されていないため、スクリプトを実行するとNoMethodErrorになります:-P))。

 class MonteCarlo
   def pi(n)
     count = 0
     (1..n).each do
       x = rand
       y = rand
       if x * x + y * y <= 1
         count += 1
       end
     end
     (count.to_f / n) * 4
   end
 end
 
 n = 10000 * 10000
 pi = MonteCarlo.new.pi(n)
 puts "pi = #{pi}"

bin/mrubyは--verboseオプションを付けるとNODEツリーがダンプされます((実行コードもダンプされますがそれは次で取り上げます))。上記のスクリプトを食わせると以下のNODEツリーが出力されました。

 $ ./mruby.exe -c --verbose ../../montecarlo.rb
 NODE_SCOPE:
   local variables:
     n
     pi
   NODE_BEGIN:
     NODE_CLASS:
       :MonteCarlo
       body:
         NODE_BEGIN:
           NODE_DEF:
             pi
             local variables:
               n
                             count
             mandatory args:
               NODE_ARG n
             NODE_BEGIN:
               NODE_ASGN:
                 lhs:
                   NODE_LVAR count
                 rhs:
                   NODE_INT 0 base 10
               NODE_CALL:
                 NODE_BEGIN:
                   NODE_DOT2:
                     NODE_INT 1 base 10
                     NODE_LVAR n
                 method='each' (170)
                 args:
                 block:
                   NODE_BLOCK:
                     body:
                       NODE_BEGIN:
                         NODE_ASGN:
                           lhs:
                             NODE_LVAR x
                           rhs:
                             NODE_CALL:
                               NODE_SELF
                               method='rand' (330)
                         NODE_ASGN:
                           lhs:
                             NODE_LVAR y
                           rhs:
                             NODE_CALL:
                               NODE_SELF
                               method='rand' (330)
                         NODE_IF:
                           cond:
                             NODE_CALL:
                               NODE_CALL:
                                 NODE_CALL:
                                   NODE_LVAR x
                                   method='*' (80)
                                   args:
                                     NODE_LVAR x
                                 method='+' (76)
                                 args:
                                   NODE_CALL:
                                     NODE_LVAR y
                                     method='*' (80)
                                     args:
                                       NODE_LVAR y
                               method='<=' (300)
                               args:
                                 NODE_INT 1 base 10
                           then:
                             NODE_BEGIN:
                               NODE_OP_ASGN:
                                 lhs:
                                   NODE_LVAR count
                                 op='+' (76)
                                 NODE_INT 1 base 10
               NODE_CALL:
                 NODE_BEGIN:
                   NODE_CALL:
                     NODE_CALL:
                       NODE_LVAR count
                       method='to_f' (109)
                     method='/' (148)
                     args:
                       NODE_LVAR n
                 method='*' (80)
                 args:
                   NODE_INT 4 base 10
     NODE_ASGN:
       lhs:
         NODE_LVAR n
       rhs:
         NODE_CALL:
           NODE_INT 10000 base 10
           method='*' (80)
           args:
             NODE_INT 10000 base 10
     NODE_ASGN:
       lhs:
         NODE_LVAR pi
       rhs:
         NODE_CALL:
           NODE_CALL:
             NODE_CONST MonteCarlo
             method='new' (6)
           method='pi' (326)
           args:
             NODE_LVAR n
     NODE_CALL:
       NODE_SELF
       method='puts' (286)
       args:
         NODE_DSTR
           NODE_STR "pi = " len 5
           NODE_BEGIN:
             NODE_LVAR pi
           NODE_STR "" len 0

それではどういうルールを通ることでこのようなNODEツリーが構築されるのかを追っていくことにします。

**primary(keyword_class cpath superclass) [#re464262]

まずクラス定義があります。これは、program → top_compstmt → top_stmts → top_stmt → stmt → expr → arg → primary → keyword_class、とルールが進んでいきます。忘れてましたがそういえばRubyではクラス定義も式でしたね。

**primary(keyword_def fname) [#d234a397]

次のメソッド定義は、bodystmt → compstmt → stmts → stmt → expr → arg → primary → keyword_defと進みます。

***f_arglist [#k04e2de0]

続いて、引数記述の解析に移ります。Rubyは、通常引数、オプション引数、残余引数、ブロック引数と引数の種類がたくさんあるので引数解析のルールも様々なパターンが書かれています。

今回は通常引数のみの一番単純なケースなので、f_arglist → f_args → f_arg → f_arg_item → f_norm_arg、とルールが進んでいきます。

(執筆中)

トップ   新規 一覧 単語検索 最終更新   ヘルプ   最終更新のRSS