SRELL

 SRELL (std::regex-like library) はC++用の正規表現テンプレートライブラリです。

目次

概要

std::regexと同じクラス構成

 SRELLはECMAScript (JavaScript) 互換の正規表現エンジンを、"std::regex"(C++11で導入された正規表現ライブラリ)とクラス構成が同じになるようにラッピングしたものです。クラスデザインが同じですので、std::regexや、その基となったboost::regexと同じ感覚で扱えます(APIもJavaScript風にしたテンプレートライブラリもあります)。

Unicodeに特化した実装

 SRELLはUnicodeに特化した正規表現ライブラリです。

  • 特別な設定をせずとも既定でUTF-8/UTF-16/UTF-32文字列が扱えます。'.' がUTF-16文字列でサロゲートペアの片割れだけにマッチしたり、UTF-8文字列のコードユニットにマッチしたりするようなことがありません。
  • 文字クラス内でプレーン1以降の文字も [丈𠀋] のように指定できます。また [\u{1b000}-\u{1b0ff}] のような範囲指定もできます。
  • ギリシア文字のΣのように小文字が2種類ある字(u+03c2 [ς] と u+03c3 [σ])や、クロアチア語で使われるラテン文字DŽのように、「大文字DŽ (upper-case)」・「小文字dž (lower-case) 」に加えて、「頭文字Dž (title-case)」なる第3のcaseがある文字でも、icase検索(大文字・小文字を区別しない検索)時にきちんと処理されます。

※ちなみにC++11以降のstd::regexは、us-asciiやiso-8859-*のような1文字が固定長の文字コードを前提としています。そのため1文字が可変長であるUTF-8やUTF-16の文字列はうまく扱えません。

 C++11に向けた改訂作業の中でもregexは比較的初期に提案された拡張であったことから、まったくと言って良いほどC++11の新機能に依存していません。そのためC++11より前のコンパイラであっても、C++のテンプレートを正しく解釈するものであればSRELLは利用可能です(動作確認済みコンパイラのうちもっとも古いものはVC++2005です)。

Download

使い方

 パスの通ったところに 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アーカイヴ内の reame_ja.txt も併せてご覧ください。

C++11以降の機能

 C++11以降で導入された機能のうち、SRELLが利用することもあるのは以下の4つです。

 2020年 6月現在、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*を定義しておくと、対応する機能を強制的にオンにすることが出来ます

参考情報

 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の正規表現

 SRELL 2ではECMAScript 2020 (ES11.0) のRegExp互換の正規表現が使えます(uフラグは常に指定されていると見なされる)。

 具体的には次の通りです

SRELLで使用可能な正規表現一覧
文字
.

改行以外の文字にマッチ(ECMAScriptにおける改行文字は、U+000A, U+000D, U+2028, U+2029の4文字)。
パターンコンパイル時にdotallオプションが指定されている時は、前記4文字も含むすべての文字にマッチする([\u{0}-\u{10ffff}]と等価)。Perl 5の//sに相当。

Note: dotallオプションフラグはRegExpにES2018/ES9.0で導入された機能です。SRELLではヴァージョン2.000以降で使用可能です。

\0

NULL文字 (\u0000) にマッチ。

\t

水平タブ (\u0009) にマッチ。

\n

Line Feed (\u000a) にマッチ。

\v

垂直タブ (\u000b) にマッチ。

\f

Form Feed (\u000c) にマッチ。

\r

Carriage Return (\u000d) にマッチ。

\cX

(Xの文字コード & 0x1f) に相当するコントロール文字にマッチ。Xの範囲は [A-Za-z] のみ有効。
\c の後ろにA-Zまたはa-zが続いていない時はerror_escapethrowされてくる。

\\

バックスラッシュそのもの (\u005c) にマッチ。

\xHH

UTF-16におけるコードユニット値が、2桁の16進数HHで表される値に等しい文字にマッチ。
\x の後ろに2桁の16進数が続いていない時はerror_escapethrowされてくる。

UTF-16において0x00-0xFFのコードユニット値はそれぞれU+0000~U+00FFの文字を表すので、この表現は事実上Unicodeのコードポイント値を表すとも言える。

\uHHHH

Unicodeのコードポイント値が、4桁の16進数HHHHで表される値に等しい文字にマッチ。
\u の後ろに4桁の16進数が続いていない時はerror_escapethrowされてくる。

SRELL 2.500以降:連続する\uHHHHがUTF-16におけるサロゲートペアを構成している場合は、そのペアによって表されるUnicode値に変換される。例えば /\uD842\uDF9F//\u{20B9F}/ と解釈される。

\u{H...}

1桁以上の16進数H...で表されるUnicodeのコードポイントにマッチ。
\u{...}{} 内が1桁以上の16進数ではない時や、コードポイントの上限値 (0x10FFFF) を超えている時、閉じ '}' がない時などにはerror_escapeがthrowされてくる。

Note: ECMAScript 6にて追加された表現です。提案書の段階では{...}内は「1~6桁の16進数」とされていたのですが、ECMAScript仕様への追加が決まった際に「1桁以上の16進数」に変更されていたようです。この変更に長らく気づかなかったため、SRELL 2.001までは提案書に基づく実装となっています。

\

\^ $ . * + ? ( ) [ ] { } | / のうちのどれかが続いている時は、その続いている文字そのものを表す。正規表現において特殊な意味を持つ字の特殊性を失わせ、文字通りに認識させたい時に使う('/' も含まれているのはおそらくECMAScriptでは正規表現を // で囲うため)。
後述する文字クラス内では前記14字に加えて '-'"\-" の形で使える。

^$.*+?()[]{}|\/
以外の文字

その文字そのものを表す。

選択
A|B

正規表現AまたはBにマッチ。/abc|def|ghi?|jkl?/ のように '|' はいくつでも並べることが出来る。
'|' によって区切られた各正規表現ブロックは左から右へと順番にマッチングが試みられ、最初にマッチングが成功したもののみが採用される。
たとえば "abcdef" に対して /abc|abcdef/ でマッチングを行った場合、結果は "abc" となる。

文字クラス
[]

文字クラス。文字集合。

  • [ABC]……ABCかにマッチ。
  • [^DEF]……最初が^の時は補集合。この例の場合DでもEでもFでもない文字にマッチ。
  • [G^H]……冒頭以外にある^^そのものを表す。この例の場合G^Hかにマッチ。
  • [I-K]……IJKかにマッチ。文字1-文字2という並びは「文字1のUnicodeにおけるコードポイント値から文字2の同コードポイント値までの範囲に含まれる文字のどれか」を意味する。
  • [-LM]……上記のような並び以外に位置する--そのものを表す。この例の場合-LMかにマッチ。
  • [N-P-R]……範囲指定直後の--そのものを表す。この例の場合はN, O, P, -, Rのいずれかにマッチ。Qは含まれず。
  • [S\-U]……'S''-''U'かにマッチ。\でエスケープされた'-''-'そのものを表す("\-"は文字クラス内でのみ使用可能)。
  • [.|({]…….|({かにマッチ。これらも文字クラスの中ではその特殊性を失う。
  • []……空集合。どの文字にもマッチせぬため、これが現れると照合は常にそこで失敗する。
  • [^]……空集合の補集合。どの文字にもマッチする。[\0-\u{10FFFF}]と同じ。

Perlの正規表現には「'[' の直後にある ']'']' そのものを表す」という特例があるが、ECMAScriptの正規表現にはそのような例外はない。従って ']' を文字クラスに含めるには常に '\' でエスケープして "\]" と書く必要がある。

'['']' とが非対称な時にはerror_brackthrowされる。また [b-a] のように範囲指定がおかしい時にはerror_rangethrowされる。

定義済み文字クラス
\d

[0-9]に同じ。[\d!"#$%&'()] のように文字クラス内([]の中)でも使用可能。

\D

[^0-9]に同じ。\d同様に文字クラス内([]の中)でも使用可能。

\s

[ \t\n\v\f\r\u00a0\u1680\u2000-\u200a\u2028-\u2029\u202f\u205f\u3000\ufeff]に同じ。\d同様に文字クラス内でも使用可能。

Note: 厳密にはWhiteSpaceとLineTerminatorとに一致します。今後UnicodeカテゴリのZsに新たな文字が追加されることがあれば、その都度WhiteSpaceの右辺値は増えます。

\S

[^ \t\n\v\f\r\u00a0\u1680\u2000-\u200a\u2028-\u2029\u202f\u205f\u3000\ufeff]に同じ。\d同様に文字クラス内でも使用可能。

\w

[0-9A-Za-z_]に同じ。\d同様に文字クラス内でも使用可能。

\W

[^0-9A-Za-z_]に同じ。\d同様に文字クラス内でも使用可能。

\p{...}

...の部分で指定されたUnicode property値を持つ文字にマッチ。例えば \p{scx=Hiragana} はUnicodeに存在するあらゆるひらがなにマッチする。\d同様に文字クラス内でも使用可能。

Note: ES2018/ES9.0でRegExpに導入された表現で、SRELL 2.000以降で使用可能です。
2020年現在、ECMAScriptもUnicodeも毎年新版がリリースされていますが、Unicodeの新版がリリースされる時期よりもECMAScriptの新版の仕様が確定する時期のほうが少し早いため、ECMAScriptの仕様書に掲載される「\p\Pで利用可能な値一覧」はほぼ1年遅れ、1つ前のUnicode仕様に準拠しているという状況が続いています。
ECMAScript仕様書のドラフトにはその時点で最新のUnicode規格に準拠した値一覧が掲載されていますので、SRELLではこちらを参照し、ECMAScriptの仕様書に翌年以降掲載されることになる値にも対応しています。

\P{...}

...の部分で指定されたUnicode property値を持たぬ文字にマッチ。\d同様に文字クラス内でも使用可能。

Note: ES2018/ES9.0でRegExpに導入された表現で、SRELL 2.000以降で使用可能です。

量指定子(回数指定)
*
*?

直前の正規表現による照合を0回以上繰り返す。*は最長一致を優先、*?は最短一致を優先する。

先行する表現なしにいきなり回数指定が現れた時にはerror_badrepeatthrowされる。以下5つも同じ。

+
+?

直前の正規表現による照合を1回以上繰り返す。+は最長一致を優先、+?は最短一致を優先する。

?
??

直前の正規表現による照合を0回ないし1回繰り返す。?は最長一致を優先、??は最短一致を優先する。

{n}

直前の正規表現による照合をきっちりn回繰り返す。

'{''}' とが非対称な時にはerror_bracethrowされる。以下2つも同じ。

{n,}
{n,}?

直前の正規表現による照合をn回以上繰り返す。{n,}は最長一致を優先、{n,}?は最短一致を優先する。

{n,m}
{n,m}?

直前の正規表現による照合をn回以上・m回以下繰り返す。{n,m}は最長一致を優先、{n,m}?は最短一致を優先する。

{3,2}のように範囲指定がおかしい時にはerror_badbracethrowされる。

括弧・後方参照・グループ化
(...)

正規表現のグループ化および文字列の捕獲。正規表現全体において開き括弧 '(' が左のほうにあるものから順に、各括弧には1, 2, 3...と参照用の番号が自動的に割り振られ、括弧内の正規表現にマッチした文字列をその番号によって正規表現中の他の場所から参照できる。
'('')' とが非対称な時にはerror_parenthrowされる。

括弧自身またはその外側の正規表現に繰り返し指定がある場合、捕獲した文字列はループのたびに未定義値相当にクリアされる。そのためキャプチャした文字列を次のループに持ち越すことは出来ない。

\N
(※Nは正の
整数)

後方参照。\の後ろに1-9で始まる十進数が続く時は、対応する番号の()で捕獲した文字列を使って照合が行われる。対応する番号の括弧が正規表現中に存在していない時はerror_backrefthrowされる。
例えば /(と|ト).\1/ は、「とまと」や「トマト」にはマッチするが、「トマと」にはマッチしない。

ECMAScriptの正規表現では、文字列を捕獲する括弧が対応する後方参照よりも先行している必要はない。そのため /\1(abc)//(abc\1)/ のような表現も有効でありエラーとはならない。

対応する括弧が何も捕獲していない時、後方参照は未定義値 (undefined) を参照しているものとされる。これは空文字相当として扱われ、照合は常に成功する。

(?<NAME>...)

名前付きの (...)。括弧内の正規表現とマッチした文字列は、括弧の番号に加えてNAMEという名前でも参照できるということ以外は (...) に同じ。
例えば /(?<year>\d+)\/(?<month>\d+)\/(?<day>\d+)/ という正規表現の場合、最初の括弧は\1という表現でも\k<year>という表現でも参照できる。

Note: ES2018/ES9.0でRegExpに導入された表現で、SRELL 2.000以降で使用可能です。

\k<NAME>

NAMEという名前の括弧によって捕獲された文字列を参照する。該当する括弧が正規表現中に存在していなければerror_backrefthrowされる。

Note: ES2018/ES9.0でRegExpに導入された表現で、SRELL 2.000以降で使用可能です。

(?:...)

グループ化。(...) とは異なりグループ化のみを行って文字列の捕獲は行わない。そのため後方参照用の番号も割り振られない。
たとえば /白(?:い|く|かった)/ は、「白い・白く・白かった」のいずれかにマッチするが、送り仮名の部分を後から参照することはできない。文字列の捕獲を行わぬかわりに照合処理が少し速くなる。

位置にマッチするもの
^

文字列の最初にマッチ。
multilineオプション指定時には、それに加えて文字列中のあらゆる改行の直後(行頭)にもマッチ。

$

文字列の最後にマッチ。
multilineオプション指定時には、それに加えて文字列中のあらゆる改行の直前にもマッチ。

\b

文字クラスの外側では\w\Wとの境界にマッチ。
文字クラスの内側ではBEL (\u0008) にマッチ。

\B

文字クラスの外側では\bがマッチしないところにマッチ。
文字クラスの内側で使うとerror_escapethrowされる。

(?=...)

肯定先読み。たとえば /白(?=い|く|かった)/ は、後ろに「い・く・かった」のいずれかが続く「白」にマッチする。

(?!...)

否定先読み。たとえば /白(?!い|く|かった)/ は、後ろに「い・く・かった」のいずれもが続かない「白」にマッチする(白黒、白鳥等)。

(?<=...)

肯定戻り読み。たとえば /(?<=あん|アン)パン/ は、前に「あん・アン」のいずれかが先行する「パン」にマッチする。

Note: SRELL 1では、(...)内は/(?<=abc|def)//(?<=\d{2})/のような固定幅の文字列にマッチする表現のみ指定可能です。固定幅でない時はerror_lookbehindthrowされてきます。SRELL 2.000以降にはこのような制限はありません。

(?<!...)

否定戻り読み。たとえば /(?<!あん|アン)パン/ は、前に「あん・アン」のいずれも先行しない「パン」にマッチする(餡パン、フライパン、シャンパン等)。

Note: SRELL 1では、(...)内は固定幅の文字列のみ指定可能。固定幅でない時はerror_lookbehindthrowされてきます。SRELL 2.000以降にはこのような制限はありません。

std::regexとの相違点

Unicode対応のためのSRELL拡張

 Unicode対応のために、次のような型がSRELLには追加されています。

基本3クラス (basic_regex, match_results, sub_match) のtypedef一覧
basic_regexmatch_resultssub_match 文字列の解釈 備考
char u8cregex u8ccmatch
u8csmatch
u8ccsub_match
u8cssub_match
UTF-8 SRELL_CPP20_CHAR8_ENABLEDマクロ非定義時は、それぞれu8regex, u8[cs]match, u8[cs]sub_matchという別名も定義される。
wchar_t u16wregex u16wcmatch
u16wsmatch
u16wcsub_match
u16wssub_match
UTF-16 WCHAR_MAX0xFFFF以上、0x10FFFF未満の場合のみ。
u32wregex u32wcmatch
u32wsmatch
u32wcsub_match
u32wssub_match
UTF-32 WCHAR_MAX0x10FFFF以上の場合のみ。
char8_t u8regex u8cmatch
u8smatch
u8csub_match
u8ssub_match
UTF-8 SRELL_CPP20_CHAR8_ENABLEDマクロ定義時のみchar8_tが使われる。非定義時は前出のu8c*型の単なる別名となる。
char16_t u16regex u16cmatch
u16smatch
u16csub_match
u16ssub_match
UTF-16 SRELL_CPP11_CHAR1632_ENABLEDマクロ定義時のみ。
char32_t u32regex u32cmatch
u32smatch
u32csub_match
u32ssub_match
UTF-32

 SRELL 2.630以降、正規表現中に不正なUTF-8のシークウェンスがあると basic_regexregex_error(regex_constants::error_utf8)throwするようになっています(regex_constants::error_utf8 はSRELLの拡張で<regex> にはありません)。

 各prefixの意味するところは次の通りです。

  • u8c: char型配列またはstd::string型インスタンスをUTF-8文字列として扱う(SRELL 2.100で導入。SRELL 2.002まではこれがu8-というprefixを使用していました)。
  • u16w: wchar_t型配列またはstd::wstring型インスタンスをUTF-16文字列として扱う(WCHAR_MAX0xFFFF以上0x10FFFF未満の時のみ定義される)。
  • u32w: wchar_t型配列またはstd::wstring型インスタンスをUTF-32文字列として扱う(WCHAR_MAX0x10FFFF以上の時のみ定義)。
  • u8: コンパイラがchar8_t型に対応しているかどうか(SRELL_CPP20_CHAR8_ENABLEDマクロ定義の有無で判断)で次のように変わります。
    • char8_t対応なら:char8_t型配列またはstd::u8string型インスタンスをUTF-8文字列として扱う。
    • char8_t未対応なら:前出のu8c-に同じ。単なる別名としてtypedefされる。
  • u16: char16_t型配列またはstd::u16string型インスタンスをUTF-16文字列として扱う。
  • u32: char32_t型配列またはstd::u32string型インスタンスをUTF-32文字列として扱う。

 先の表では省略しましたが、このルールに基づいてregex_iteratorregex_token_iteratorでも同じようにu(8c?|16w?|32w?) prefixの付いた型がtypedefされています。

 Unicode対応版の基本的な使い方は次の通りです。

srell::u8regex u8re(u8"UTF-8文字列による正規表現");
srell::u8cmatch u8cm;
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::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...");
			

 C++11より前のコンパイラでは、wchar_tが16ビット以上かつ21ビット未満の環境ならu8c-型とu16w-型とが、wchar_tが21ビット以上ある環境ならu8c-型とu32w-型とがそれぞれ利用可能です。もっともそのような環境でも、SRELLをincludeする前に次のようなコードを書いておけば、u8c-型、u16-型、u32-型を3つとも利用することができます。

typedef uint_least16_t char16_t;    //  16ビット以上ある符号無しの型をtypedefする。
typedef uint_least32_t 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_traitsu16regex_traitsです。このことを利用して、たとえば basic_regex<uint32_t, u16regex_traits<uint32_t> > のような「UTF-16文字列をuint32_t型の配列で処理する型」なるものを作ることもできます。

名前付きキャプチャ用のSRELL拡張

 SRELL 2では名前付きキャプチャ (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 2の独自拡張。

    std::printf("results: bynumber=%s byname=%s\n", by_number.c_str(), by_name.c_str());
}
//  results: bynumber=5678 byname=5678 と表示される。
			
syntax_option_typeへのSRELL拡張

 SRELL 2には次のフラグオプションが追加されています。

namespace regex_constants
{
    static const syntax_option_type dotall; //  シングルライン指定。
        //  シングルラインモード用フラグオプションを参照。
}
			

 他のsyntax_option_type型の値と同様に、この値はbasic_regex内でも定義されています。

dotall(シングルラインモード指定用フラグオプション)

 正規表現パターンのコンパイル時に dotall フラグオプション(regex_constants::syntax_option_type型またはbasic_regex::flag_type型)が指定されていると '.' の挙動が変わります
 このフラグオプションはPerl 5の //s に相当します。

regex_searchへのSRELL拡張

 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) が行われる際の「逆行して良い限界位置」を指定するためのものです。

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_resultsposition()は第3引数として渡した位置からの距離を返すようになります。これに対してmatch_resultsprefix().firstには、第1引数として渡した位置がセットされます。

※3イテレータ版追加に伴い、SRELL 2.300~2.500で導入していた指定方法は廃止しました。

正規表現エンジンとフラグ

 <regex>は、次の6つの正規表現エンジンから選択する仕組みになっています(既定はECMAScript)。

<regex>の正規表現エンジン
ECMAScript
(既定)

ECMAScriptの正規表現からUnicodeに依存した要素(\sの定義など)を取り除き、代わりにlocale依存の処理とC++の規格書29.13 [re.grammar] にある拡張とを追加したもの。

\uxxxxのような表現も使用可能ではあるものの、現在のlocaleの文字集合にない文字が指定された場合どうなるのかは不明。

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_regexflag_type

  • icasemultiline以外のすべて

match_flag_type

  • match_any
  • format_sed

 <regex>のECMAScriptモードとSRELLとの違いは次の通りです。

  • <regex>のECMAScriptモード: ECMAScript 3rd editionのRegExpの仕様から「Unicode依存の定義」を取り去り、「localeへの依存」「[:クラス名:], [.クラス名.], [=クラス名=]という表現」を追加。
  • SRELL 2: ECMAScript 2018以降のRegExpの仕様に準拠。
  • SRELL 1: ECMAScript 2017のRegExpの仕様に「固定幅の戻り読み」を追加。

 同じECMAScriptベースでありながらも<regex>とSRELLとはどちらもどちらの上位互換になっていません。

SRELLとchar, wchar_t

 u8regex, u8cregex, u16regex, u16wregex以外の型は、入力文字列をUnicode値の連続として解釈します。例えばCHAR_BIT8である環境でsrell::regex (srell::basic_regex<char>) を使うと、入力文字列に現れる0x000xFFはそのまま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>) も同じで、文字列中の0x00WCHAR_MAXまでがそのまま同じ値のUnicode値として解釈されます。

長考対策

 ECMAScriptの正規表現(およびその元となったPerlの正規表現)では通常、バックトラッキングと呼ばれる方法を使って照合が行われます。このバックトラッキング方式には、「量指定子(回数指定)が入れ子になっている正規表現」や「量指定子を伴う文字ないし文字クラスが連続していて、かつそれらが互いに排他的な集合になっていない正規表現」で検索を行うと、オートマトンが著しい長考に入ってしまうことがあるという問題が存在します。

 次のような例が有名です。

  • "aaaaaaaaaaaaaaaaaaaaaaaaaaaaa" =~ /(a*)*b/
  • "aaaaaaaaaaaaaaaaaaaaaaaaaaaaa" =~ /a?a?a?a?a?a?a?a?a?a?a?a?a?a?a?a?a?a?a?a?a?a?a?a?a?a?a?a?a?aaaaaaaaaaaaaaaaaaaaaaaaaaaaa/

 残念ながらこの現象に対しては、あらゆる状況に適用できる根本的な解決策というものが見つかっていません。そこで制御が長時間返ってこなくなる事態を避けるため、SRELLは特定の位置からの照合が一定回数以上失敗すると、regex_error(regex_constants::error_complexity)throw するようになっています。
 回数の既定値は16777216(256の3乗)ですが、アルゴリズム函数(regex_search()regex_match()など)に渡すbasic_regex型インスタンスのlimit_counter変数に任意の値を代入することで変更することも出来ます。

非互換の変更

match_lblim_availフラグとmatch_results.lookbehind_limitメンバ

 SRELL 2.300~2.500には次のような拡張がありました。

 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イテレータ方式」を最初から採用しなかった理由についてはこちらに記してあります。

u8-prefixとu8c-prefix

 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-に変更しました。

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として定義されるようになっています。

u8-とu8c-
PrefixSRELL 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.xxxでは、ECMAScript 2017 (ES8) 規格書の21.2 RegExp (Regular Expression) Objects で定義されている正規表現に固定幅の戻り読み (lookbehind assertions) を加えたものが利用できました。

 ただ次のような経緯により、SRELL 1とSRELL 2とでは戻り読みの挙動に違いがあります。

 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には、RegExpの機能拡張に追随して可変幅の戻り読みが実装されています。このためSRELL 1 → SRELL 2では breaking change が発生しています。

 以下は外部のサイトです。

C++のUnicode対応

ECMAScriptのRegExp関連

C++の<regex>関連