[インデックス 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プロジェクトが内部で使用するコードレビューシステムへのリンクです)