11. 正規表現

正規表現、あるいは regex は、パターンマッチに使われる文字の並びです。
一番簡単な理解の仕方は、正規表現はひとつのパターンだというものです。

if 'enlightenment' ~~ m/ light / {
    say "enlightenment contains the word light";
}

この例では、スマートマッチ演算子 ~~ を使って、ある文字列(enlightenment)にある単語(light)が含まれているかチェックしています。
"Enlightenment" は、正規表現 m/ light / にマッチしました。

11.1. 正規表現の定義

正規表現は、以下のように定義されます:

  • /light/

  • m/light/

  • rx/light/

明示的に指定しない限り、空白は意味を持ちません。 m/light/m/ light / は同じです。

11.2. 文字にマッチ

英数字と下線( _ )は、そのまま書けます。
他のすべての文字はバックスラッシュでエスケープするか、引用符で囲まなければなりません。

バックスラッシュ
if 'Temperature: 13' ~~ m/ \: / {
    say "The string provided contains a colon :";
}
単一引用符
if 'Age = 13' ~~ m/ '=' / {
    say "The string provided contains an equal character = ";
}
二重引用符
if 'name@company.com' ~~ m/ "@" / {
    say "This is a valid email address because it contains an @ character";
}

11.3. 文字カテゴリにマッチ

文字はカテゴリに分けることができ、そのカテゴリに対してマッチすることができます。
また、そのカテゴリの逆(それ以外のすべて)にマッチすることもできます:

カテゴリ

正規表現

正規表現

ワード文字(英字、数字、下線)

\w

ワード文字以外

\W

数字

\d

数字以外

\D

空白文字

\s

空白文字以外

\S

水平空白文字

\h

水平空白文字以外

\H

垂直空白文字

\v

垂直空白文字以外

\V

タブ

\t

タブ以外

\T

改行

\n

改行以外

\N

if "John123" ~~ / \d / {
  say "This is not a valid name, numbers are not allowed";
} else {
  say "This is a valid name"
}
if "John-Doe" ~~ / \s / {
  say "This string contains whitespace";
} else {
  say "This string doesn't contain whitespace"
}

11.4. ユニコード文字プロパティ

前の章で見たように、文字カテゴリへのマッチはとても便利なものです。
しかし、もっと系統的なアプローチは、ユニコード文字プロパティを使う方法です。
ユニコード文字プロパティは、 <: > で囲みます。

if "John123" ~~ / <:N> / {
  say "Contains a number";
} else {
  say "Doesn't contain a number"
}
if "John-Doe" ~~ / <:Lu> / {
  say "Contains an uppercase letter";
} else {
  say "Doesn't contain an upper case letter"
}
if "John-Doe" ~~ / <:Pd> / {
  say "Contains a dash";
} else {
  say "Doesn't contain a dash"
}

11.5. ワイルドカード

正規表現ではワイルドカードも使えます。

ドット . は、何か1文字を意味します。

if 'abc' ~~ m/ a.c / {
    say "Match";
}
if 'a2c' ~~ m/ a.c / {
    say "Match";
}
if 'ac' ~~ m/ a.c / {
    say "Match";
  } else {
    say "No Match";
}

11.6. 量指定子

量指定子は、文字の後に付いて、その文字がいくつ欲しいのか指定します。

疑問符 ? は、0または1という意味です。

if 'ac' ~~ m/ a?c / {
    say "Match";
  } else {
    say "No Match";
}
if 'c' ~~ m/ a?c / {
    say "Match";
  } else {
    say "No Match";
}

星印 * は、0以上という意味です。

if 'az' ~~ m/ a*z / {
    say "Match";
  } else {
    say "No Match";
}
if 'aaz' ~~ m/ a*z / {
    say "Match";
  } else {
    say "No Match";
}
if 'aaaaaaaaaaz' ~~ m/ a*z / {
    say "Match";
  } else {
    say "No Match";
}
if 'z' ~~ m/ a*z / {
    say "Match";
  } else {
    say "No Match";
}

+ は、少なくとも1という意味です。

if 'az' ~~ m/ a+z / {
    say "Match";
  } else {
    say "No Match";
}
if 'aaz' ~~ m/ a+z / {
    say "Match";
  } else {
    say "No Match";
}
if 'aaaaaaaaaaz' ~~ m/ a+z / {
    say "Match";
  } else {
    say "No Match";
}
if 'z' ~~ m/ a+z / {
    say "Match";
  } else {
    say "No Match";
}

11.7. マッチの結果

文字列の正規表現へのマッチが成功すると、 マッチの結果が特殊変数 $/ に保存されます。

スクリプト
if 'Rakudo is a Perl 6 compiler' ~~ m/:s Perl 6/ {
    say "The match is: " ~ $/;
    say "The string before the match is: " ~ $/.prematch;
    say "The string after the match is: " ~ $/.postmatch;
    say "The matching string starts at position: " ~ $/.from;
    say "The matching string ends at position: " ~ $/.to;
}
出力
The match is: Perl 6
The string before the match is: Rakudo is a
The string after the match is:  compiler
The matching string starts at position: 12
The matching string ends at position: 18
解説

$/マッチオブジェクト (正規表現にマッチした文字列) を返します。
Match Object に対して、以下のメソッドを呼び出すことができます:
.prematch は、マッチ部分の前の文字列を返します。
.postmatch は、マッチ部分に続く文字列を返します。
.from は、マッチ部分の開始位置を返します。
.to は、マッチ部分の終了位置を返します。

デフォルトでは、正規表現内の空白は意味を持ちません。
もし空白を含む正規表現にマッチさせたい場合には、明示的にそうすることができます。
正規表現 m/:s Perl 6/ 中の :s で、空白が無視されないようにしています。
あるいは、 m/ Perl\s6 / という正規表現を書くこともできます。ここで \s は空白文字を表します。
正規表現内に空白が複数含まれる場合には、 :s を使う方が、空白のたびに \s を書くよりも効率的です。

11.8. 例

ある電子メールのアドレスが有効なものかどうかチェックしてみましょう。
この例では、有効な電子メールアドレスは次の形をしているものとします:
ファーストネーム [ドット] ラストネーム [アットマーク] 会社名 [ドット] (com/org/net)

この例で使われている電子メールアドレスの有効性チェックはそれほど厳密なものではありません。
あくまでも、Perl6の正規表現の機能をお見せするためだけのものです。
製品コードでは使わないでください。
スクリプト
my $email = 'john.doe@perl6.org';
my $regex = / <:L>+\.<:L>+\@<:L+:N>+\.<:L>+ /;

if $email ~~ $regex {
  say $/ ~ " is a valid email";
} else {
  say "This is not a valid email";
}
出力

john.doe@perl6.org is a valid email

解説

<:L> は、文字1つにマッチします。
<:L>+ は、文字1つ以上にマッチします。
\. は、 [ドット] 1つにマッチします。
\@ は、 [アットマーク] 1つにマッチします。
<:L+:N> は、文字か数字1文字にマッチします。
<:L+:N>+ は、文字か数字1つ以上にマッチします。

この正規表現を分解すると以下のようになります:

  • ファーストネーム <:L>+

  • [ドット] \.

  • ラストネーム <:L>+

  • [アットマーク] \@

  • 会社名 <:L+:N>+

  • [ドット] \.

  • com/org/net <:L>+

別の方法として、1つの正規表現を複数の名前付き正規表現に分けることもできます。
my $email = 'john.doe@perl6.org';
my regex many-letters { <:L>+ };
my regex dot { \. };
my regex at { \@ };
my regex many-letters-numbers { <:L+:N>+ };

if $email ~~ / <many-letters> <dot> <many-letters> <at> <many-letters-numbers> <dot> <many-letters> / {
  say $/ ~ " is a valid email";
} else {
  say "This is not a valid email";
}

名前付き正規表現は次の構文で定義します: my regex 正規表現の名前 { 正規表現の定義 }
名前付き正規表現は次の構文で呼び出すことができます: <正規表現の名前>

正規表現のさらに詳しい説明は、 http://doc.perl6.org/language/regexes(英語) を参照してください。