正規表現の文字列を正規表現ライブラリへ渡す前にフィルタリングしたくなったので正規表現自体の正規表現を書いてみました。
つまり事前に必要の無い表現を弾いたり、新たな表現を追加したりなんて事がしたいわけです。
記述に当たって普通の正規表現で書こうとすれば混乱しそうだったのでxpressiveのstatic regexを使用しました。
基本的な表現はサポートしているので必要に応じて拡張してみてください。
以下の例では独自の表現[:cat:]がneko|cat|kittyにマッチするようにしています。また\dという表現を見つけた場合例外を投げるようにしてみました。
#include <string> #include <iostream> #include <algorithm> #include <boost/xpressive/xpressive.hpp> using namespace boost::xpressive; sregex GetRegexOfRegex() { sregex regexofregex, anchors, wildcard, characlass, collating, equivalence, normal, escape, setitemchara, chara, setitem, posset, negset, charaset, group, nonmarkgroup, comment, element, basic; anchors = as_xpr('^') | '$'; wildcard = as_xpr('.'); characlass = (s1 = as_xpr('[') >> ':' >> +alpha >> ':' >> ']'); //ここでs1にキャプチャー collating = as_xpr('[') >> '.' >> +alpha >> '.' >> ']'; equivalence = as_xpr('[') >> '=' >> +alpha >> '=' >> ']'; normal = ~(set='^', '$', '\\', '.', '(', ')', '?', '+', '*', '[', ']', '|', '{', '}', '-'); escape = (s2 = '\\' >> _);//ここでs2にキャプチャー setitemchara = escape | normal | characlass | collating | equivalence; chara = setitemchara | '-'; setitem = (setitemchara >> '-' >> setitemchara) | setitemchara; posset = '[' >> +setitem >> ']'; negset = as_xpr('[') >> '^' >> +setitem >> ']'; charaset = posset | negset; group = as_xpr('(') >> !by_ref(regexofregex) >> ')'; nonmarkgroup = as_xpr('(') >> '?' >> ':' >> !by_ref(regexofregex) >> ')'; comment = as_xpr('(') >> '?' >> '#' >> *~(set=')') >> ')'; element = wildcard | anchors | chara | charaset | group | nonmarkgroup | comment; basic = element >> !((as_xpr('*') | '+' | '?' | ('{' >> +_d >> !(',' >> *_d) >> '}')) >> !(as_xpr('?')|'+')); regexofregex = +basic >> *('|' >> +basic); return regexofregex; } struct escape_unsupported {}; struct EditRegex { EditRegex(const smatch& m, std::string& dest):buf(dest),lastitr(m[0].first) { (*this)(m); buf.append(lastitr, m[0].second); } private: void operator () (const smatch& result) { //s1 独自の表現[:cat:]を普通の正規表現(?:neko|cat|kitty)へ展開 if (result[1].matched && result[1].str() == "[:cat:]") { buf.append(lastitr, result[1].first); lastitr = result[1].second; buf.append("(?:neko|cat|kitty)"); } //s2 \dを弾いてみる if (result[2].matched && result[2].str() == "\\d") { throw escape_unsupported(); } struct myref { void operator()(const smatch& result) { er(result); } EditRegex& er; } me = {*this}; std::for_each(result.nested_results().begin(), result.nested_results().end(), me); } std::string& buf; std::string::const_iterator lastitr; }; void test() { std::string input = "^a*ab.c|ef+(g[^hi])?$(?:(?#123)[:cat:]opq+?st{2,})"; try { smatch result; if (!regex_match(input, result, GetRegexOfRegex())) { std::cout << "not matched\n"; return; } std::string buffer; EditRegex(result, buffer); std::cout << buffer << std::endl; } catch (...) { std::cout << "error\n"; } }
出力
^a*ab.c|ef+(g[^hi])?$(?:(?#123)(?:neko|cat|kitty)opq+?st{2,})
メモ
sregexを複数組み合わせているためsmatchがネストしている。
sub_match.first, sub_match.secondで該当箇所のイテレータが取れるのでそれを利用して編集している。
NFA正規表現一般の話になりますが書き方によってはバックトラッキングにより実用できないほど遅くなるので注意が必要。その場合適宜 keep() を使用してバックトラッキングを抑止する必要があるかも。
似た構文でSpirit.Qiにパーサーを流用できるのでそっちの方が速くて確実だと思う。