2010/06/16

refe.rb を読む

これの続き。

たとえば

$ rurema String 

とすると

bitclust の

bitclust/bin/refe.rb String -d db_path

を実行させているってことがわかったので↓
rurema/myrurema/bin/rurema

 92     sh "#{bitclust_path/'bin/refe.rb'}" +
 93           " #{query} -d #{db_path(ver)}", :silent => true


それでふと refe.rb は "String" などの文字列うけとった後どうしているのだろう?と思っておいかけてみた。
(デバッガーみたいなものを使い倒せる人じゃないので p self;exit を付け足すといった原始的な方法)

bitclust/bin/refe.rb

def _main
  refe = BitClust::Searcher.new


BitClust::Searcher をnew して、class Searcher の method parse に "String" という引数を渡しているらしきことがわかったので
class Searcher を探す...

% grep Searcher lib/bitclust
class Searcher は searcher.rb ファイルに納められていることがわかった。

bitclust/lib/bitclust/searcher.rb

 20 module BitClust
 21 
 22   class Searcher


public method parse は80行あたりにあった。

 81     def parse(argv)
 82       @parser.parse! argv


引数としてうけとった argv の中身は
["String", "-d", "/path/to/data/db/1.9.2"]
配列になっていて "/path/to/data/db/1.9.2" は、myrurema でいうところの db_path がわたされていた。

そして条件分岐した後に
refe_mode_check argv
を実行していた。この時 refe_mode_check に渡している argv には ["String"] がおさめられていた。

refe_mode_check をみてみると、(line 118あたり)
@target_type によって条件分岐していて、@target_type が :class だった場合とそうでない場合の処理があった。

でも
% rurema String を実行した場合 @target_type は nil になるので case 中の条件のどれにもあてはまらない。
case がおわった後の箇所から実行されることになる。
(line 135 あたり)

       @view = TerminalView.new(compiler,
...略


class TerminalView を new して @view にそれを代入していることがわかった。
それでこのあとの続きはどこにあるのだろうか?
@view には class TerminalView が代入されているだけで、ほかにメソッドが与えられていない。

これまで辿ってきた道を引き返していくほかなさそうだ... 。
最初までもどってみたら続きがあった。

bitclust/bin/refe.rb

def _main
  refe = BitClust::Searcher.new
  refe.parse ARGV
  refe.exec nil, ARGV

要は
1, _main は ローカル変数 refe に class Searcher を代入する。
2, class Searcher のパブリックメソッドである parse を実行し@view にTerminalView.new を代入する。
3, class Searcher のパブリックメソッドである exec を実行。

ということのようだ。

では早速続きにあたる3番目の箇所 class Searcher の method exec をみてみよう。

 90     def exec(db, argv)
 91       if @listen_url


@listen_url 変数に何か値がはいっていたら spawn_server を、そうでなければ search_pattern を実行する、と。
% rurema String を実行した時 @listen_url は nil なので
line 94 の search_pattern
が実行される。

def search_pattern みてみよう。

200     def search_pattern(db, argv)
201       db ||= new_database()
202       case @target_type || db
203       when :class
...略
214       else


引数として "String" を渡した時は @target_type は nil なので line 214 以降に移動する。移動すると argv のサイズによっての条件分岐が始まる。サイズが 1 の場合は

219         when 1
220           find_class_or_method db, argv[0]

find_class_or_method を実行する...

262     def find_class_or_method(db, pattern)
263       case pattern


pattern の値によって処理を分けている。
pattern には "String" がおさまっている。
db には#<BitClust::MethodDatabase>
 
"String" という文字列受け取った場合は when /\A[A-Z]/ に該当するのでそれに続く begin以降にジャンプする。

270       when /\A[A-Z]/   # Method name or class name, but class name is better.
271         begin
272           find_class db, pattern

次に探すべきメソッドは find_class 。

find_class

241     def find_class(db, c)
242       @view.show_class db.search_classes(c)
243     end


をーここで @view と出会いました。やっと話がつながった感じがします。
つまり

1, _main は ローカル変数 refe に class Searcher を代入する。
2, class Searcher のパブリックメソッドである parse を実行し@view にTerminalView.new を代入する。
3, class Searcher のパブリックメソッドである exec を実行。


この3番目の処理が line 242line 90 にあたると。
2番目の処理で「あらかじめ」Searcher のインスタンス変数である @view に class TerminalView が代入されているからここ(line 242 )で@view.show_class を使うことができるわけね、と。

一応、確認してみると

def find_class(db, c)
  p @view.class, c ; exit
end

% rurema String
BitClust::TerminalView
"String"

ちゃんと TerminalView が代入されている。

ではでは
class TerminalView の methods show_class をみてみよう。

324     def show_class(cs)
325       if cs.size == 1

def show_class は cs をうけとって そのサイズが1だった場合とそうじゃなかった場合とで処理を分岐させている。

% rurema String
の場合、cs は [#<class String>]

% rurema Hash 
の場合、cs は [#<class Hash>]
と変化する。@line が false の場合で、csのサイズが1ならば describe_class を実行する。

なので
% rurema String
した時は

line. 329 describe_class cs.first
を実行するのだとわかった。
では def describe_class はどこだろう? line 411 あたりにあった。

describe_class

411     def describe_class(c)
...略
416       puts "#{c.type} #{c.name}#{c.superclass ? " < #{c.superclass.name}" : ''}"
417       p c.class.name; p c; exit


line. 417 を上のようにしてみると

% rurema String
class String < Object
"BitClust::ClassEntry"
#<class String>

と返ってきたのでローカル変数c には ClassEntry が代入されていてそのクラスは type や name といったメソッドを持っているとわかった。
つづいて

417       unless c.included.empty?
...略
423       unless c.source.strip.empty?

とあって c (すなわち BitClust::ClassEntry)のincluded が empty じゃなかったら空行を1行出力し
c.included.each ...
を実行し
同様に、c.source.strip.empty? がempty じゃなかったら空行を1行出力し
@compiler.compile(c.source.strip)
を実行する。
ようだ。

% rurema String
を実行した場合は何行目の処理が実行されるのか?

line 417 のunless c.included.empty? にキャッチされるとわかった。従ってこの部分

419         c.included.each do |mod|
420           puts "include #{mod.name}"
421         end

を実行していることになる。

変数c には class ClassEntry が代入されているので c.included は、ClassEntryクラスの included メソッド。included メソッドはブロックを受け取ることができるのだなぁ、と。

では class ClassEntry がどうなっているかをみてみよう...。 classentry.rb を開いてみると

bitclust/lib/bitclust/classentry.rb

class ClassEntry < Entry

Entry クラスを継承していた。searcher.rb が使っていた included はどこだろ?継承元の Entry の方なのかな?いや無い。てことは ClassEntry が自分でもっている?ないみたい。

うむむ、どうやら何かにくるまれているようだ...。

hash のキーとしてならあるんだけども...

bitclust/lib/bitclust/classentry.rb

 77       property :included,   '[ClassEntry]'


included のゆくえがわからなくなってしまった。

こういう時は最初に initialize がどうなっているかをみてみよう。

 16   class ClassEntry < Entry
 ... 略 
 36       init_properties


init_properties って何かな?classentry.rb ファイル中にその実態は無いようだ。
継承元のクラスのファイルを開いてみると
% vim entry.rb
init_properties は module_eval によって定義されていた...。

もう一度頭を整理して考えてみよう。呼び出した側は下記のようになっていて
bitclust/lib/bitclust/searcher.rb

419         c.included.each do |mod|
420           puts "include #{mod.name}"
421         end


ClassEntryクラスのパブリックメソッドの中に included という名前のメソッドはない。
classentry.rb ファイル中に included という名前で存在しているのはハッシュのキーのみ。

最終的なところまで近づいてきた感じがしつつもこの先はちょっと難しそうだな。読み解けるかなぁ...
今日はここまで。

0 件のコメント: