正規表現
正規表現は、 テキストの文字列に対してパターンマッチを行う強力な道具です。 PerlやRuby,JavaScriptといった言語では、 言語のコア機能として組み込まれています。Perl と Ruby は特に正規表現を巧みに扱うことでよく知られています。 さて、それならばなぜ正規表現はDの言語機能として組み込まれていないのでしょう? 以下では、Rubyとの比較を通してDでどのように正規表現が扱われているかを示します。
この記事は、Dでの正規表現の扱い方について解説するものです。 正規表現そのものについては説明しません (説明し出すと本が一冊書けてしまいます)。 ここで説明する D の正規表現の具体的な実装は、 Phobos標準ライブラリのモジュール std.regexp に完全に含まれています。 テンプレートメタプログラミングと組み合わせる 高度な正規表現処理については、 テンプレート再訪 をご参照ください。
Ruby では、 正規表現は特別なリテラルとして生成します:
r = /pattern/ s = /p[1-5]\s*/
D には正規表現を表す特別なリテラルはありませんが、 以下のように生成できます。
r = RegExp("pattern"); s = RegExp(r"p[1-5]\s*");
pattern がバックスラッシュ文字 \ を含むときは、 'r'を頭につけた WYSIWYG文字列リテラルを使います。 r と s は RegExp 型の変数ですが、 この型は宣言の左辺から自動的に推論させることができます:
auto r = RegExp("pattern"); auto s = RegExp(r"p[1-5]\s*");
文字列 s と正規表現がマッチするかを Ruby でチェックするには、 =~ 演算子を使用します。 この演算子は先頭のマッチのインデックスを返します:
s = "abcabcabab" s =~ /b/ /* マッチ。1を返す */ s =~ /f/ /* マッチしない。nilを返す */
D では、これは次のようになります:
auto s = "abcabcabab"; std.regexp.find(s, "b"); /* マッチ。1を返す */ std.regexp.find(s, "f"); /* マッチしない。-1を返す */
正規表現ではなく部分文字列とのマッチを探す std.string.find と同じ動作とも見ることができます。
Ruby の =~ 演算子は、 マッチ結果に基づいて暗黙に定義された変数をいくつかセットします:
s = "abcdef" if s =~ /c/ "#{$`}[#{$&}]#{$'}" /* 文字列 ab[c]def になる
関数 std.regexp.search() はマッチ情報を表す RegExp オブジェクトを返し、以下のように使用できます:
auto m = std.regexp.search("abcdef", "c"); if (m) writefln("%s[%s]%s", m.pre, m.match(0), m.post);
あるいはもっと簡潔に:
if (auto m = std.regexp.search("abcdef", "c")) writefln("%s[%s]%s", m.pre, m.match(0), m.post); // ab[c]def と出力
検索と置換
検索と置換はより興味深い話になります。 Ruby で "a" の出現を "ZZ" で置き換えるには…:
s = "Strap a rocket engine on a chicken." s.sub(/a/, "ZZ") // 結果: StrZZp a rocket engine on a chicken. s.gsub(/a/, "ZZ") // 結果: StrZZp ZZ rocket engine on ZZ chicken.
D では:
s = "Strap a rocket engine on a chicken."; sub(s, "a", "ZZ"); // 結果: StrZZp a rocket engine on a chicken. sub(s, "a", "ZZ", "g"); // 結果: StrZZp ZZ rocket engine on ZZ chicken.
置換文字列は、マッチの情報をg $&, $$, $', $`, .. 9 記法で参照できます:
sub(s, "[ar]", "[$&]", "g"); // 結果: St[r][a]p [a] [r]ocket engine on [a] chicken.
あるいは、置換文字列をdelegateで指定することもできます:
sub(s, "[ar]", (RegExp m) { return toupper(m.match(0)); }, "g"); // 結果: StRAp A Rocket engine on A chicken.(toupper() は std.string の関数です。)
ループ処理
文字列中の全てのマッチを検索することも 可能です:
import std.stdio; import std.regexp; void main() { foreach(m; RegExp("ab").search("abcabcabab")) { writefln("%s[%s]%s", m.pre, m.match(0), m.post); } } // 表示: // [ab]cabcabab // abc[ab]cabab // abcabc[ab]ab // abcabcab[ab]
まとめ
D の正規表現処理は Ruby 並に強力です。 しかし構文は Ruby ほど簡潔ではありません:
- 正規表現リテラル構文 - これを追加すると、 字句解析フェーズを 構文解析や意味解析から独立させることができなくなってしまいます。
- マッチ情報変数の暗黙の名前付け - これは名前の衝突の問題を引き起こしますし、 Dの他の部分の設計ともうまくフィットしません。
しかし同じくらい強力なのです。