( JavaWorld ’98年2月号原稿より抜粋。) 「EPP の設計方針」 EPP は以下の点を特に注意して設計した。 ・ EPP のユーザが容易に plug-in を利用できること システムを部品化することによって拡張性や再利用性を高めたシステムは一般 に数多く存在する。しかし、新しい部品の使い方が難しければ、エンドユーザ にとって必ずしも使いやすいものにはならない。EPP の場合、使いたい plug-in ファイルを CLASSPATH の通ったディレクトリに置いて、ソースコー ド中で名前を書くだけで簡単に利用することができる。 plug-in の数が増えてくると、「 plug-in A が動作するためには B も同時に 指定することが必要」とか、「A と B からどちらか1つだけ選んで指定しな ければならない」といった状況が起きるようになる。つまり plug-in の使い 方に複雑な制約条件が生じてくる。もし制約条件を満たすように正しく plug-in を選択しないと、システムとして意味のない挙動を起こしたり、実行 時エラーを起こしたりするだろう。EPP の記述言語 Ld-2 は、この問題に対処 するための簡単なメカニズムを持っている。このメカニズムにより、指定され た plug-in の組み合わせに問題があるかどうかをある程度自動的に検出する ことができる。 ・複数の拡張を組み合わせて使えること 「組み合わせて使う」とは、ようするに1つのファイルに複数のplug-in を指 定することである。EPP 本体の設計がもしまずければ、複数の拡張部品の組み 合わせはすぐに破綻を引き起こしてしまう。 拡張の組み合わせが破綻する例として、ソフトウエアを patch file で拡張し ていく状況を考えてみよう。あるシステムに対して機能拡張を行う patch file が複数あったとする。patch file はオリジナルのソースコードに対する 差分を行単位で抜き出したものである。1つ patch を当てると、そのソース コードはオリジナルのものとは異なってしまう。すると、いくつか複数の patch を当てていくうちに、新しい patch がうまく当たらなくなってしまう。 EPP では、 patch に相当する機能は記述言語 Ld-2 が直接持っている。拡張 はソースコードの行単位ではなく、メソッド単位であり、メカニズム的には拡 張をいくらでも追加していくことができる。 ここまでは拡張するためのメカニズムの話だが、メカニズム的には問題なくて も拡張どうしの「機能」が衝突を起こすことがある。本質的に両立し得ない2 つの plug-in が衝突するのはしょうがないからあきらめるとして、あきらか に無関係な機能を提供する plug-in ならば、組み合わせて使えて欲しい、と いうのが当然の要求である。そこで、できるだけ plug-in どうしが衝突を起 こさないように、EPP の内部を、相互依存の少ないモジュールに細かく分離し た。個々のモジュールは独立性が高いので、1つを拡張しても他のモジュール への影響は少ないため、衝突が起きにくくなっている。 この他にも、マクロ展開の手順や、構文解析木のフォーマットなど細部の設計 においても常に「拡張の組み合わせ」を意識した設計を行った。 ・幅広い範囲できめこまかく拡張可能であることきめこまかい拡張を可能にす るために、 EPP には多くの「拡張のための hook 」が、あらかじめ入れてある。ここで 言う hook は、実はメソッドである。plug-in は、 Ld-2 が持つ継承機構を使 いメソッドの動作を拡張することで、機能を追加していく。拡張のための hook は、入力ファイルを open してから close するまでを含むあらゆる部分 に入れてある。特にパーザ部には、新しい構文や演算子を追加するための hook があり、たいていの文法拡張はこれを用いて簡単にできるようになって いる。 しかし、メソッドの継承だけでは、オリジナルのシステムが想定した範囲の拡 張しかできない。想定された範囲を超える拡張をするためにはオリジナルのソー スコードを変更するしかないとすれば、「拡張可能システム」としては、ある 意味で不十分である。実は EPP の場合、plug-in は EPP を構成する任意の部 品を、新しいものに交換することができる。この手段を使えば、 EPP が想定 していなかった拡張も、原理的に可能になる。もっともこれは最後の手段であっ て、この手段を使った plug-in は他の plug-in と衝突を起こす可能性が高く なる。 ・容易に plug-in が書けること EPP では plug-in ユーザの便宜を最優先しているが、それを損なわない範囲 において、 plug-in プログラマーもできるだけ簡単に plug-in を作れるよう に設計した。基本的方針として、全体のアーキテクチャをできるだけシンプル にすることを心がけた。 特にパーザが扱う基本的なデータ構造であるトークンと抽象構文木は、シンプ ルで扱いやすくなっている。例えばトークンに関しては、キーワード、演算子 などの記号、識別子はすべて lisp の symbol で表現され、区別されない。し たがって、新しい構文を追加する場合でも、多くの場合は字句解析部に対して 特別な変更を加える必要がない。また例えば == というトークンは、パーザ記 述において "==" という名前を持った symbol で直接表現できるので、 EQUAL などといった名前をつけてどこかで宣言しておく必要はない。 パーザは、 yacc などの parser generator は使わずに再帰下降のスタイルで 書かれており、高度な構文解析の理論を知らないプログラマー(私を含む)で も多分容易に理解できるだろう。バックトラックや文脈依存も自由に行うこと ができる。何か特殊な処理をする必要にせまられた時でも、「parser generator の動作の裏をかく」といったことをする必要がない。