正規表現ライブラリ boost::xpressive を使う

kinabaさんとこでboost::xpressiveを知ったので、早速ネットワークプログラミングのほうで使ってみたらなかなかよい感じ。

Boost.Regex++ と比較して、ヘッダのみで構成されている(ライブラリのリンクが不要)という点が気楽に使う向きにはありがたいのですが、何よりイカれているすばらしいのはテンプレートと演算子オーバーロードの黒魔術によって、 目的のパターンマッチを行う有限状態機械を、C++ ソースのコンパイル時に生成してしまう静的正規表現なのです。

Boost.Xpressive, 初音ミクの10倍高い機械に「GO MY WAY!!」を歌わせてみた(おまけつき) - underscore.jp/diary(2007-09-27)

すげぇ。

以下使う方法を適当に。

はじめに

Ubuntuな人は apt-get install libboost-* でおk。必要ないパッケージまで入ってしまうのを嫌う人は、自分で取捨選択してくださいな。

使うときは

#include <boost/xpressive/xpressive.hpp>

そして使いたいスコープで

using namespace boost::xpressive;

するとよい。注意点としては、グローバルにusing namespace std; していると、後から使うsetという名前が衝突してしまうらしいので注意。

とりあえず使う

いつぞやにid:suu-gのところからもってきた浮動小数点の正規表現 /[+-]?([1-9][0-9]*)(?:\.\d+)?(?:[Ee][+-]?\d+)?/ をxpressiveで解析してみよう。

void ex1(){
    using namespace boost::xpressive;
    std::string str("+123.4e-10");

    sregex rex = !(set= '+','-')                   // [+-]?
        >> (s1= range('1','9') >> *range('0','9')) // ([1-9][0-9]*)
        >> !('.' >> +_d)                           // (?:\.\d+)?
        >> !((set= 'e','E') >> !(set= '+','-') >> +_d); // (?:[Ee][+-]?\d+)?

    smatch match;
    if(regex_match(str, match, rex)){
        std::cout << "whole string: " << match[0] << std::endl;
        std::cout << "match[1]: " << match[1] << std::endl;
    }
}
~ $ ./a.out
whole string: +123.4e-10
match[1]: 123

std::stringを扱うには、sregex、smatchという型の変数を使う。名前からわかる通りsregexは正規表現オブジェクト、smatchはマッチ結果のオブジェクトを表す。sregex は basic_regex の別名であり、他にも const char* のcregex、ワイドキャラクタ用のwsregex、wcregexなどがtypedefされている。

sregexを使って、文字列とのマッチングを実行するのが regex_match / regex_search 関数。regex_match は文字列全体が、与えられた正規表現にマッチすると成功する。regex_search は、文字列の一部が正規表現にマッチすると成功する。用途によって使いわけよう。

マッチ結果 smatch には、[]演算子を使ってアクセスできる。smatch[0]にはマッチした文字列全体が、smatch[1]以降には、(s1= ... )で捕捉した文字列が保存されている。

xpressiveの基本

Perlスタイルと対比させてみる。

Perl スタイル xpressive
\d _d
abc… (a,b,c,…は正規表現) a >> b >> c >> ...
量化子 a* *a
a+ +a
a? !a
文字セット [abc] (set= 'a','b','b')
[a-z] range('a','z')
捕捉と前方参照 (a) (s1= a)
\1 s1

他の表現については、User's Guide - 1.70.0 のCheat sheetに一覧が載っている。

正規表現を超える

Perlなどの正規表現では、再帰的な構造になっている文字列にマッチすることができなかったが、xpressiveではそれが可能。例えば、括弧が入れ子になった計算式なんかもマッチできる。

void ex2(){
    using namespace boost::xpressive;
    std::string str("12+23*(34+(4+5)*6)");

    sregex add, mult, prim, decim;

    decim = *_d;
    prim = ('(' >> by_ref(add) >> ')') | decim;
    mult = (prim >> '*' >> by_ref(mult)) | prim;
    add = (mult >> '+' >> by_ref(add)) | mult;

    smatch match;
    if(regex_match(str, match, add)){
        std::cout << "whole string: " << match[0] << std::endl;
    }
}
~ $ ./a.out
whole string: 12+23*(34+(4+5)*6)

ミソは、by_refによって特定の正規表現への参照を埋めこめるようになっていることらしい。こうして、xpressiveを使うと軽々と従来の正規表現の限界を飛び越えることができる。boost::spiritを使うほどではないけど、正規表現じゃ力不足という時には役に立つと思う。

ようこそ再帰的な世界へ。