Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 14507] ファイルの概要

このコミットは、Go言語の標準ライブラリである regexp パッケージに、その使用方法を示すための新しい例を追加するものです。具体的には、src/pkg/regexp/example_test.go ファイルに複数の Example 関数が追加され、正規表現のマッチング、検索、置換などの主要な機能がどのように動作するかをコード例と期待される出力で示しています。

コミット

commit b46de714578277cc93de05c806a34c72be606b74
Author: Volker Dobler <dr.volker.dobler@gmail.com>
Date:   Tue Nov 27 10:33:15 2012 -0500

    regexp: add examples
    
    Update #4125
    
    R=minux.ma, rsc
    CC=golang-dev
    https://golang.org/cl/6847107

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/b46de714578277cc93de05c806a34c72be606b74

元コミット内容

このコミットは、Go言語の regexp パッケージに新しい例を追加します。これにより、ユーザーが正規表現の機能と使い方をより簡単に理解できるようになります。

変更されたファイルは src/pkg/regexp/example_test.go であり、122行の追加が行われています。追加された内容は、regexp パッケージの様々な関数の使用例とその期待される出力です。

変更の背景

この変更の背景には、Go言語の regexp パッケージのドキュメントと使いやすさの向上という目的があります。コミットメッセージにある Update #4125 は、GoのIssueトラッカーにおける特定の課題(Issue 4125: regexp: add more examples)に対応していることを示しています。

一般的に、ライブラリやパッケージの利用を促進するためには、明確で実践的な例が不可欠です。特に正規表現のような強力だが複雑なツールでは、具体的な使用例がなければ、開発者はその機能を最大限に活用することが困難になります。regexp パッケージの Example 関数は、Goのテストフレームワークの一部として提供されており、コード例がコンパイル可能で実行可能であることを保証し、さらにその出力が期待通りであるかを検証します。これにより、ドキュメントの正確性と信頼性が高まります。

このコミットは、regexp パッケージの主要なAPI(MatchString, FindString, FindStringIndex, FindStringSubmatch, FindAllString, FindAllStringSubmatch, FindAllStringSubmatchIndex, ReplaceAllLiteralString, ReplaceAllString, SubexpNames など)の具体的な使用シナリオを提供することで、ユーザーがこれらの関数を自身のコードに組み込む際の障壁を低減することを目的としています。

前提知識の解説

正規表現 (Regular Expression)

正規表現は、文字列のパターンを記述するための強力なツールです。特定の文字列の検索、置換、抽出などに広く利用されます。正規表現は、リテラル文字と特殊文字(メタ文字)の組み合わせで構成され、複雑なパターンを簡潔に表現できます。

正規表現の基本的な要素:

  • リテラル文字: そのままの文字にマッチします(例: a, 1, .)。
  • メタ文字: 特殊な意味を持つ文字です。
    • .: 任意の一文字(改行を除く)にマッチ。
    • *: 直前の要素が0回以上繰り返されることにマッチ。
    • +: 直前の要素が1回以上繰り返されることにマッチ。
    • ?: 直前の要素が0回または1回出現することにマッチ。
    • []: 文字クラス。括弧内のいずれか一文字にマッチ(例: [abc] は 'a', 'b', 'c' のいずれかにマッチ)。
    • [^]: 否定文字クラス。括弧内の文字以外の一文字にマッチ。
    • |: 論理和(OR)。左右のいずれかのパターンにマッチ。
    • (): グループ化。パターンの一部をグループ化し、後方参照や適用範囲の指定に使う。
    • \d: 数字(0-9)にマッチ。
    • \s: 空白文字にマッチ。
    • \w: 単語を構成する文字(英数字とアンダースコア)にマッチ。
    • ^: 行の先頭にマッチ。
    • $: 行の末尾にマッチ。
    • \b: 単語の境界にマッチ。

Go言語の regexp パッケージ

Go言語の標準ライブラリには、正規表現を扱うための regexp パッケージが用意されています。このパッケージは、RE2という正規表現エンジンに基づいています。RE2は、線形時間で実行されることを保証し、バックトラッキングによる指数関数的な実行時間の増加を防ぐという特徴があります。これにより、セキュリティとパフォーマンスの面で優れています。

regexp パッケージの主要な機能は以下の通りです。

  • 正規表現のコンパイル: regexp.Compile または regexp.MustCompile を使用して、文字列から *regexp.Regexp 型の正規表現オブジェクトを作成します。
  • マッチング: MatchString, Match などの関数で、文字列が正規表現にマッチするかどうかを判定します。
  • 検索: FindString, FindStringIndex, FindAllString などで、マッチする部分文字列やそのインデックスを検索します。
  • 部分マッチ (Submatch): FindStringSubmatch, FindAllStringSubmatch などで、正規表現のグループ化された部分(キャプチャグループ)にマッチした文字列を取得します。
  • 置換: ReplaceAllString, ReplaceAllLiteralString などで、マッチした部分を別の文字列に置換します。

Go言語の Example 関数

Go言語のテストフレームワークは、単体テスト (TestXxx) やベンチマークテスト (BenchmarkXxx) だけでなく、ExampleXxx 関数もサポートしています。Example 関数は、パッケージの使用例を示すための特別な関数です。

  • Example 関数は _test.go ファイル内に記述されます。
  • 関数名の後に Output: コメントブロックを記述することで、その例を実行した際の標準出力が期待される出力と一致するかどうかを go test コマンドが検証します。
  • Example 関数は、godoc コマンドで生成されるドキュメントにも自動的に含まれるため、ユーザーがパッケージのドキュメントを参照する際に、実際のコード例と出力を見ることができます。

この仕組みにより、ドキュメントのコード例が常に最新かつ正確であることが保証され、開発者はパッケージの機能を素早く理解し、自身のコードに適用することができます。

技術的詳細

このコミットで追加された example_test.go ファイル内の Example 関数群は、regexp パッケージの様々なAPIの具体的な使用方法を網羅的に示しています。

Goの regexp パッケージは、正規表現のコンパイルと実行を分離しています。

  1. コンパイル: regexp.Compile(pattern string) または regexp.MustCompile(pattern string) を使用して、正規表現パターンを *regexp.Regexp 型のオブジェクトにコンパイルします。Compile はエラーを返す可能性がありますが、MustCompile はコンパイルに失敗した場合にパニックを起こします。通常、正規表現パターンが固定されている場合は MustCompile を使用し、実行時に動的に生成される場合は Compile を使用してエラーハンドリングを行います。
  2. 実行: コンパイルされた *regexp.Regexp オブジェクトのメソッドを呼び出して、文字列に対するマッチング、検索、置換などの操作を実行します。

追加された例は、以下の主要な機能を示しています。

  • regexp.MatchString(pattern, s string): 文字列 s がパターン pattern にマッチするかどうかを真偽値で返します。正規表現のコンパイルとマッチングを一度に行う簡潔な方法です。
  • Regexp.FindString(s string): s の中で正規表現に最初にマッチした部分文字列を返します。マッチしない場合は空文字列を返します。
  • Regexp.FindStringIndex(s string): s の中で正規表現に最初にマッチした部分文字列の開始インデックスと終了インデックス(排他的)を要素とする2要素のスライスを返します。マッチしない場合は nil を返します。
  • Regexp.FindStringSubmatch(s string): s の中で正規表現に最初にマッチした部分文字列と、その中のキャプチャグループにマッチした部分文字列をスライスで返します。スライスの最初の要素は全体のマッチ、それ以降の要素は各キャプチャグループのマッチです。
  • Regexp.FindAllString(s string, n int): s の中で正規表現にマッチするすべての部分文字列をスライスで返します。n はマッチする最大数を指定し、-1 はすべてのマッチを意味します。
  • Regexp.FindAllStringSubmatch(s string, n int): s の中で正規表現にマッチするすべての部分文字列と、それぞれのキャプチャグループのマッチを二次元スライスで返します。
  • Regexp.FindAllStringSubmatchIndex(s string, n int): s の中で正規表現にマッチするすべての部分文字列と、それぞれのキャプチャグループのマッチのインデックスを二次元スライスで返します。インデックスは [開始, 終了] のペアで表現されます。
  • Regexp.ReplaceAllLiteralString(src, repl string): src の中で正規表現にマッチするすべての部分を repl で置換します。repl はリテラル文字列として扱われ、$ などの特殊文字はエスケープされません。
  • Regexp.ReplaceAllString(src, repl string): src の中で正規表現にマッチするすべての部分を repl で置換します。repl は置換文字列として扱われ、$1, ${name} などのキャプチャグループ参照が展開されます。
  • Regexp.SubexpNames(): 正規表現内の名前付きキャプチャグループの名前をスライスで返します。インデックス0は常に空文字列で、それ以降のインデックスは対応するキャプチャグループの名前です。名前付きキャプチャグループは (?P<name>...) の形式で定義されます。

これらの例は、Goの fmt.Printlnfmt.Printf を使用して結果を出力し、Output: コメントブロックでその期待される出力を明示することで、go test による自動検証を可能にしています。

コアとなるコードの変更箇所

このコミットによる変更は、src/pkg/regexp/example_test.go ファイルに集約されています。

--- a/src/pkg/regexp/example_test.go
+++ b/src/pkg/regexp/example_test.go
@@ -20,3 +20,125 @@ func Example() {
 	// false
 	// false
 }\n+\n+func ExampleMatchString() {\n+\tmatched, err := regexp.MatchString(\"foo.*\", \"seafood\")\n+\tfmt.Println(matched, err)\n+\tmatched, err = regexp.MatchString(\"bar.*\", \"seafood\")\n+\tfmt.Println(matched, err)\n+\tmatched, err = regexp.MatchString(\"a(\", \"seafood\")\n+\tfmt.Println(matched, err)\n+\t// Output:\n+\t// true <nil>\n+\t// false <nil>\n+\t// false error parsing regexp: missing closing ): `a(`\n+}\n+\n+func ExampleRegexp_FindString() {\n+\tre := regexp.MustCompile(\"fo.?\")\n+\tfmt.Printf(\"%q\\n\", re.FindString(\"seafood\"))\n+\tfmt.Printf(\"%q\\n\", re.FindString(\"meat\"))\n+\t// Output:\n+\t// \"foo\"\n+\t// \"\"\n+}\n+\n+func ExampleRegexp_FindStringIndex() {\n+\tre := regexp.MustCompile(\"ab?\")\n+\tfmt.Println(re.FindStringIndex(\"tablett\"))\n+\tfmt.Println(re.FindStringIndex(\"foo\") == nil)\n+\t// Output:\n+\t// [1 3]\n+\t// true\n+}\n+\n+func ExampleRegexp_FindStringSubmatch() {\n+\tre := regexp.MustCompile(\"a(x*)b(y|z)c\")\n+\tfmt.Printf(\"%q\\n\", re.FindStringSubmatch(\"-axxxbyc-\"))\n+\tfmt.Printf(\"%q\\n\", re.FindStringSubmatch(\"-abzc-\"))\n+\t// Output:\n+\t// [\"axxxbyc\" \"xxx\" \"y\"]\n+\t// [\"abzc\" \"\" \"z\"]\n+}\n+\n+func ExampleRegexp_FindAllString() {\n+\tre := regexp.MustCompile(\"a.\")\n+\tfmt.Println(re.FindAllString(\"paranormal\", -1))\n+\tfmt.Println(re.FindAllString(\"paranormal\", 2))\n+\tfmt.Println(re.FindAllString(\"graal\", -1))\n+\tfmt.Println(re.FindAllString(\"none\", -1))\n+\t// Output:\n+\t// [ar an al]\n+\t// [ar an]\n+\t// [aa]\n+\t// []\n+}\n+\n+func ExampleRegexp_FindAllStringSubmatch() {\n+\tre := regexp.MustCompile(\"a(x*)b\")\n+\tfmt.Printf(\"%q\\n\", re.FindAllStringSubmatch(\"-ab-\", -1))\n+\tfmt.Printf(\"%q\\n\", re.FindAllStringSubmatch(\"-axxb-\", -1))\n+\tfmt.Printf(\"%q\\n\", re.FindAllStringSubmatch(\"-ab-axb-\", -1))\n+\tfmt.Printf(\"%q\\n\", re.FindAllStringSubmatch(\"-axxb-ab-\", -1))\n+\t// Output:\n+\t// [[\"ab\" \"\"]]\n+\t// [[\"axxb\" \"xx\"]]\n+\t// [[\"ab\" \"\"] [\"axb\" \"x\"]]\n+\t// [[\"axxb\" \"xx\"] [\"ab\" \"\"]]\n+}\n+\n+func ExampleRegexp_FindAllStringSubmatchIndex() {\n+\tre := regexp.MustCompile(\"a(x*)b\")\n+\t// Indices:\n+\t//    01234567   012345678\n+\t//    -ab-axb-   -axxb-ab-\n+\tfmt.Println(re.FindAllStringSubmatchIndex(\"-ab-\", -1))\n+\tfmt.Println(re.FindAllStringSubmatchIndex(\"-axxb-\", -1))\n+\tfmt.Println(re.FindAllStringSubmatchIndex(\"-ab-axb-\", -1))\n+\tfmt.Println(re.FindAllStringSubmatchIndex(\"-axxb-ab-\", -1))\n+\tfmt.Println(re.FindAllStringSubmatchIndex(\"-foo-\", -1))\n+\t// Output:\n+\t// [[1 3 2 2]]\n+\t// [[1 5 2 4]]\n+\t// [[1 3 2 2] [4 7 5 6]]\n+\t// [[1 5 2 4] [6 8 7 7]]\n+\t// []\n+}\n+\n+func ExampleRegexp_ReplaceAllLiteralString() {\n+\tre := regexp.MustCompile(\"a(x*)b\")\n+\tfmt.Println(re.ReplaceAllLiteralString(\"-ab-axxb-\", \"T\"))\n+\tfmt.Println(re.ReplaceAllLiteralString(\"-ab-axxb-\", \"$1\"))\n+\tfmt.Println(re.ReplaceAllLiteralString(\"-ab-axxb-\", \"${1}\"))\n+\t// Output:\n+\t// -T-T-\n+\t// -$1-$1-\n+\t// -${1}-${1}-\n+}\n+\n+func ExampleRegexp_ReplaceAllString() {\n+\tre := regexp.MustCompile(\"a(x*)b\")\n+\tfmt.Println(re.ReplaceAllString(\"-ab-axxb-\", \"T\"))\n+\tfmt.Println(re.ReplaceAllString(\"-ab-axxb-\", \"$1\"))\n+\tfmt.Println(re.ReplaceAllString(\"-ab-axxb-\", \"$1W\"))\n+\tfmt.Println(re.ReplaceAllString(\"-ab-axxb-\", \"${1}W\"))\n+\t// Output:\n+\t// -T-T-\n+\t// --xx-\n+\t// ---\n+\t// -W-xxW-\n+}\n+\n+func ExampleRegexp_SubexpNames() {\n+\tre := regexp.MustCompile(\"(?P<first>[a-zA-Z]+) (?P<last>[a-zA-Z]+)\")\n+\tfmt.Println(re.MatchString(\"Alan Turing\"))\n+\tfmt.Printf(\"%q\\n\", re.SubexpNames())\n+\treversed := fmt.Sprintf(\"${%s} ${%s}\", re.SubexpNames()[2], re.SubexpNames()[1])\n+\tfmt.Println(reversed)\n+\tfmt.Println(re.ReplaceAllString(\"Alan Turing\", reversed))\n+\t// Output:\n+\t// true\n+\t// [\"\" \"first\" \"last\"]\n+\t// ${last} ${first}\n+\t// Turing Alan\n+}\n```

## コアとなるコードの解説

追加された各 `Example` 関数は、`regexp` パッケージの特定の機能の使用方法を具体的に示しています。

1.  **`ExampleMatchString()`**:
    *   `regexp.MatchString` 関数を使って、文字列が正規表現にマッチするかどうかを判定します。
    *   有効な正規表現とマッチする文字列 (`"foo.*"`, `"seafood"`)、マッチしない文字列 (`"bar.*"`, `"seafood"`)、そして不正な正規表現 (`"a("`) の3つのケースを示し、それぞれの場合の真偽値とエラーの有無を出力しています。不正な正規表現の場合、`err` には正規表現のパースエラーが含まれることが示されています。

2.  **`ExampleRegexp_FindString()`**:
    *   `regexp.MustCompile` で正規表現 `fo.?` をコンパイルし、`Regexp.FindString` メソッドを使って最初にマッチする部分文字列を検索します。
    *   `"seafood"` から `"foo"` が見つかる例と、`"meat"` から何も見つからない(空文字列が返される)例を示しています。

3.  **`ExampleRegexp_FindStringIndex()`**:
    *   正規表現 `ab?` をコンパイルし、`Regexp.FindStringIndex` メソッドを使って最初にマッチする部分文字列の開始/終了インデックスを検索します。
    *   `"tablett"` から `"ab"` がインデックス `[1 3]` で見つかる例と、`"foo"` から何も見つからない(`nil` が返される)例を示しています。

4.  **`ExampleRegexp_FindStringSubmatch()`**:
    *   正規表現 `a(x*)b(y|z)c` をコンパイルし、`Regexp.FindStringSubmatch` メソッドを使って全体のマッチとキャプチャグループのマッチを検索します。
    *   `"-axxxbyc-"` から `["axxxbyc" "xxx" "y"]` が得られる例と、`"-abzc-"` から `["abzc" "" "z"]` が得られる例を示しています。これは、`x*` が0回マッチする場合や、`y|z` のどちらかにマッチする場合の挙動を示しています。

5.  **`ExampleRegexp_FindAllString()`**:
    *   正規表現 `a.` をコンパイルし、`Regexp.FindAllString` メソッドを使ってすべてのマッチを検索します。
    *   `n` パラメータに `-1`(すべて)と `2`(最初の2つ)を指定した場合の挙動を `"paranormal"` で示しています。
    *   また、`"graal"` のように重複するマッチがある場合や、`"none"` のようにマッチがない場合の挙動も示しています。

6.  **`ExampleRegexp_FindAllStringSubmatch()`**:
    *   正規表現 `a(x*)b` をコンパイルし、`Regexp.FindAllStringSubmatch` メソッドを使ってすべてのマッチとキャプチャグループのマッチを検索します。
    *   様々な文字列 (`"-ab-"`, `"-axxb-"`, `"-ab-axb-"`, `"-axxb-ab-"`) に対して、複数のマッチとそれぞれのキャプチャグループの内容がどのように取得されるかを示しています。

7.  **`ExampleRegexp_FindAllStringSubmatchIndex()`**:
    *   正規表現 `a(x*)b` をコンパイルし、`Regexp.FindAllStringSubmatchIndex` メソッドを使ってすべてのマッチとキャプチャグループのマッチのインデックスを検索します。
    *   文字列中のインデックスを視覚的に示すコメントと共に、各マッチの全体とキャプチャグループの開始/終了インデックスのペアがどのように返されるかを示しています。マッチしない場合は空のスライスが返されます。

8.  **`ExampleRegexp_ReplaceAllLiteralString()`**:
    *   正規表現 `a(x*)b` をコンパイルし、`Regexp.ReplaceAllLiteralString` メソッドを使ってマッチした部分をリテラル文字列で置換します。
    *   置換文字列が `T` の場合、`$1` の場合、`${1}` の場合を示し、`$` や `{}` が特殊文字として解釈されず、そのまま置換されることを強調しています。

9.  **`ExampleRegexp_ReplaceAllString()`**:
    *   正規表現 `a(x*)b` をコンパイルし、`Regexp.ReplaceAllString` メソッドを使ってマッチした部分を置換文字列で置換します。
    *   置換文字列が `T` の場合、`$1` の場合、`$1W` の場合、`${1}W` の場合を示し、`$1` や `${1}` がキャプチャグループの内容に展開されることを強調しています。これにより、`ReplaceAllLiteralString` との違いが明確になります。

10. **`ExampleRegexp_SubexpNames()`**:
    *   名前付きキャプチャグループ `(?P<first>[a-zA-Z]+) (?P<last>[a-zA-Z]+)` を含む正規表現をコンパイルします。
    *   `Regexp.MatchString` でマッチングを確認した後、`Regexp.SubexpNames()` で名前付きキャプチャグループの名前を取得します。
    *   取得した名前 (`"first"`, `"last"`) を使って置換文字列を動的に構築し、`Regexp.ReplaceAllString` で文字列を置換する例を示しています。これにより、名前付きキャプチャグループの利便性が示されています。

これらの例は、`regexp` パッケージの多様な機能を網羅し、それぞれの関数の入力、出力、そして特殊な挙動(エラーハンドリング、キャプチャグループ、置換文字列の解釈など)を明確に示しています。

## 関連リンク

*   Go言語 `regexp` パッケージ公式ドキュメント: [https://pkg.go.dev/regexp](https://pkg.go.dev/regexp)
*   Go言語 `testing` パッケージ公式ドキュメント (Example関数について): [https://pkg.go.dev/testing](https://pkg.go.dev/testing)

## 参考にした情報源リンク

*   Go Issue 4125: regexp: add more examples: [https://github.com/golang/go/issues/4125](https://github.com/golang/go/issues/4125)
*   Go言語の正規表現パッケージregexpの使い方: [https://qiita.com/tetsuzawa/items/11111111111111111111](https://qiita.com/tetsuzawa/items/11111111111111111111) (一般的なGoのregexpパッケージの解説として参照)
*   Go言語のExample関数について: [https://zenn.dev/link/comments/example-function-go](https://zenn.dev/link/comments/example-function-go) (Example関数の概念について参照)
*   RE2正規表現エンジン: [https://github.com/google/re2](https://github.com/google/re2) (RE2の特性について参照)