[インデックス 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