序章 導入

Rubyの特徴

読者の中には既にRubyに慣れ親しんでいる人もいるだろうが、そうでない人も たくさんいるだろう(そうであってほしい)。まずはそのような人達のために Rubyの特徴をおおざっぱにまとめる。

以下、言語仕様としてのRuby言語を大文字で「Ruby」、その実装としての rubyコマンドを小文字で「ruby」と書き分けて示す。

開発形態

Rubyは、まつもとゆきひろ氏個人の手によって作成されている言語である。 CやJavaやSchemeのように標準があるわけではない。その仕様はrubyという 実装によって示されるだけであり、しかも常に変化している。良くも悪くも 自由なのだ。

またrubyがフリーソフトウェアである……つまりソースコードが公開されていること、 しかも無料で配布されていること、の二点は書いておかないといけないだろう。 そのような条件だからこそ本書のような試みも成立するわけだ。

正確なライセンスは本体に含まれるREADMELEGALを読んでもらうとして、 とりあえずは以下のようなことはできるということを覚えておいて もらえばよい。

いずれの場合も特別な許諾や料金は必要ない。

なお本書はオリジナルのrubyを読むのが目的なので特に断らない限り変更なし のソースである。ただし空白・改行・コメントの追加削除だけは断りなしに行っ た。

保守的である

Rubyはとても保守的な言語である。いろいろな言語で検証され使い古されたも のばかりを選んで搭載しており、新奇な機能や実験的な仕様はあまり付いてい ない。だからRubyはどちらかというと実用重視のプログラマに受ける傾向があ り、SchemeやHaskellラヴな根っからのハッカーには、少なくともちょっと見 には、受けが良くないようだ。

ライブラリもそうだ。新しい機能には省略無しのわかりやすい名前が付いてい るのだが、CやPerlのライブラリにある名前はそのまま使われている。例えば printfgetpwentsubtrなど。

また実装においても保守的だ。速度のためにアセンブラを使ったりすることは なく、スピードと移植性が相反するときは常に移植性の高い方法が使われる。

オブジェクト指向言語である

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言語で書いてある

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/}、 trussktraceと言ったツールがある。

printしまくる

printfデバッグという言葉があるが、この手法はデバッグでなくても役に立つ。 特定の変数の移り変わりなどはデバッガでチマチマ辿ってみるよりもprint文を 埋め込んで結果だけまとめて見るほうがわかりやすい。

書き換えて動かす

例えば動作のわかりにくいところでパラメータやコードを少しだけ変えて動 かしてみる。そうすると当然動きが変わるから、コードがどういう意味なのか 類推できる。

言うまでもないが、オリジナルのバイナリは残しておいて 同じことを両方にやってみるべきである。

静的な解析

名前の大切さ

静的解析とはつまりソースコードの解析だ。そしてソースコードの解析とは名 前の調査である。ファイル名・関数名・変数名・型名・メンバ名など、プログ ラムは名前のかたまりだ。名前はプログラムを抽象化する最大の武器なのであ たりまえと言えばあたりまえだが、この点を意識して読むとかなり効率が違う。

またコーディングルールについてもあたりをつけておきたい。例えばCの関数 名ならextern関数にはプリフィクスを使っていることが多く、関数の種類を見 分けるのに使える。またオブジェクト指向様式のプログラムだと関数の所属情 報がプリフィクスに入っていることがあり、貴重な情報になる。 (例:rb_str_length

ドキュメントを読む

内部構造を解説したドキュメントが入っていることもある。 特に「HACKING」といった名前のファイルには注意だ。

ディレクトリ構造を読む

どういう方針でディレクトリが分割されているのか見る。 そのプログラムがどういう作りになっているのか、 どういうパートがあるのか、概要を把握する。

ファイル構成を読む

ファイルの中に入っている関数(名)も合わせて見ながら、 どういう方針でファイルが分割されているのか見る。ファイル名は 有効期間が非常に長いコメントのようなものであり、注目すべきである。

さらに、ファイルの中にまたモジュールがある場合、モジュールを構成する関 数は近くにまとまっているはずだ。つまり関数の並び順からモジュール構成 を見付けることができる。

略語の調査

わかりにくい略語があればリストアップしておいて早めに調べる。 例えば「GC」と書いてあった場合、それがGarbage Collectionなのか それともGraphic Contextなのかで随分と話が違ってしまう。

プログラム関係の略語はたいてい単語の頭文字を取るとか、単語から母音を落とす、 という方法で作られる。特に対象プログラムの分野で有名な略語は問答無用で 使われるのであらかじめチェックしておこう。

データ構造を知る

データとコードが並んでいたら、まずデータ構造から調べるべきである。つま りCならヘッダファイルから眺めるほうが、たぶんいい。そのときはファイル 名から想像力を最大限に働かせよう。例えば言語処理系でframe.hというファ イルがあったら恐らくスタックフレームの定義だ。

また構造体の型とメンバ名だけでも随分といろいろなことがわかる。例え ば構造体の定義中に自分の型へのポインタでnextというメンバがあればリンク リストだろうと想像できる。同様に、parentchildrensiblingと言った要 素があれば十中八九ツリーだ。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でのビルド

さて、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でのビルド

Windowsでのビルドとなるとどうも話がややこしくなる。 問題の根源はビルド環境が複数あることだ。

まずCygwin環境はWindowsよりもUNIXに条件が近いのでUNIX系のビルド手順に 従えばいい。

Visual C++でコンパイルする場合はVisual C++ 5.0以上が 必要である。バージョン6か.NETならまず問題ないだろう。

MinGW、Minimalist GNU for WindowsというのはGNUのコンパイル環境(ようするに gccbinutils)を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++

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

前述のようにMinGWはコンパイル環境のみなので、一般的なUNIXのツール、 例えばsedshが存在しない。しかし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のビルドは三段階に分かれている。 即ちconfiguremakemake installだ。make installはいいとして、 configuremakeの段階について解説しよう。

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を実行した後にこのファイルができていることを確かめてほしい。 中身はこんな感じだ。

config.h

         :
         :
#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_Toffset_t型が定義されているかを示している。これに限らず configureでは「ある/ない」の情報はHAVE_xxxxというマクロで定義される (する)。

以上のことからわかるように、configureは違いを検出してはくれるが、 その違いを自動的に吸収してくれるわけではない。ここで定義された値を 使って差を埋めるのはあくまで各プログラマの仕事である。例えば次の ように。

HAVE_マクロの典型的な使いかた

  24  #ifdef HAVE_STDLIB_H
  25  # include <stdlib.h>
  26  #endif

(ruby.h)

autoconf

configurerubyの専用ツールではない。関数があるか、ヘッダファイルが あるか……といったテストには明らかに規則性があるのだから、プログラムを 書く人がみんなでそれぞれに別のものを書くのは無駄だ。

そこで登場するのがautoconfというツールである。configure.inとか configure.acというファイルに「こういうチェックがしたいんだ」と 書いておき、それをautoconfで処理すると適切なconfigureを作ってくれる。 configure.in.ininputの略だろう。MakefileMakefile.inの関係と 同じである。.acのほうはもちろんAutoConfの略だ。

ここまでを絵にすると図1のようになる。

(build)
図1: Makefileができるまで

もっと詳しいことが知りたい読者には『GNU Autoconf/Automake/Libtool』 \footnote{『GNU Autoconf/Automake/Libtool』Gary V.Vaughan, Ben Elliston, Tom Tromey, Ian Lance Taylor共著、でびあんぐる監訳、オーム社} をお勧めする。

ところでrubyconfigureは言ったとおりautoconfを使って生成してい るのだが、世の中にあるconfigureが必ずしもautoconfで生成されている とは限らない。手書きだったり、別の自動生成ツールを使っていたりすること もある。なんにせよ、最終的にMakefileconfig.hやその他いろいろがで きればそれでいいのだ。

make

第二段階、makeでは何をするのだろうか。もちろんrubyのソースコードを コンパイルするわけだが、makeの出力を見ているとどうもその他にいろいろ やっているように見える。その過程を簡単に説明しておこう。

  1. ruby自体を構成するソースコードをコンパイルする。
  2. rubyの主要部分を集めたスタティックライブラリlibruby.aを作る。
  3. 常にスタティックリンクされるrubyminiruby」を作る。
  4. --enable-sharedのときは共有ライブラリlibruby.soを作る。
  5. minirubyを使って拡張ライブラリ(ext/以下)をコンパイルする。
  6. 最後に、本物のrubyを生成する。

minirubyrubyの生成が分かれているのには二つ理由がある。一つめは拡張ラ イブラリのコンパイルにrubyが必要になることだ。--enable-sharedの場合は ruby自身がダイナミックリンクされるので、ライブラリのロードパスの関係で すぐに動かせないかもしれない。そこでスタティックリンクしたminirubyを作り、 ビルドの過程ではそちらを使うようにする。

二つめの理由は、共有ライブラリが使えないプラットフォームでは拡張ライブ ラリをruby自体にスタティックリンクしてしまう場合があるということだ。そ の場合、rubyは拡張ライブラリを全てコンパイルしてからでないと作れないが、 拡張ライブラリはrubyがないとコンパイルできない。そのジレンマを解消する ためにminirubyを使うのである。

CVS

本書の添付CD-ROMに入っているrubyのアーカイブにしても公式のリリースパッ ケージにしても、それはrubyという、変化しつづているプログラムのほんの一 瞬の姿をとらえたスナップショットにすぎない。rubyがどう変わってきたか、 どうしてそうだったのか、ということはここには記述されていない。では過去 も含めた全体を見るにはどうしたらいいだろうか。CVSを使えばそれができる。

CVSとは

CVSを一言で言うとエディタのundoリストである。 ソースコードをCVSの管理下に入れておけばいつでも昔の姿に戻せるし、誰が、 どこを、いつ、どう変えたのかすぐにわかる。一般にそういうことをしてくれ るプログラムのことをソースコード管理システムと言うが、オープンソースの 世界で一番有名なソースコード管理システムがCVSである。

rubyもやはりCVSで管理されているのでCVSの仕組みと使いかたについて少し説 明しよう。まずCVSの最重要概念はレポジトリとワーキングコピーである。 CVSはエディタのundoリストのようなものと言ったが、そのためには歴代の変更の 記録を どこかに残しておかないといけない。それを全部まとめて保存しておく場所が 「CVSレポジトリ」である。

ぶっちゃけて言うと、過去のソースコードを全部集めてあるのがレポジトリで ある。もちろんそれはあくまで概念であって、実際には容量を節約するために、 最新の姿一つと、そこに至るまでの変更差分(ようするにパッチ)の形で集積 されている。なんにしてもファイルの過去の姿をどの時点だろうと取り出せる ようになっていればそれでいいのだ。

一方、レポジトリからある一点を選んでファイルを取り出したものが 「ワーキングコピー」だ。レポジトリは一つだけだがワーキングコピーは いくつあってもいい(図2)。

(repo)
図2: レポジトリとワーキングコピー

自分がソースコードを変更したいときはまずワーキングコピーを取り出して、 それをエディタなどで編集してからレポジトリに「戻す」。するとレポジトリ に変更が記録される。レポジトリからワーキングコピーを取り出すことを 「チェックアウト(checkout)」、戻すことを「チェックイン (checkin)」 または「コミット(commit)」と言う(図3)。チェックインするとレ ポジトリに変更が記録されて、いつでもそれを取り出せるようになる。

(ci)
図3: チェックインとチェックアウト

そしてCVS最大の特徴はCVSレポジトリにネットワーク越しにアクセスできると いうところだ。つまりレポジトリを保持するサーバが一つあればインターネッ ト越しに誰でもどこからでもチェックアウト・チェックインすることができる。 ただし普通はチェックインにはアクセス制限がかかっているので無制限 にできるというわけではない。

リビジョン

レポジトリから特定の版を取り出すにはどうしたらいいだろうか。一つには時 刻で指定する方法がある。「この当時の最新版をくれ」と要求するとそれを選 んでくれるわけだ。しかし実際には時刻で指定することはあまりない。普通は 「リビジョン(revision)」というものを使う。

「リビジョン」は「バージョン」とほとんど同じ意味である。ただ普通はプロ ジェクト自体に「バージョン」が付いているので、バージョンという言葉を使 うと紛らわしい。そこでもうちょっと細かい単位を意図してリビジョンという 言葉を使う。

CVSでは、レポジトリに入れたばかりのファイルはリビジョン1.1である。 チェックアウトして、変更して、チェックインするとリビジョン1.2になる。 その次は1.3になる。その次は1.4になる。

CVSの簡単な使用例

以上をふまえてごくごく簡単にCVSの使いかたを話す。まずcvsコマンドがな いとどうにもならないのでインストールしておいてほしい。添付CD-ROMにも cvsのソースコードを収録した \footnote{cvsarchives/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の構成

物理構造

さてそろそろソースコードを見ていこうと思うのだが、まず最初にしなければ ならないことはなんだろうか。それはディレクトリ構造を眺めることである。 たいていの場合ディレクトリ構造すなわちソースツリーはそのままプログラム のモジュール構造を示している。いきなりgrepmain()を探して頭から処理順 に読んでいく、なんていうのは賢くない。もちろん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自身の実装に関することも書いてある。

ソースコードの腑分け

ここからはruby自身のソースコードについてさらに細かく分割していく。 主要なファイルについてはREADME.EXTに分類が書いてあったので それに従う。記載がないものは筆者が分類した。

Ruby言語のコア

class.cクラス関連API
error.c例外関連API
eval.c評価器
gc.cガーベージコレクタ
lex.c予約語テーブル
object.cオブジェクトシステム
parse.yパーサ
variable.c定数、グローバル変数、クラス変数
ruby.hrubyの主要マクロとプロトタイプ
intern.hrubyの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.crubyコマンドの主要部分(librubyにも必要)
version.crubyのバージョン

コマンドラインでrubyと打って実行するときのrubyコマンドの実装。コマンドライン オプションの解釈などを行っている部分だ。rubyコマンド以外にrubyコアを利 用するコマンドとしてはmod_rubyvimが挙げられる。これらのコマンドは ライブラリlibruby.a/.so/.dllなど)とリンクして動作する。

クラスライブラリ

array.cclass Array
bignum.cclass Bignum
compar.cmodule Comparable
dir.cclass Dir
enum.cmodule Enumerable
file.cclass File
hash.cclass Hash(実体はst.c
io.cclass IO
marshal.cmodule Marshal
math.cmodule Math
numeric.cclass NumericIntegerFixnumFloat
pack.cArray#packString#unpack
prec.cmodule Precision
process.cmodule Process
random.cKernel#srand()rand()
range.cclass Range
re.cclass Regexp(実体はregex.c
signal.cmodule Signal
sprintf.cruby専用のsprintf()
string.cclass String
struct.cclass Struct
time.cclass 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のように 表現される。

(syntree)
図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.