std::regex を独自イテレータに適用する


 ```std::regex``` を独自イテレータに適用する場合の可用性について考察しました。

## 要約

- ```wchar_t``` を使えば、 Windows と Linux でOKでした
- ```char``` はマルチバイト文字を一文字と認識しませんでした

## 考察

```std::regex``` は、ユニコードへの対応を求められていません。　http://www.akenotsuki.com/misc/srell/uniregex.html によると、 C++ 標準化委員会は、 std::regex のユニコード対応に後ろ向きなようです。

```std::regex```は、```char8_t```、```char16_t``` 、```char32_t```をサポートしません。```std::regex```は、```char```、```wchar_t```をサポートします。

```char```の文字コードはロケールに依存します。 ```wchar_t```の文字コードは環境固有です。

### wchar_t

```std::regex``` はユニコード対応でないが、たいていの環境で```wchar_t```がユニコードなので、```wchar_t```を使えばユニコード対応になりそうです。

```wchar_t```の文字コードを Linux と Windowsで調べました。

- [glibc を使用する GNU/Linux システムでは UCS4](https://gcc.gnu.org/onlinedocs/libstdc++/manual/facets.html#codecvt.design.wchar_t_size)
- [Windows 上の Microsoft コンパイラでは UTF-16LE](https://docs.microsoft.com/en-us/cpp/cpp/char-wchar-t-char16-t-char32-t?view=msvc-160) 

UTF-16 と UCS4 があるので、環境ごとの場合分けが必要そうです。

### char

システムのロケールを調べました。システムデフォルトのロケールは LANG 環境変数、ユーザーのロケールは LC_* 環境変数で設定されます。Windows で　POSIX スタイルのロケール設定を調べる方法はわかりませんでした。

- 手元の Debian10
    - LANG=en_US.UTF-8
    - LC_CTYPE="en_US.UTF-8"
- 手元の Ubuntu20.04
    - LANG=C.UTF-8
    - LC_CTYPE="C.UTF-8"

でした。

### イテレータ

```std::regex``` は双方向イテレータしか要求しないため、独自イテレータの実装は容易です。　双方向イテレータはマルチパス保証を要求するため、実際にコンテナが保持するデータを指すのが現実的です。　デコードによって別の文字コードを公開するのは現実的でありません。

## 実験

### wchar_t

文字を走査するイテレータ・アダプタを使って、 HTML 文書 Tree を正規表現検索してみました。

実行した環境は以下の通りです。

- Ubuntu 20.04 gcc9.3.0 libstdc++6
- Windows10 2004 Visualstudio2019 16.8.4 clang-cl
- Windows10 2004 Visualstudio2019 16.8.4 cl

```c++
using namespace wordring;
using namespace wordring::html;

// Windows と Linux で wchar_t の文字コードが違うため、宣言を切り替えます。
#ifdef __linux__
    using tree_type = u32simple_tree;
#else
    using tree_type = u16simple_tree;
#endif // __linux__

// HTML 文書 Tree を作成します。
std::string_view sv = u8"<span>ABCあ</span><a>い</a><strong>うDEF</strong>";
auto doc = make_document<tree_type>(sv.begin(), sv.end());

// char16_t あるいは char32_t を wchar_t に見せかけるイテレータ・アダプタの型を宣言します。
using wchar_iterator = tree_type::wchar_iterator;
// wchar_t へのキャストが付いた「文字を走査するイテレータ・アダプタ」を作成します。
auto it1 = wchar_iterator(doc.begin());
auto it2 = wchar_iterator(doc.end());

// 正規表現検索します。
std::basic_regex<wchar_t> rx(L"あ.う");
std::match_results<wchar_iterator> mr;
std::regex_search(it1, it2, mr, rx);

// アサーションは成功します。
assert(mr.length() == 3);
assert(mr.str() == L"あいう");

```

以上のコードで

- [**make_document()**](https://wordring.github.io/libwordring/api/html/namespacewordring_1_1html.html#ae52e0499a88c09f71b5e1341d926d216) は、 HTML 文書を返す HTML パーサーの便利関数
- [**u8simple_tree**](https://wordring.github.io/libwordring/api/html/namespacewordring_1_1html.html#afad5a01e27f008c84051442f6e152868) は、 HTML 文書を格納する Tree 
- [**wchar_iterator**](https://wordring.github.io/libwordring/api/html/classwordring_1_1tag__tree.html#aa10ebc6a3afa5204a9b180da005144a9) は、 「文字を走査するイテレータ・アダプタ」の逆参照を強制的に```wchar_t``` へキャストするイテレータ・アダプタ

です。

```wchar_t``` は ```char16_t``` あるいは ```char32_t``` ではありません。　したがって、```char16_t``` を指すイテレータを ```wchar_t``` として逆参照すると、エラーとなります。（※clではエラーとなりませんでした。）そこで、 逆参照を強制的にキャストして返すイテレータ・アダプタを試作しました。　これは完全に動作しました。

```character_iterator``` は単なるアダプタですから、 base となる HTML ノードへのイテレータへ変換可能です。  上記のコードに続けて、正規表現とマッチした最初の範囲の親要素のタグ名を表示します。

```c++
// マッチした最初の範囲を示す文字イテレータを取得し、 HTML ノードへのイテレータへキャストします。
tree_type::iterator it3 = mr.begin()->first;
// ノードへのイテレータから親のタグ名を取り出し文字コードを UTF-16 へ変換します。
auto tag = encoding_cast<std::u16string>(it3.parent()->local_name());

// アサーションは成功します。
assert(tag == u"span");
```

以上のコードで、

- [**encoding_cast()**](https://wordring.github.io/libwordring/api/html/namespacewordring_1_1whatwg.html#ae22cb690f38c2f20f183f272b0c9d034) は、文字エンコーディングを変換する便利関数

です。

これが**独自イテレータの旨味**です。　```std::string``` へ変換してから検索したのでは、親のタグ名を取り出せません。

アノテーション付きテキストを直接イテレータで正規表現検索する利点は計り知れないです。　独自データ構造に対する独自イテレータは、文字より多くの情報を返せる可能性を持ちます。

## char

UTF-8 において仮名文字は 3 バイト必要とします。　この 3 バイトを一文字として扱うか実験します。

```c++
using namespace wordring::html;

setlocale(LC_ALL, ".UTF8");

// HTML 文書 Tree を作成します。
std::string_view sv = u8"<p>ABCあ</p><p>い</p><p>うDEF</p>";
auto doc = make_document<u8simple_tree>(sv.begin(), sv.end());

// 「文字を走査するイテレータ・アダプタ」を作成します。
auto it = u8simple_tree::character_iterator(doc.begin());

// 正規表現検索します。
std::basic_regex<char> rx("あ.う");
std::match_results<u8simple_tree::character_iterator> mr;
std::regex_search(it, it.end(), mr, rx);

// アサーションは失敗します。
assert(mr.length() == 9);
assert(mr.str() == u8"あいう");
```

以上のコードで

- [**make_document()**](https://wordring.github.io/libwordring/api/html/namespacewordring_1_1html.html#ae52e0499a88c09f71b5e1341d926d216) は、 HTML 文書を返す HTML パーサーの便利関数
- [**u8simple_tree**](https://wordring.github.io/libwordring/api/html/namespacewordring_1_1html.html#afad5a01e27f008c84051442f6e152868) は、 HTML 文書を格納する Tree 
- [**character_iterator**](https://wordring.github.io/libwordring/api/html/classwordring_1_1tag__tree.html#a8a4217404bdceb7d30ef88abcd40684c) は、「文字を走査するイテレータ・アダプタ」

です。

Windows 、 Linux の両方でマルチバイト文字を一文字と認識しませんでした。　Windows の「ワールドワイド言語サポートで Unicode UTF-8 を使用」はオフのままです。

c++20 で char8_t が標準化されると、以上のコードはコンパイル出来なくなるかもしれません。　```char8_t``` は```char```ではないからです。 

## その他の選択肢

Google で検索すると、 std::regex 互換のユニコード対応正規表現エンジン実装として **[SRELL](http://www.akenotsuki.com/misc/srell/)** がたびたび紹介されています。