[インデックス 11146] ファイルの概要
このコミットは、Go言語のパーサーライブラリである go/parser パッケージにおける不要な関数の削除と、ParseExpr 関数のシグネチャの簡素化を目的としています。具体的には、ParseStmtList および ParseDeclList 関数が削除され、ParseExpr の引数が (fset *token.FileSet, filename string, src interface{}) から (x string) へと変更されました。これにより、パーサーのAPIがよりシンプルになり、内部でのみ使用されるパーシングロジックが整理されました。
コミット
commit 74cb96322502ab686be92ce7bd07464a62afb011
Author: Robert Griesemer <gri@golang.org>
Date: Thu Jan 12 16:04:48 2012 -0800
go/parser: Remove unused Parse* functions. Simplified ParseExpr signature.
Only ParseFile, ParseDir, and ParseExpr are used in the tree.
If partial parsing of code is required, it is fairly simple
to wrap the relevant piece of code into a dummy package for
parsing (see parser.ParseExpr).
Also: minor cleanups.
R=rsc
CC=golang-dev
https://golang.org/cl/5535055
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/74cb96322502ab686be92ce7bd07464a62afb011
元コミット内容
このコミットの元の内容は、go/parser パッケージから未使用の Parse* 関数を削除し、ParseExpr のシグネチャを簡素化することです。コミットメッセージによると、Goのソースツリー内で実際に使用されているパーシング関数は ParseFile、ParseDir、そして ParseExpr のみでした。部分的なコードのパースが必要な場合でも、関連するコードをダミーのパッケージでラップすることで簡単に実現できると説明されています(新しい parser.ParseExpr の実装を参照)。また、いくつかの小さなクリーンアップも含まれています。
変更の背景
この変更の背景には、Go言語の標準ライブラリのコードベースを整理し、不要なAPIを削減するという意図があります。go/parser パッケージはGoのソースコードを抽象構文木(AST)に変換するための重要なツールですが、時間の経過とともに、一部の関数が外部から利用されなくなったり、より汎用的な関数で代替可能になったりすることがあります。
具体的には、ParseStmtList(ステートメントリストのパース)や ParseDeclList(宣言リストのパース)といった関数は、Goのソースツリー内の他のツールやコンポーネントで直接使用されていませんでした。これらの関数は、ファイル全体をパースする ParseFile や、ディレクトリ全体をパースする ParseDir、あるいは単一の式をパースする ParseExpr と比較して、利用シーンが限定的であったと考えられます。
また、ParseExpr 関数は、以前は token.FileSet、ファイル名、ソースコードのインターフェースといった複数の引数を必要としていましたが、コミットメッセージにあるように、式をパースする際には通常、その式が属する完全なコンテキスト(パッケージなど)を必要としないか、あるいはダミーのコンテキストで十分であることが判明しました。そのため、APIを簡素化し、より使いやすくするために、引数をパース対象の式を表す文字列のみに絞る変更が行われました。これにより、go/parser の利用者がより直感的に式をパースできるようになります。
前提知識の解説
このコミットを理解するためには、以下のGo言語の概念と go/parser パッケージに関する知識が必要です。
- Go言語の抽象構文木 (AST: Abstract Syntax Tree): Goのソースコードは、コンパイラやツールによって解析され、その構造が抽象構文木として表現されます。ASTは、プログラムの構造を階層的なツリー形式で表現したもので、各ノードがプログラムの要素(式、ステートメント、宣言など)に対応します。
go/astパッケージがASTの型定義を提供します。 go/parserパッケージ: Go言語のソースコードを解析し、ASTを生成するためのパッケージです。Goコンパイラやgofmt、go vetなどのGoツール群の基盤となっています。go/tokenパッケージ: ソースコード内の位置情報(ファイル、行、列など)を管理するためのパッケージです。token.FileSetは、複数のソースファイルをまとめて管理し、ファイル内のオフセットから正確な位置情報を取得するために使用されます。ast.Expr: ASTにおける「式」を表すインターフェースです。例えば、a + bやf(x)などが式に該当します。ast.Stmt: ASTにおける「ステートメント(文)」を表すインターフェースです。例えば、return xやif x > 0 { ... }などがステートメントに該当します。ast.Decl: ASTにおける「宣言」を表すインターフェースです。例えば、変数宣言 (var x int) や関数宣言 (func f() {}) などが宣言に該当します。ParseFile: 単一のGoソースファイルをパースし、そのファイルのAST (*ast.File) を生成する関数です。ParseDir: 指定されたディレクトリ内のGoソースファイルをすべてパースし、パッケージごとのASTのマップを生成する関数です。ParseExpr: 単一のGoの式をパースし、その式のAST (ast.Expr) を生成する関数です。
これらの関数は、Goのソースコードをプログラム的に操作したり、分析したりする際に不可欠なものです。
技術的詳細
このコミットの技術的な変更点は、主に src/pkg/go/parser/interface.go と src/pkg/go/parser/parser.go に集中しています。
-
ParseStmtListおよびParseDeclListの削除:src/pkg/go/parser/interface.goから、ParseStmtListとParseDeclListの両関数が完全に削除されました。これに伴い、src/pkg/go/parser/parser.go内の対応する内部パーシングメソッドparseStmtList()とparseDeclList()も削除されています。これは、これらの関数がGoのソースツリー内で使用されていなかったため、APIの簡素化とコードベースの軽量化を目的としたものです。 -
ParseExprのシグネチャ変更と実装の簡素化:-
以前の
ParseExprのシグネチャはfunc ParseExpr(fset *token.FileSet, filename string, src interface{}) (ast.Expr, error)でした。 -
新しい
ParseExprのシグネチャはfunc ParseExpr(x string) (ast.Expr, error)となり、引数がパース対象の式を表す文字列xのみになりました。 -
新しい
ParseExprの実装は非常に興味深いものです。単に式をパースするのではなく、その式をダミーのGoパッケージと関数の中に埋め込むことで、完全なGoの構文解析コンテキストを利用して式をパースしています。具体的には、以下の形式の文字列を内部的に生成してParseFileに渡しています。"package p;func _(){_=\n//line :1\\n" + x + ";}"この文字列は、
package pというダミーのパッケージ、func _(){...}というダミーの関数、そしてその関数内で_=の右辺にパースしたい式xを配置しています。//line :1ディレクティブは、エラーメッセージの行番号を正しく表示するためのものです。ParseFileでこの全体をパースした後、生成されたASTから目的の式 (file.Decls[0].(*ast.FuncDecl).Body.List[0].(*ast.AssignStmt).Rhs[0]) を抽出して返しています。これにより、ParseExprはより堅牢なパーシングロジックを利用しつつ、外部からはシンプルなAPIとして提供されるようになりました。
-
-
src/cmd/cgo/gcc.go,src/cmd/gofix/fix.go,src/cmd/gofmt/rewrite.goの変更: これらのファイルでは、以前のparser.ParseExpr(fset, "", n.Define)のような呼び出しが、新しいシグネチャに合わせてparser.ParseExpr(n.Define)のように変更されています。これは、ParseExprのAPI変更に伴う既存コードの修正です。 -
src/pkg/go/parser/parser_test.goの変更:TestParseExprという新しいテスト関数が追加され、新しいParseExprの動作が検証されています。有効な式と無効な式の両方でテストが行われ、パースが正しく行われること、エラーが適切に報告されること、そしてパース中にクラッシュしないことが確認されています。
全体として、このコミットは go/parser パッケージのAPIを合理化し、内部実装をより効率的かつ堅牢にするための重要なステップです。
コアとなるコードの変更箇所
このコミットにおけるコアとなるコードの変更箇所は以下のファイルに集中しています。
src/pkg/go/parser/interface.go:ParseExpr,ParseStmtList,ParseDeclList関数の削除。- 新しい
ParseExpr関数の追加と実装。 readSource関数の不要なdefaultケースの削除。
src/pkg/go/parser/parser.go:parseStmtList()およびparseDeclList()メソッドの削除。scannerMode関数の削除と、parser.init内でのスキャナーモード設定の簡素化。parser.errors()メソッドの移動(interface.goからparser.goへ)。
src/cmd/cgo/gcc.go,src/cmd/gofix/fix.go,src/cmd/gofmt/rewrite.go:parser.ParseExprの呼び出し箇所のシグネチャ変更への対応。
src/pkg/go/parser/parser_test.go:TestParseExprの追加。
コアとなるコードの解説
最も重要な変更は、src/pkg/go/parser/interface.go における ParseExpr の新しい実装です。
// ParseExpr is a convenience function for obtaining the AST of an expression x.
// The position information recorded in the AST is undefined.
//
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)
if err != nil {
return nil, err
}
return file.Decls[0].(*ast.FuncDecl).Body.List[0].(*ast.AssignStmt).Rhs[0], nil
}
このコードは、与えられた式 x をパースするために、以下のような手順を踏んでいます。
-
ダミーのGoコードの生成:
"package p;func _(){_=\n//line :1\\n"+x+";}"という文字列を生成します。package p;: ダミーのパッケージ宣言。func _(){...}: ダミーの関数宣言。関数名は_で、引数もありません。_=\n//line :1\\n"+x+";}: 関数本体内で、_に式xを代入するステートメント。//line :1は、パースエラーが発生した際に、元の式xの行番号が正しく報告されるようにするためのGoの特殊なディレクティブです。
-
ParseFileによるパース: 生成されたダミーのGoコード全体をParseFile関数に渡してパースします。ParseFileはファイル全体をパースする機能を持つため、この方法で式をパースすることが可能です。token.NewFileSet()で新しいFileSetを作成し、ファイル名は空文字列、モードは0(デフォルト) を指定しています。 -
ASTからの式抽出:
ParseFileが成功すると、ダミーのGoコード全体のAST (*ast.File) が返されます。このASTから、目的の式xに対応するast.Exprを抽出します。file.Decls[0]: パッケージ内の最初の宣言(この場合はダミーの関数宣言func _(){...})。(*ast.FuncDecl): 型アサーションにより、これが関数宣言であることを確認します。.Body.List[0]: 関数本体内の最初のステートメント(この場合は代入ステートメント_ = x)。(*ast.AssignStmt): 型アサーションにより、これが代入ステートメントであることを確認します。.Rhs[0]: 代入ステートメントの右辺の最初の式(これが目的の式xです)。
この巧妙な実装により、ParseExpr は内部的に ParseFile の強力な機能を利用しつつ、外部の利用者にはシンプルなインターフェースを提供しています。これにより、部分的なコードのパースが必要な場合でも、Goのパーサーの完全な機能とエラー報告メカニズムを活用できるようになっています。
関連リンク
- Go言語の公式ドキュメント: https://golang.org/doc/
go/parserパッケージのドキュメント: https://pkg.go.dev/go/parsergo/astパッケージのドキュメント: https://pkg.go.dev/go/astgo/tokenパッケージのドキュメント: https://pkg.go.dev/go/token
参考にした情報源リンク
- コミットハッシュ:
74cb96322502ab686be92ce7bd07464a62afb011 - GitHub上のコミットページ: https://github.com/golang/go/commit/74cb96322502ab686be92ce7bd07464a62afb011
- Gerrit Change-Id:
https://golang.org/cl/5535055(これはGoプロジェクトが内部で使用するコードレビューシステムへのリンクです)