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

[インデックス 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のソースツリー内で実際に使用されているパーシング関数は ParseFileParseDir、そして 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コンパイラや gofmtgo vet などのGoツール群の基盤となっています。
  • go/token パッケージ: ソースコード内の位置情報(ファイル、行、列など)を管理するためのパッケージです。token.FileSet は、複数のソースファイルをまとめて管理し、ファイル内のオフセットから正確な位置情報を取得するために使用されます。
  • ast.Expr: ASTにおける「式」を表すインターフェースです。例えば、a + bf(x) などが式に該当します。
  • ast.Stmt: ASTにおける「ステートメント(文)」を表すインターフェースです。例えば、return xif 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.gosrc/pkg/go/parser/parser.go に集中しています。

  1. ParseStmtList および ParseDeclList の削除: src/pkg/go/parser/interface.go から、ParseStmtListParseDeclList の両関数が完全に削除されました。これに伴い、src/pkg/go/parser/parser.go 内の対応する内部パーシングメソッド parseStmtList()parseDeclList() も削除されています。これは、これらの関数がGoのソースツリー内で使用されていなかったため、APIの簡素化とコードベースの軽量化を目的としたものです。

  2. 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として提供されるようになりました。

  3. 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変更に伴う既存コードの修正です。

  4. 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 をパースするために、以下のような手順を踏んでいます。

  1. ダミーのGoコードの生成: "package p;func _(){_=\n//line :1\\n"+x+";}" という文字列を生成します。

    • package p;: ダミーのパッケージ宣言。
    • func _(){...}: ダミーの関数宣言。関数名は _ で、引数もありません。
    • _=\n//line :1\\n"+x+";}: 関数本体内で、_ に式 x を代入するステートメント。//line :1 は、パースエラーが発生した際に、元の式 x の行番号が正しく報告されるようにするためのGoの特殊なディレクティブです。
  2. ParseFile によるパース: 生成されたダミーのGoコード全体を ParseFile 関数に渡してパースします。ParseFile はファイル全体をパースする機能を持つため、この方法で式をパースすることが可能です。token.NewFileSet() で新しい FileSet を作成し、ファイル名は空文字列、モードは 0 (デフォルト) を指定しています。

  3. 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のパーサーの完全な機能とエラー報告メカニズムを活用できるようになっています。

関連リンク

参考にした情報源リンク