Rust で正規表現による文字列の検索・置換

Rust に入門したばかりで，公式ガイド「[プログラミング言語Rust](http://rust-lang-ja.github.io/the-rust-programming-language-ja/1.6/book/)」（和訳）もまだ一部しか読んでない素人だけど，文字列の検索・置換はどうやるのか調べたのを記事にします。

（動作確認は Rust 1.12.0；改訂時は Rust 1.20.0 + regex 0.2.2 を使用）

［2017-09-02 追記］regex のバージョンが 0.2 系に上がって一部互換性がなくなった。0.2 で大きな問題がなければ 1.0 をリリースするとのことなので，今回のような大きな変更はしばらく無いのかもしれない。
変更の詳細については公式の [CHANGELOG](https://github.com/rust-lang/regex/blob/master/CHANGELOG.md) を参照のこと。
0.2 系で違いがあるところに加筆していきます。

# 予備知識

## 文字列について

文字列には `&str` と `String` の2種類があるらしい。

公式ガイドの「[文字列](http://rust-lang-ja.github.io/the-rust-programming-language-ja/1.6/book/strings.html)」が分かりやすい。
（2016-10-07 追記）「[Rustの文字列操作 - Qiita](http://qiita.com/aflc/items/f2be832f9612064b12c6) 」も役に立つ。

`&str` は「文字列のスライス」と呼ばれ，サイズ固定で変更不可。文字列リテラルはこれになる。

`String` は伸張可能。`&str` から `to_string()` で作れる。`String` に `&` を付ければ `&str` になる。

いずれも，文字コードは UTF-8 固定で，エンコードが正しいことが保証されているそう。

文字列リテラルにバックスラッシュとかが含まれるとき，

```rust
let s = "The sign \\ is called backslash.";
```

とエスケープして書くのは面倒だけど，

```rust
let s = r"The sign \ is called backslash.";
```

という書き方もできる。


## 正規表現について

正規表現は言語には組み込まれておらず，[`regex`](https://crates.io/crates/regex) というクレートを使う。

まず Cargo.toml に

```toml
[dependencies]
regex = "0.1"
```

とかって書いておいて，

```rust
extern crate regex;

use regex::Regex;
```

としておく。

すると，以下のように書ける。

```rust
let re = Regex::new(r"\d+").unwrap();
```

言語組込みではないので正規表現リテラルは無いのだろう。

正規表現が正しくない場合，`unwrap()` でパニクる。

参考：[regex::Regex 構造体](https://doc.rust-lang.org/regex/regex/struct.Regex.html)（英文）



# 検索

検索してみる。

検索して位置情報を得るには [find](https://doc.rust-lang.org/regex/regex/struct.Regex.html#method.find) や [find_iter](https://doc.rust-lang.org/regex/regex/struct.Regex.html#method.find_iter) を使う（前者は最初の一つだけ，後者は連続して）。

［2017-09-02 加筆］
`find` の返り値も regex 0.2 系で `Option<Match>` に変更になった。`Match` については「名前付きキャプチャー」の加筆箇所を参照。
［加筆おわり］


検索で見出された部分文字列を得るには，[`captures`](https://doc.rust-lang.org/regex/regex/struct.Regex.html#method.captures) や [`captures_iter`](https://doc.rust-lang.org/regex/regex/struct.Regex.html#method.captures_iter) を使う。

まず `captures` から。

```rust
extern crate regex;

use regex::Regex;

fn main() {
    let str = "MZ-80K2E";

    // 最初にマッチした箇所全体を取り出す例
    let re = Regex::new(r"\d+").unwrap();
    let caps = re.captures(str).unwrap();
    println!("{}", caps.at(0).unwrap()); // => "80" が表示される

    // ( ) であらわにキャプチャーを書いて，それぞれを取り出す例
    let re = Regex::new(r"([A-Z])(\d)").unwrap();
    let caps = re.captures(str).unwrap();
    println!("{} before {}", caps.at(1).unwrap(), caps.at(2).unwrap());
    // => "K before 2" が表示される。
}
```

このように，`at(0)` だとマッチした部分全体が，`at(1)` だと 1 番目の `( )` でキャプチャーされた部分文字列が得られる。

ええと，`caps.at(n).unwrap()` の代わりに `&caps[n]` でもいいようだ。

［2017-09-02 加筆］
regex 0.2 系では `at` が無くなった。

0.1 系で

```rust
caps.at(0).unwrap()
```

と書いていたのは

```rust
caps.get(0).unwrap().as_str()
```

と書くことになった。

`&caps[n]` の形式は 0.2 系でも有効。

［加筆おわり］

次に，`captures_iter` を。これは結果を `for` で回せるということらしい。

```rust
extern crate regex;

use regex::Regex;

fn main() {
    let str = "MZ-80K2E";

    let re = Regex::new(r"\d").unwrap();

    for caps in re.captures_iter(str) {
        println!("{}", &caps[0]);
    }
    // => "8", "0", "2" が順に表示される
}
```

# 置換

置換には [`replace`](https://doc.rust-lang.org/regex/regex/struct.Regex.html#method.replace) や [`replace_all`](https://doc.rust-lang.org/regex/regex/struct.Regex.html#method.replace_all) を使う。前者は最初にマッチした箇所だけ，後者は検索して見出された箇所すべてを置換。

## 置換文字列を指定して置換

```rust
extern crate regex;

use regex::Regex;

fn main() {
    let str = "MZ-80K2E";

    let re = Regex::new(r"\d").unwrap();

    // 数字をアスタリスクに
    let result = re.replace_all(str, "*");
    println!("{}", result); // => "MZ-**K*E" が表示される

    // 数字を [ ] で囲む
    let result = re.replace_all(str, "[$0]");
    println!("{}", result); // => "MZ-[8][0]K[2]E" が表示される
}
```

置換文字列の指定では，マッチ部分が `$0` で参照できる。（この「参照」は Rust 用語の参照ではなく正規表現用語の参照）

`( )` でキャプチャーしたときは，例えば 2 番目のキャプチャー文字列が `$2` で参照できる。

## 置換文字列をクロージャーで構成

複雑な置換では，置換文字列を構成するのにロジックが必要になる。

たとえば，文字列中の数字列部分を整数とみて，ゼロ埋め4桁にしたい，つまり，「`MZ-80K2E`」を「`MZ-0080K0002E`」にしたい，といった場合。

`replace` や `replace_all` の第 2 引数にクロージャーを与えればよいようだ。以下のように書ける。

```rust
extern crate regex;

use regex::{Regex, Captures};

fn main() {
    let str = "MZ-80K2E";

    let re = Regex::new(r"\d+").unwrap();

    let result = re.replace_all(str,
        |caps: &Captures| {
            let num: u32 = (&caps[0]).parse().unwrap();
            format!("{:04}", num)
        }
    );
    println!("{}", result);
}
```

クロージャーの引数の型を `&regex::Captures` と書くのが面倒なので，`regex::Captures` を `use` しておいた。

# regex クレートの表現力

`regex` クレートはどの程度の表現が可能なのだろうか。

公式ドキュメントの「[Syntax](https://doc.rust-lang.org/regex/regex/index.html#syntax)」によれば，RE2 という正規表現エンジンに概ね沿っているようだ。

## 後方参照・先読み・戻り読みはダメ

RE2 も Rust の regex も，入力サイズに対して線型時間で実行できるようにつくられており，それを実現するため後方参照は使えないとのこと。先読み・戻り読みも無いようだ。

## 文字プロパティー

Unicode の文字のプロパティーなんかは使えて，たとえば漢字を検索するのに

```rust
let re = Regex::new(r"\p{Han}").unwrap();
```

なんて書ける。

## 名前付きキャプチャー

名前付きキャプチャーは `(?P<name>exp)` でいける。

名前付きキャプチャーを使った場合，`caps.name("name")` みたいな感じで取り出せる。

［2017-09-02 加筆］
regex 0.1 系では

```rust
caps.name("name").unwrap()
```

で名前付きキャプチャーの**文字列**が取り出せたが，0.2 系の場合，これは [`regex::Match`](https://doc.rust-lang.org/regex/regex/struct.Match.html) を返す。

`regex::Match` にはキャプチャー文字列の位置情報も含まれる。

ここから `as_str()` で文字列を取り出すこともできるし，単に文字列が欲しいだけなら

```rust
&caps["name"]
```

でよいようだ。
［加筆おわり］

## オプション

大文字・小文字の同一視はどうか。

Ruby だと，正規表現リテラルで `/foo/i` みたいな `i` オプションで書けるし，`Regexp.new("foo", Regexp::IGNORECASE)` といった書き方もできる。

Rust では正規表現リテラルは無いようだし，`Regex::new` に与える引数は一つだけ。

どうするかというと，正規表現中にオプションを書くことができる。（あまり知られてないけど，実は Ruby の正規表現エンジン，鬼雲なんかでもこういう書き方ができる）

```rust
let re = Regex::new(r"(?i)foo").unwrap();
```

このように「`(?i)`」よりも右側で大文字・小文字が同一視される。「`(?-i)`」を置くとそこから右側では区別をする。

また，

```rust
let re = Regex::new(r"(?i:foo)").unwrap();
```

のような書き方もできて，`(?i: )` で囲った箇所だけ大文字・小文字が同一視される。

この手のオプションには他に，`m`，`s`，`U`，`u`，`x` がある。いずれも `-` を頭に付ければ OFF になる。

`u` はデフォルトで ON になっているが，「Unicode サポート」というやつ。例えば `\d` が ASCII の数字だけでなく全角数字なんかにもマッチする。

`x` は鬼車の `x` オプションと同じで，空白文字が無視され，`#` でコメントが書けるようになる。つまり，次のような書き方ができるのだ。

```rust
let re = Regex::new(r"(?x)
    \d{3} # 郵便番号前半
    -     # ハイフン
    \d{4} # 郵便番号後半
").unwrap();
```

このオプションについては拙文「[Ruby の正規表現を複数行で書く](http://qiita.com/scivola/items/04270095eb4c17ab8c92)」を参考にしていただけると嬉しい。

`m` オプションについては次節で述べる。

## マルチラインモード（2016-11-09 追記）

`m` オプションは「マルチラインモード」にするもので，デフォルトは OFF になっている。

これはアンカー `^` および `$` の意味を変更するものだ。Ruby の正規表現の `m` オプションとはまるで違う[^ruby-m]ので，Rubyist さんは要注意。

[^ruby-m]: Ruby の場合は `.` の意味を変える（`m` を付けると改行にもマッチするようになる）。

デフォルトでは，`^`，`$` はそれぞれ文字列の先頭・末尾にマッチする。

マルチラインモードでは行頭・行末にマッチするようになる。

ところで，何をもって行頭・行末とするのかは実は正規表現エンジンによってバラツキがあるので要注意だ。文字列が改行で終わっているとき，`^` や `$` が文字列末尾にマッチするかどうかは，エンジンに依るのだ。

regex クレートのドキュメントには明記されていないので，実験してみよう。（参考としてシングルラインモードも併記）

```rust
extern crate regex;
use regex::Regex;

// Regex のインスタンスを簡潔に得る
fn regex(re_str: &str) -> Regex {
    Regex::new(re_str).unwrap()
}

fn main() {
    let s = "foo\nbar\n";

    // シングルラインモード
    let re_s = regex("^");
    let re_e = regex("$");
    println!("{:?}", re_s.replace_all(s, "^")); // "^foo\nbar\n"
    println!("{:?}", re_e.replace_all(s, "$")); // "foo\nbar\n$"

    // マルチラインモード
    let re_s = regex("(?m)^");
    let re_e = regex("(?m)$");
    println!("{:?}", re_s.replace_all(s, "^")); // "^foo\n^bar\n^"
    println!("{:?}", re_e.replace_all(s, "$")); // "foo$\nbar$\n$"
}
```

このように，`$` だけでなく `^` も文字列末尾にマッチするが，これは Perl や Ruby（鬼雲）の動作とは異なる。

もう一つ注意したいことがある。改行コードが CR+LF，つまり `"\r\n"` だったとき，`"\r"` の直前ではなく `"\n"` の直前が行末になる。
改行コードの種類に頓着せず行末のアンカーを使うとエラい目に遭うだろう。