[インデックス 18921] ファイルの概要
src/pkg/regexp/syntax/parse.go
は、Go言語の標準ライブラリであるregexp
パッケージの一部であり、正規表現文字列を解析して抽象構文木(Abstract Syntax Tree: AST)に変換する役割を担っています。このファイルは、正規表現の構文解析ロジックを実装しており、より高レベルなregexp
パッケージが内部的に利用する低レベルなコンポーネントです。具体的には、Parse
関数が正規表現文字列を受け取り、それを*syntax.Regexp
オブジェクト(正規表現の構文木ノード)として表現します。
コミット
このコミットは、src/pkg/regexp/syntax/parse.go
ファイルから未使用の変数min
とmax
を削除するものです。これらの変数は、case '{'
(繰り返し指定)の処理ブロック内で定義されていましたが、外側のスコープで定義された同名の変数が常に0の値しか持たず、実際の計算には使用されていなかったため、コードの整理と最適化のために削除されました。
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/ed97788af9d3c38e48a7f34dd18f05ae80a48c9e
元コミット内容
regexp: remove unused variables
"min" and "max" in "case '{'" clause are fresh variables.
The variables defined in the outer scope never get value
other than 0.
LGTM=bradfitz
R=golang-codereviews, bradfitz
CC=golang-codereviews
https://golang.org/cl/78750044
変更の背景
この変更の背景には、コードの品質向上と保守性の維持があります。Go言語では、未使用の変数はコンパイルエラーとなることが一般的ですが、特定のケース(例えば、異なるスコープで同名の変数が定義されている場合など)では、未使用と判断されずに残ってしまうことがあります。
このコミットでは、src/pkg/regexp/syntax/parse.go
内の正規表現の繰り返し指定(例: {n,m}
)を処理する部分において、min
とmax
という変数が定義されていましたが、これらが実際に使用されていなかったことが判明しました。具体的には、case '{'
ブロック内で新たに宣言されたmin
とmax
は使用されていましたが、その外側のスコープで宣言されていた同名のmin
とmax
は、常に初期値の0のままであり、正規表現の繰り返し回数の計算には寄与していませんでした。
このような未使用の変数は、コードの可読性を低下させ、将来的なバグの原因となる可能性を秘めています。また、コンパイラが不要なコードを最適化する手間を省くためにも、明示的に削除することが推奨されます。このコミットは、このようなコードの「お掃除」の一環として行われました。
前提知識の解説
Go言語の正規表現パッケージ (regexp
)
Go言語の標準ライブラリには、正規表現を扱うためのregexp
パッケージが用意されています。このパッケージは、RE2という正規表現エンジンに基づいています。RE2は、線形時間でのマッチングを保証し、バックトラッキングによる指数関数的な時間計算量を避けるように設計されています。
regexp
パッケージは、大きく分けて以下の2つのサブパッケージで構成されています。
regexp
: ユーザーが直接利用する高レベルなAPIを提供します。Compile
関数で正規表現をコンパイルしたり、MatchString
関数で文字列とのマッチングを行ったりします。regexp/syntax
:regexp
パッケージが内部的に利用する低レベルなパッケージです。正規表現文字列を解析して構文木を構築し、それを実行可能なプログラムにコンパイルする役割を担います。今回のコミットで変更されたparse.go
はこのサブパッケージに属します。
正規表現の繰り返し指定 ({n,m}
)
正規表現において、{n,m}
は直前の要素がn
回以上m
回以下繰り返されることを意味します。例えば、a{2,4}
は"aa"、"aaa"、"aaaa"にマッチします。{n}
はちょうどn
回、{n,}
はn
回以上繰り返されることを意味します。
Go言語の変数スコープ
Go言語では、変数のスコープはブロック({}
で囲まれた領域)によって決定されます。あるブロック内で宣言された変数は、そのブロック内でのみ有効です。同じ名前の変数が異なるスコープで宣言された場合、それらは別々の変数として扱われます。内側のスコープで宣言された変数は、外側のスコープの同名変数を「シャドウイング(shadowing)」します。つまり、内側のスコープからは内側の変数が参照され、外側の変数は直接参照できなくなります。
今回のコミットでは、このスコープの概念が重要です。外側のスコープで宣言されたmin
とmax
は、case '{'
ブロック内で新たにmin
とmax
が宣言されたため、シャドウイングされていました。そして、外側のmin
とmax
は初期値の0のまま使用されることがなかった、というのが変更の背景にあります。
技術的詳細
このコミットは、src/pkg/regexp/syntax/parse.go
ファイル内のParse
関数における、正規表現の繰り返し指定({}
)を処理する部分のコードを修正しています。
元のコードでは、Parse
関数の冒頭でmin, max int
という変数が宣言されていました。
func Parse(s string, flags Flags) (*Regexp, error) {
// ...
c rune
op Op
lastRepeat string
min, max int // ここで宣言されている
)
// ...
}
そして、case '{'
の処理ブロック内で、正規表現の繰り返し回数を解析した後、p.repeat
関数を呼び出す際に、この外側のスコープのmin
とmax
が引数として渡されていました。
// 変更前
if after, err = p.repeat(op, min, max, before, after, lastRepeat); err != nil {
return nil, err
}
しかし、コミットメッセージが示唆するように、case '{'
ブロック内で正規表現の繰り返し回数を解析する際に、min
とmax
という新しい変数が宣言され、そこに解析結果が格納されていました。この新しいmin
とmax
は、外側のスコープのmin
とmax
とは別物であり、外側のmin
とmax
は常に初期値の0のままでした。
このコミットでは、この冗長性を解消するために、以下の変更が行われました。
Parse
関数の冒頭で宣言されていた未使用のmin, max int
変数を削除しました。p.repeat
関数を呼び出す際に、常に0を直接引数として渡すように変更しました。これは、外側のmin
とmax
が常に0であったため、その値を渡すことと実質的に同じ意味になります。
この変更により、コードはより簡潔になり、未使用の変数が存在しないため、可読性と保守性が向上します。機能的な変更は一切なく、正規表現の解析動作に影響はありません。
コアとなるコードの変更箇所
--- a/src/pkg/regexp/syntax/parse.go
+++ b/src/pkg/regexp/syntax/parse.go
@@ -668,7 +668,6 @@ func Parse(s string, flags Flags) (*Regexp, error) {
c rune
op Op
lastRepeat string
- min, max int
)
p.flags = flags
p.wholeRegexp = s
@@ -740,7 +739,7 @@ func Parse(s string, flags Flags) (*Regexp, error) {\n op = OpQuest
}\n after := t[1:]
- if after, err = p.repeat(op, min, max, before, after, lastRepeat); err != nil {\n+\t\t\tif after, err = p.repeat(op, 0, 0, before, after, lastRepeat); err != nil {\n \t\t\t\treturn nil, err
}\n repeat = before
コアとなるコードの解説
上記の差分は、以下の2つの主要な変更を示しています。
-
- min, max int
:Parse
関数の冒頭で宣言されていたmin
とmax
変数の宣言が削除されました。これは、これらの変数が外側のスコープで定義されていても、case '{'
ブロック内で同名の新しい変数が宣言され、そちらが使用されていたため、この外側のmin
とmax
は常に初期値の0のままで未使用であったことを意味します。未使用の変数を削除することで、コードの冗長性が排除され、コンパイラによる最適化の機会が増えます。 -
if after, err = p.repeat(op, 0, 0, before, after, lastRepeat); err != nil {
:case '{'
ブロック内でp.repeat
関数を呼び出す際の引数が変更されました。変更前は、削除された外側のスコープのmin
とmax
が引数として渡されていましたが、これらが常に0であったため、直接0, 0
を渡すように修正されました。この変更は、機能的には全く同じ結果をもたらしますが、コードの意図をより明確にし、未使用の変数を排除することで、コードベース全体の健全性を向上させます。
この変更は、Go言語のコードレビュープロセスにおいて、コードの品質と効率性を維持するための典型的な改善例と言えます。
関連リンク
- Go CL 78750044: https://golang.org/cl/78750044
参考にした情報源リンク
- Go言語
regexp
パッケージ公式ドキュメント: https://pkg.go.dev/regexp - Go言語
regexp/syntax
パッケージ公式ドキュメント: https://pkg.go.dev/regexp/syntax - RE2 正規表現エンジン: https://github.com/google/re2/wiki/Syntax
- Go言語の変数スコープに関する解説記事 (一般的な情報源): https://go.dev/doc/effective_go#declarations_and_scope (Go公式ドキュメントのEffective Goより)
- Web検索結果:
Go regexp syntax parse.go
(上記で利用した検索結果のソース)