D言語erの方もそうでない方も、こんちには!
D言語 Advent Calendar 2012の2日目の記事です。
この記事では、ctpgと言うライブラリを、とても軽く紹介します。
ctpg is 何
@repeatedlyさんの最初の記事にもありましたが、パーサジェネレータです。ScalaのParserCombinatorの影響を強く受けています。
ctpgの特徴
さすがに「パーサジェネレータです!」だけではアレなので、特徴をつらつらと挙げていきます。
コンパイル時にパーサが生成される
パーサジェネレータといえば、YaccやANTLRなどが有名だと思います。
これらのソフトウェアは、予め構文規則が書かれているファイルから、パーサが書かれたソースコードを生成する必要があります。
さくっと小さなパーサが書きたい時、この作業は少し面倒くさいですね。
一方、ctpgではその作業の必要がありません。なぜなら、パーサがコンパイル時に生成されるからです。文字列mixinとCTFEの合わせ技により、実現しています。
構文規則を別のファイルに書く必要もありません。Dのソースコード内に書くことができます。
パーサジェネレータはよくコンパイラコンパイラと呼ばれます。なのでこのctpgは、コンパイル時にパーサを生成するという点で、コンパイル時コンパイラコンパイラと呼べるかもしれません。
生成したパーサもコンパイル時に動作する
ctpgが生成したパーサは、基本的にCTFE可能な関数になるので、コンパイル時に動作させることができます。
なので、ctpgを使うことによって、以下のことが簡単に実現できます。
- JSONをコンパイル時にパースする。
- GUIが定義されたXMLをコンパイル時にパースし、そのGUIを作るDのソースコードに変換して、mixinする。
入力は、割と何でもいい
文字列はもちろん、任意の型を要素型とするForwardRangeを入力とすることができます。
なので、やろうと思えば、バイナリのパースもできます。
やはり、D言語でライブラリを書いている以上、Range対応は避けて通れませんね。
導入方法
- https://github.com/youkei/ctpg から、ZIPで落としたり、git cloneしたりします。
- src/ctpg.dを自分のプロジェクトの適当なディレクトリに置きます。
- ctpgを使うソースファイルでctpgをimportします。
- ctpg.dをおいたディレクトリをインクルードパスに追加しつつ、ctpg.dと一緒にコンパイルします。
例:ctpg.dをincludeディレクトリにおいた場合
import ctpg;
import std.stdio;
void main(){
"Hello world!".writeln();
}
$ dmd -Iinclude include/ctpg.d src/hoge.d
どう使うの?
基本的には
以下のように使います。
import ctpg;
mixin(generateParsers(q{
string hello = "Hello";
});
void main(){
auto parsed = "Hello world!".parse!hello();
assert(parsed.match);
assert(parsed.value == "Hello");
}
generateParsers
関数が、Dのソースコードを返すので、それをmixinします。そして、parse
テンプレート関数のテンプレート引数に規則を渡して、使います。
generateParsers
と言う関数名が、どうにもダサいですね・・・
それはさておき、generateParsers
に渡す文字列の中身ですが、上に書いてある通り、
規則名 = 規則 ;
と言う感じに構文規則を書いていきます。
規則は型を持っている
気づいた方は気づいたと思いますが、規則の前に型が書かれています。上の例では、string
と書かれていますね。
規則の型は、パースした時の結果のvalue
の型になります。
上の例で言うと、規則hoge
の型がstring
なので、parsed.value
の型がstring
になります。
演算子とか
演算子などは、基本的にPEG(ポリエチレングリコールじゃないですよ!)に従っています。
なので、普通に並べればつなげることが出来たり、/
、*
、+
、?
などを使うことができます。
import ctpg, std.string;
mixin(generateParsers(q{
string Hel = "Hel";
string o = "o";
Tuple!(string, string[], string) hello = Hel "l"+ o;
});
void main(){
auto parsed = "Hello world!".parse!hello();
assert(parsed.match);
assert(parsed.value[1].join() == "l");
parsed = "Hellllllo world!".parse!hello();
assert(parsed.match);
assert(parsed.value[1].join() == "lllll");
}
変換する
>>
で、結果の値を関数に渡して、変換することができます。
ScalaのParserCombinatorで言うところの^^
のことですね^^
import ctpg, std.conv;
mixin(generateParsers(q{
int num = "3141592" >> to!int;
});
void main(){
auto parsed = "31415926535".parse!num();
assert(parsed.match);
assert(parsed.value == 3141592);
その他
その他の使い方について知りたい方は、ドキュメントを見てみるといいかもしれません。
と、言いたいところなんですが、ドキュメントは全然書けてません。
本当に申し訳 of the world
まとめ
今回の記事は、軽く紹介という事で、このような感じになりました。次回の記事では、例を挙げながら、複雑な使い方を紹介したいと思います。
ドキュメントは早急に書きます・・・
3日目は、Phobosコミッターでもある@mono_shooさんです!