読者の中には既にRubyに慣れ親しんでいる人もいるだろうが、そうでない人も たくさんいるだろう(そうであってほしい)。まずはそのような人達のために Rubyの特徴をおおざっぱにまとめる。
以下、言語仕様としてのRuby言語を大文字で「Ruby」、その実装としての
ruby
コマンドを小文字で「ruby
」と書き分けて示す。
Rubyは、まつもとゆきひろ氏個人の手によって作成されている言語である。
CやJavaやSchemeのように標準があるわけではない。その仕様はruby
という
実装によって示されるだけであり、しかも常に変化している。良くも悪くも
自由なのだ。
またruby
がフリーソフトウェアである……つまりソースコードが公開されていること、
しかも無料で配布されていること、の二点は書いておかないといけないだろう。
そのような条件だからこそ本書のような試みも成立するわけだ。
正確なライセンスは本体に含まれるREADME
とLEGAL
を読んでもらうとして、
とりあえずは以下のようなことはできるということを覚えておいて
もらえばよい。
ruby
のソースコードを再配布できる。ruby
のソースコードを改変できる。いずれの場合も特別な許諾や料金は必要ない。
なお本書はオリジナルのruby
を読むのが目的なので特に断らない限り変更なし
のソースである。ただし空白・改行・コメントの追加削除だけは断りなしに行っ
た。
Rubyはとても保守的な言語である。いろいろな言語で検証され使い古されたも のばかりを選んで搭載しており、新奇な機能や実験的な仕様はあまり付いてい ない。だからRubyはどちらかというと実用重視のプログラマに受ける傾向があ り、SchemeやHaskellラヴな根っからのハッカーには、少なくともちょっと見 には、受けが良くないようだ。
ライブラリもそうだ。新しい機能には省略無しのわかりやすい名前が付いてい
るのだが、CやPerlのライブラリにある名前はそのまま使われている。例えば
printf
やgetpwent
、sub
、tr
など。
また実装においても保守的だ。速度のためにアセンブラを使ったりすることは なく、スピードと移植性が相反するときは常に移植性の高い方法が使われる。
Rubyはオブジェクト指向言語である。これはRubyの特徴として絶対に外すこと はできない。
オブジェクト指向とは何なのか、という話は本書にとっては筋違いなので省略 する。「Rubyのオブジェクト指向」について言えば、これから解説するコード が表現しようとしているものがソレである。
Rubyはスクリプト言語である。これもRubyの特徴として絶対に外すことはでき ないらしい。Rubyを紹介するときには「オブジェクト指向スクリプト言語」 という枕詞を付けないとみんなに満足してもらえない。
しかし、スクリプト言語とはどういう言語のことだろうか。どうもこれがはっき
りしない。例えばTcl/Tkの作者のJohn K.Ousterhoutは「UNIXで#!
を使って
実行できる言語」という定義を与えているし、他にも人によって定義はいろい
ろある。一行で役に立つプログラムが書けること、コマンドラインからプログ
ラムのファイルを渡して実行できること、などなど。
しかし筆者はあえて別の定義を使うことにする。なぜかというと、スクリプト 言語が「何」か、なんてことには興味がないからである。ある言語をスクリプ ト言語と呼ぶときの筆者の基準はただ一つ、その言語をスクリプト言語と呼ん でも文句を言われないかどうかだ。これを満たすために、筆者は以下のように 「スクリプト言語」の意味を定義する。
その言語の作者がスクリプト言語と呼ぶもの。
この定義なら絶対に間違いようはない。そしてRubyはこの点を確かに満たす。 だから筆者もRubyをスクリプト言語と呼ぶことにする。
ruby
はインタプリタである。それは事実だ。ではなぜインタプリタなのだろう。
例えばコンパイラではだめなのだろうか。それはきっとコンパイラよりもイン
タプリタのほうが良い……少なくともRubyにとっては良い、ことがあるからに
違いない。ではインタプリタのどういうところが良かったのだろうか。
それを考える前段階として、まずインタプリタとコンパイラの違うところを考 えてみよう。プログラムが実行される過程を理論的に比べてみるだけなら、イ ンタプリタ言語だろうとコンパイル言語だろうと違いはない。機械語にコンパ イルしたところでCPUにインタープリットしてもらって動くのだから、インタ プリタで動いていると言えないこともない。ではどこで違いが出るかと言えば、 それはもっと現実的なところ、つまり開発の過程である。
そういうことを言うとすぐに「インタプリタはコンパイルしなくていいので手 間が減って開発が楽」という決まり文句を出す人がいる。だがそれは正確では ないと筆者は考える。コンパイル言語だろうとなんだろうとコンパイルの過程 を開発者に見せないような工夫は可能である。実際にDelphiなんかはF5一発で 実行できるし、コンパイルに時間がかかると言ってもそれはコンパイルする プログラムが大きかったり、最適化していたりするのが原因だ。コンパイルし ていることそれ自体が悪いわけではない。
ではなぜインタプリタとコンパイラのイメージがこれほどまでに違うのだろう か。それはこれまでの言語開発者が自分の言語の特徴によって両者の実装を使 いわけてきたからだと思う。即ち、比較的小規模な、日常のツールとしての 言語を構築する場合はインタプリタ。多人数で開発を行う、巨大で確実性が要 求されるプログラムのための言語を構築する場合はコンパイラ。そうなったの は速度のせいもあるだろうし、言語の作りやすさの違いもあるだろう。
だから、「インタプリタだから」手軽だというのは大いなる神話だと筆者は思 う。言語がインタプリタだと使いやすいのではなく、使いやすい言語を作ろう と思うと自然とインタプリタに足が向くのである。
それはともあれ、ruby
がインタプリタであるというのは本書の行方を左右する
重要な事実であるのでここで一度強調しておく。それだから使いやすいのかど
うかなんて知らないが、とにかくruby
はインタプリタとして実現されているのだ。
根本的にインターフェイスがUNIX中心であるという問題はあるものの、それを
差し引いてもruby
は移植性が高いと言えるだろう。あまりに特殊なライブラリ
を要求することもないし、アセンブラでゴリゴリ書いているところもほとんど
ない。だから新しいプラットフォームにもわりと素直に持っていける。具体的
には以下のようなプラットフォームで動いている。
作者のまつもとさんのメインマシンはLinuxだそうで、Linuxならばどんなタイ ミングでコンパイルしてもまず大丈夫だ。
また基本的に(普通の)UNIX系ならば安定動作を期待できる。パッケージの追
従の早さも考えると、現時点ではruby
をいじるなら各種PC UNIXが最も有利な
環境だろう。
一方、問題が起こりやすいのはなんと言ってもWin32環境である。対象とする OSのモデルがまるきり違うため、マシンスタックやリンカの周辺で問題が起き やすい。もっとも最近はWindowsハッカー達の手によって随分対応が改善され てきた。筆者もWindows 2000とMeでネイティブ版を使っているが、一度動いて しまえば特に落ちやすいとかいうことはない。Windowsにおいて問題となるの はむしろ仕様面のギャップなのだと思う。
この他で興味がある人が多そうなOSと言えばMac OS(v9以前)と、Palmなどの ハンドヘルド系だろう。
ruby 1.2
のころまではMac OSにも対応していたのだが最近は開発が止まってし
まっておりコンパイルすら通らない。最大の原因はMac OSのコンパイル環境と
開発者が減ってしまったことである。ただしMac OS Xは中身がUNIXなの
でほとんど問題はない。
またPalmで動かないかという話は何度か出ているのだが、移植できたという話
は聞いていない。この場合はどう実装するかよりもstdio
をどうするかなど仕
様上の問題を解決するほうが難しいのではないだろうか。Psionには移植したと
いう話題が出ていた([ruby-list:36028])。
それから最近とみに騒がしいJavaや.NETのVMはどうだろう。これについては 実装とからめて話したいので終章で改めて話すことにする。
機能的にはガーベージコレクション、GCと言う。C言語で言えばmalloc()
しても
free()
しなくていいという機能である。使っていないメモリはシステムが勝手に
検出して解放してくれるのだ。これはあまりに便利な機能で、GCに慣れてしま
うともはや手動のメモリ管理なぞやる気にもならない。
最近の言語ではGCはたいてい標準装備になっているのでかなり普遍的な話題だ し、アルゴリズムにも工夫の余地がたくさんあって楽しい。
Rubyの変数には型がない。理由は、オブジェクト指向最強の武器の一つ、 ポリモーフィズム(多態性)が使いやすくなるからだろう。 もちろん型有りの言語でポリモーフィズムが使えないわけではない。 あくまで「使いやすくなる」だけだ。
またこの場合の使いやすさとは「お手軽」とほぼ同義語で、それは非常に重要 な場合もあるしどうでもいいこともある。しかし「お手軽簡単ラクチン」を目 指すならこれは間違いなく長所であり、Rubyはそれを目指している。
この項はすぐにはわからないと思うので少し説明する。 例えばC言語では次のようなプログラムは文法エラーになる。
result = if (cond) { process(val); } else { 0; }
なぜならCの文法上ではif
は文だからだ。しかし次のようには書ける。
result = cond ? process(val) : 0;
こう書けるのは条件演算子(a?b:c
)が文法上、式に入るからだ。
一方Rubyではif
が式なので次のように書いてもよい。
result = if cond then process(val) else nil end
大雑把に言うと、関数やメソッドの引数にできるものは式だと思っていい。
もちろん「ほとんどの文法要素が式」という言語は他にもいろいろある。例えば Lispはその最たるものだ。このあたりの特徴からなんとなく「RubyはLispに似 てる」と感じる人が多いようである。
Rubyにはイテレータがある。イテレータとは何か。いやその前にイテレータと いう言葉は最近嫌われているので別の言葉を使うべきかもしれない。だがい い言葉を思いつかないので当面イテレータと呼ぶことにする。
それでイテレータとは何か。高階の関数を知っているなら、とりあえずはそれ
と似たようなものだと思っておけばいい。Cで言えば関数ポインタを引数
に渡すやつである。C++で言えばSTLにあるIterator
の操作部分までをメソッド
に封入したものである。shやPerlを知っているのなら、独自に定義できる
for
文みたいなもんだと思って見てみるといい。
もっともあくまでここに挙げたのは全て「似たようなもの」であって、どれも Rubyのイテレータと似てはいるが、同じでは、全くない。いずれその時が来た らもう少し厳密な話をしよう。
Cで書いたプログラムなどいまどき珍しくもないが、特徴であることは間違い ない。少なくともHaskellやPL/Iで書いてあるわけではないので一般人にも 読める可能性が高い(本当にそうかどうかはこれから自分で確かめてほしい)。
それからC言語と言ってもruby
が対象としているのは基本的にK&R Cだ。
少し前まではK&R onlyの環境が、たくさんとは言わないが、それなりにあったからだ。
しかしさすがに最近はANSI Cが通らない環境はなくなってきており技術的には
ANSI Cに移っても特に問題はない。だが作者のまつもとさん個人の趣味もあっ
てまだK&Rスタイルを通している。
そんなわけで関数定義は全てK&Rスタイルだし、プロトタイプ宣言もあまり真面
目に書かれていない。gcc
でうっかり-Wall
を付けると大量に警告が出て
くるとか、
C++コンパイラでコンパイルするとプロトタイプが合わないと怒られてコンパ
イルできない……なんて話がポロポロとメーリングリストに流れている。
RubyのライブラリをCで書くことができ、Rubyを再コンパイルすることなく 実行時にロードできる。このようなライブラリを「Ruby拡張ライブラリ」 または単に「拡張ライブラリ」と言う。
単にCで書けるだけでなくRubyレベルとCレベルでのコードの表現の差が小さい のも大きな特徴である。Rubyで使える命令はほとんどそのままCでも使うこと ができる。例えば以下のように。
# メソッド呼び出し obj.method(arg) # Ruby rb_funcall(obj, rb_intern("method"), 1, arg); # C # ブロック呼び出し yield arg # Ruby rb_yield(arg); # C # 例外送出 raise ArgumentError, 'wrong number of arguments' # Ruby rb_raise(rb_eArgError, "wrong number of arguments"); # C # オブジェクトの生成 arr = Array.new # Ruby VALUE arr = rb_ary_new(); # C
拡張ライブラリを書くうえでは非常に楽をできていいし、現実に
このことが代えがたいruby
の長所にもなっている。しかしそのぶん
ruby
の実装にとっては非常に重い足枷となっており、随所にその
影響を見ることができる。特にGCやスレッドへの影響は顕著である。
Rubyにはスレッドがある。さすがに最近はスレッドを知らない人はほとんどい ないと思うのでスレッド自体に関する説明は省略する。以下はもう少し細かい 話だ。
ruby
のスレッドはオリジナルのユーザレベルスレッドである。この実装の
特徴は、仕様と実装、両方の移植性が非常に高いことである。なにしろDOS上で
さえスレッドが動き、どこでも同じ挙動で使えるのだ。この点をruby
の最大の
長所として挙げる人も多い。
しかしruby
スレッドは凄まじい移植性を実現した反面で速度をおもいきり犠牲
にしている。どのくらい遅いかというと、世の中に数あるユーザレベルスレッ
ドの実装の中でも一番遅いのではないか、というくらい遅い。これほどruby
の
実装の傾向を明確に表しているところもないだろう。
さて。ruby
の紹介も終わっていよいよソースコード読みに入ろうか、というと
ころだが、ちょっと待ってほしい。
ソースコードを読む、というのはプログラマならば誰しもやらなければいけな いことだが、その具体的な方法を教えてもらえることはあまりないのではない だろうか。どうしてだろう。プログラムが書けるなら読むのも当然できるとい うのだろうか。
しかし筆者には人の書いたプログラムを読むことがそんなに簡単なことだとは
思えない。プログラムを書くのと同じくらい、読むことにも技術や定石がある
はずだし、必要だと考える。そこでruby
を読んでいく前にもう少し一般的に、
ソースコードを読むにはどういう考えかたをすればいいのか、整理することに
しよう。
まずは原則について触れる。
「ソースコードを読むための極意」は『目的をもって読む』ことです。
これはRuby作者のまつもとさんの言だ。なるほど、この言葉には非常にうなず けるものがある。「カーネルくらいは読んどかなきゃいかんかなあ」と思って ソースコードを展開したり解説本を買ったりしてはみたものの、いったいどう していいのかわからないまま放ってしまった、という経験のある人は多いので はないだろうか。その一方で、「このツールのどこかにバグがある、とにかく これを速攻で直して動かさないと納期に間に合わない」……というときには他 人のプログラムだろうとなんだろうと瞬く間に直せてしまうこともあるのでは ないだろうか。
この二つのケースで違うのは、意識の持ちかたである。自分が何を知ろうと しているのかわからなければ「わかる」ことはありえない。だからまず自分が 何を知りたいのか、それを明確に言葉にすることが全ての第一歩である。
だがこれだけではもちろん「技術」たりえない。「技術」とは、意識すれば誰に でもできるものでなければならないからだ。続いて、この第一歩から最終的に 目的を達成するところまで敷衍する方法について延べる。
いま「ruby
全部を理解する」を最終目標に決めたとしよう。これでも「目的を
決めた」とは言えそうだが、しかし実際にソースコードを読む役に立たないこ
とは明らかである。具体的な作業には何にもつながっていないからだ。従って
まずはこの曖昧な目標を具体的なところまで引きずり下ろさなければならない。
どうすればいいだろうか。まず第一に、そのプログラムを書いた人間になった つもりで考えてみることだ。そのときにはプログラムを作るときの知識が流用 できる。例えば伝統的な「構造化」プログラムを読むとしたら、こちらも 構造化プログラムの手法に則って考えるようにする。即ち目的を徐々に徐々に分割 していく。あるいはGUIプログラムのようにイベントループに入ってグルグル するものならば、とりあえず適当にイベントループを眺めてからイベントハン ドラの役割を調べてみる。あるいはMVC(Model View Controler)のMをまず調 べてみる。
第二に解析の手法を意識することだ。誰しも自分なりの解析方法というのはそ れなりに持っていると思うが、それは経験と勘に頼って行われていることが多 い。どうしたらうまくソースコードを読めるのか、そのこと自体を考え、意識 することが非常に重要である。
ではそのような手法にはどんなものがあるだろうか。それを次に説明する。
ソースコードを読む手法は大雑把に言って静的な手法と動的な手法の二つに分 類できる。静的な手法とはプログラムを動かさずソースコードを読んだり解析 したりすること。動的な手法とはデバッガなどのツールを使って実際の動きを 見ることだ。
プログラムを調査するときはまず動的な解析から始めたほうがよい。なぜなら それは「事実」だからだ。静的な解析では現実にプログラムを動かしていない のでその結果は多かれ少なかれ「予想」になってしまう。真実を知りたいのな らばまず事実から始めるべきなのだ。
もちろん動的な解析の結果が本当に事実であるかどうかはわからない。デバッガがバ グっているかもしれないし、CPUが熱暴走しているかもしれない。自分が設定 した条件が間違っているかもしれない。しかし少なくとも静的解析よりは動的 な解析の結果のほうが事実に近いはずである。
これがなければ始まらない。そもそもそのプログラムがどういうものなのか、 どういう動作をすべきなのか、あらかじめ知っておく。
例えば実際にコードがどこを通ってどういうデータ構造を作るか、なんていう ことは頭の中で考えているよりも実際にプログラムを動かしてみてその結果を 見たほうが早い。それにはデバッガを使うのが簡単だ。
実行時のデータ構造を絵にして見られるとさらに嬉しいのだが、そういうツー
ルはなかなかない(特にフリーのものは少ない)。比較的単純な構造のスナッ
プショットくらいならテキストでさらっと書き出し
graphviz
\footnote{graphviz
……添付CD-ROMのdoc/graphviz.html
参照}の
ようなツールを使って絵にすることもできそうだが、汎用・リアルタイムを
目指すとかなり難しい。
コードがどの手続きを通っているか調査したければトレーサを使えばいい。
C言語なら
ctrace
\footnote{ctrace
……http://www.vicente.org/ctrace
}と
いうツールがある。
またシステムコールのトレースには
strace
\footnote{strace
……http://www.wi.leidenuniv.nl/~wichert/strace/
}、
truss
、ktrace
と言ったツールがある。
printf
デバッグという言葉があるが、この手法はデバッグでなくても役に立つ。
特定の変数の移り変わりなどはデバッガでチマチマ辿ってみるよりもprint文を
埋め込んで結果だけまとめて見るほうがわかりやすい。
例えば動作のわかりにくいところでパラメータやコードを少しだけ変えて動 かしてみる。そうすると当然動きが変わるから、コードがどういう意味なのか 類推できる。
言うまでもないが、オリジナルのバイナリは残しておいて 同じことを両方にやってみるべきである。
静的解析とはつまりソースコードの解析だ。そしてソースコードの解析とは名 前の調査である。ファイル名・関数名・変数名・型名・メンバ名など、プログ ラムは名前のかたまりだ。名前はプログラムを抽象化する最大の武器なのであ たりまえと言えばあたりまえだが、この点を意識して読むとかなり効率が違う。
またコーディングルールについてもあたりをつけておきたい。例えばCの関数
名ならextern
関数にはプリフィクスを使っていることが多く、関数の種類を見
分けるのに使える。またオブジェクト指向様式のプログラムだと関数の所属情
報がプリフィクスに入っていることがあり、貴重な情報になる。
(例:rb_str_length
)
内部構造を解説したドキュメントが入っていることもある。
特に「HACKING
」といった名前のファイルには注意だ。
どういう方針でディレクトリが分割されているのか見る。 そのプログラムがどういう作りになっているのか、 どういうパートがあるのか、概要を把握する。
ファイルの中に入っている関数(名)も合わせて見ながら、 どういう方針でファイルが分割されているのか見る。ファイル名は 有効期間が非常に長いコメントのようなものであり、注目すべきである。
さらに、ファイルの中にまたモジュールがある場合、モジュールを構成する関 数は近くにまとまっているはずだ。つまり関数の並び順からモジュール構成 を見付けることができる。
わかりにくい略語があればリストアップしておいて早めに調べる。 例えば「GC」と書いてあった場合、それがGarbage Collectionなのか それともGraphic Contextなのかで随分と話が違ってしまう。
プログラム関係の略語はたいてい単語の頭文字を取るとか、単語から母音を落とす、 という方法で作られる。特に対象プログラムの分野で有名な略語は問答無用で 使われるのであらかじめチェックしておこう。
データとコードが並んでいたら、まずデータ構造から調べるべきである。つま
りCならヘッダファイルから眺めるほうが、たぶんいい。そのときはファイル
名から想像力を最大限に働かせよう。例えば言語処理系でframe.h
というファ
イルがあったら恐らくスタックフレームの定義だ。
また構造体の型とメンバ名だけでも随分といろいろなことがわかる。例え
ば構造体の定義中に自分の型へのポインタでnext
というメンバがあればリンク
リストだろうと想像できる。同様に、parent
・children
・sibling
と言った要
素があれば十中八九ツリーだ。prev
ならスタックだろう。
関数同士の関係は名前の次に重要な情報だ。呼び出し関係を表現したものを 特に「コールグラフ」と言うが、これは非常に便利である。このへんは ツールを活用したい。
ツールはテキストベースで十分だが、図にしてくれれば文句無しだ。
ただそういう便利なものはなかなかない(特にフリーのものは少ない)。
筆者が本書のためにruby
を解析したときは、小さなコマンド言語と
パーサを適当にRubyで書き、graphviz
というツールに渡して半自動生成した。
動作を読んで、関数のやることを一言で説明できるようにする。関数関連図を 見ながらパートごとに読んでいくのがいい。
関数を読むときに重要なのは「何を読むか」ではなく「何を読まないか」であ る。どれだけコードを削るかで読みやすさが決まると言ってよい。具体的に何 を削ればいいか、というのは実際に見せてみないとわかりづらいので本文で解 説する。
それとコーディングスタイルが気にいらないときはindent
のようなツールを
使って変換してしまえばいい。
人間の身体というのは不思議なもので、できるだけ身体のいろんな場所を使い ながらやったことは記憶に残りやすい。パソコンのキーボードより原稿用紙の ほうがいい、という人が少なからずいるのは、単なる懐古趣味ではなくそうい うことも関係しているのではないかと思う。
そういうわけで単にモニタで読むというのは非常に身体に残りにくいので、 書き換えながら読む。そうするとわりと早く身体がコードに馴染んでくること が多い。気にくわない名前やコードがあったら書き換える。わかりづらい略語 は置換して省略しないようにしてしまえばよい。
ただし当然のことだが書き換えるときはオリジナルのソースは別に残しておき、 途中で辻褄が合わないと思ったら元のソースを見て確認すること。でないと自 分の単純ミスで何時間も悩む羽目になる。それに書き換えるのはあくまで馴染 むためであって書き換えること自体が目的ではないので熱中しすぎないように 注意してほしい。
プログラムにはたいてい変更個所の履歴を書いた文書が付いている。例えば
GNUのソフトウェアだと必ずChangeLog
というファイルがある。これは
「プログラムがそうなっている理由」を知るのには最高に役に立つ。
またCVSやSCCSのようなバージョン管理システムを使っていてしかもそれにア
クセスできる場合は、ChangeLog
以上に利用価値が高い。CVSを例に取ると、特
定の行を最後に変更した場所を表示するcvs annotate
、指定した版からの差分
を取るcvs diff
などが便利だ。
さらに、開発用のメーリングリストやニュースグループがある場合はその過去 ログを入手してすぐに検索できるようにしておく。変更の理由がズバリ載って いることが多いからだ。もちろんWeb上で検索できるならそれでもいい。
いろいろな目的のためにいろいろなツールがあるので一口には言えないが、筆
者が一つだけ選ぶとしたらglobal
をお勧めする。なんと言っても他の用途に応
用しやすい作りになっているところがポイントだ。例えば同梱されている
gctags
は本当はタグファイルを作るためのツールなのだが、
これを使ってファイルに含まれる関数名のリストを取ることもできる。
~/src/ruby % gctags class.c | awk '{print $1}' SPECIAL_SINGLETON SPECIAL_SINGLETON clone_method include_class_new ins_methods_i ins_methods_priv_i ins_methods_prot_i method_list : :
とは言えこれはあくまでも筆者のお勧めなので読者は自分の好きなツールを使っ てもらえばいい。ただその時は最低でも次の機能を備えているものを選ぶように すべきだ。
本書で解説しているruby
のバージョンは1.7の2002-09-12版である。ruby
はマ
イナーバージョンが偶数だと安定版で奇数だと開発版だから、1.7は開発版と
いうことになる。しかも9月12日は特に何かの区切りというわけではないの
で、該当バージョンの公式パッケージは配布されていない。従ってこの版を入
手するには本書添付のCD-ROMまたはサポートサイト
\footnote{本書のサポートサイト……http://i.loveruby.net/ja/rhg/
}
から入手するか、後述のCVSを使うしかない。
安定版の1.6でなく1.7にした理由は、1.7のほうが仕様・実装ともに整理され ていて扱いやすいことが一つ。次に、開発版先端のほうがCVSが使いやすい。 さらに、わりと近いうちに次の安定版の1.8が出そうな雰囲気になってきたこと。 そして最後に、最先端を見ていくほうが気分的に楽しい。
添付CD-ROMに解説対象の版のアーカイブを収録した。 CD-ROMのトップディレクトリに
ruby-rhg.tar.gz ruby-rhg.zip ruby-rhg.lzh
の三種類が置いてあるので、便利なものを選んで使ってほしい。
もちろん中身はどれも同じだ。例えばtar.gz
のアーカイブなら
次のように展開すればいいだろう。
~/src % mount /mnt/cdrom ~/src % gzip -dc /mnt/cdrom/ruby-rhg.tar.gz | tar xf - ~/src % umount /mnt/cdrom
ソースコードを見るだけでも「読む」ことはできる。しかしプログラムを知る ためには実際にそれを使い、改造し、実験してみることが必要だ。実験をする なら見ているソースコードと同じものを使わなければ意味がないので、当然自 分でコンパイルすることになる。
そこでここからはコンパイルの方法を説明する。まずはUNIX系OSの場合から話 を始めよう。Windows上ではいろいろあるので次の項でまとめて話す。ただし CygwinはWindows上ではあるがほとんどUNIXなので、こちらの話を読んでほし い。
さて、UNIX系OSなら普通Cコンパイラは標準装備なので、次の手順でやれば
たいがい通ってしまう。
~/src/ruby
にソースコードが展開されているとする。
~/src/ruby % ./configure ~/src/ruby % make ~/src/ruby % su ~/src/ruby # make install
以下、いくつか注意すべき点を述べる。
Cygwin、UX/4800など一部のプラットフォームではconfigure
の段階で
--enable-shared
オプションを付けないとリンクに失敗する。
--enable-shared
というのはruby
のほとんどを共有ライブラリ
(libruby.so
)としてコマンドの外に出すオプションである。
~/src/ruby % ./configure --enable-shared
ビルドに関するより詳しいチュートリアルを添付CD-ROMの
doc/build.html
に入れたので、それを読みながらやってみてほしい。
Windowsでのビルドとなるとどうも話がややこしくなる。 問題の根源はビルド環境が複数あることだ。
まずCygwin環境はWindowsよりもUNIXに条件が近いのでUNIX系のビルド手順に 従えばいい。
Visual C++でコンパイルする場合はVisual C++ 5.0以上が 必要である。バージョン6か.NETならまず問題ないだろう。
MinGW、Minimalist GNU for WindowsというのはGNUのコンパイル環境(ようするに
gcc
とbinutils
)をWindowsに移植したものだ。CygwinがUNIX環境全体を移植し
たのに対し、MinGWはあくまでコンパイルのためのツールだけを移植してある。
またMinGWでコンパイルしたプログラムは実行時に特別なDLLを必要としない。
つまりMinGWでコンパイルしたruby
はVisual C++版と全く同じに扱える。
また個人利用ならばBorland C++ Compilerのバージョン5.5がBorlandのサイト
\footnote{Borlandのサイト:http://www.borland.co.jp
}
から無料でダウンロードできる。ruby
がサポートしたのがかなり最近なのが
多少不安だが、本書出版前に行ったビルドテストでは特に問題は出ていない。
さて以上四つの環境のうちどれを選べばいいだろうか。まず基本的には Visual C++版が最も問題が出にくいのでそれをお勧めする。UNIXの経験がある ならCygwin一式入れてCygwinを使うのもよい。UNIXの経験がなくVisual C++も 持っていない場合はMinGWを使うのがいいだろう。
以下ではVisual C++とMinGWでのビルド方法について説明するが、
あくまで概要だけに留めた。より細かい解説とBorland C++ Compilerでの
ビルド方法は添付CD-ROMのdoc/build.html
に収録したので適宜そちらも
参照してほしい。
Visual C++と言っても普通はIDEは使わず、DOSプロンプトからビルドする。そ のときはまずVisual C++自体を動かせるようにするために環境変数の初期化を しなければいけない。Visual C++にそのためのバッチファイルが付いてくるの で、まずはそれを実行しよう。
C:\> cd "\Program Files\Microsoft Visual Studio .NET\Vc7\bin" C:\Program Files\Microsoft Visual Studio .NET\Vc7\bin> vcvars32
これはVisual C++.NETの場合だ。バージョン6なら以下の場所にある。
C:\Program Files\Microsoft Visual Studio\VC98\bin\
vcvars32
を実行したらその後はruby
のソースツリーの中のフォルダ
win32\
に移動してビルドすればいい。以下、ソースツリーはC:\src
に
あるとしよう。
C:\> cd src\ruby C:\src\ruby> cd win32 C:\src\ruby\win32> configure C:\src\ruby\win32> nmake C:\src\ruby\win32> nmake DESTDIR="C:\Program Files\ruby" install
これでC:\Program Files\ruby\bin\
にruby
コマンドが、
C:\Program Files\ruby\lib\
以下にRubyのライブラリが、
それぞれインストールされる。ruby
はレジストリなどは一切使わない
ので、アンインストールするときはC:\ruby
以下を消せばよい。
前述のようにMinGWはコンパイル環境のみなので、一般的なUNIXのツール、
例えばsed
やsh
が存在しない。しかしruby
のビルドにはそれが必要なので
それをどこかから調達しなければならない。それにはまた二つの方法が
存在する。CygwinとMSYS(Minimal SYStem)である。
だがMSYSのほうは本書の出版前に行ったビルド大会でトラブルが続出してしまっ たのでお勧めできない。対照的にCygwinを使う方法だと非常に素直に通った。 従って本書ではCygwinを使う方法を説明する。
まずCygwinのsetup.exe
でMinGWと開発ツール一式を入れておく。
CygwinとMinGWは添付CD-ROMにも収録した
\footnote{CygwinとMinGW……添付CD-ROMのdoc/win.html
を参照}。
あとはCygwinのbash
プロンプトから以下のように打てばよい。
~/src/ruby % ./configure --with-gcc='gcc -mno-cygwin' \ --enable-shared i386-mingw32 ~/src/ruby % make ~/src/ruby % make install
これだけだ。ここではconfigure
の行を折り返しているが実際には一行に
入れる。またバックスラッシュを入れる必要はない。インストール先は
コンパイルしたドライブの\usr\local\
以下になる。このあたりはかなり
ややこしいことが起こっていて説明が長くなるので、
添付CD-ROMのdoc/build.html
で徹底的に説明しよう。
ここまでがREADME
的な解説である。今度はこれまでやったことが具体的に
何をしているのか、つっこんで見ていこう。ただしここの話は部分的に
かなり高度な知識が必要になる。わからない場合はいきなり次の節に
飛んでほしい。本書全体を読んでから戻ってきてもらえばわかるように
なっているはずだ。
さて、どのプラットフォームでもruby
のビルドは三段階に分かれている。
即ちconfigure
、make
、make install
だ。make install
はいいとして、
configure
とmake
の段階について解説しよう。
configure
まずconfigure
である。この中身はシェルスクリプトになっており、これ
でシステムのパラメータを検出する。例えば「ヘッダファイルsetjmp.h
が存
在するか」とか、「alloca()
は使えるか」ということを調べてくれる。調べ
る方法は意外と単純である。
チェック対象 | 方法 | ||
コマンド | 実際に実行してみて$? を見る | ||
ヘッダファイル | if [ -f $includedir/stdio.h ] | ||
関数 | 小さいプログラムをコンパイルしてみてリンクが成功するかどうか試す |
違いを検出したら、それをどうにかしてこちらに伝えてもらわないと
いけない。その方法は、まずMakefile
が一つ。パラメータを@PARAM@
の
ように埋め込んだMakefile.in
を置いておくと、それを実際の値に変換
したMakefile
を生成してくれる。例えば次のように。
Makefile.in: CFLAGS = @CFLAGS@ ↓ Makefile : CFLAGS = -g -O2
もう一つ、関数やヘッダファイルがあるかどうかといった情報を
ヘッダファイルにして出力してくれる。出力ファイルの名前は変更
できるのでプログラムによって違うが、ruby
ではconfig.h
である。
configure
を実行した後にこのファイルができていることを確かめてほしい。
中身はこんな感じだ。
: : #define HAVE_SYS_STAT_H 1 #define HAVE_STDLIB_H 1 #define HAVE_STRING_H 1 #define HAVE_MEMORY_H 1 #define HAVE_STRINGS_H 1 #define HAVE_INTTYPES_H 1 #define HAVE_STDINT_H 1 #define HAVE_UNISTD_H 1 #define _FILE_OFFSET_BITS 64 #define HAVE_LONG_LONG 1 #define HAVE_OFF_T 1 #define SIZEOF_INT 4 #define SIZEOF_SHORT 2 : :
どれも意味はわかりやすい。HAVE_xxxx_H
ならヘッダファイルが存在するか
どうかのチェックだろうし、SIZEOF_SHORT
ならCのshort
型が何バイトかを
示しているに違いない。同じくSIZEOF_INT
ならint
のバイト長だし、
HAVE_OFF_T
はoffset_t
型が定義されているかを示している。これに限らず
configure
では「ある/ない」の情報はHAVE_xxxx
というマクロで定義される
(する)。
以上のことからわかるように、configure
は違いを検出してはくれるが、
その違いを自動的に吸収してくれるわけではない。ここで定義された値を
使って差を埋めるのはあくまで各プログラマの仕事である。例えば次の
ように。
24 #ifdef HAVE_STDLIB_H 25 # include <stdlib.h> 26 #endif (ruby.h)
autoconf
configure
はruby
の専用ツールではない。関数があるか、ヘッダファイルが
あるか……といったテストには明らかに規則性があるのだから、プログラムを
書く人がみんなでそれぞれに別のものを書くのは無駄だ。
そこで登場するのがautoconf
というツールである。configure.in
とか
configure.ac
というファイルに「こういうチェックがしたいんだ」と
書いておき、それをautoconf
で処理すると適切なconfigure
を作ってくれる。
configure.in
の.in
はinput
の略だろう。Makefile
とMakefile.in
の関係と
同じである。.ac
のほうはもちろんAutoConf
の略だ。
ここまでを絵にすると図1のようになる。
図1: Makefile
ができるまで
もっと詳しいことが知りたい読者には『GNU Autoconf/Automake/Libtool』 \footnote{『GNU Autoconf/Automake/Libtool』Gary V.Vaughan, Ben Elliston, Tom Tromey, Ian Lance Taylor共著、でびあんぐる監訳、オーム社} をお勧めする。
ところでruby
のconfigure
は言ったとおりautoconf
を使って生成してい
るのだが、世の中にあるconfigure
が必ずしもautoconf
で生成されている
とは限らない。手書きだったり、別の自動生成ツールを使っていたりすること
もある。なんにせよ、最終的にMakefile
やconfig.h
やその他いろいろがで
きればそれでいいのだ。
make
第二段階、make
では何をするのだろうか。もちろんruby
のソースコードを
コンパイルするわけだが、make
の出力を見ているとどうもその他にいろいろ
やっているように見える。その過程を簡単に説明しておこう。
ruby
自体を構成するソースコードをコンパイルする。ruby
の主要部分を集めたスタティックライブラリlibruby.a
を作る。ruby
「miniruby
」を作る。--enable-shared
のときは共有ライブラリlibruby.so
を作る。miniruby
を使って拡張ライブラリ(ext/
以下)をコンパイルする。ruby
を生成する。
miniruby
とruby
の生成が分かれているのには二つ理由がある。一つめは拡張ラ
イブラリのコンパイルにruby
が必要になることだ。--enable-shared
の場合は
ruby
自身がダイナミックリンクされるので、ライブラリのロードパスの関係で
すぐに動かせないかもしれない。そこでスタティックリンクしたminiruby
を作り、
ビルドの過程ではそちらを使うようにする。
二つめの理由は、共有ライブラリが使えないプラットフォームでは拡張ライブ
ラリをruby
自体にスタティックリンクしてしまう場合があるということだ。そ
の場合、ruby
は拡張ライブラリを全てコンパイルしてからでないと作れないが、
拡張ライブラリはruby
がないとコンパイルできない。そのジレンマを解消する
ためにminiruby
を使うのである。
本書の添付CD-ROMに入っているruby
のアーカイブにしても公式のリリースパッ
ケージにしても、それはruby
という、変化しつづているプログラムのほんの一
瞬の姿をとらえたスナップショットにすぎない。ruby
がどう変わってきたか、
どうしてそうだったのか、ということはここには記述されていない。では過去
も含めた全体を見るにはどうしたらいいだろうか。CVSを使えばそれができる。
CVSを一言で言うとエディタのundoリストである。 ソースコードをCVSの管理下に入れておけばいつでも昔の姿に戻せるし、誰が、 どこを、いつ、どう変えたのかすぐにわかる。一般にそういうことをしてくれ るプログラムのことをソースコード管理システムと言うが、オープンソースの 世界で一番有名なソースコード管理システムがCVSである。
ruby
もやはりCVSで管理されているのでCVSの仕組みと使いかたについて少し説
明しよう。まずCVSの最重要概念はレポジトリとワーキングコピーである。
CVSはエディタのundoリストのようなものと言ったが、そのためには歴代の変更の
記録を
どこかに残しておかないといけない。それを全部まとめて保存しておく場所が
「CVSレポジトリ」である。
ぶっちゃけて言うと、過去のソースコードを全部集めてあるのがレポジトリで ある。もちろんそれはあくまで概念であって、実際には容量を節約するために、 最新の姿一つと、そこに至るまでの変更差分(ようするにパッチ)の形で集積 されている。なんにしてもファイルの過去の姿をどの時点だろうと取り出せる ようになっていればそれでいいのだ。
一方、レポジトリからある一点を選んでファイルを取り出したものが 「ワーキングコピー」だ。レポジトリは一つだけだがワーキングコピーは いくつあってもいい(図2)。
図2: レポジトリとワーキングコピー
自分がソースコードを変更したいときはまずワーキングコピーを取り出して、 それをエディタなどで編集してからレポジトリに「戻す」。するとレポジトリ に変更が記録される。レポジトリからワーキングコピーを取り出すことを 「チェックアウト(checkout)」、戻すことを「チェックイン (checkin)」 または「コミット(commit)」と言う(図3)。チェックインするとレ ポジトリに変更が記録されて、いつでもそれを取り出せるようになる。
図3: チェックインとチェックアウト
そしてCVS最大の特徴はCVSレポジトリにネットワーク越しにアクセスできると いうところだ。つまりレポジトリを保持するサーバが一つあればインターネッ ト越しに誰でもどこからでもチェックアウト・チェックインすることができる。 ただし普通はチェックインにはアクセス制限がかかっているので無制限 にできるというわけではない。
レポジトリから特定の版を取り出すにはどうしたらいいだろうか。一つには時 刻で指定する方法がある。「この当時の最新版をくれ」と要求するとそれを選 んでくれるわけだ。しかし実際には時刻で指定することはあまりない。普通は 「リビジョン(revision)」というものを使う。
「リビジョン」は「バージョン」とほとんど同じ意味である。ただ普通はプロ ジェクト自体に「バージョン」が付いているので、バージョンという言葉を使 うと紛らわしい。そこでもうちょっと細かい単位を意図してリビジョンという 言葉を使う。
CVSでは、レポジトリに入れたばかりのファイルはリビジョン1.1である。 チェックアウトして、変更して、チェックインするとリビジョン1.2になる。 その次は1.3になる。その次は1.4になる。
以上をふまえてごくごく簡単にCVSの使いかたを話す。まずcvs
コマンドがな
いとどうにもならないのでインストールしておいてほしい。添付CD-ROMにも
cvs
のソースコードを収録した
\footnote{cvs
:archives/cvs-1.11.2.tar.gz
}。
cvs
のインストールの方法はあまりにも本筋から外れるのでここでは書かな
い。
インストールしたら試しにruby
のソースコードをチェックアウトしてみよう。
インターネットに接続中に次のように打つ。
% cvs -d :pserver:anonymous@cvs.ruby-lang.org:/src login CVS Password: anonymous % cvs -d :pserver:anonymous@cvs.ruby-lang.org:/src checkout ruby
何もオプションを付けないと自動的に最新版がチェックアウトされるので、
ruby/
以下にruby
の真の最新版が現れているはずだ。
また、とある日の版を取り出すにはcvs checkout
に-D
オプションをつけれ
ばいい。次のように打てば本書が解説しているバージョンのワーキングコピー
が取り出せる。
% cvs -d :pserver:anonymous@cvs.ruby-lang.org:/src checkout -D2002-09-12 ruby
このとき、オプションは必ずcheckout
の直後に書かないといけないことに注
意。先に「ruby
」を書いてしまうと「モジュールがない」という変なエラー
になる。
ちなみにこの例のようなanonymousアクセスだとチェックインはできないようになっている。
チェックインの練習をするには適当に(ローカルの)レポジトリを作って
Hello, World!プログラムでも入れてみるのがいいだろう。具体的な入れかた
はここには書かない。cvs
に付いてくるマニュアルが結構親切だ。日本語の書
籍ならオーム社の『CVSによるオープンソース開発』
\footnote{『CVSによるオープンソース開発』Karl Fogel, Moshe Bar共著、竹内利佳訳、オーム社}
をお勧めする。
ruby
の構成
さてそろそろソースコードを見ていこうと思うのだが、まず最初にしなければ
ならないことはなんだろうか。それはディレクトリ構造を眺めることである。
たいていの場合ディレクトリ構造すなわちソースツリーはそのままプログラム
のモジュール構造を示している。いきなりgrep
でmain()
を探して頭から処理順
に読んでいく、なんていうのは賢くない。もちろんmain()
を探すのも大切だが、
まずはのんびりとls
したりhead
したりして全体の様子をつかもう。
以下はCVSレポジトリからチェックアウトした直後の トップディレクトリの様子だ。 スラッシュで終わっているのはサブディレクトリである。
COPYING compar.c gc.c numeric.c sample/ COPYING.ja config.guess hash.c object.c signal.c CVS/ config.sub inits.c pack.c sprintf.c ChangeLog configure.in install-sh parse.y st.c GPL cygwin/ instruby.rb prec.c st.h LEGAL defines.h intern.h process.c string.c LGPL dir.c io.c random.c struct.c MANIFEST djgpp/ keywords range.c time.c Makefile.in dln.c lex.c re.c util.c README dln.h lib/ re.h util.h README.EXT dmyext.c main.c regex.c variable.c README.EXT.ja doc/ marshal.c regex.h version.c README.ja enum.c math.c ruby.1 version.h ToDo env.h misc/ ruby.c vms/ array.c error.c missing/ ruby.h win32/ bcc32/ eval.c missing.h rubyio.h x68/ bignum.c ext/ mkconfig.rb rubysig.h class.c file.c node.h rubytest.rb
最近はプログラム自体が大きくなってきてサブディレクトリが細かく分割され
ているソフトウェアも多いが、ruby
はかなり長いことトップディレクトリ
一筋である。あまりにファイル数が多いと困るが、この程度なら慣れればな
んでもない。
トップレベルのファイルは六つに分類できる。即ち
ruby
自身のソースコードruby
ビルド用のツールである。ソースコードとビルドツールが重要なのは当然として、その他に 我々の役に立ちそうなものを挙げておこう。
ChangeLog
ruby
への変更の記録。変更の理由を調べるうえでは非常に重要。
README.EXT README.EXT.ja
拡張ライブラリの作成方法が書いてあるのだが、その一環として
ruby
自身の実装に関することも書いてある。
ここからはruby
自身のソースコードについてさらに細かく分割していく。
主要なファイルについてはREADME.EXT
に分類が書いてあったので
それに従う。記載がないものは筆者が分類した。
class.c | クラス関連API | ||
error.c | 例外関連API | ||
eval.c | 評価器 | ||
gc.c | ガーベージコレクタ | ||
lex.c | 予約語テーブル | ||
object.c | オブジェクトシステム | ||
parse.y | パーサ | ||
variable.c | 定数、グローバル変数、クラス変数 | ||
ruby.h | ruby の主要マクロとプロトタイプ | ||
intern.h | ruby のC APIのプロトタイプ。intern はinternalの略だと思われるが、ここに載っている関数を拡張ライブラリで使うのは別に構わない。 | ||
rubysig.h | シグナル関係のマクロを収めたヘッダファイル | ||
node.h | 構文木ノード関連の定義 | ||
env.h | 評価器のコンテキストを表現する構造体の定義 |
ruby
インタプリタのコアを構成する部分。本書が解説するのは
ここのファイルがほとんどである。ruby
全体のファイル数と比べれば
非常に少ないが、バイトベースでは全体の50%近くを占める。
特にeval.c
は200Kバイト、parse.y
が100Kバイトと大きい。
dln.c | 動的ローダ | ||
regex.c | 正規表現エンジン | ||
st.c | ハッシュテーブル | ||
util.c | 基数変換やソートなどのライブラリ |
ruby
にとってのユーティリティという意味。ただしユーティリティという
言葉からは想像できないほど大きいものもある。例えばregex.c
は120Kバイトだ。
ruby
コマンドの実装dmyext.c | 拡張ライブラリ初期化ルーチンのダミー(DumMY EXTention) | ||
inits.c | コアとライブラリの初期化ルーチンのエントリポイント | ||
main.c | コマンドのエントリポイント(libruby には不要) | ||
ruby.c | ruby コマンドの主要部分(libruby にも必要) | ||
version.c | ruby のバージョン |
コマンドラインでruby
と打って実行するときのruby
コマンドの実装。コマンドライン
オプションの解釈などを行っている部分だ。ruby
コマンド以外にruby
コアを利
用するコマンドとしてはmod_ruby
やvim
が挙げられる。これらのコマンドは
ライブラリlibruby
(.a
/.so
/.dll
など)とリンクして動作する。
array.c | class Array | ||
bignum.c | class Bignum | ||
compar.c | module Comparable | ||
dir.c | class Dir | ||
enum.c | module Enumerable | ||
file.c | class File | ||
hash.c | class Hash (実体はst.c ) | ||
io.c | class IO | ||
marshal.c | module Marshal | ||
math.c | module Math | ||
numeric.c | class Numeric 、Integer 、Fixnum 、Float | ||
pack.c | Array#pack 、String#unpack | ||
prec.c | module Precision | ||
process.c | module Process | ||
random.c | Kernel#srand() 、rand() | ||
range.c | class Range | ||
re.c | class Regexp (実体はregex.c ) | ||
signal.c | module Signal | ||
sprintf.c | ruby 専用のsprintf() | ||
string.c | class String | ||
struct.c | class Struct | ||
time.c | class Time |
Rubyのクラスライブラリの実装。ここにあるものは基本的に通常の Ruby拡張ライブラリと全く同じ方法で実装されている。つまりこの ライブラリが拡張ライブラリの書きかたの例にもなっているということだ。
bcc32/ | Borland C++(Win32) | ||
beos/ | BeOS | ||
cygwin/ | Cygwin(Win32でのUNIXエミュレーションレイヤー) | ||
djgpp/ | djgpp(DOS用のフリーな開発環境) | ||
vms/ | VMS(かつてDECがリリースしていたOS) | ||
win32/ | Visual C++(Win32) | ||
x68/ | Sharp X680x0系(OSはHuman68k) |
各プラットフォーム特有のコードが入っている。
missing/
各種プラットフォームにない関数を補うためのファイル。
主にlibc
の関数が多い。
さて、以上四つのグループのうちコアはさらに大きく三つに分けられる。 一つめはRubyのオブジェクト世界を作りだす「オブジェクト空間(object space)」。 二つめはRubyプログラム(テキスト)を内部形式に変換する「パーサ(parser)」。 三つめはRubyプログラムを駆動する「評価器(evaluator)」。 パーサも評価器もオブジェクト空間の上に成立し、 パーサがプログラムを内部形式に変換し、 評価器がプログラムを駆動する。 順番に解説していこう。
一つめのオブジェクト空間。これは非常に、理解しやすい。なぜならこれが扱 うものは基本的にメモリ上のモノが全てであり、関数を使って直接表示したり 操作したりすることができるからだ。従って本書ではまずここから解説を 始める。第2章から 第7章までが第一部である。
二つめのパーサ。これは説明が必要だろう。
ruby
コマンドはRuby言語のインタプリタである。つまり起動時にテキストの入
力を解析し、それに従って実行する。だからruby
はテキストとして書かれたプ
ログラムの意味を解釈できなければいけないのだが、不幸にしてテキストとい
うのはコンピュータにとっては非常に理解しづらいものである。コンピュータ
にとってはテキストファイルはあくまでバイト列であって、それ以上ではない。
そこからテキストの意味を読みとるには何か特別な仕掛けが必要になる。そ
の仕掛けがパーサだ。このパーサを通すことでRubyプログラム(であるテキス
ト)はruby
専用の、プログラムから扱いやすい内部表現に変換される。
その内部表現とは具体的には「構文木」というものだ。構文木はプログラムを
ツリー構造で表現したもので、例えばif
文ならば図4のように
表現される。
図4: if
文と、それに対応する構文木
パーサの解説は第二部『構文解析』で行う。
第二部は第10章から第12章までだ。
対象となるファイルはparse.y
だけである。
オブジェクトは実際に触ることができるのでわかりやすい。パーサにしてもやっ ていること自体はようするにデータ形式の変換なんだから、まあわかる。しか し三つめの評価器、こいつはつかみどころが全くない。
評価器がやるのは構文木に従ってプログラムを「実行」していくことだ。と言
うと簡単そうに見えるのだが、では「実行する」とはどういうことか、ちゃん
と考えるとこれが結構難しい。if
文を実行するとはどういうことだろうか。
while
文を実行するとはどういうことだろうか。ローカル変数に代入するとは
どういうことだろうか。メソッドを呼ぶとはどういうことだろうか。その
全てにキチンキチンと答えを出していかなければ評価器はわからないのだ。
本書では第三部『評価』で評価器を扱う。対象ファイルはeval.c
だ。
「評価器」は英語でevaluatorと言うので、それを省略してeval
である。
さて、ruby
の作りについて簡単に説明してきたが、プログラムの動作なんてい
くら概念を説明してもわかりにくいものだ。次の章ではまず実際にruby
を使う
ことから始めるとしよう。
御意見・御感想・誤殖の指摘などは 青木峰郎 <aamine@loveruby.net> までお願いします。
『Rubyソースコード完全解説』 はインプレスダイレクトで御予約・御購入いただけます (書籍紹介ページへ飛びます)。
Copyright (c) 2002-2004 Minero Aoki, All rights reserved.