はいこんにちはこんにちは。プログラミング初心者のZonu.EXEです。
今日はRuby Advent Calendar 2012の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の環境を作るやrbenv & ruby-buildの使い方メモを参考にすると、たいへん良いですね。
餘談ですが、そらはー師匠の記事で触れられてるXcodeの代りにkennethreitz/osx-gcc-installer · GitHubのパッケージを使っても良さげらしいですね。僕自身は未検証なんですけど。
最近ナウい対話環境は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
=> #
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で構文木をダンプする
こんな感じのファイルを用意します。
a = "foo"
puts a
ruby --dump parsetree ./foo1.rb
みたいにして実行してみませう。
% 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
が変数名、みたいな感じですね。
じゃあ今度はこんなコード。
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で実行されるバイトコードにコンパイルされて実行されます。
バイトコードとは… って、やっぱりja.Wpでも読んだ方が良いですね。
```ruby:pry
pry(main)> puts RubyVM::InstructionSequence.compile("puts !true").disasm
== disasm: @>==========
0000 trace 1 ( 1)
0002 putself
0003 putobject true
0005 opt_not
0007 send
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以前の差異のまとめとしても、たいへん勉強になります。