パーサー恐怖症
http://martinfowler.com/bliki/ParserFear.html
最近はドメイン特化言語についてみんなと話すことが多いのだが、外部DSLのことになると、だいたい決まって「パーサーを書くのは難しいよ」とか言われる。外部DSLの構文としてXMLがよく使われるのは、「パーサーが無料で手に入るから」だったりする。でも、パーサーを書くのは思ったよりも簡単なことなのだよ。いやマジで。 XMLのパースができれば簡単なことだよ。
証拠だってあるのだ……つっても、私の話だけど。でもでも、十分に証拠となるものだから引き合いに出そうと思う。現在執筆中の書籍に入門的な例を書いたんだけど、簡単なステートマシンを作るのに外部DSLを2つ作ったのだ。 1つは(ゲートウェイドラッグ(’[訳注] 導入に使う薬品)として)XMLを使ったもので、もう1つはカスタム構文をAntlrを使ってパースしたものだ。完全にパースするコードを書くのにかかった時間はだいたい同じくらいだった。
XMLパーサーは無料で手に入るのはいいけれど(私はElliotte Rusty Haroldの素晴らしいXOMフレームワークを使っている)、出力がXML DOMツリー形式になってしまう。そこから使いやすい形式にするには、またひと手間かける必要がある。この演習の目的は、明快な意味モデルを作ることだった。つまり、ここで求められるパース結果は、動くステートマシンのモデルというわけだ。そこで私は、XML DOMを経由して、自分なりの道を歩むコードを書くことになった。別に難しいことではない。 XPath式を使って必要なDOM要素を抜き出せれば簡単な話だ。実際、ここではDOMツリーをすべて走査するようなことはせずに、必要な要素に対してXPathクエリを発行するメソッドを作り、結果ノードをイテレートして、ステートマシンのモデルを作った。
XMLの処理は簡単なものだが、それでも約100行のコードにはなった。所要時間は数時間。しばらくXOMを使っていなかったので思い出すのにちょっと時間がかかった。それでもこれは、簡単に使えるライブラリである。
Antlrの処理も似たような感じだ。 Antlrでは、ASTを作るために文法ファイルに簡単なルールを入れることができる。 ASTを処理するコードと意味モデルを作るコードは、XMLを処理するコードと良く似ている——ツリー上にある適切なノードを取り出して処理すればいい。文法ファイルなども含めると、最終的なコードは約250行になった。ただし、所要時間は同じくらいだった。以前からAntlrには馴染みがあって、何度か使ったことはあったけど、実際にこれでASTの構築をしたことはなかった(もし興味があれば、現在執筆中の書籍にこの例の説明が載っている)。
これまでパーサージェネレータについて調べてきて、思っていたよりも簡単にパーサーを書けるということは分かっていたが、今回驚いたのは、XMLの処理よりも時間がかからなかったということである。厳密に計測すれば、時間はもっとかかっていただろう。 Antlrの例は、XMLの例の次に行ったものだ。プログラマなら分かると思うが、2回目の実装には概して時間がかからないものだ。だが、たとえそうであっても、「パーサー」の言葉の意味が「複雑」だと考えているような多くの人たちにとってみれば、それほど違いがなかったのではないだろうか。
ただし、パーサージェネレータを習得するのに一定の学習曲線があることは否めない。文法ファイルそのものや文法ファイルとコードサンプルとのやり取りに慣れなければいけない。そのために使える戦略はいくつかある(私はこれをツリー構築(Tree Construction)、組み込みトランスレーション(Embedded Translation)、組み込みインタープリテーション(Embedded Interpretation)と呼んでいる)。カスタム構文についても考えなければならない。それには、XMLを使うときにどれを属性や要素にするかと悩むよりも多くの決定が必要となる。しかし、学習曲線はそれほど高くはない。現代にあるツールを使えば、比較的簡単だ。私のデフォルトの選択肢はAntlrである。素敵なIDEが文法式を探索してきて、どのようにASTにパースされるのかを教えてくれる。もちろん、どれか1つのパーサージェネレータに慣れれば、他の選択肢も楽に選べることだろう。
じゃあ、なぜDSLのパーサーを書くのをそんなにも恐れなきゃいけないんだろう?結局、主な理由は次の2つになるんじゃないかと思う。
- 大学でコンパイラの講義を受けなかったので、パーサーは恐ろしいものだと思い込んでいる。
- 大学でコンパイラの講義を受けたので、パーサーは恐ろしいものだと思い込んでいる。
1番目の理由は分かりやすい。人間というものは、生まれつき、知らないものに対する恐怖心を持っているからだ。 2番目の理由は、実に興味深い。要するに、大学でどのように構文解析と出会ったのかということだ。構文解析はコンパイラの講義でのみ教えられている。ここでは、完全な汎用言語のパースを目的としている。汎用言語のパースは、DSLのパースに比べて遥かに難しいものだ。なんといっても、文法が大きくなるし、DSLでは不要な醜いしわ寄せが含まれてしまう。
パースして出力処理やコード生成をするようなコードにしようとすると、この問題はさらに悪化する。事態をうまく進めるには、意味モデルを使うとよい。これならパーサーはモデルに息を吹き込むだけでよい。必要なことをやるためには、オブジェクト指向フレームワークのように、意味モデルを実行しさえすればいい。私は、コード生成が必要になることはほとんどない。もし必要になったなら、パーサーからは独立させて、意味モデルとは別のところに作るだろう。コード生成の命令文を文法に入れると、密結合しすぎだと思う。
外部DSLを扱う人は、汎用言語のパースとは全く違う方法を教わる必要がある。言語や言語内のスクリプトが小さいので、パースに対する典型的な懸念事項の多くに違いがでてくる。本当に必要なとき以外はコード生成をしないことで、複雑さの大部分を取り除くことができる。明快な意味モデルを使うことで、手続きをより素直な塊に分けることができる。
問題は、もちろん、こうしたガイドラインについて書かれたものがほとんどないということだ(だから私がこうやって時間を使って書こうとしてるわけだね)。 パーサージェネレータツールについての文書を見つけることは難しいと思う。本当に素晴らしい文書(Terence ParrのAntlr本とか)でも、汎用言語を使うためのマインドセットが書かれている。誤解しないで欲しい。 Antlr本は、本当に役に立つ本だ(私がAntlrを選んだのはこの本があったからだ)。しかし、汎用言語よりもDSLのパースのほうが扱いにくいという思い込みがあるのだと思う。
こんな状況でもなお、学習曲線を上ることはできる。 パーサージェネレータを試したことがないのであれば、是非試してみて欲しい。自分用の簡単なDSLを書いてみて欲しい。最初はコード生成のことは考えなくていい。いつも通りにドメインモデルを作って、DSLで息を吹き込めばいい。ごくごく些細なことから始めるといい(私みたいにHelloAntlrとかね)。そこから、じょじょに上を目指していくのだ。 DSLを使っているオープンソースプロジェクトをのぞいてみて、どんなことをやってるかを調べてみるのもいいだろう。
"”我々がやろうとしているのは、コンパイラでよく使われるツールを紹介することだ。ただし、そう教えられてきたために、ツールをコンパイラとのみ関連付けてしまう人たちにとってのものではなく、もっと汎用的なツールを紹介したい。–Rebecca Parsons