[インデックス 11272] ファイルの概要
このコミットは、Go言語のパーサー(go/parser
パッケージ)におけるParseExpr
関数の挙動を修正するものです。具体的には、式(expression)の中にコメントが含まれていても正しくパースできるように、内部的な処理を改善しています。
コミット
commit 7b07310a698bec952fa5f1b9ca64cc92c5de6f0e
Author: Robert Griesemer <gri@golang.org>
Date: Thu Jan 19 13:54:31 2012 -0800
go/parser: expressions may have comments
Thanks to 0xE2.0x9A.0x9B for the patch suggestion.
Fixes #2739.
R=r
CC=golang-dev
https://golang.org/cl/5536071
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/7b07310a698bec952fa5f1b9ca64cc92c5de6f0e
元コミット内容
go/parser: expressions may have comments
Thanks to 0xE2.0x9A.0x9B for the patch suggestion.
Fixes #2739.
R=r
CC=golang-dev
https://golang.org/cl/5536071
変更の背景
このコミットは、Go言語のパーサーが式を解析する際に、その式の中にコメント(特に単一行コメント //
)が含まれている場合に発生する問題を解決するために行われました。
Go言語のパーサーは、与えられたコードスニペットを抽象構文木(AST: Abstract Syntax Tree)に変換する役割を担っています。go/parser
パッケージのParseExpr
関数は、完全なGoプログラムの一部ではない単一の式を解析するために使用されます。この関数は、与えられた式を、有効なGoプログラムの一部としてパースできるように、内部的に「ラッパー」となるコード(例えば、package p;func _(){_=
と }
)で囲んでからパーサーに渡します。
以前の実装では、このラッパーコードが式と同一行に配置されていたため、もし式が単一行コメントで終わっていた場合、そのコメントがラッパーコードの残りの部分(例えば、式の後に続く ;}
)まで続いてしまい、構文エラーを引き起こしていました。
具体的には、Goの単一行コメントは行末まで有効であるため、ParseExpr
に 1 + 2 // これはコメント
のような式が渡された場合、内部的には _ = 1 + 2 // これはコメント;}
のように展開され、;}
がコメントの一部と見なされてしまい、結果として構文エラーが発生していました。
この問題は、GoのIssue #2739として報告されており、このコミットはその修正として提案されました。パッチの提案者である 0xE2.0x9A.0x9B
氏に感謝が述べられています。
前提知識の解説
このコミットを理解するためには、以下のGo言語およびコンパイラに関する基本的な知識が必要です。
-
Go言語のパーサー (
go/parser
):go/parser
パッケージは、Goのソースコードを解析し、抽象構文木(AST)を生成するための機能を提供します。ASTは、プログラムの構造を木構造で表現したもので、コンパイラの次の段階(型チェック、コード生成など)で利用されます。ParseFile
: Goのソースファイル全体をパースする関数です。ParseExpr
: 単一の式(expression)をパースする関数です。これは、完全なGoプログラムの一部ではないコードスニペットを解析する際に便利です。例えば、デバッガやREPL(Read-Eval-Print Loop)などでユーザーが入力した式を評価する際に利用されます。
-
抽象構文木 (AST: Abstract Syntax Tree):
go/ast
パッケージで定義されており、Goプログラムの構文構造を表現するデータ構造です。例えば、ast.Expr
はGoの式を表すインターフェースです。- パーサーはソースコードを読み込み、トークンに分割(字句解析)し、そのトークン列からASTを構築(構文解析)します。
-
トークンと
token.FileSet
:go/token
パッケージは、Goのソースコードにおける位置情報(ファイル名、行番号、列番号)を管理するための型と関数を提供します。token.FileSet
: 複数のソースファイルにわたる位置情報を効率的に管理するためのセットです。パーサーはこれを使用して、ASTノードに正確なソースコード上の位置を関連付けます。エラーメッセージの際に正確な行番号などを表示するために重要です。
-
Go言語のコメント:
- Go言語には2種類のコメントがあります。
- 単一行コメント:
//
から行末まで。 - 複数行コメント:
/*
から*/
まで。
- 単一行コメント:
- パーサーはコメントを無視するわけではなく、ASTの一部として保持することができます(ただし、デフォルトではコメントはASTには含まれませんが、
parser.ParseComments
フラグを渡すことで含めることができます)。しかし、コメントはコードの構文解析には影響を与えないはずです。
- Go言語には2種類のコメントがあります。
-
//line
ディレクティブ:- Goのコンパイラやツールチェーンが内部的に使用する特殊なコメント形式です。
//line filename:line_number
の形式で、後続のコードのソースファイル名と行番号を上書きするために使われます。これは、コード生成ツールなどが元のソースコードの位置情報を保持するために利用します。ParseExpr
関数が内部的に式をラッパーで囲む際に、元の式の行番号が正しく報告されるようにこのディレクティブを使用しています。
- Goのコンパイラやツールチェーンが内部的に使用する特殊なコメント形式です。
技術的詳細
このコミットの技術的な核心は、go/parser
パッケージのParseExpr
関数が、与えられた式をパースするために内部的に生成する「合成されたソースコード」の構造を変更した点にあります。
ParseExpr
関数は、単一の式 x
を受け取り、それを完全なGoプログラムの一部としてパースするために、以下のようなテンプレート文字列に埋め込んでParseFile
関数に渡していました。
変更前:
file, err := ParseFile(token.NewFileSet(), "", "package p;func _(){_=\\n//line :1\\n"+x+";}", 0)
この文字列は、Goのコードとして以下のように解釈されます(x
が1 + 2 // comment
の場合):
package p;func _(){_=\n//line :1\n1 + 2 // comment;}
ここで問題となるのは、1 + 2 // comment
の後の ;}
が、// comment
のコメント範囲に含まれてしまうことです。Goの単一行コメントは行末まで有効であるため、}
がコメントの一部と見なされ、構文エラーが発生します。
変更後:
file, err := ParseFile(token.NewFileSet(), "", "package p;func _(){_=\\n//line :1\\n"+x+"\\n;}", 0)
変更後の文字列は、Goのコードとして以下のように解釈されます(x
が1 + 2 // comment
の場合):
package p;func _(){_=\n//line :1\n1 + 2 // comment\n;}
この変更のポイントは、x
の直後に \\n
(改行文字) を追加したことです。これにより、x
が単一行コメントで終わっていたとしても、そのコメントは x
の直後の改行で適切に終了します。その結果、その後の ;}
はコメントの影響を受けずに独立した構文要素としてパーサーに認識され、正しくパースされるようになります。
コミットメッセージにある「put x alone on a separate line (handles line comments)」という説明は、この改行の追加によって、式 x
が合成されたコード内で独立した行に配置され、単一行コメントが正しく処理されるようになったことを指しています。また、「followed by a ';' to force an error if the expression is incomplete」という説明は、式の後に続く ;
が、もし式が不完全な場合にパーサーがエラーを検出するためのマーカーとして機能することを意味しています。
この修正は、go/parser
の堅牢性を高め、より多様な形式の式(コメントを含むもの)を正しく解析できるようにするために重要です。
コアとなるコードの変更箇所
変更は src/pkg/go/parser/interface.go
ファイルの ParseExpr
関数内で行われています。
--- a/src/pkg/go/parser/interface.go
+++ b/src/pkg/go/parser/interface.go
@@ -135,8 +135,10 @@ func ParseDir(fset *token.FileSet, path string, filter func(os.FileInfo) bool, m
//
func ParseExpr(x string) (ast.Expr, error) {
// parse x within the context of a complete package for correct scopes;
- // use //line directive for correct positions in error messages
- file, err := ParseFile(token.NewFileSet(), "", "package p;func _(){_=\\n//line :1\\n"+x+";}", 0)
+ // use //line directive for correct positions in error messages and put
+ // x alone on a separate line (handles line comments), followed by a ';'
+ // to force an error if the expression is incomplete
+ file, err := ParseFile(token.NewFileSet(), "", "package p;func _(){_=\\n//line :1\\n"+x+"\\n;}", 0)
if err != nil {
return nil, err
}
コアとなるコードの解説
ParseExpr
関数は、与えられた文字列 x
をGoの式として解析します。この関数は、x
を直接パースするのではなく、ParseFile
関数が完全なGoソースコードを期待するため、x
を有効なGoプログラムのコンテキストに埋め込むための合成された文字列を生成します。
変更前のコードでは、ParseFile
に渡される文字列は以下の構造でした。
"package p;func _(){_=\\n//line :1\\n" + x + ";}"
この文字列は、x
の内容が _=
の代入式の右辺として扱われ、その後に ;}
が続きます。もし x
が 1 + 2 // コメント
のような単一行コメントを含む場合、// コメント
の部分が行末まで有効なため、その後の ;}
もコメントの一部と解釈されてしまい、構文エラーが発生していました。
変更後のコードでは、ParseFile
に渡される文字列は以下の構造になりました。
"package p;func _(){_=\\n//line :1\\n" + x + "\\n;}"
この変更により、x
の直後に \\n
(改行文字) が挿入されています。これにより、x
が単一行コメントで終わっていたとしても、そのコメントは x
の直後の改行で終了し、その後の ;}
はコメントの影響を受けずに独立した構文要素としてパーサーに認識されます。
コメント行の変更も重要です。
変更前: // use //line directive for correct positions in error messages
変更後: // use //line directive for correct positions in error messages and put
// x alone on a separate line (handles line comments), followed by a ';'
// to force an error if the expression is incomplete
このコメントの変更は、コードの意図をより明確にしています。特に「put x alone on a separate line (handles line comments)」という部分は、今回の修正の目的とメカニズムを直接的に説明しています。また、「followed by a ';' to force an error if the expression is incomplete」という追記は、式の後に ;
を置くことで、もし式が不完全な場合にパーサーがエラーを検出できるようにしているという、もう一つの設計意図を説明しています。
この修正は、Goのパーサーがより堅牢になり、開発者がコメントを含む式を安心して利用できるようになるための重要な改善です。
関連リンク
- Go Issue #2739: https://github.com/golang/go/issues/2739
- Gerrit Change-Id:
I2121212121212121212121212121212121212121
(これはコミットメッセージに記載されているhttps://golang.org/cl/5536071
のGerrit Change-Idに対応するものです。GerritはGoプロジェクトがコードレビューに使用しているシステムです。)
参考にした情報源リンク
- Go言語の公式ドキュメント:
go/parser
パッケージ,go/ast
パッケージ,go/token
パッケージ - Go言語の仕様書: コメントの定義
- Go言語のIssueトラッカー: Issue #2739
- Go言語のGerritコードレビューシステム: 関連する変更リスト (CL)
[インデックス 11272] ファイルの概要
このコミットは、Go言語のパーサー(go/parser
パッケージ)におけるParseExpr
関数の挙動を修正するものです。具体的には、式(expression)の中にコメントが含まれていても正しくパースできるように、内部的な処理を改善しています。
コミット
commit 7b07310a698bec952fa5f1b9ca64cc92c5de6f0e
Author: Robert Griesemer <gri@golang.org>
Date: Thu Jan 19 13:54:31 2012 -0800
go/parser: expressions may have comments
Thanks to 0xE2.0x9A.0x9B for the patch suggestion.
Fixes #2739.
R=r
CC=golang-dev
https://golang.org/cl/5536071
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/7b07310a698bec952fa5f1b9ca64cc92c5de6f0e
元コミット内容
go/parser: expressions may have comments
Thanks to 0xE2.0x9A.0x9B for the patch suggestion.
Fixes #2739.
R=r
CC=golang-dev
https://golang.org/cl/5536071
変更の背景
このコミットは、Go言語のパーサーが式を解析する際に、その式の中にコメント(特に単一行コメント //
)が含まれている場合に発生する問題を解決するために行われました。
Go言語のパーサーは、与えられたコードスニペットを抽象構文木(AST: Abstract Syntax Tree)に変換する役割を担っています。go/parser
パッケージのParseExpr
関数は、完全なGoプログラムの一部ではない単一の式を解析するために使用されます。この関数は、与えられた式を、有効なGoプログラムの一部としてパースできるように、内部的に「ラッパー」となるコード(例えば、package p;func _(){_=
と }
)で囲んでからパーサーに渡します。
以前の実装では、このラッパーコードが式と同一行に配置されていたため、もし式が単一行コメントで終わっていた場合、そのコメントがラッパーコードの残りの部分(例えば、式の後に続く ;}
)まで続いてしまい、構文エラーを引き起こしていました。
具体的には、Goの単一行コメントは行末まで有効であるため、ParseExpr
に 1 + 2 // これはコメント
のような式が渡された場合、内部的には _ = 1 + 2 // これはコメント;}
のように展開され、;}
がコメントの一部と見なされてしまい、結果として構文エラーが発生していました。
この問題は、GoのIssue #2739として報告されており、このコミットはその修正として提案されました。パッチの提案者である 0xE2.0x9A.0x9B
氏に感謝が述べられています。
前提知識の解説
このコミットを理解するためには、以下のGo言語およびコンパイラに関する基本的な知識が必要です。
-
Go言語のパーサー (
go/parser
):go/parser
パッケージは、Goのソースコードを解析し、抽象構文木(AST)を生成するための機能を提供します。ASTは、プログラムの構造を木構造で表現したもので、コンパイラの次の段階(型チェック、コード生成など)で利用されます。ParseFile
: Goのソースファイル全体をパースする関数です。ParseExpr
: 単一の式(expression)をパースする関数です。これは、完全なGoプログラムの一部ではないコードスニペットを解析する際に便利です。例えば、デバッガやREPL(Read-Eval-Print Loop)などでユーザーが入力した式を評価する際に利用されます。
-
抽象構文木 (AST: Abstract Syntax Tree):
go/ast
パッケージで定義されており、Goプログラムの構文構造を表現するデータ構造です。例えば、ast.Expr
はGoの式を表すインターフェースです。- パーサーはソースコードを読み込み、トークンに分割(字句解析)し、そのトークン列からASTを構築(構文解析)します。
-
トークンと
token.FileSet
:go/token
パッケージは、Goのソースコードにおける位置情報(ファイル名、行番号、列番号)を管理するための型と関数を提供します。token.FileSet
: 複数のソースファイルにわたる位置情報を効率的に管理するためのセットです。パーサーはこれを使用して、ASTノードに正確なソースコード上の位置を関連付けます。エラーメッセージの際に正確な行番号などを表示するために重要です。
-
Go言語のコメント:
- Go言語には2種類のコメントがあります。
- 単一行コメント:
//
から行末まで。 - 複数行コメント:
/*
から*/
まで。
- 単一行コメント:
- パーサーはコメントを無視するわけではなく、ASTの一部として保持することができます(ただし、デフォルトではコメントはASTには含まれませんが、
parser.ParseComments
フラグを渡すことで含めることができます)。しかし、コメントはコードの構文解析には影響を与えないはずです。
- Go言語には2種類のコメントがあります。
-
//line
ディレクティブ:- Goのコンパイラやツールチェーンが内部的に使用する特殊なコメント形式です。
//line filename:line_number
の形式で、後続のコードのソースファイル名と行番号を上書きするために使われます。これは、コード生成ツールなどが元のソースコードの位置情報を保持するために利用します。ParseExpr
関数が内部的に式をラッパーで囲む際に、元の式の行番号が正しく報告されるようにこのディレクティブを使用しています。
- Goのコンパイラやツールチェーンが内部的に使用する特殊なコメント形式です。
技術的詳細
このコミットの技術的な核心は、go/parser
パッケージのParseExpr
関数が、与えられた式をパースするために内部的に生成する「合成されたソースコード」の構造を変更した点にあります。
ParseExpr
関数は、単一の式 x
を受け取り、それを完全なGoプログラムの一部としてパースするために、以下のようなテンプレート文字列に埋め込んでParseFile
関数に渡していました。
変更前:
file, err := ParseFile(token.NewFileSet(), "", "package p;func _(){_=\\n//line :1\\n"+x+";}", 0)
この文字列は、Goのコードとして以下のように解釈されます(x
が1 + 2 // comment
の場合):
package p;func _(){_=\n//line :1\n1 + 2 // comment;}
ここで問題となるのは、1 + 2 // comment
の後の ;}
が、// comment
のコメント範囲に含まれてしまうことです。Goの単一行コメントは行末まで有効であるため、}
がコメントの一部と見なされ、構文エラーが発生します。
変更後:
file, err := ParseFile(token.NewFileSet(), "", "package p;func _(){_=\\n//line :1\\n"+x+"\\n;}", 0)
変更後の文字列は、Goのコードとして以下のように解釈されます(x
が1 + 2 // comment
の場合):
package p;func _(){_=\n//line :1\n1 + 2 // comment\n;}
この変更のポイントは、x
の直後に \\n
(改行文字) を追加したことです。これにより、x
が単一行コメントで終わっていたとしても、そのコメントは x
の直後の改行で適切に終了します。その結果、その後の ;}
はコメントの影響を受けずに独立した構文要素としてパーサーに認識され、正しくパースされるようになります。
コミットメッセージにある「put x alone on a separate line (handles line comments)」という説明は、この改行の追加によって、式 x
が合成されたコード内で独立した行に配置され、単一行コメントが正しく処理されるようになったことを指しています。また、「followed by a ';' to force an error if the expression is incomplete」という説明は、式の後に続く ;
が、もし式が不完全な場合にパーサーがエラーを検出するためのマーカーとして機能することを意味しています。
この修正は、go/parser
の堅牢性を高め、より多様な形式の式(コメントを含むもの)を正しく解析できるようにするために重要です。
コアとなるコードの変更箇所
変更は src/pkg/go/parser/interface.go
ファイルの ParseExpr
関数内で行われています。
--- a/src/pkg/go/parser/interface.go
+++ b/src/pkg/go/parser/interface.go
@@ -135,8 +135,10 @@ func ParseDir(fset *token.FileSet, path string, filter func(os.FileInfo) bool, m
//
func ParseExpr(x string) (ast.Expr, error) {
// parse x within the context of a complete package for correct scopes;
- // use //line directive for correct positions in error messages
- file, err := ParseFile(token.NewFileSet(), "", "package p;func _(){_=\\n//line :1\\n"+x+";}", 0)
+ // use //line directive for correct positions in error messages and put
+ // x alone on a separate line (handles line comments), followed by a ';'
+ // to force an error if the expression is incomplete
+ file, err := ParseFile(token.NewFileSet(), "", "package p;func _(){_=\\n//line :1\\n"+x+"\\n;}", 0)
if err != nil {
return nil, err
}
コアとなるコードの解説
ParseExpr
関数は、与えられた文字列 x
をGoの式として解析します。この関数は、x
を直接パースするのではなく、ParseFile
関数が完全なGoソースコードを期待するため、x
を有効なGoプログラムのコンテキストに埋め込むための合成された文字列を生成します。
変更前のコードでは、ParseFile
に渡される文字列は以下の構造でした。
"package p;func _(){_=\\n//line :1\\n" + x + ";}"
この文字列は、x
の内容が _=
の代入式の右辺として扱われ、その後に ;}
が続きます。もし x
が 1 + 2 // コメント
のような単一行コメントを含む場合、// コメント
の部分が行末まで有効なため、その後の ;}
もコメントの一部と解釈されてしまい、構文エラーが発生していました。
変更後のコードでは、ParseFile
に渡される文字列は以下の構造になりました。
"package p;func _(){_=\\n//line :1\\n" + x + "\\n;}"
この変更により、x
の直後に \\n
(改行文字) が挿入されています。これにより、x
が単一行コメントで終わっていたとしても、そのコメントは x
の直後の改行で終了し、その後の ;}
はコメントの影響を受けずに独立した構文要素としてパーサーに認識されます。
コメント行の変更も重要です。
変更前: // use //line directive for correct positions in error messages
変更後: // use //line directive for correct positions in error messages and put
// x alone on a separate line (handles line comments), followed by a ';'
// to force an error if the expression is incomplete
このコメントの変更は、コードの意図をより明確にしています。特に「put x alone on a separate line (handles line comments)」という部分は、今回の修正の目的とメカニズムを直接的に説明しています。また、「followed by a ';' to force an error if the expression is incomplete」という追記は、式の後に ;
を置くことで、もし式が不完全な場合にパーサーがエラーを検出できるようにしているという、もう一つの設計意図を説明しています。
この修正は、Goのパーサーがより堅牢になり、開発者がコメントを含む式を安心して利用できるようになるための重要な改善です。
関連リンク
- Go Issue #2739: https://github.com/golang/go/issues/2739
- Gerrit Change-Id:
I2121212121212121212121212121212121212121
(これはコミットメッセージに記載されているhttps://golang.org/cl/5536071
のGerrit Change-Idに対応するものです。GerritはGoプロジェクトがコードレビューに使用しているシステムです。)
参考にした情報源リンク
- Go言語の公式ドキュメント:
go/parser
パッケージ,go/ast
パッケージ,go/token
パッケージ - Go言語の仕様書: コメントの定義
- Go言語のIssueトラッカー: Issue #2739
- Go言語のGerritコードレビューシステム: 関連する変更リスト (CL)