SRELL (std::regex-like library) はC++用のUnicode対応正規表現テンプレートライブラリです。
SRELLはECMAScript (JavaScript) 互換の正規表現エンジンを、"std::regex"(C++11で導入された正規表現ライブラリ)とクラス構成が同じになるようにラッピングしたものです。API/クラスデザインが同じですので、std::regexや、その基となったboost::regexと同じように扱えます。
また、ヘッダファイルのみの純粋なテンプレートライブラリですのでincludeするだけですぐに使えます。事前のセットアップやインストールは不要です。
SRELLはUnicodeに特化した正規表現ライブラリです。
'.'
がUTF-16文字列でサロゲートペアの片割れだけにマッチしたり、UTF-8文字列のコードユニットにマッチしたりするようなことがありません。[丈𠀋]
のように指定できます。また [\u{1b000}-\u{1b0ff}]
のような範囲指定もできます。※ちなみにC++11以降のstd::regexは、us-asciiやiso-8859-*のような1文字が固定長の文字コードを前提としています。そのため1文字が可変長であるUTF-8, UTF-16, Shift_JIS, EUC-JPの文字列はうまく扱えません。
SRELLはicase検索(大文字・小文字を区別しない検索)時の速度低下が極力軽減されるようチューニングされています。
C++11に向けた改訂作業の中でもregexは比較的初期に提案された拡張であったことから、まったくと言って良いほどC++11の新機能に依存していません。そのためC++11より前のコンパイラであっても、C++のテンプレートを正しく解釈するものであればSRELLは利用可能です(動作確認済みコンパイラのうちもっとも古いものはVC++2005です)。
パスの通ったところに srell*.hpp(srell.hpp, srell_ucfdata2.hpp, srell_updata.hpp の3ファイル)を置いて srell.hpp をincludeするだけです。
// Example 01: #include <cstdio> #include <string> #include <iostream> #include "srell.hpp" int main() { srell::regex e; // 正規表現オブジェクト。 srell::cmatch m; // 結果を納めるオブジェクト。 e = "\\d+[^-\\d]+"; // 正規表現をコンパイル。 if (srell::regex_search("1234-5678-90ab-cdef", m, e)) { // printfを使うなら。 const std::string s(m[0].first, m[0].second); // 上は下のどちらかでも良い。 // const std::string s(m[0].str()); // const std::string s(m.str(0)); std::printf("result: %s\n", s.c_str()); // iostreamを使うなら。 std::cout << "result: " << m[0] << std::endl; } return 0; }
この例のように、SRELLを構成するクラスやアルゴリズムはすべてnamespace srellの下に置かれています。この点を除けば使い方はstd::regexに準じます。
現時点ではまだstd::regexに関する日本語の文書があまりないようですので、さしあたってSRELLを使ううえで必要となりそうな情報を次のページにまとめました。
Zipアーカイヴ内の readme_ja.txt も併せてご覧ください。
C++11以降で導入された機能のうち、SRELLが利用することもあるのは以下の4つです。
char16_t
型とchar32_t
型initializer_list
char8_t
型SRELLではこれらの使用可否を次のようにして判定しています。
#ifdef __cpp_unicode_characters #ifndef SRELL_CPP11_CHAR1632_ENABLED #define SRELL_CPP11_CHAR1632_ENABLED // char16_t, char32_t用のtypedefを行う。 #endif #endif #ifdef __cpp_initializer_lists #include <initializer_list> #ifndef SRELL_CPP11_INITIALIZER_LIST_ENABLED #define SRELL_CPP11_INITIALIZER_LIST_ENABLED // 引数にinitializer_listを渡せるようにする。 #endif #endif #ifdef __cpp_rvalue_references #ifndef SRELL_CPP11_MOVE_ENABLED #define SRELL_CPP11_MOVE_ENABLED // コンストラクタや代入でmoveを有効にする。 #endif #endif #ifdef __cpp_char8_t #ifdef __cpp_lib_char8_t #define SRELL_CPP20_CHAR8_ENABLED 2 // char8_t対応とstd::u8string対応との両方を行う。 #else #define SRELL_CPP20_CHAR8_ENABLED 1 // char8_t対応のみ行う。 #endif #endif
実際には該当する機能が使えるにもかかわらず、コンパイラが__cpp_*
マクロを適切に定義しないためにC++11/C++20の機能が有効にならない場合、SRELLをincludeする前に上記マクロのうち必要なSRELL_CPP*
を定義しておくと、対応する機能を強制的にオンにすることが出来ます。
ECMAScript 2022 のRegExpに定義されている表現が使えます。既定ではu
フラグは常に指定されていると見なされます。
SRELL 4.000以降ではunicodesets
フラグを指定すると、ECMAScriptの将来の版に追加される予定のv
フラグモード (/.../v
) 相当の振る舞いをするようになります。vモードの詳細については専用の頁を設けてあります。
対応している表現の詳細は次の通りです(注記なきものはuモード/vモード共通)。
文字 | |
---|---|
. |
改行以外の文字にマッチ(ECMAScriptにおける改行文字は、U+000A, U+000D, U+2028, U+2029の4文字)。
Note: |
\0 |
NULL文字 ( |
\t |
水平タブ ( |
\n |
Line Feed ( |
\v |
垂直タブ ( |
\f |
Form Feed ( |
\r |
Carriage Return ( |
\cX |
|
\\ |
バックスラッシュそのもの ( |
\xHH |
UTF-16におけるコードユニット値が、2桁の16進数
UTF-16において |
\uHHHH |
Unicodeのコードポイント値が、4桁の16進数
SRELL 2.500以降:連続する |
\u{H...} |
1桁以上の16進数
Note: ECMAScript 6にて追加された表現です。提案書の段階では |
\ |
Note: ECMAScriptの |
^$.*+?()[]{}|\/ 以外の文字 |
その文字そのものを表す。 |
選択 | |
A|B |
正規表現AまたはBにマッチ。 |
文字クラス | |
[] |
文字クラス。文字集合。
大文字・小文字を区別しない検索の時(
Perlの正規表現には「
|
vモード(
同じ階層の
注意事項1:vモードでは
注意事項2:次の18種類の二重記号は将来の機能拡張用に予約されています。これらを[]内に書くことは出来ません。使用された場合は
|
|
定義済み文字クラス | |
\d |
|
\D |
|
\s |
Note: 厳密にはWhiteSpaceとLineTerminatorとに一致します。今後UnicodeカテゴリのZsに新たな文字が追加されることがあれば、その都度WhiteSpaceの右辺値は増えます。 |
\S |
|
\w |
|
\W |
|
\p{...} |
vモードでは文字列プロパティー(properties of strings. 複数のコードポイントによって表現される文字列にマッチするUnicode property)にも対応。文字クラス内でも使えるが、補集合のクラス (
Note: ES2018/ES9.0でRegExpに導入された表現で、SRELL 2.000以降で使用可能です。 |
\P{...} |
先の
Note: ES2018/ES9.0でRegExpに導入された表現で、SRELL 2.000以降で使用可能です。 |
量指定子(回数指定) | |
* *? |
直前の正規表現による照合を0回以上繰り返す。
先行する表現なしにいきなり回数指定が現れた時には |
+ +? |
直前の正規表現による照合を1回以上繰り返す。 |
? ?? |
直前の正規表現による照合を0回ないし1回繰り返す。 |
{n} |
直前の正規表現による照合をきっちり
|
{n,} {n,}? |
直前の正規表現による照合を |
{n,m} {n,m}? |
直前の正規表現による照合を
|
括弧・後方参照・グループ化 | |
(...) |
正規表現のグループ化および文字列の捕獲。正規表現全体において開き括弧 括弧自身またはその外側の正規表現に繰り返し指定がある場合、捕獲した文字列はループのたびに未定義値相当にクリアされる。そのためキャプチャした文字列を次のループに持ち越すことは出来ない。 |
\N (※Nは正の 整数) |
後方参照。
ECMAScriptの正規表現では、文字列を捕獲する括弧が対応する後方参照よりも先行している必要はない。そのため
対応する括弧が何も捕獲していない時、後方参照は未定義値 ( |
(?<NAME>...) |
名前付きの Note: ES2018/ES9.0でRegExpに導入された表現で、SRELL 2.000以降で使用可能です。 |
\k<NAME> |
Note: ES2018/ES9.0でRegExpに導入された表現で、SRELL 2.000以降で使用可能です。 |
(?:...) |
グループ化。 |
位置にマッチするもの | |
^ |
文字列の最初にマッチ。 |
$ |
文字列の最後にマッチ。 |
\b |
文字クラスの外側では |
\B |
文字クラスの外側では |
(?=...) |
肯定先読み。たとえば |
(?!...) |
否定先読み。たとえば |
(?<=...) |
肯定戻り読み。たとえば
Note: SRELL 1では、(...)内は |
(?<!...) |
否定戻り読み。たとえば
Note: SRELL 1では、(...)内は固定幅の文字列のみ指定可能。固定幅でない時は |
埋込フラグ | |
(?ism-ism) |
正規表現内でフラグ指定をする。
この表現は正規表現の先頭でのみ使用可能(Python 3.11と同じ)。もし他の場所で使われたら
註:SRELL 4.007以降で利用可能。なおこの機能はSRELLの独自拡張で、ECMAScriptの仕様にはありません。この機能は |
'\'
で終わっていたり '\'
がこの表にない組み合わせで使われたりした時は、error_escape
がthrow
されてきます。後者についてはSRELL 2.300まで「'\' に続く文字そのもの」として解釈されていましたが、2.301以降はECMAScriptの仕様に合わせてエラー扱いするようになりました。/\1\u0030/
のように数字のほうはコードポイントで書く」「/\1[0]/
のように、数字のほうはその1文字だけからなる文字クラスとして書く」の2つがあります。SRELLのパターンコンパイラはどちらの書き方も同じ内部表現に変換します。\ooo
や \0ooo
のような8進数表現が存在しません。ECMAScriptの仕様では、\
に0
が続く時は<NUL> (\u0000
) として解釈し、1-9で始まる数字が続く時は後方参照として解釈、その際対応する()
が正規表現中に存在しなければエラーと定められています。
[^]
(空集合の補集合)で代用できます。/(?=\p{sc=Latin})\p{Ll}/
→ ラテン文字の小文字にのみマッチ)。またこの応用で、否定先読みを使うと減算相当の処理も出来ます(例:/(?!\p{sc=Latin})\p{Ll}/
→ ラテン文字ではない小文字にマッチ)。Unicode対応のために、次のような型がSRELLには追加されています。
Prefixと文字列の解釈 | 特殊化に使われる型 | basic_regex | match_results | sub_match |
備考 |
---|---|---|---|---|---|
u8- |
char8_t または char |
u8regex |
u8cmatch u8smatch |
u8csub_match u8ssub_match |
char8_t 型対応時のみchar8_t が使われる(__cpp_char8_t または SRELL_CPP20_CHAR8_ENABLED マクロが定義されているか否かで判別)。非定義時は後述するu8c- 型の単なる別名となる。 |
u16- |
char16_t |
u16regex |
u16cmatch u16smatch |
u16csub_match u16ssub_match |
char16_t 型、char32_t 型に対応している時のみ定義される(__cpp_unicode_characters または SRELL_CPP11_CHAR1632_ENABLED マクロが定義されているか否かで判別)。 |
u32- |
char32_t |
u32regex |
u32cmatch u32smatch |
u32csub_match u32ssub_match |
|
u8c- |
char |
u8cregex |
u8ccmatch u8csmatch |
u8ccsub_match u8cssub_match |
|
u16w- |
wchar_t |
u16wregex |
u16wcmatch u16wsmatch |
u16wcsub_match u16wssub_match |
WCHAR_MAX が0xFFFF 以上、0x10FFFF 未満の場合のみ。 |
u32w- |
u32wregex |
u32wcmatch u32wsmatch |
u32wcsub_match u32wssub_match |
WCHAR_MAX が0x10FFFF 以上の場合のみ。 |
|
u1632w- |
u1632wregex |
u1632wcmatch u1632wsmatch |
u1632wcsub_match u1632wssub_match |
WCHAR_MAX の値によって、上記 u16w- または u32w- の別名となる。
|
各prefixの意味するところは次の通りです。
char8_t
型に対応しているかどうか(__cpp_char8_t
またはSRELL_CPP20_CHAR8_ENABLED
マクロ定義の有無で判断)で次のように変わります。
char8_t
対応なら:char8_t
型配列またはstd::u8string
型インスタンスをUTF-8文字列として扱う。char8_t
未対応なら:後述のu8c-に同じ。単なる別名としてtypedef
される。u8"..."
) の型がchar
からchar8_t
に変更されましたが、上記の切替によりSRELLのu8-型は常にu8"..."
に適するようになっています。
char16_t
型配列またはstd::u16string
型インスタンスをUTF-16文字列として扱う。
UTF-16の文字列リテラル (u"..."
) に適しています。
char32_t
型配列またはstd::u32string
型インスタンスをUTF-32文字列として扱う。
UTF-32の文字列リテラル (U"..."
) に適しています。
char
型配列またはstd::string
型インスタンスをUTF-8文字列として扱う(SRELL 2.100で導入。SRELL 2.002まではこれがu8-というprefixを使用していました)。wchar_t
型配列またはstd::wstring
型インスタンスをUTF-16文字列として扱う(WCHAR_MAX
が0xFFFF
以上0x10FFFF
未満の時のみ定義される)。wchar_t
型配列またはstd::wstring
型インスタンスをUTF-32文字列として扱う(WCHAR_MAX
が0x10FFFF
以上の時のみ定義)。WCHAR_MAX
が0xFFFF
以上0x10FFFF
未満なら上記u16wに同じ。WCHAR_MAX
が0x10FFFF
以上なら上記u32wに同じ。
上記u16w-
, u32w-
と異なり、このu1632w-
型はWCHAR_MAX
が0xFFFF
以上なら常に定義される。
SRELL 2.930以降で利用可能。
※u16w-
型とu32w-
型とはWCHAR_MAX
の大きさによって排他的に定義されます。そのためソースコードのポータビリティーに問題が発生しうるということに後になって気づいたため、SRELL 2.930でu1632w-
が導入されました。
先の表では省略しましたが、このルールに基づいてregex_iterator
, regex_iterator2
, regex_token_iterator
でも同じようにu(8c?|16w?|32w?|1632w)
prefixの付いた型がtypedef
されています。
Unicode対応版の基本的な使い方は次の通りです。
srell::u8regex u8re(u8"UTF-8文字列による正規表現"); srell::u8cmatch u8cm; // 検索対象がbasic_string型なら-smatch。以下同様。 std::printf("%s\n", srell::regex_search(u8"検索対象となるUTF-8文字列", u8cm, u8re) ? "found!" : "not found..."); srell::u16regex u16re(u"UTF-16文字列による正規表現"); srell::u16cmatch u16cm; std::printf("%s\n", srell::regex_search(u"検索対象となるUTF-16文字列", u16cm, u16re) ? "found!" : "not found..."); srell::u32regex u32re(U"UTF-32文字列による正規表現"); srell::u32cmatch u32cm; std::printf("%s\n", srell::regex_search(U"検索対象となるUTF-32文字列", u32cm, u32re) ? "found!" : "not found..."); srell::u1632wregex u1632wre(L"UTF-16またはUTF-32の文字列による正規表現"); srell::u1632wcmatch u1632wcm; std::printf("%s\n", srell::regex_search(L"検索対象となるUTF-16またはUTF-32の文字列", u1632wcm, u1632wre) ? "found!" : "not found..."); srell::u16wregex u16wre(L"UTF-16文字列による正規表現"); srell::u16wcmatch u16wcm; std::printf("%s\n", srell::regex_search(L"検索対象となるUTF-16文字列", u16wcm, u16wre) ? "found!" : "not found..."); // 上3行と下3行とは排他的。wchar_tが21ビット未満なら上、以上なら下。 srell::u32wregex u32wre(L"UTF-32文字列による正規表現"); srell::u32wcmatch u32wcm; std::printf("%s\n", srell::regex_search(L"検索対象となるUTF-32文字列", u32wcm, u32wre) ? "found!" : "not found...");
SRELL 2.000以降では名前付きキャプチャ (named-capture) 用に、次のメンバ函数がmatch_results
クラスに追加されています。
difference_type length(const string_type &sub) const;
difference_type position(const string_type &sub) const;
string_type str(const string_type &sub) const;
const_reference operator[](const string_type &sub) const;
// 以下はSRELL 2.650以降。
difference_type length(const char_type *sub) const;
difference_type position(const char_type *sub) const;
string_type str(const char_type *sub) const;
const_reference operator[](const char_type *sub) const;
使い方は<regex>にある同名の函数と同じです。ただ引数として括弧の番号ではなく括弧の名前を渡すという点のみが異なります。
// 使用例。 srell::regex e("-(?<digits>\\d+)-"); srell::cmatch m; if (srell::regex_search("1234-5678-90ab-cdef", m, e)) { const std::string by_number(m.str(1)); // std::regexにもある括弧の番号を使ったアクセス。 const std::string by_name(m.str("digits")); // 名前を使って同じ括弧へアクセス。SRELLの独自拡張。 std::printf("results: bynumber=%s byname=%s\n", by_number.c_str(), by_name.c_str()); } // results: bynumber=5678 byname=5678 と表示される。
次のフラグオプションが追加されています。
namespace regex_constants
{
static const syntax_option_type dotall; // (SRELL 2.000以降)
// シングルライン指定。'.' の挙動を変える。ECMAScript/Perl 5の //s
に相当。
static const syntax_option_type unicodesets; // (SRELL 4.000以降)
// vモードを使用する。
}
他のsyntax_option_type
型の値と同様に、この値はbasic_regex
内でも定義されています。
次のerror_type値が追加されています。
namespace regex_constants { static const error_type error_utf8; // (SRELL 2.630以降) //basic_regex
に渡された正規表現中に、不正なUTF-8のシークウェンスが見つかった。 static const error_type error_property; // (SRELL 3.010以降) //\p{...}
または\P{...}
で、対応していないUnicodeプロパティー名または値が指定された。 static const error_type error_noescape; // (SRELL 4.000以降。vモードのみ) // 文字クラス内で( ) [ ] { } / - \ |
は\
を前置してエスケープする必要あり。 static const error_type error_operator; // (SRELL 4.000以降。vモードのみ) // 文字クラスの演算エラー。 // 予約されている二重記号を使用した。または同じ階層で異なる演算をした。 static const error_type error_complement; // (SRELL 4.000以降。vモードのみ) // 文字列の補集合を使おうとした。 //\P{POSName}
、[^\p{POSName}]
、[^\q{strings}]
(POSName
は文字列プロパティー名)が見つかった。 static const error_type error_modifier; // (SRELL 4.007以降) // 埋込フラグを先頭以外で使った。または一つの括弧内で同じフラグを複数回指定した。
SRELL 2.600以降、引数としてBidirectionalIterator
を3つ取るオーヴァーロードが追加されています。
template <class BidirectionalIterator, class Allocator, class charT, class traits> bool regex_search( BidirectionalIterator first, BidirectionalIterator last, BidirectionalIterator lookbehind_limit, match_results<BidirectionalIterator, Allocator> &m, const basic_regex<charT, traits> &e, const regex_constants::match_flag_type flags = regex_constants::match_default); template <class BidirectionalIterator, class charT, class traits> bool regex_search( BidirectionalIterator first, BidirectionalIterator last, BidirectionalIterator lookbehind_limit, const basic_regex<charT, traits> &e, const regex_constants::match_flag_type flags = regex_constants::match_default);
3つ目のイテレータであるlookbehind_limit
は、戻り読み (lookbehind) が行われる際の「逆行して良い限界位置」を指定するためのものです。
違う言い方をしますと、この3イテレータ版では「検索対象範囲 [lookbeind_limit, last)
の途中、first
から検索を始める」という処理が行われます。
const char text[] = "0123456789abcdefghijklmnopqrstuvwxyz"; const char* const begin = text; const char* const end = text + std::strlen(text); const char* const first = text + 10; // 'a' の位置に合わせる。 const srell::regex re("(?<=^\\d+)."); srell::cmatch match; std::printf("matched %d\n", srell::regex_search(first, end, match, re)); // 戻り読みも [first, end) の範囲でのみ行われるのでマッチしない。 std::printf("matched %d\n", srell::regex_search(first, end, begin, match, re)); // beginまで逆行できるのでマッチする。 // 即ち3イテレータ版は、[begin, end) というシークウェンスに対して // firstよりsearchを始める。
上の例にもあります通り3イテレータ版では、^
は first
(第1引数として渡したほう)ではなく begin
(第3引数として渡したほう)にマッチするようになります。
また3イテレータ版使用時には、match_results
のposition()
は第3引数として渡した位置からの距離を返すようになります。これに対してmatch_results
のprefix().first
には、第1引数として渡した位置がセットされます。
※3イテレータ版追加に伴い、SRELL 2.300~2.500で導入していた指定方法は廃止しました。
SRELL 4.009以降、SRELLのbasic_regex
クラスには次のメンバ函数が独自拡張して追加されています。
match()
: srell::regex_match()
と同じ処理を行う。search()
: srell::regex_search()
と同じ処理を行う。replace()
: 元の文字列には手を加えないsrell::regex_replace()
と異なり、渡された文字列中の正規表現にマッチした箇所を直接別の文字列に置換する。split()
: 文字列を分割する。
srell::regex_match()
と同じ処理を行います。basic_regex
型インスタンスをre
とした時、re.match(...)
は、srell::regex_match(..., re, ...)
のように書いたのと同じです。
Overload函数の種類や引数の順番も、引数re
が抜かされる点を除けばregex_match()
のものと同じです。
template <typename BidirectionalIterator, typename Allocator> bool match( const BidirectionalIterator begin, const BidirectionalIterator end, match_results<BidirectionalIterator, Allocator> &m, const regex_constants::match_flag_type flags = regex_constants::match_default) const; // srell::regex_match(begin, end, m, re, flags) に同じ。 template <typename BidirectionalIterator> bool match( const BidirectionalIterator begin, const BidirectionalIterator end, const regex_constants::match_flag_type flags = regex_constants::match_default) const; // srell::regex_match(begin, end, re, flags) に同じ。 template <typename Allocator> bool match( const charT *const str, match_results<const charT *, Allocator> &m, const regex_constants::match_flag_type flags = regex_constants::match_default) const; // srell::regex_match(str, re, flags) に同じ。 bool match( const charT *const str, const regex_constants::match_flag_type flags = regex_constants::match_default) const; // srell::regex_match(str, re, flags) に同じ。 template <typename ST, typename SA, typename MA> bool match( const std::basic_string<charT, ST, SA> &s, match_results<typename std::basic_string<charT, ST, SA>::const_iterator, MA> &m, const regex_constants::match_flag_type flags = regex_constants::match_default) const; // srell::regex_match(s, m, re, flags) に同じ。 template <typename ST, typename SA> bool match( const std::basic_string<charT, ST, SA> &s, const regex_constants::match_flag_type flags = regex_constants::match_default) const; // srell::regex_match(s, re, flags) に同じ。
srell::regex_search()
と同じ処理を行います。basic_regex
型インスタンスをre
とした時、re.search(...)
は、srell::regex_search(..., re, ...)
のように書いたのと同じです。
Overload函数の種類や引数の順番も、引数re
が抜かされる点を除けばregex_search()
のものと同じです。
template <typename BidirectionalIterator, typename Allocator> bool search( const BidirectionalIterator begin, const BidirectionalIterator end, match_results<BidirectionalIterator, Allocator> &m, const regex_constants::match_flag_type flags = regex_constants::match_default ) const; // srell::regex_search(begin, end, m, re, flags) に同じ。 template <typename BidirectionalIterator> bool search( const BidirectionalIterator begin, const BidirectionalIterator end, const regex_constants::match_flag_type flags = regex_constants::match_default ) const; // srell::regex_search(begin, end, re, flags) に同じ。 template <typename Allocator> bool search( const charT *const str, match_results<const charT *, Allocator> &m, const regex_constants::match_flag_type flags = regex_constants::match_default ) const; // srell::regex_search(str, m, re, flags) に同じ。 bool search( const charT *const str, const regex_constants::match_flag_type flags = regex_constants::match_default ) const; // srell::regex_search(str, re, flags) に同じ。 template <typename ST, typename SA, typename MA> bool search( const std::basic_string<charT, ST, SA> &s, match_results<typename std::basic_string<charT, ST, SA>::const_iterator, MA> &m, const regex_constants::match_flag_type flags = regex_constants::match_default ) const; // srell::regex_search(s, m, re, flags) に同じ。 template <typename ST, typename SA> bool search( const std::basic_string<charT, ST, SA> &s, const regex_constants::match_flag_type flags = regex_constants::match_default ) const; // srell::regex_search(s, re, flags) に同じ。 // 以下2つはstd::regex_search()にはありません。 template <typename BidirectionalIterator, typename Allocator> bool search( const BidirectionalIterator begin, const BidirectionalIterator end, const BidirectionalIterator lookbehind_limit, match_results<BidirectionalIterator, Allocator> &m, const regex_constants::match_flag_type flags = regex_constants::match_default) const; // srell::regex_search(begin, end, lookbehind_limit, m, re, flags) に同じ。 template <typename BidirectionalIterator> bool search( const BidirectionalIterator begin, const BidirectionalIterator end, const BidirectionalIterator lookbehind_limit, const regex_constants::match_flag_type flags = regex_constants::match_default ) const; // srell::regex_search(begin, end, lookbehind_limit, re, flags) に同じ。
これまでの2つと異なり、これはsrell::regex_replace()
の略記ではありません。regex_replace()
は元の文字列には手を加えず、置換後の文字列は内部で新規に作ったものをreturnしてくるのに対して、このreplace()
は元の文字列中の正規表現にマッチした箇所を、直接別の文字列に書き換えます。
直接文字列を書き換える点以外は、ECMAScriptのString.prototype.split(RegExpオブジェクト, リミット)
に準拠した動作をします。
置換対象とできるのは、std::basic_string
型またはこれと同じメンバを持つクラスです(.010まではstd::basic_string
型のみでした)。
置換後の文字列の指定方法としては「1) 書式文字列を渡す」「2) コールバック函数を渡す」の2通りがあります。
// charT はbasic_regexの第1テンプレート引数の型。 template <typename StringLike> void replace( StringLike &s, const charT *const fmt_begin, const charT *const fmt_end, const bool global = false) const; template <typename StringLike> void replace( StringLike &s, const charT *const fmt, const bool global = false) const; template <typename StringLike, typename FST, typename FSA> void replace( StringLike &s, const std::basic_string<charT, FST, FSA> &fmt, const bool global = false) const;
global
引数がfalse
の時は、最初に正規表現にマッチした部分だけが置換されます。true
の時は、対象文字列中の正規表現にマッチする箇所すべてが置換の対象となります。
書式文字列fmt
の書式はsrell::regex_replace()
のものと同じで、ECMAScript仕様書の Runtime Semantics: GetSubstitutionに準拠しています。
次の表に掲載されている文字の並びは特殊な意味を持ち、それ以外のものはそのまま置換後の文字列になります。
テキストシンボル | 置換テキスト |
---|---|
$$ |
$ そのもの。 |
$& |
マッチした箇所全体。 |
$` |
マッチした箇所に先行する部分。 |
$' |
マッチした箇所より後方の部分。 |
$1 $2 $3 $4 $5 $6 $7 $8 $9 (後ろに数字が続かぬこと) |
正規表現中の対応する括弧で捕獲された文字列。該当する括弧が何も捕獲していない場合は空文字に置換される。正規表現中のキャプチャ括弧の個数より大きな数が指定された場合は置換されず。 |
$nn (nn は01から99までの範囲) |
正規表現中の対応する括弧で捕獲された文字列。該当する括弧が何も捕獲していない場合は空文字に置換される。正規表現中のキャプチャ括弧の個数より大きな数が指定された場合は置換されず。 |
$<NAME> |
|
// 書式指定による置換例。 #include <cstdio> #include <string> #include "srell.hpp" int main() { const srell::regex re("(\\d)(\\d)"); // 数字が2つ続いている箇所を探す。 std::string text("ab0123456789cd"); re.replace(text, "($2$1)"); // $1と$2との順番を入れ替えて括弧でくくる。 std::printf("Result: %s\n", text.c_str()); return 0; } ---- 実行結果 ---- Result: ab(10)(32)(54)(76)(98)cd
replace()
は書式文字列の代わりにコールバック函数を引数として受け取ると、正規表現にマッチする箇所を見つける度にその函数を呼び出します。
// charT はbasic_regexの第1テンプレート引数の型。 template <typename StringLike, typename RandomAccessIterator, typename MA> void replace( StringLike &s, bool (*repfunc)( std::basic_string<charT, typename StringLike::traits_type, typename StringLike::allocator_type> &replacement_text, const match_results<RandomAccessIterator, MA> &m, void *), void *ptr = NULL) const; template <typename MatchResults, typename StringLike> void replace( StringLike &s, bool (*repfunc)( std::basic_string<charT, typename StringLike::traits_type, typename StringLike::allocator_type> &replacement_text, const MatchResults &m, void *), void *ptr = NULL) const;
コールバック函数のシグネチュアは次の通りです。
// charT はbasic_regexの第1テンプレート引数の型。 bool replacement_function( std::basic_string<charT> &replacemen_text, const match_results<const charT *> &m, // *cmatch用。 void *); bool replacement_function( std::basic_string<charT> &replacemen_text, const match_results<typename std::basic_string<charT>::const_iterator> &m, // *smatch用。 void *);
マッチした箇所の情報はmatch_results
型インスタンスに詰め込まれ、コールバック函数には第2引数として渡されてきます。
match_results
のtypedef
には、const charT *
ベースの*cmatch
系と、std::basic_string<charT>::const_iterator
ベースの*smatch
系とがありますので、コールバック函数のシグネチュアも2種類あります。
第3引数は、replace()
函数の第3引数がそのまま渡されてきます。コールバック函数に何か渡したい時に使います。
コールバック函数側では、置換後の文字列を第1引数に詰めてからreturn
します。戻り値としてtrue
を返すと、引き続き正規表現にマッチする箇所が見つかるたびにコールバック函数が呼ばれ、false
を返すとそれ以降コールバック函数は呼ばれなくなります。
// コールバック函数による置換例。 // %エスケープされた文字列のデコード。 #include <cstdio> #include <string> #include "srell.hpp" bool repfunc(std::string &out, const srell::u8cmatch &m, void *) { out.push_back(std::strtoul(m[1].str().c_str(), NULL, 16)); return true; } int main() { const srell::regex re("%([0-9A-Fa-f]{2})"); std::string c14("%E3%81%82%E3%81%84%E3%81%86%E3%81%88%E3%81%8A"); std::string c9803(c14), c11(c14); re.replace(c9803, repfunc); // C++98/03 re.template replace<srell::smatch>(c11, [](std::string &out, const srell::smatch &m, void *) -> bool { // C++11 out.push_back(std::strtoul(m[1].str().c_str(), NULL, 16)); return true; }); re.template replace<srell::smatch>(c14, [](auto &out, const auto &m, auto) -> bool { // C++14以降 out.push_back(std::strtoul(m[1].str().c_str(), NULL, 16)); return true; }); std::printf("Result(C++98/03): %s\n", c9803.c_str()); std::printf("Result(C++11): %s\n", c11.c_str()); std::printf("Result(C++14-): %s\n", c14.c_str()); return 0; } ---- 実行結果 ---- Result(C++98/03): あいうえお Result(C++11): あいうえお Result(C++14-): あいうえお
コールバック函数へのポインタの代わりにラムダを使用する時は、ラムダの第2引数として受け取りたいmatch_results<RandomAccessIterator, Alloc>
の型をreplace()
のテンプレート実引数として明示的に指定する必要があります。match_results
のテンプレート引数が推論できないためです。
creplace()
と、*smatch系を渡してほしい場合専用のsreplace()
とがありましたが、煩雑化してしまったreplace()
系を整理するため4.013で廃止しました。std::basic_string<charT, ST, SA>
型そのものと、2) コールバック函数に渡してほしいmatch_results<RandomAccessIterator, Alloc>
との2つを、この順番で明示的に指定する必要がありました。
これはbasic_regex
のメンバではなくnamespace srell
の直下にあるクラスですが、replace()
で使うことを意図したものですのでここでご紹介します。
SRELL 4.011以降、str_clip()
というテンプレートクラスが追加されています。これはreplace()
で検索、置換する範囲を制限するためのユーティリティーです。
// str_clip()の使用例。 #include <cstdio> #include <string> #include "srell.hpp" int main() { const srell::regex re("."); std::string text("0123456789ABCDEF"); srell::str_clip<std::string> ctext(text); // テンプレート実引数には、割り当てるbasic_string型インスタンスtextの型 (std::string) を指定。 // pos & countペアでクリップ。4文字目から6文字分。 re.replace(ctext.clip(4, 6), "x", true); std::printf("By pos&count: %s\n", text.c_str()); // "0123xxxxxxABCDEF" // イテレータペアでクリップ。 re.replace(ctext.clip(text.begin() + 6, text.end() - 6), "y", true); std::printf("By iterators: %s\n", text.c_str()); // "0123xxyyyyABCDEF" re.template replace<srell::smatch>(ctext.clip(6, 2), [](std::string &out, const srell::cmatch &, void *) { out = "Zz"; return true; }); std::printf("By lambda: %s\n", text.c_str()); // "0123xxZzZzyyABCDEF" return 0; }
split()
は、検索対象文字列中の「正規表現とマッチする箇所」の前後を分割してゆき、その位置情報を収めたsub_match
型インスタンスを、第1引数として渡された配列コンテナの参照にpushしてゆきます。
次の点を除いて、ECMAScriptのString.prototype.split(RegExpオブジェクト, リミット)
の仕様に準じた動作をします。
limit
が指定されている場合、limit-1
回まではECMAScriptの仕様書通りに振る舞い、残り1回となったら未処理の文字列をまとめてpushする。
split()
の分割個数の上限を指定する機能というのはよく見られるものですが、JavaScriptのそれは少し変わっていて、分割をlimit
回行うとそれ以降のまだ調べていない部分の文字列はそのまま捨ててしまいます。この挙動は個人的にあまり嬉しくありませんので、上のような変更を加えました。
template <typename container, typename ST, typename SA> void split( container &c, const std::basic_string<charT, ST, SA> &s, const std::size_t limit = static_cast<std::size_t>(-1)) const; // 以下2つは4.011で追加。 template <typename container, typename BidirectionalIterator> void split( container &c, const BidirectionalIterator begin, // container::value_type::iteratorと同型もしくはキャスト可能。 const BidirectionalIterator end, const std::size_t limit = static_cast<std::size_t>(-1)) const; template <typename container> void split( container &c, const charT *const str, const std::size_t limit = static_cast<std::size_t>(-1)) const;
結果を受け取るコンテナ型c
は、メンバ函数としてpush_back()
が実装されていれば何でも使えます。
正規表現の中にキャプチャ括弧があった場合はそれらによって捕獲された文字列もpushされます。何も捕獲していない括弧のところも飛ばされず、空文字列がpushされます。
#include <cstdio> #include <string> #include <vector> #include "srell.hpp" template <typename Container> void print(const Container &c) { for (typename Container::size_type i = 0; i < c.size(); ++i) std::printf("%s\"%s\"", i == 0 ? "{ " : ", ", c[i].str().c_str()); std::puts(" }"); } int main() { std::string text("01:23:45"); srell::regex re(":"); std::vector<srell::csub_match> res; // またはsrell::ssub_match. re.split(res, text); // 無制限分割。 print(res); // { "01", "23", "45" } res.clear(); // split()内ではclear()されないので注意。 re.split(res, text, 2); // 2分割。 print(res); // { "01", "23:45" } // JavaScriptの場合 { "01", "23" } になる。 re.assign("(?<=(\\d?)):(?=(\\d?))"); // ':'の前後の文字を捕獲。 res.clear(); re.split(res, text); print(res); // { "01", "1", "2", "23", "3", "4", "45" } text.assign("caf\xC3\xA9"); // "café" re.assign(""); res.clear(); re.split(res, text); // char型の1文字単位で分割。 print(res); // { "c", "a", "f", "\xC3", "\xA9" } srell::u8cregex u8re(""); res.clear(); u8re.split(res, text); // UTF-8の1文字単位で分割。 print(res); // { "c", "a", "f", "é" } return 0; }
SRELL 4.013以降、regex_iterator2
が追加されています。Grep, replace, splitなどをこれ1つでまかなえるように、regex_iterator
に次のような変更を加えたものです。
match_not_null | match_continuous
を付加して、その位置から再度regex_search
する)を削除。assign()
を追加。template <typename BidirectionalIterator, typename BasicRegex = basic_regex<typename std::iterator_traits<BidirectionalIterator>::value_type, regex_traits<typename std::iterator_traits<BidirectionalIterator>::value_type> >, typename MatchResults = match_results<BidirectionalIterator> > class regex_iterator2;
見にくくて分かりづらいかもしれませんが、テンプレート引数の2つ目はbasic_regex
の型、3つ目はmatch_results
の型です。regex_iterator
よりも単純化してあります。
regex_iterator
に倣って次のtypedef
が定義済みです。
typedef regex_iterator2<const char *> cregex_iterator2; typedef regex_iterator2<const wchar_t *> wcregex_iterator2; typedef regex_iterator2<std::string::const_iterator> sregex_iterator2; typedef regex_iterator2<std::wstring::const_iterator> wsregex_iterator2; // charでUTF-8文字列を処理する。 typedef regex_iterator2<const char *, u8cregex> u8ccregex_iterator2; typedef regex_iterator2<std::string::const_iterator, u8cregex> u8csregex_iterator2; // char16_t, char32_t利用可能時のみ定義。 typedef regex_iterator2<const char16_t *> u16cregex_iterator2; typedef regex_iterator2<const char32_t *> u32cregex_iterator2; typedef regex_iterator2<std::u16string::const_iterator> u16sregex_iterator2; typedef regex_iterator2<std::u32string::const_iterator> u32sregex_iterator2; // char8_t利用可能時のみ定義。 typedef regex_iterator2<const char8_t *> u8cregex_iterator2; // std::u8string利用可能時のみ定義。 typedef regex_iterator2<std::u8string::const_iterator> u8sregex_iterator2; // char8_tがない時のみ定義。 typedef u8ccregex_iterator2 u8cregex_iterator2; // std::u8stringがない時のみ定義。 typedef u8csregex_iterator2 u8sregex_iterator2; // WCHAR_MAXが0x10FFFF以上の時のみ定義。 typedef wcregex_iterator2 u32wcregex_iterator2; typedef wsregex_iterator2 u32wsregex_iterator2; typedef u32wcregex_iterator2 u1632wcregex_iterator2; typedef u32wsregex_iterator2 u1632wsregex_iterator2; // WCHAR_MAXが0xFFFF以上、10FFFF未満の時のみ定義。 typedef regex_iterator2<const wchar_t *, u16wregex> u16wcregex_iterator2; typedef regex_iterator2<std::wstring::const_iterator, u16wregex> u16wsregex_iterator2; typedef u16wcregex_iterator2 u1632wcregex_iterator2; typedef u16wsregex_iterator2 u1632wsregex_iterator2;
regex_iterator
と同じように、引数なしのコンストラクタでend-of-sequenceイテレータを作り、それとの比較がtrueになるまでfor
ループでぐるぐる回すという方法も使えますが、もっと簡単な判定方法として「検索が終端まで行ったかどうかを示すdone()
というメンバ変数」が用意されています。
srell::sregex_iterator2 eit; srell::sregex_iterator2 it(text.begin(), text.end, re); // for (; it != eit; ++it) { // 下と同じ。 for (; !it.done(); ++it) { // 何かする。 }
コンストラクタに渡した範囲がstd::basic_string
型インスタンスの一部であり、かつイテレータ作成後に余所でサイズ変更をしていない(メモリの割り当て位置が移動していない)場合は、イテレータのreplace()
メンバ函数により、現在マッチしている箇所 ((*it)[0]
) を別の文字列に置き換えることが出来ます。
regex_iterator2::replace()
はstd::basic_string
型の文字列全体のインスタンスを第1引数、置換用文字列を第2引数として取ります。
// [entire.begin(), entire.end()) 内の // [(*it)[0].first, (*it)[0].second) の範囲を // replacementないし[begin, end)に置換する。 template <typename ST, typename SA> void replace(std::basic_string<char_type, ST, SA> &entire, const std::basic_string<char_type, ST, SA> &replacement); template <typename ST, typename SA> void replace(std::basic_string<char_type, ST, SA> &entire, BidirectionalIterator begin, BidirectionalIterator end); template <typename ST, typename SA> void replace(std::basic_string<char_type, ST, SA> &entire, const char_type *const replacement);
置換により検索対象文字列が伸び縮みした場合はそれに合わせてイテレータ内部の位置情報を修正し、またメモリの再割り当てが発生した場合は位置情報を自動的に作り直します。
regex_iterator2::replace()
の使用例と、regex_iterator
との違いとを示すサンプルプログラムです。
#include <cstdio> #include <string> #include <regex> #include "srell.hpp" template <typename Iterator, typename Regex> void replace(const Regex &re, const std::string &text, const char *const title) { std::string::const_iterator prevend = text.begin(); Iterator it(text.begin(), text.end(), re), eit; std::string out; for (; it != eit; ++it) { out += it->prefix(); out += "."; prevend = (*it)[0].second; } const std::string::const_iterator end = text.end(); out.append(prevend, end); std::printf("[%s] by %s\n", out.c_str(), title); } int main() { std::string text("a1b"); std::regex re1("\\d*?"); srell::regex re2("\\d*?"); replace<std::sregex_iterator>(re1, text, "std::sregex_iterator"); replace<srell::sregex_iterator>(re2, text, "srell::sregex_iterator"); replace<srell::sregex_iterator2>(re2, text, "srell::sregex_iterator2"); srell::sregex_iterator2 it(text, re2); for (; !it.done(); ++it) it.replace(text, "."); // replace()の使用。 std::printf("[%s] by srell::sregex_iterator2::replace()\n", text.c_str()); text = "a1b"; // 上で置換したので戻す。 re2.replace(text, ".", true); std::printf("[%s] by srell::basic_regex::replace()\n", text.c_str()); return 0; } ---- 実行結果 ---- [.a...b.] by std::sregex_iterator [.a...b.] by srell::sregex_iterator [.a.1.b.] by srell::sregex_iterator2 [.a.1.b.] by srell::sregex_iterator2::replace() [.a.1.b.] by srell::basic_regex::replace()
前述の特例によりregex_iterator
を使った置換では "1" の部分が置換されてしまっているのに対して、JavaScript互換の振る舞いをする下3つではそのまま残っています。
イテレータがマッチした箇所のprefixと、最後にマッチした箇所より後ろの部分を集めるとsplit相当の処理となります(下図参照。it
はイテレータ)。
対象文字列 | 間 | マッチ1箇所目 | 間 | マッチ2箇所目 | 間 |
---|---|---|---|---|---|
イテレータ it | 1回目の it->prefix() |
(*it)[0] | 2回目の it->prefix() |
(*it)[0] | 2回目の it->suffix() |
そこでsplitにも対応できるように、上図の青いところを集めやすくするためのヘルパ函数を用意しました。
bool split_ready()
:
現在のit->prefix()
がsplitされた文字列として有効な範囲を指しているか否かを返す。判断基準はECMAScriptのsplit()準拠。
const typename value_type::value_type &remainder(bool only_after_match = false)
:
繰り返し終了後に残余区間を取り出す。上の表で言うところの「2回目のit->suffix()」の部分。
もっとも単純なsplit処理は次の通りです。
for (; !it.done(); ++it) { if (it.split_ready()) list.push_back(it->prefix()); } list.push_back(it.remainder());
他言語のsplitによくある「正規表現に括弧が含まれていた場合は、それがキャプチャしたものも追加する」処理や、分割回数 (LIMIT) を指定する機能も追加したい場合は次のように書きます。
for (std::size_t count = 0; !it.done(); ++it) { if (it.split_ready()) { if (++count == LIMIT) break; list.push_back(it->prefix()); for (std::size_t i = 1; i < it->size(); ++i) { if (++count == LIMIT) { list.push_back(it.remainder(true)); // 上でpush済みのprefix()区間を除外するため // trueにする。 return; } list.push_back((*it)[i]); } } } list.push_back(it.remainder());
ヘルパーを使っても長くなってしまうので、さらなるヘルパ函数を用意しました。
上のコードは次のように書けます。
std::size_t count = 0;
for (it.split_begin(); !it.done(); it.split_next()) {
if (++count == LIMIT)
break;
list.push_back(it.split_range());
}
list.push_back(it.split_remainder()); // remainder()ではなくsplit_remainder()であることに注意。
なおこのsplit対応はおまけです。「マッチする箇所を渡り歩く」というイテレータの本分と両立できない、もしくは難しいケースが発覚した場合には、split用のヘルパー函数は隠し機能としたり削除したりする可能性もあります。
ECMAScriptの正規表現(およびその元となったPerlの正規表現)では通常、バックトラッキングと呼ばれる方法を使って照合が行われます。このバックトラッキング方式には、「繰り返しが入れ子になっている正規表現」や「量指定子(回数指定)を伴う文字ないし文字クラスが連続していて、かつそれらが互いに排他的な集合になっていない正規表現」で検索を行うと、オートマトンが著しい長考に入ってしまうことがあるという問題が存在します。
次のような例が有名です。
残念ながらこの現象に対しては、あらゆる状況に適用できる根本的な解決策というものが見つかっていません。そこで制御が長時間返ってこなくなる事態を避けるため、SRELLは特定の位置からの照合が一定回数以上失敗すると、regex_error(regex_constants::error_complexity)
を throw
するようになっています。
回数の既定値は16777216(256の3乗)ですが、アルゴリズム函数(regex_search()
やregex_match()
など)に渡すbasic_regex
型インスタンスのlimit_counter
変数に任意の値を代入することで変更することも出来ます。
<regex>は、次の6つの正規表現エンジンから選択する仕組みになっています(既定はECMAScript)。
ECMAScript (既定) |
ECMAScriptの正規表現からUnicodeに依存した要素(\sの定義など)を取り除き、代わりにlocale依存の処理とC++の規格書29.13 [re.grammar] にある拡張とを追加したもの。
|
---|---|
basic |
POSIXのbasic regular expression相当。 |
extended |
POSIXのextended regular expression相当。 |
awk |
POSIXのawk相当。 |
grep |
POSIXのgrep相当。 |
egrep |
POSIXのegrep相当。 |
一方SRELLにはこのような選択肢はなく、常にECMAScript互換のエンジンで動きます。この関係で、<regex>で定義されているフラグオプションのうち次のものは、たとえ指定してもSRELLでは無視されます。
syntax_option_type
(かつbasic_regex
のflag_type
)nosubs
, optimize
, collate
, basic
, extended
, awk
, grep
, egrep
(即ちicase
, multiline
以外のすべて)match_flag_type
match_any
, format_sed
<regex>のECMAScriptモードとSRELLとの違いは次の通りです。
同じECMAScriptベースでありながらも<regex>とSRELLとはどちらもどちらの上位互換になっていません。
余分なオーヴァーヘッドを避けるため、SRELLでは次のメンバ函数の実装が簡略化されています。
basic_regex::assign()
: <regex>では例外がthrow
された場合(文字列からのコンパイルに失敗した場合)、*this
は元の値が維持されることになっていますが([re.regex.assign] の11)、SRELLでは*this
はclearされてしまいます。これはコンパイル開始時に元の内容を退避しないためです。match_results::operator[](size_type n)
: <regex>はn >= match_results::size()
(つまり配列の範囲外アクセス)でも安全性を保証していますが([re.results.acc] の8)、SRELLでは保証されません。安全性を保証するには、範囲外アクセスに備えるためだけにダミーのsub_match
型メンバ変数が必要となるためです。
basic_regex
のtypedef
のうちUnicode用のprefix (u8-
, u8c-
, u16-
, u16w-
, u1632w-
, u32-
, u32w-
) を持たぬ型は、入力文字列をUnicode値の連続として解釈します。
例えばCHAR_BIT
が8
である環境でsrell::regex
(srell::basic_regex<char>
) を使うと、入力文字列に現れる0x00
~0xFF
はそのままU+0000~U+00FFとして解釈されます。
UnicodeのU+0000~U+00FFはISO-8859-1と同じですので、結果的にsrell::regex
はISO-8859-1に対応しているとも言えます。
またsrell::regex
は、バイナリデータ中から特定のパターンを見つけ出すような場合にも使うことが出来ます。
srell::wregex
(srell::basic_regex<wchar_t>
) も同じで、文字列中の0x00
~WCHAR_MAX
までがそのまま同じ値のUnicode値として解釈されます。
WinAPIのW函数と組み合わせて使うのに適しているのは、UTF-16対応の srell::u16wregex
または srell::u1632wregex
です。srell::wregex
は事実上UCS-2対応となってしまいます。
C++11より前のコンパイラでは、wchar_t
が16ビット以上かつ21ビット未満の環境ならu8c-型とu16w-型とが、wchar_t
が21ビット以上ある環境ならu8c-型とu32w-型とがそれぞれ利用可能です。もっともそのような環境でも、SRELLをincludeする前に次のようなコードを書いておけば、u8c-型、u16-型、u32-型を3つとも利用することができます。
typedef unsigned short char16_t; // 16ビット以上ある符号無しの型をtypedefする。 typedef unsigned long char32_t; // 32ビット以上ある符号無しの型をtypedefする。 namespace std { typedef basic_string<char16_t> u16string; typedef basic_string<char32_t> u32string; } #define SRELL_CPP11_CHAR1632_ENABLED // 手動でonにする。
ちなみにUTF-8やUTF-16に固有の処理をしているのは、basic_regex
に対してテンプレート引数として渡されるu8regex_traits
、u16regex_traits
です。このことを利用して、たとえば basic_regex<uint32_t, u16regex_traits<uint32_t> >
のような「UTF-16文字列をuint32_t
型の配列で処理する型」なるものを作ることもできます。
SRELLの動作確認には主にVCとMinGWを使用していますが、Compiler Explorer上で簡単にテストしてみたところによりますと、2019年5月現在、少なくとも次の環境でもSRELL 2.200を使ったサンプルコード(srell::u16regexを使用してUTF-16文字列に対する正規表現検索をするもの)からバイナリの生成ができるようです。
charやwchar_tのみを使う場合、Compiler Explorerで一番古いと思われるx86-64 gcc 4.1.2でもコンパイル可能なようです。
SRELL 2.300~2.500には次のような拡張がありました。
match_results
: BidirectionalIterator lookbehind_limit
(Lookbehindの逆行限界指定)match_flag_type
: match_lblim_avail
(アルゴリズム函数に対して前記match_results.lookbehind_limit
が有効であることを伝えるためのフラグオプション)
match_lblim_avail
フラグがセットされると戻り読み (lookbehind) が行われる際、アルゴリズム函数に渡したmatch_results
型インスタンスのlookbehind_limit
メンバの値を「逆行できる限界」と見なすようになります。
const char text[] = "0123456789abcdefghijklmnopqrstuvwxyz"; const char* const begin = text; const char* const end = text + std::strlen(text); const char* const first = text + 10; // 'a' の位置に合わせる。 const srell::regex re("(?<=^\\d+)."); srell::cmatch match; match.lookbehind_limit = begin; std::printf("matched %d\n", srell::regex_search(first, end, match, re)); // 戻り読みも [first, end) の範囲でのみ行われるのでマッチしない。 std::printf("matched %d\n", srell::regex_search(first, end, match, re, srell::regex_constants::match_lblim_avail)); // match.lookbehind_limitまで逆行できるのでマッチする。 // 即ち match_lblim_avail指定時は [match.lookbehind_limit, end) というシークウェンスに対して // firstよりsearchを始める。
上の例にもあります通りmatch_lblim_avail
が指定された時は、^
は first
ではなく match.lookbehind_limit
にマッチするようになります。
SRELL 2.600以降では、regex_searchの引数としてlookbehindの逆行限界位置を指定できるようにしました。これに伴い上記の方法は廃止しました。
ちなみに2.600で導入した「3イテレータ方式」を最初から採用しなかった理由についてはこちらに記してあります。
SRELL 2.002までu8-というprefixは、「char型の文字列をUTF-8文字列として扱うクラス型」であることを意味していました。しかしC++20よりchar8_t
型が導入されることが決まり、今後u8-というprefixは標準ライブラリでもchar8_t
型用に使われるようになってゆくことが予想されます。
そこで標準ライブラリの命名規則との不整合を避けるため、SRELL 2.100以降では「char
型の文字列をUTF-8文字列として扱う」ことを意味するprefixをu8-からu8c-に変更しました。
basic_regex
: u8cregex
match_results
: u8ccmatch
, u8csmatch
sub_match
: u8ccsub_match
, u8cssub_match
regex_iterator
: u8ccregex_iterator
, u8csregex_iterator
regex_token_iterator
: u8ccregex_token_iterator
, u8csregex_token_iterator
一方u8-というprefixはSRELL 2.100以降、char8_t
型と関連づけられるようになっています。ただしお使いのコンパイラがchar8_t
に未対応である場合、後方互換のためu8-というprefix付きのクラスは、対応するu8c-付きクラスの単なるtypedef
として定義されるようになっています。
Prefix | SRELL 2.002まで | SRELL 2.100以降 | |
---|---|---|---|
char8_t未対応コンパイラ | char8_t対応コンパイラ | ||
u8- | char 型の文字列をUTF-8として扱う |
char8_t 型の文字列をUTF-8として扱う |
|
u8c- | (Prefix自体未使用) | char 型の文字列をUTF-8として扱う |
SRELLのversion 1.nnnでは、ECMAScript 2017 (ES8) 規格書の21.2 RegExp (Regular Expression) Objects で定義されている正規表現に固定幅の戻り読み (lookbehind assertions) を加えたものが利用できました。
ただ次のような経緯により、SRELL 1.nnnとSRELL 2.000以降とでは戻り読みの挙動に違いがあります。
ECMAScript (JavaScript) の仕様を策定しているTC39はRegExpに追加する戻り読みについて、Perl5, Pythonなど多くのスクリプト言語が採用している固定幅限定の戻り読みではなく、制限のない可変幅の戻り読みを採用しました。
一見すると後者は前者の上位互換のように思われますが、実のところこれらは次のような場合に異なる結果をもたらします。
"abcd" =~ /(?<=(.){2})./
// 固定幅の戻り読みなら: $& = "c", $1 = "b".
// オートマトンは戻り読み内でも通常通り左から右へと文字列を照合してゆくため、
// "c" の直前にある "b" が「$1によって最後にキャプチャされたもの」となる。
// 可変幅の戻り読みなら: $& = "c", $1 = "a".
// オートマトンは戻り読み内では右から左へと走るため、"c" から2つ離れた
// "a"が「$1によって最後にキャプチャされたもの」となる。
SRELL 1が独自拡張として採用していたのは固定幅の戻り読みでした。対してSRELL 2.000以降には、RegExpの機能拡張に追随して可変幅の戻り読みが実装されています。このためSRELL 1.401 → SRELL 2.000では breaking change が発生しています。
以下は外部のサイトです。
(?imsx-imsx)
は除外され、(?imsx-imsx:subexpression)
形式のみの対応とすることに。i
フラグのオンオフはSRELLの実装と相性が悪く、正式に仕様の一部となった暁にはどう対応したものか思案中です。
\Z
は除外して\A, \z
のみとすることに。
\p{Line_Terminator_sequence}
のような選択肢も考慮すべきとの声が出て現状維持。