01 Mar 2014, 05:47

Rubyのコードをripperでパースする方法

Rubyのコードをパースしたい。 自前で正規表現をつくったり、strscanでゴリゴリ解析するのは工数不足そう。

ということで、Rubyのコードを字句解析するのためのツールをしらべた。

Ruby標準ライブラリのなかの以下の2つが利用できそうだ。

それぞれ、つかってみる。

やりたいこと

とりあえず、こんなことができればOK.

  • クラスの定数を抜きだし
  • メソッドに含まれるメソッドとその引数の抜きだし

解析対象

今回の解析対象Rubyコードは以下。


TEST  = "test"
TEST2 = "test2"

def method1
  foo(1, 2,"Hello")
  bar(1, 2,"Hello")
end

def method2
  bar(5, 6,"Hi")
  foo(3, 4,"Hi")
end

def foo(val1, val2, str)
end

def bar(val1, val2, str)
end

rdoc

rdocはRubyのドキュメント生成のためのツールだけれども、Rubyコード解析用のライブラリもあるみたい。

そういえば、このまえ記事にしたrspec-kickstarterも rdocでrspecを解析しているっぽい。

コマンドラインから以下を実行すると、カレントディレクトリのコードを解析してHTMLを生成してくれる。

rdoc .

こんな感じ。ちゃんとメソッドと定数が抜き出せている。

どうも、引数は抜き出せなさそう。調査不足かもしれないが、採用は却下。

ripper

Rubyのコード解析をするための標準ライブラリ。

トークン指向型解析(tokenize)

<div class="outline-text-3" id="text-4-1">
  <p>
    文字列を単語に分解してくれる。
  </p></p>
</div>

<div id="outline-container-4-1-1" class="outline-4">
  <h4 id="sec-4-1-1">
    sample code
  </h4>

  <div class="outline-text-4" id="text-4-1-1">
    <pre><code>

require ‘ripper’

File.open(“./sample.rb”) do |io| io.each_line do |line| p Ripper.tokenize(line) end end

<div id="outline-container-4-1-2" class="outline-4">
  <h4 id="sec-4-1-2">
    実行結果
  </h4>

  <div class="outline-text-4" id="text-4-1-2">
    <pre><code>["TEST", "  ", "=", " ", "\"", "test", "\"", "\n"]

[“TEST2”, “ “, “=”, “ “, “\“”, “test2”, “\“”, “\n”] [”\n”] [“def”, “ “, “method1”, “\n”] [” “, “foo”, “(”, “1”, “,”, “ “, “2”, “,”, “\“”, “Hello”, “\“”, “)”, “\n”] [” “, “bar”, “(”, “1”, “,”, “ “, “2”, “,”, “\“”, “Hello”, “\“”, “)”, “\n”] [“end”, “\n”] [”\n”] [“def”, “ “, “method2”, “\n”] [” “, “bar”, “(”, “5”, “,”, “ “, “6”, “,”, “\“”, “Hi”, “\“”, “)”, “\n”] [” “, “foo”, “(”, “3”, “,”, “ “, “4”, “,”, “\“”, “Hi”, “\“”, “)”, “\n”] [“end”, “\n”] [”\n”] [“def”, “ “, “foo”, “(”, “val1”, “,”, “ “, “val2”, “,”, “ “, “str”, “)”, “\n”] [“end”, “\n”] [”\n”] [“def”, “ “, “bar”, “(”, “val1”, “,”, “ “, “val2”, “,”, “ “, “str”, “)”, “\n”] [“end”, “\n”]

    <p>
      tokenizeの他には、sexp(S式)、lexer(位置情報つき)がある。
    </p>

    <ul>
      <li>
        <a href="http://ruby-doc.org/stdlib-2.0.0/libdoc/ripper/rdoc/Ripper.html">Class: Ripper (Ruby 2.0.0)</a>
      </li>
    </ul>
  </div></p>
</div></p>

イベントドリブン型解析

<div class="outline-text-3" id="text-4-2">
  <p>
    特定の構文に出会うたびに、イベントハンドラがコールされる。 on_XXXで定義する。XXXの部分には、Ripper:EVENTSでとれる値が入る。
  </p>

  <pre><code>pp Ripper::EVENTS

  <ul>
    <li>
      <a href="https://gist.github.com/tsu-nera/9272622">https://gist.github.com/tsu-nera/9272622</a>
    </li>
  </ul>

  <p>
    ripper-tagsのソースコードとかも、使い方の勉強になる。
  </p>

  <ul>
    <li>
      <a href="https://github.com/tmm1/ripper-tags">https://github.com/tmm1/ripper-tags</a>
    </li>
  </ul>
</div></p>

ripper つかってみる

S式配列を取得する

<div class="outline-text-3" id="text-5-1">
  <p>
    イベントドリブン型でsample.rbをパースしてみる。 まずは、以下のようなコードでS式の配列を出力する。
  </p>

  <pre><code>

require ‘ripper’ require ‘pp’

File.open(“./sample.rb”) do |io| pp Ripper.sexp(io) end

  <p>
    ずらずらとS式の配列が現れる。
  </p>

  <pre><code>

[:program, [[:assign, [:var_field, [:@const, “TEST”, [1, 0]]], [:string_literal, [:string_content, [:@tstring_content, “test”, [1, 9]]]]], [:assign, [:var_field, [:@const, “TEST2”, [2, 0]]], [:string_literal, [:string_content, [:@tstring_content, “test2”, [2, 9]]]]], [:def, [:@ident, “method1”, [4, 4]], [:params, nil, nil, nil, nil, nil, nil, nil], [:bodystmt, [[:method_add_arg, [:fcall, [:@ident, “foo”, [5, 2]]], [:arg_paren, [:args_add_block, [[:@int, “1”, [5, 6]], [:@int, “2”, [5, 9]], [:string_literal, [:string_content, [:@tstring_content, “Hello”, [5, 12]]]]], false]]], [:method_add_arg, [:fcall, [:@ident, “bar”, [6, 2]]], [:arg_paren, [:args_add_block, [[:@int, “1”, [6, 6]], [:@int, “2”, [6, 9]], [:string_literal, [:string_content, [:@tstring_content, “Hello”, [6, 12]]]]], false]]]], nil, nil, nil]],

  <p>
    @という記号の後にイベント名っぽいものがある。 パースしたいキーワードの近くにあるイベント名っぽいものをXXXとして処理を書いていく。
  </p>

  <p>
    以下のようなことをする。
  </p>

  <ul>
    <li>
      定数を抽出
    </li>
    <li>
      定義されているメソッドをインデックスとする配列を作成
    </li>
    <li>
      配列の要素にcallされているメソッドをキー、引数をvalとするハッシュを作成。
    </li>
  </ul>
</div></p>

結果

<div class="outline-text-3" id="text-5-2">
</div></p>

まとめ

はじめに使い方を覚えるのに苦労した。

一度分かってしまえば正規表現でパースするよりも簡単そうだ。