LoginSignup
10
11

More than 5 years have passed since last update.

ctpgはコンパイル時コンパイラコンパイラ?

Last updated at Posted at 2012-12-02

D言語erの方もそうでない方も、こんちには!

D言語 Advent Calendar 2012の2日目の記事です。

この記事では、ctpgと言うライブラリを、とても軽く紹介します。

ctpg is 何

@repeatedlyさんの最初の記事にもありましたが、パーサジェネレータです。ScalaのParserCombinatorの影響を強く受けています。

ctpgの特徴

さすがに「パーサジェネレータです!」だけではアレなので、特徴をつらつらと挙げていきます。

コンパイル時にパーサが生成される

パーサジェネレータといえば、YaccやANTLRなどが有名だと思います。

これらのソフトウェアは、予め構文規則が書かれているファイルから、パーサが書かれたソースコードを生成する必要があります。

さくっと小さなパーサが書きたい時、この作業は少し面倒くさいですね。

一方、ctpgではその作業の必要がありません。なぜなら、パーサがコンパイル時に生成されるからです。文字列mixinCTFEの合わせ技により、実現しています。

構文規則を別のファイルに書く必要もありません。Dのソースコード内に書くことができます。

パーサジェネレータはよくコンパイラコンパイラと呼ばれます。なのでこのctpgは、コンパイル時にパーサを生成するという点で、コンパイル時コンパイラコンパイラと呼べるかもしれません。

生成したパーサもコンパイル時に動作する

ctpgが生成したパーサは、基本的にCTFE可能な関数になるので、コンパイル時に動作させることができます。

なので、ctpgを使うことによって、以下のことが簡単に実現できます。

  • JSONをコンパイル時にパースする。
  • GUIが定義されたXMLをコンパイル時にパースし、そのGUIを作るDのソースコードに変換して、mixinする。

入力は、割と何でもいい

文字列はもちろん、任意の型を要素型とするForwardRangeを入力とすることができます。

なので、やろうと思えば、バイナリのパースもできます。

やはり、D言語でライブラリを書いている以上、Range対応は避けて通れませんね。

導入方法

  1. https://github.com/youkei/ctpg から、ZIPで落としたり、git cloneしたりします。
  2. src/ctpg.dを自分のプロジェクトの適当なディレクトリに置きます。
  3. ctpgを使うソースファイルでctpgをimportします。
  4. ctpg.dをおいたディレクトリをインクルードパスに追加しつつ、ctpg.dと一緒にコンパイルします。

例:ctpg.dをincludeディレクトリにおいた場合

src/hoge.d
import ctpg;
import std.stdio;

void main(){
    "Hello world!".writeln();
}
shell
$ dmd -Iinclude include/ctpg.d src/hoge.d

どう使うの?

基本的には

以下のように使います。

hello.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(ポリエチレングリコールじゃないですよ!)に従っています。

なので、普通に並べればつなげることが出来たり、/*+?などを使うことができます。

hello2.d
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で言うところの^^のことですね^^

parse_num.d
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さんです!

10
11
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
10
11