Rubyのコードを解析する…?


はいこんにちはこんにちは。プログラミング初心者のZonu.EXEです。

今日は[Ruby Advent Calendar 2012](http://qiita.com/advent-calendar/2012/ruby)の20日めです…?

ちょっと前まで自宅警備員で、一個月ほど前からアルバイトでるびーおんれーるずを書くお仕事に就いたのですが、Ruby基礎力が低すぎて困ることが多々あります。

そんなわけで、Rubyをきちんと学ぶために基礎の基礎から調べてみましょか、みたいなテンションで書いてました。

ほんとは字句解析だけでがっつりと書きたかったんですけれど、ねたを集める時間がなかったので枝葉末節がひどいです。

タイトルと内容が合ってない気もするけど気にするな! 全然毛色が違ってるねたが混ざってても気にするな! オムニバス!

##はじめに

プログラミング言語で書かれたソースコードを読んで何らかの処理を行ふソフトウェアを「処理系」と呼びます。よくわかんないですけど、何らかの処理をしてくれるんですね。処理系はコンパイラだったり、インタプリタだったりします。

Rubyの文脈においては、プログラミング言語の名前はRuby、処理系は本家の**Ruby**、Javaで書かれた**JRuby**、.NET環境で動くIronRuby、Rubyで書かれた**Rubinius**があります。

まあ、ふつうにふつうの**Ruby**で全然問題ありませんね。

> 本家本元のRubyは言語名と区別するために(コマンド名と同じ)小文字で`ruby`、(Jと対比して)C言語で書かれてるから**CRuby**、Matzが作った処理系だから_Matz Ruby Implementation_ 、縮めて**MRI**と呼ばれたりします。
> いろんな呼びかたがありますね ヾ(〃＞＜)ﾉﾞ

##コード解析とは

別にそんなに難しいことばを持ち出して警戒する必要もないのですが、要するに書いたコードがどんな意味を持つのかを調べるってだけの話です。

コード解析の種類には**動的解析**と**静的解析**がありますが、別にこれも難しく捉へる必要はないです。「コードだけを見ればわかる」のが**静的解析**、「実際に動かしてみればわかる」のが**動的解析**です。

ただし動的解析にはテストフレームワークなどを使ったテストを含みますが、今回はテストについては触れません。RSpecやTest::Unitを使ったテストのことは、いつかもっとえろいひとがしてくれるのでは。

この記事では静的とか動的って話はあまりしない気がします。

##環境構築

いまどきMacに標準添付**のRuby1.8**(_賞味期限切れ_)はださいので、ちゃんとしたRubyを入れること。

2012年12月20日現在でいちばんナウい安定版は**Ruby 1.9.3-p327**です。**2.0 preview2**で痛い目に遭っても泣かない子はそっちでもたぶん可。

[OS Xでrbenvを使ってruby 1.9.3の環境を作る](http://qiita.com/items/9dd797f42e7bea674705)や[rbenv & ruby-buildの使い方メモ](http://qiita.com/items/b07beebca21ba7ed8e7f)を参考にすると、たいへん良いですね。

餘談ですが、そらはー師匠の記事で触れられてる**Xcode**の代りに[kennethreitz/osx-gcc-installer · GitHub](https://github.com/kennethreitz/osx-gcc-installer)のパッケージを使っても良さげらしいですね。僕自身は未検証なんですけど。

最近ナウい対話環境は**Pry**なので、`gem install rdoc pry-doc`とかやってください。依存でいろいろgemが入ります。

##開発環境
**Pry**が入ったので使ってみませう。コマンドは`pry`です。rbenvを使ってる人は`gem install`した後に`rbenv rehash`を忘れないでね。

**Pry**は、Rubyに最初からくっついてきてる**IRB**よりも、さらに強いやつです。色がついたり、キーボードの`TAB`キーでメソッド名補完もできたり、いたれりつくせりですね。

##Pryでメソッドの説明を読む
この記事でわざわざ**Pry**の話から始めたのは、この説明がしたかったからです。

Pryでは`show-doc`コマンドで、メソッドの説明を読むことができます。

```ruby:`pry`
pry(main)> show-doc Ripper.lex

From: /Users/megurine/.rbenv/versions/2.0.0-preview1/lib/ruby/2.0.0/ripper/lexer.rb @ line 38:
Number of lines: 18
Owner: #<Class:Ripper>
Visibility: public
Signature: lex(src, filename=?, lineno=?)

Tokenizes the Ruby program and returns an Array of an Array,
which is formatted like [[lineno, column], type, token].

  require 'ripper'
  require 'pp'

  p Ripper.lex("def m(a) nil end")
    #=> [[[1,  0], :on_kw,     "def"],
         [[1,  3], :on_sp,     " "  ],
         [[1,  4], :on_ident,  "m"  ],
         [[1,  5], :on_lparen, "("  ],
         [[1,  6], :on_ident,  "a"  ],
         [[1,  7], :on_rparen, ")"  ],
         [[1,  8], :on_sp,     " "  ],
         [[1,  9], :on_kw,     "nil"],
         [[1, 12], :on_sp,     " "  ],
         [[1, 13], :on_kw,     "end"]]
```

これ、実際に端末で実行してみてもちゃんと色がつきます。かっこいいですね。

注意なのですが、Rubyレベルではクラスメソッドの呼び出しは、たとへば`String::new`でも`String.new`でも良いのですが、(現在のバージョンのpry-docでは)`show-doc`コマンドの引数としては`String.new`と書かないとだめです。気をつけてくださいね。

> 閑話休題。Pryの実装は読んでないのですが、`method(:'show-doc')`とかやっても`Method`オブジェクトを得られないので、Rubyレベルのメソッドではないコマンドみたいですね。

##クラスの継承関係を辿る
```ruby:`pry`
pry(main)> class Foo
pry(main)*   nil
pry(main)* end
=> nil
pry(main)> Foo.ancestors
=> [Foo, Object, PP::ObjectMixin, Kernel, BasicObject]
```

`Class.ancestors`メソッドはクラスの継承関係を配列で並べて返します。**Rubyに多重継承はない**ので、常に直線です。

##オブジェクトのメソッド一覧を調べる
```ruby:`pry`
pry(main)> class Z;end
=> nil
pry(main)> z = Z.new
=> #<Z:0x007f990509cdd8>
pry(main)> z.methods
=> [:pry,
 :__binding__,
 :pretty_print,
 :pretty_print_cycle,
 :pretty_print_instance_variables,
 :pretty_print_inspect,
…
```

実際にはもっとたくさんのメソッドが定義されますし、表示することもできます。

じゃあ定義されたメソッドの数を数へみましょっかー、といふときにはどうするのが良いですかね。

```ruby:`pry`
pry(main)> z.methods.size
=> 63
```

はい、お手軽にできました。

ここからはちょっとした応用篇。

##`Fixnum`オブジェクトの`to_xxx`メソッドを列挙したい

`Array#grep`メソッドと正規表現で、お望みのメソッド名を抽出してみるのが良さげですね。

```ruby:`pry`
pry(main)> 1.methods.grep(/^to_.*/)
=> [:to_s, :to_f, :to_i, :to_int, :to_r, :to_c, :to_enum]
```


##「シンボルにあって文字列にないメソッド」を列挙したい

配列同士の引き算を利用してらどうですかね。

```ruby:`pry`
pry(main)> :''.methods - ''.methods
=> [:id2name, :to_proc]
pry(main)> ''.methods - :''.methods
=> "[:+, :*, :%, :[]=, :insert, :bytesize, :succ!, :next!, :upto, :index, :rindex, :replace, :clear, :chr, :getbyte, :setbyte, :byteslice, :to_i, :to_f, :to_str, :dump, :upcase!, :downcase!, :capitalize!, :swapcase!, :hex, :oct, :split, :lines, :bytes, :chars, :codepoints, :reverse, :reverse!, :concat, :<<, :prepend, :crypt, :ord, :include?, :start_with?, :end_with?, :scan, :ljust, :rjust, :center, :sub, :gsub, :chop, :chomp, :strip, :lstrip, :rstrip, :sub!, :gsub!, :chop!, :chomp!, :strip!, :lstrip!, :rstrip!, :tr, :tr_s, :delete, :squeeze, :count, :tr!, :tr_s!, :delete!, :squeeze!, :each_line, :each_byte, :each_char, :each_codepoint, :sum, :slice!, :partition, :rpartition, :force_encoding, :valid_encoding?, :ascii_only?, :unpack, :encode, :encode!, :to_r, :to_c, :shellsplit, :shellescape, :shell_split]
```

`Symbol`と`String`は相互変換可能だけど全然別物なんだぜ? みたいな傍證のひとつでした。

##ソースコードを字句解析してみる
**字句解析**(*lexical analysis*)とは、文字列をプログラミング言語のソースコードとして認識するためのプロセスのひとつです。

たとへば `def f(a,b);nil;end` といふ文字の並びを、Rubyはどのように字句解析するのか調べてみませう。

Ruby本体に添付されてゐる**Ripper**といふライブラリを利用することで、ソースコードに対してRubyと同じ字句解析を行うことができます。

**Ripper**は標準添付のライブラリなので、Rubyをちゃんとインストールされた方ならば、特にインストール作業の必要ありません。 

`Ripper.tokenize`は、文字列を単純にトークン単位に分解します。 **トークン**とは、意味をもった文字の塊の単位のことです。

そして、`Ripper.lex`は、その切り分けられたトークンの位置と、そのトークンの具体的な意味、つまり何者であるかを調べることができます。

```ruby:`pry`
pry(main)> require 'ripper'
=> true

pry(main)> Ripper.tokenize("def f(a,b);nil;end")
=> ["def", " ", "f", "(", "a", ",", "b", ")", ";", "nil", ";", "end"]

pry(main)> Ripper.lex("def f(a,b);nil;end")
=> [[[1, 0], :on_kw, "def"],
 [[1, 3], :on_sp, " "],
 [[1, 4], :on_ident, "f"],
 [[1, 5], :on_lparen, "("],
 [[1, 6], :on_ident, "a"],
 [[1, 7], :on_comma, ","],
 [[1, 8], :on_ident, "b"],
 [[1, 9], :on_rparen, ")"],
 [[1, 10], :on_semicolon, ";"],
 [[1, 11], :on_kw, "nil"],
 [[1, 14], :on_semicolon, ";"],
 [[1, 15], :on_kw, "end"]]
```

はい、綺麗に分解してくれましたね。`on_kw`は、いはゆる予約語です。

`on_ident`は**識別子**です。この場合での`f`はメソッド名、`a`と`b`は仮引数名と素性はあきらかですが、いつでもそうとは限りません。識別子はメソッド呼び出しかもしれませんし、変数名かもしれません。


```ruby:`pry`
pry(main)> Ripper.lex('a = "foo"')
=> [[[1, 0], :on_ident, "a"],
 [[1, 1], :on_sp, " "],
 [[1, 2], :on_op, "="],
 [[1, 3], :on_sp, " "],
 [[1, 4], :on_tstring_beg, "\""],
 [[1, 5], :on_tstring_content, "foo"],
 [[1, 8], :on_tstring_end, "\""]]
```

> 話は逸れますが、`(a??a)?:a: :a`や`_?(?:,:'?')`は、まったく有効なRubyのコードです。このコードをRipperで字句解析してみたりするとおもしろいですね。
> あと、実際にこのコードを動作させるにはどんな準備が必要なのかチャレンジしてみませうヾ(〃＞＜)ﾉﾞ☆

## parsetreeで構文木をダンプする

こんな感じのファイルを用意します。

```ruby:foo1.rb
a = "foo"
puts a
```

`ruby --dump parsetree ./foo1.rb` みたいにして実行してみませう。

```zsh
% vim foo1.rb
% ruby --dump parsetree ./foo1.rb
###########################################################
## Do NOT use this node dump for any purpose other than  ##
## debug and research.  Compatibility is not guaranteed. ##
###########################################################

# @ NODE_SCOPE (line: 3)
# +- nd_tbl: :a
# +- nd_args:
# |   (null node)
# +- nd_body:
#     @ NODE_BLOCK (line: 1)
#     +- nd_head:
#     |   @ NODE_DASGN_CURR (line: 1)
#     |   +- nd_vid: :a
#     |   +- nd_value:
#     |       @ NODE_STR (line: 1)
#     |       +- nd_lit: "foo"
#     +- nd_next:
#         @ NODE_BLOCK (line: 2)
#         +- nd_head:
#         |   @ NODE_FCALL (line: 2)
#         |   +- nd_mid: :puts
#         |   +- nd_args:
#         |       @ NODE_ARRAY (line: 2)
#         |       +- nd_alen: 1
#         |       +- nd_head:
#         |       |   @ NODE_DVAR (line: 2)
#         |       |   +- nd_vid: :a
#         |       +- nd_next:
#         |           (null node)
#         +- nd_next:
#             (null node)
```

構文木とは… とか説明を書いてる時間はないのですが、この構文木はCのデータ構造になってます。Ruby1.8以前はこの構文木を辿って実行されるインタプリタでした。

`ruby`に`--dump parsetree`を付けて実行すると、Rubyは実行時の構文木を人間に見やすい形式で出力してくれます。

さて、`NODE_DASGN_CURR`は**代入文**、`NODE_DVAR`は**変数の参照**、それぞれの下にぶら下がってる`nd_vid: :a`が変数名、みたいな感じですね。

じゃあ今度はこんなコード。

```ruby:foo2.rb
def a ()
  return "Foo"
end


puts a
```

実行してみると…

```
% ruby --dump parsetree ./foo2.rb
###########################################################
## Do NOT use this node dump for any purpose other than  ##
## debug and research.  Compatibility is not guaranteed. ##
###########################################################

# @ NODE_SCOPE (line: 7)
# +- nd_tbl: (empty)
# +- nd_args:
# |   (null node)
# +- nd_body:
#     @ NODE_BLOCK (line: 1)
#     +- nd_head:
#     |   @ NODE_DEFN (line: 1)
#     |   +- nd_mid: :a
#     |   +- nd_defn:
#     |       @ NODE_SCOPE (line: 3)
#     |       +- nd_tbl: (empty)
#     |       +- nd_args:
#     |       |   @ NODE_ARGS (line: 1)
#     |       |   +- nd_frml: 0
#     |       |   +- nd_next:
#     |       |   |   @ NODE_ARGS_AUX (line: 1)
#     |       |   |   +- nd_rest: (null)
#     |       |   |   +- nd_body: (null)
#     |       |   |   +- nd_next:
#     |       |   |       (null node)
#     |       |   +- nd_opt:
#     |       |       (null node)
#     |       +- nd_body:
#     |           @ NODE_STR (line: 2)
#     |           +- nd_lit: "Foo"
#     +- nd_next:
#         @ NODE_BLOCK (line: 6)
#         +- nd_head:
#         |   @ NODE_FCALL (line: 6)
#         |   +- nd_mid: :puts
#         |   +- nd_args:
#         |       @ NODE_ARRAY (line: 6)
#         |       +- nd_alen: 1
#         |       +- nd_head:
#         |       |   @ NODE_VCALL (line: 6)
#         |       |   +- nd_mid: :a
#         |       +- nd_next:
#         |           (null node)
#         +- nd_next:
#             (null node)
```

よく見てくださいよ奥さん、`print a`の行はまったく同じはずなのに、出来上がった構文木では`NODE_DVAR`だった箇所が`NODE_VCALL`になってますよ… おっかないですね…

じゃあ、このコードのどこかに`a = "foo"`って足したらどうなるんですかね……?

##VMのバイトコードを読む

先程の章で1.8では構文木を辿って実行されるのだ、と書きましたが、1.9ではさらに通称**YARV**と呼ばれるVMで実行されるバイトコードにコンパイルされて実行されます。

[バイトコード](http://ja.wikipedia.org/wiki/%E3%83%90%E3%82%A4%E3%83%88%E3%82%B3%E3%83%BC%E3%83%89)とは… って、やっぱりja.Wpでも読んだ方が良いですね。

```ruby:`pry`
pry(main)> puts RubyVM::InstructionSequence.compile("puts !true").disasm
== disasm: <RubyVM::InstructionSequence:<compiled>@<compiled>>==========
0000 trace            1                                               (   1)
0002 putself
0003 putobject        true
0005 opt_not          <callinfo!mid:!, argc:0, ARGS_SKIP>
0007 send             <callinfo!mid:puts, argc:1, FCALL|ARGS_SKIP>
0009 leave
```

流れでバイトコードの見方も紹介しましたが、正直僕自身は構文木ほどには心惹かれるものは… けふんけふん。

> ちなみにPythonはバイトコード形式にコンパイルしたものが`.pyc`や`.pyo`の拡張子のファイルとして保存されますが、Rubyにはそれに相当するようなファイル形式は存在せず、毎度毎度バイトコードにコンパイルをしてから実行するようになってます。
> Python方式とRuby方式のメリットデメリットを比較してみるとおもしろいかもしれませんねヾ(〃＞＜)ﾉﾞ☆

## 参考資料

この記事を書く参考にはあまりしてなくて、ふつうにRubyの勉強に役立つんじゃないかなー、って資料ですねヾ(〃＞＜)ﾉﾞ

###Rubyソースコード完全解説(RHG)

 * [『Rubyソースコード完全解説』サポートページ](http://i.loveruby.net/ja/rhg/)

えっと、バイブルです。通称:**RHG**。

出版された本ですが全文無料で読めます(書籍は入手困難)。ベースのRubyが古いですが、Rubyの**基礎**([foundation的な意味で](http://togetter.com/li/333205))を理解するには、未だにこれ以上ない金字塔。

###Rubyist Magazine - YARV Maniacs

 * [Rubyist Magazine - YARV Maniacs 【第 1 回】 『Ruby ソースコード完全解説』不完全解説](http://jp.rubyist.net/magazine/?0006-YarvManiacs)

若き日のささだこういち先生の書かれた連載記事(の第1回)です。

**RHG**をつまみ食ひするための手がかりとしても、Ruby1.9と1.8以前の差異のまとめとしても、たいへん勉強になります。