[インデックス 1941] ファイルの概要
このコミットは、Go言語のコンパイラにおける抽象構文木(AST: Abstract Syntax Tree)の構造を改善し、宣言の表現方法をより汎用的かつ明確にするための変更です。具体的には、DeclList
ノードをGenDecl
ノードに置き換え、個別の宣言(import, const, type, var)に対応するSpec
インターフェースと具体的な*Spec
ノード(ImportSpec
, ValueSpec
, TypeSpec
)を導入しています。これにより、ASTの設計がより柔軟になり、コードの解析と処理が効率化されます。
コミット
commit 3ba69bf08b507eb8fa9c889b230d3de1ba8fc1a6
Author: Robert Griesemer <gri@golang.org>
Date: Thu Apr 2 10:15:58 2009 -0700
Some AST tuning:
- have explicit XSpec nodes for declarations
- have a general GenDecl node instead of DeclList
R=rsc
DELTA=164 (52 added, 52 deleted, 60 changed)
OCL=27005
CL=27027
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/3ba69bf08b507eb8fa9c889b230d3de1ba8fc1a6
元コミット内容
このコミットの目的は、Go言語のAST(抽象構文木)を調整することです。主な変更点は以下の2点です。
- 宣言のために明示的な
XSpec
ノード(ImportSpec
,ValueSpec
,TypeSpec
)を持つようにする。 DeclList
の代わりに、より汎用的なGenDecl
ノードを持つようにする。
これにより、ASTの構造がより整理され、宣言の表現が一貫性を持つようになります。
変更の背景
Go言語のコンパイラは、ソースコードを解析してASTを構築します。ASTは、プログラムの構造を木構造で表現したもので、その後の型チェック、最適化、コード生成などのフェーズで利用されます。初期のGoコンパイラでは、宣言(import, const, type, var)の表現方法に改善の余地がありました。
以前のASTでは、複数の宣言をグループ化する際にDeclList
というノードが使用されていました。また、個々の宣言はImportDecl
, ConstDecl
, TypeDecl
, VarDecl
といった専用のノードで表現されていました。この設計にはいくつかの課題がありました。
- 冗長性: 各宣言タイプごとに専用のASTノードが存在するため、コードの重複や管理の複雑さが増していました。
- 柔軟性の欠如: 宣言の構造が将来的に変更された場合、多くのASTノードとそれらを処理するロジックを修正する必要がありました。
- 一貫性の欠如: 宣言のグループ化と個々の宣言の表現方法に、より汎用的なパターンを適用できる可能性がありました。
このコミットは、これらの課題に対処し、ASTの設計をよりクリーンで拡張性の高いものにすることを目指しています。特に、Go言語の宣言構文が、単一の宣言と括弧で囲まれた複数の宣言リストの両方をサポートしていることを考慮すると、これらを統一的に扱える汎用的なノードの導入は理にかなっています。
前提知識の解説
このコミットを理解するためには、以下の概念についての知識が必要です。
-
抽象構文木 (AST: Abstract Syntax Tree): コンパイラやインタプリタがソースコードを解析する際に生成する、プログラムの構造を抽象的に表現した木構造のデータ構造です。ソースコードの各要素(変数、関数、式、宣言など)がノードとして表現され、それらの関係がエッジで結ばれます。ASTは、ソースコードの意味を理解し、その後の処理(型チェック、最適化、コード生成)を行うための基盤となります。
-
Go言語の宣言: Go言語には、主に以下の種類の宣言があります。
- import 宣言: パッケージをインポートします。例:
import "fmt"
またはimport ( "fmt"; "math" )
- const 宣言: 定数を宣言します。例:
const Pi = 3.14
またはconst ( A = 1; B = 2 )
- type 宣言: 新しい型を宣言します。例:
type MyInt int
またはtype ( MyInt int; MyString string )
- var 宣言: 変数を宣言します。例:
var x int
またはvar ( x int; y string )
- func 宣言: 関数を宣言します。例:
func main() { ... }
これらの宣言は、単一で記述することも、括弧
()
で囲んで複数の宣言をグループ化して記述することもできます。このコミットは、このグループ化された宣言のAST表現を改善することに焦点を当てています。 - import 宣言: パッケージをインポートします。例:
-
token
パッケージ: Go言語の字句解析器(lexer)が生成するトークン(語彙要素)を定義するパッケージです。token.Position
は、ソースコード内の位置(ファイル名、行番号、列番号)を表します。token.Token
は、キーワード(IMPORT
,CONST
,TYPE
,VAR
,FUNC
など)や演算子、識別子などの種類を表す列挙型です。 -
ast
パッケージ: Go言語のASTノードを定義するパッケージです。このパッケージ内の構造体は、ソースコードの構文要素に対応します。例えば、ast.Ident
は識別子、ast.Expr
は式、ast.Stmt
は文を表します。このコミットでは、特に宣言に関連するASTノードの定義が変更されています。 -
parser
パッケージ: Go言語のソースコードを解析し、ASTを構築する役割を担うパッケージです。このパッケージ内の関数は、字句解析器から受け取ったトークン列を基に、構文規則に従ってASTノードを生成します。
技術的詳細
このコミットの核心は、Go言語のASTにおける宣言の表現を、より汎用的なGenDecl
ノードと、個々の宣言を表すSpec
インターフェースおよびその実装(ImportSpec
, ValueSpec
, TypeSpec
)に再構築した点にあります。
変更前:
- 個々の宣言は、
ImportDecl
,ConstDecl
,TypeDecl
,VarDecl
といった専用の構造体で表現されていました。 - 括弧で囲まれた複数の宣言(例:
import ( "fmt"; "math" )
)は、DeclList
というノードでグループ化されていました。DeclList
は、token.Token
フィールドで宣言の種類(IMPORT
,CONST
,VAR
,TYPE
)を保持し、List
フィールドにDecl
インターフェースを実装する個々の宣言ノードのリストを持っていました。
変更後:
-
Spec
インターフェースの導入:Spec
という新しいインターフェースが導入されました。これは、単一の(括弧で囲まれていない)import、constant、type、またはvariable宣言を表すノードの共通の型となります。type ( // The Spec type stands for any of *ImportSpec, *ValueSpec, and *TypeSpec. Spec interface {};
-
具体的な
*Spec
ノードの定義:ImportSpec
: 単一のパッケージインポートを表します。ImportSpec struct { Doc Comments; // associated documentation; or nil Name *Ident; // local package name (including "."); or nil Path []*StringLit; // package path };
ValueSpec
: 定数または変数宣言(ConstSpec
またはVarSpec
プロダクション)を表します。これにより、ConstDecl
とVarDecl
がValueSpec
に統合されました。ValueSpec struct { Doc Comments; // associated documentation; or nil Names []*Ident; Type Expr; // value type; or nil Values []Expr; };
TypeSpec
: 型宣言(TypeSpec
プロダクション)を表します。TypeSpec struct { Doc Comments; // associated documentation; or nil Name *Ident; // type name Type Expr; };
これらの
*Spec
ノードは、以前の*Decl
ノードからtoken.Position
フィールド(キーワードの位置)が削除されています。これは、GenDecl
ノードがその情報を持つためです。 -
GenDecl
ノードの導入とDeclList
の廃止:DeclList
ノードが廃止され、代わりにGenDecl
(汎用宣言ノード)が導入されました。GenDecl struct { Doc Comments; // associated documentation; or nil token.Position; // position of Tok Tok token.Token; // IMPORT, CONST, TYPE, VAR Lparen token.Position; // position of '(', if any Specs []Spec; Rparen token.Position; // position of ')', if any };
GenDecl
は、Tok
フィールドで宣言の種類(IMPORT
,CONST
,TYPE
,VAR
)を保持し、Specs
フィールドにSpec
インターフェースを実装するノードのリストを持ちます。これにより、単一の宣言も、括弧で囲まれた複数の宣言も、このGenDecl
ノードで統一的に表現できるようになりました。Lparen
とRparen
フィールドは、括弧で囲まれた宣言の場合にその位置を記録します。 -
DeclVisitor
インターフェースの変更: ASTノードを巡回するためのDeclVisitor
インターフェースも、新しいAST構造に合わせて変更されました。個別のDoImportDecl
,DoConstDecl
,DoTypeDecl
,DoVarDecl
,DoDeclList
メソッドが削除され、代わりにDoGenDecl
メソッドが追加されました。 -
parser
パッケージの変更:parser
パッケージ内の宣言を解析する関数も、新しいAST構造に合わせて修正されました。parseImportSpec
,parseConstSpec
,parseTypeSpec
,parseVarSpec
関数は、それぞれ*ast.ImportDecl
,*ast.ConstDecl
,*ast.TypeDecl
,*ast.VarDecl
を返す代わりに、ast.Spec
インターフェースを実装する*ast.ImportSpec
,*ast.ValueSpec
,*ast.TypeSpec
を返すようになりました。parseDecl
関数が削除され、parseGenDecl
関数が導入されました。parseGenDecl
は、token.Token
と、個々のSpec
を解析するための関数parseSpecFunction
を受け取り、*ast.GenDecl
を構築します。これにより、import
,const
,type
,var
の各宣言の解析ロジックがparseGenDecl
に集約され、コードの重複が削減されました。parseDeclaration
関数も、parseGenDecl
を呼び出すように変更されました。
この変更により、ASTの構造がよりシンプルになり、宣言の処理ロジックが一元化され、将来的な拡張が容易になりました。
コアとなるコードの変更箇所
src/lib/go/ast.go
Spec
インターフェースの追加。ImportSpec
,ValueSpec
,TypeSpec
構造体の追加。ImportDecl
,ConstDecl
,TypeDecl
,VarDecl
構造体の削除。DeclList
構造体の削除。GenDecl
構造体の追加。DeclVisitor
インターフェースから個別のDo*Decl
メソッド(DoImportDecl
,DoConstDecl
,DoTypeDecl
,DoVarDecl
,DoDeclList
)を削除し、DoGenDecl
を追加。Visit()
メソッドの実装を新しいASTノードに合わせて変更。
src/lib/go/parser.go
parseSpecFunction
型の追加。parseImportSpec
,parseConstSpec
,parseTypeSpec
,parseVarSpec
関数のシグネチャと戻り値の型をast.Spec
に変更し、内部で新しい*Spec
ノードを生成するように変更。parseSpec
関数の削除。parseGenDecl
関数の追加。この関数が、単一または括弧で囲まれた複数の宣言を解析し、GenDecl
ノードを構築する主要なロジックを担う。parseDecl
関数の削除。parseDeclaration
関数が、token.CONST
,token.TYPE
,token.VAR
の場合にparseGenDecl
を呼び出すように変更。parsePackage
関数内でtoken.IMPORT
の解析にparseGenDecl
を使用するように変更。
コアとなるコードの解説
src/lib/go/ast.go
の変更点
以前は、ImportDecl
, ConstDecl
, TypeDecl
, VarDecl
といった個別の宣言ノードが存在し、これらをグループ化する際にはDeclList
が使われていました。このコミットでは、これらの個別の宣言ノードを廃止し、より汎用的なSpec
インターフェースと、その具体的な実装であるImportSpec
, ValueSpec
, TypeSpec
を導入しました。
ValueSpec
は、ConstDecl
とVarDecl
の役割を統合しています。これにより、定数宣言と変数宣言がAST上で同じ構造で扱えるようになり、コードの重複が削減されます。
そして、DeclList
の代わりにGenDecl
が導入されました。GenDecl
は、Tok
フィールドで宣言の種類(IMPORT
, CONST
, TYPE
, VAR
)を保持し、Specs
フィールドにSpec
インターフェースを実装するノードのリストを持ちます。これにより、単一の宣言(例: var x int
)も、括弧で囲まれた複数の宣言(例: var (x int; y string)
)も、すべてGenDecl
ノードとして表現できるようになりました。Lparen
とRparen
フィールドは、括弧の有無とその位置を記録するために使用されます。
この変更により、ASTの宣言部分の構造が大幅に簡素化され、統一的な処理が可能になりました。
src/lib/go/parser.go
の変更点
parser
パッケージは、ソースコードを読み込み、ASTを構築する役割を担っています。ASTの構造変更に伴い、パーサーのロジックも大きく変更されました。
以前は、parseImportSpec
, parseConstSpec
, parseTypeSpec
, parseVarSpec
といった関数が、それぞれ対応する*Decl
ノードを直接生成していました。変更後は、これらの関数はast.Spec
インターフェースを返すように変更され、内部で新しい*Spec
ノード(*ast.ImportSpec
, *ast.ValueSpec
, *ast.TypeSpec
)を生成します。
最も重要な変更は、parseGenDecl
関数の導入です。この関数は、token.Token
(宣言の種類)と、個々のSpec
を解析するための関数(parseSpecFunction
型)を受け取ります。
parseGenDecl
の内部では、まず宣言のキーワード(import
, const
, type
, var
)を解析し、次に括弧の有無をチェックします。
- もし括弧があれば、括弧内の各宣言をループで解析し、
parseSpecFunction
を使って個々のSpec
ノードを生成し、それらをリストに追加します。 - 括弧がなければ、単一の宣言として
parseSpecFunction
を一度だけ呼び出し、Spec
ノードを生成します。
最終的に、parseGenDecl
は、解析したSpec
ノードのリストを含む*ast.GenDecl
ノードを返します。
これにより、import
, const
, type
, var
の各宣言の解析ロジックがparseGenDecl
に集約され、パーサーのコードがよりDRY(Don't Repeat Yourself)になり、保守性が向上しました。parseDeclaration
関数も、このparseGenDecl
を呼び出すように変更され、宣言の解析フローが統一されました。
関連リンク
- Go言語のASTパッケージのドキュメント(現在のバージョン): https://pkg.go.dev/go/ast
- Go言語のパーサーパッケージのドキュメント(現在のバージョン): https://pkg.go.dev/go/parser
- Go言語のトークンパッケージのドキュメント(現在のバージョン): https://pkg.go.dev/go/token
参考にした情報源リンク
- Go言語の公式ドキュメント
- コンパイラの設計に関する一般的な情報(AST、パーシングなど)
- Go言語のソースコード(特に
go/ast
とgo/parser
パッケージの歴史的な変更点) - Go言語の初期のコミット履歴
- 抽象構文木 (Abstract Syntax Tree) - Wikipedia: https://ja.wikipedia.org/wiki/%E6%8A%BD%E8%B1%A1%E6%A7%8B%E6%96%87%E6%9C%A8
- Go言語の仕様書 (Declarations and scope): https://go.dev/ref/spec#Declarations_and_scope