[インデックス 1615] ファイルの概要
このコミットは、Go言語のコンパイラにおける抽象構文木(AST)のステートメント表現を、より柔軟で拡張性の高いインターフェースベースの設計に移行するための重要なステップを示しています。具体的には、ASTノードの型を具体的な構造体からインターフェースに切り替えることで、将来的な言語仕様の変更や、異なるステートメントタイプの追加に対する適応性を高めることを目的としています。
コミット
commit 1595a1947c047070542b6c8d7478eaf485489bbb
Author: Robert Griesemer <gri@golang.org>
Date: Wed Feb 4 18:28:41 2009 -0800
today's snapshot: steps towards using interfaces for statements in ast
R=r
OCL=24380
CL=24380
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/1595a1947c047070542b6c8d7478eaf485489bbb
元コミット内容
today's snapshot: steps towards using interfaces for statements in ast
R=r
OCL=24380
CL=24380
変更の背景
この変更は、Go言語のコンパイラ(特にpretty
パッケージ内のAST表現)の設計改善の一環として行われました。初期のGo言語開発段階において、コンパイラの内部構造は進化を続けており、ASTの表現方法もその一つでした。従来のASTステートメントの表現は、具体的な構造体(Stat
)に依存しており、新しいステートメントタイプを追加する際や、ステートメントに対する共通の操作を定義する際に、柔軟性に欠ける可能性がありました。
インターフェースを導入することで、以下の利点が得られます。
- 拡張性: 新しいステートメントタイプを容易に追加できるようになります。新しい構造体は既存の
Stat
インターフェースを実装するだけでよく、既存のコードの大部分を変更する必要がなくなります。 - ポリモーフィズム: ASTを走査する際に、具体的な型に依存せず、
Stat
インターフェースとして統一的に扱うことができるようになります。これにより、コードの重複を減らし、より汎用的な処理ロジックを記述できます。 - 保守性: コードベースが大きくなるにつれて、特定のASTノードに対する変更が他の部分に与える影響を局所化しやすくなります。
- Visitorパターンへの移行: インターフェースの導入は、ASTの走査と操作に広く用いられるVisitorパターンを適用するための基盤となります。これにより、ASTの構造と操作ロジックを分離し、コードの可読性と保守性を向上させることができます。
このコミットは、特にステートメント(Stat
)に関するASTの設計を、より現代的でGoらしいインターフェースベースのアプローチに移行するための「最初の一歩」と位置づけられます。
前提知識の解説
抽象構文木 (Abstract Syntax Tree, AST)
ASTは、プログラミング言語のソースコードの抽象的な構文構造を木構造で表現したものです。コンパイラやインタプリタは、ソースコードを解析(パース)してASTを生成し、その後のセマンティック解析、最適化、コード生成などの処理に利用します。ASTは、具体的な構文要素(括弧、セミコロンなど)を省略し、プログラムの論理的な構造に焦点を当てます。
例えば、if x > 0 { y = 1 }
というコードは、以下のようなASTで表現されることがあります。
IfStatement
├── Condition: BinaryExpression (>)
│ ├── Left: Identifier (x)
│ └── Right: Literal (0)
└── Body: BlockStatement
└── AssignmentStatement (=)
├── Left: Identifier (y)
└── Right: Literal (1)
Go言語のインターフェース
Go言語のインターフェースは、メソッドのシグネチャの集合を定義する型です。Goのインターフェースは「暗黙的」に実装されます。つまり、ある型がインターフェースで定義されたすべてのメソッドを実装していれば、その型はそのインターフェースを実装しているとみなされます。明示的なimplements
キーワードは不要です。
インターフェースは、異なる具体的な型を統一的に扱うための強力なメカニズムを提供します。これにより、ポリモーフィックなコードを記述し、柔軟な設計を実現できます。
type Shape interface {
Area() float64
Perimeter() float64
}
type Circle struct {
Radius float64
}
func (c Circle) Area() float64 {
return math.Pi * c.Radius * c.Radius
}
func (c Circle) Perimeter() float64 {
return 2 * math.Pi * c.Radius
}
// Circle型はShapeインターフェースを暗黙的に実装している
Visitorパターン
Visitorパターンは、オブジェクト構造(この場合はAST)から操作を分離するためのデザインパターンです。これにより、オブジェクト構造を変更することなく、新しい操作を追加できます。
Visitorパターンは通常、以下の要素で構成されます。
- Element (要素): 訪問されるオブジェクト(ASTノード)。各要素は
Accept
メソッドを持ち、Visitorオブジェクトを受け入れます。 - Visitor (訪問者): 要素に対して実行される操作を定義するインターフェース。各要素タイプに対応する
Visit
メソッド(例:VisitIfStatement
,VisitForStatement
)を持ちます。 - ConcreteElement (具象要素): Elementインターフェースを実装する具体的なクラス(例:
IfStat
,ForStat
)。Accept
メソッド内で、対応するVisitorのVisit
メソッドを呼び出します。 - ConcreteVisitor (具象訪問者): Visitorインターフェースを実装する具体的なクラス。特定の操作(例: コード生成、型チェック、整形)を実行します。
このコミットでは、Stat
インターフェースとStatVisitor
インターフェースの導入により、VisitorパターンをASTステートメントに適用するための基盤が構築されています。
技術的詳細
このコミットの核心は、Go言語のASTにおけるステートメント(Stat
)の表現を、従来の具体的な構造体からインターフェースベースの設計へと移行することです。
ast.go
の変更
Stat
構造体のリネームとインターフェース化:- 既存の
type Stat struct;
がtype StatImpl struct;
にリネームされました。これは、古いスタイルのステートメント表現を保持しつつ、新しいインターフェースベースの設計との共存を可能にするためです。 - 新たに
type Stat interface { Visit(v StatVisitor); };
が定義されました。これにより、すべてのステートメントはStat
インターフェースを実装し、Visit
メソッドを通じてStatVisitor
を受け入れるようになります。
- 既存の
StatVisitor
インターフェースの導入:type StatVisitor interface { ... }
が追加されました。このインターフェースは、各ステートメントタイプ(BadStat
,LabelDecl
,DeclarationStat
,ExpressionStat
,IfStat
,ForStat
,SwitchStat
,SelectStat
,ControlFlowStat
)に対応するDoXxx
メソッドを定義します。これはVisitorパターンの「Visitor」部分です。
- 新しいステートメント構造体の定義:
BadStat
,LabelDecl
,DeclarationStat
,ExpressionStat
,IfStat
,ForStat
,SwitchStat
,SelectStat
,ControlFlowStat
といった、Go言語の様々なステートメントに対応する新しい構造体が定義されました。これらの構造体は、それぞれがStat
インターフェースを実装し、自身のVisit
メソッド内で対応するStatVisitor
のDoXxx
メソッドを呼び出します。これはVisitorパターンの「ConcreteElement」部分です。
ExprVisitor
へのリネーム:- 式(Expression)のVisitorインターフェースも、
Visitor
からExprVisitor
へとリネームされ、命名の一貫性が保たれています。
- 式(Expression)のVisitorインターフェースも、
parser.go
の変更
- 新旧パーサーの共存と切り替え:
var newstat = flag.Bool("newstat", false, "use new statement parsing - work in progress");
というフラグが導入されました。これは、開発中に新旧のパーシングロジックを切り替えるためのメカニズムです。ParseStatementList
関数内で、*newstat
フラグの値に応じて、新しいP.ParseStatement()
または古いP.OldParseStatement()
が呼び出されるようになりました。
- 新しいパーシング関数の導入:
ParseSimpleStat
,ParseInvocationStat
,ParseReturnStat
,ParseControlClause
,ParseIfStat
,ParseForStat
,ParseSwitchStat
,ParseSelectStat
といった新しいパーシング関数が追加されました。これらの関数は、新しいAST.Stat
インターフェース型を返すように変更されています。
- 古いパーシング関数のリネーム:
- 既存のパーシング関数は、
OldParseSimpleStat
,OldParseInvocationStat
などのようにOld
プレフィックスが付けられ、*AST.StatImpl
型を返すように変更されました。
- 既存のパーシング関数は、
ParseStatement
の更新:ParseStatement
関数は、新しいAST.Stat
インターフェースを返すように更新され、様々なトークン(CONST
,TYPE
,VAR
,FUNC
,IDENT
など)に基づいて適切な新しいステートメントタイプをパースするロジックが追加されました。
printer.go
の変更
Stat
関数のインターフェース対応:func (P *Printer) Stat(s AST.Stat)
関数が、AST.Stat
インターフェースを受け取るように変更され、s.Visit(P)
を呼び出すことで、Visitorパターンを通じて適切な印刷ロジックが実行されるようになりました。
StatImpl
関数の導入:func (P *Printer) StatImpl(s *AST.StatImpl)
が導入され、古い*AST.StatImpl
型のステートメントを処理するためのロジックが分離されました。
StatVisitor
の実装:Printer
型がStatVisitor
インターフェースを実装するようになりました。具体的には、DoBadStat
,DoLabelDecl
,DoDeclarationStat
,DoExpressionStat
,DoIfStat
,DoForStat
,DoSwitchStat
,DoSelectStat
,DoControlFlowStat
といったメソッドが追加され、それぞれのステートメントタイプに応じた印刷ロジックが記述されています。
StatementList
の更新:StatementList
関数は、リスト内の要素が*AST.StatImpl
型かAST.Stat
インターフェース型かをチェックし、それぞれに対応するP.StatImpl
またはs.Visit(P)
を呼び出すように変更されました。
ControlClause
のリファクタリング:ControlClause
関数は、新しいインターフェースベースのステートメントを扱うように変更され、古いバージョンはOldControlClause
としてリネームされました。
これらの変更により、ASTのステートメント表現がインターフェース化され、パーサーとプリンターがその新しい構造に対応するように更新されました。これは、Goコンパイラの柔軟性と拡張性を高めるための重要なアーキテクチャ変更です。
コアとなるコードの変更箇所
このコミットのコアとなる変更は、usr/gri/pretty/ast.go
におけるStat
インターフェースと関連する新しいステートメント構造体の定義、およびStatVisitor
インターフェースの導入です。
usr/gri/pretty/ast.go
// 変更前
type (
Block struct;
Expr interface;
Stat struct; // <-- 変更前は具体的な構造体
Decl struct;
)
// 変更後
type (
Block struct;
Expr interface;
StatImpl struct; // <-- 旧Statをリネーム
Decl struct;
)
// 新たにStatインターフェースを定義
type (
StatVisitor interface; // <-- 新しいVisitorインターフェース
Stat interface { // <-- 新しいStatインターフェース
Visit(v StatVisitor);
};
// 各ステートメントタイプに対応する新しい構造体
BadStat struct { Pos int; };
LabelDecl struct { Pos int; Label *Ident; };
DeclarationStat struct { Decl *Decl; };
ExpressionStat struct { Pos int; Tok int; Expr Expr; };
IfStat struct { Pos int; Init Stat; Cond Expr; Body *Block; Else Stat; };
ForStat struct { Pos int; Init Stat; Cond Expr; Post Stat; Body *Block; };
SwitchStat struct { Pos int; Init Stat; Tag Expr; Body *Block; };
SelectStat struct { Pos int; Body *Block; };
ControlFlowStat struct { Pos int; Tok int; Label *Ident; };
)
// StatVisitorインターフェースの定義
type StatVisitor interface {
DoBadStat(s *BadStat);
DoLabelDecl(s *LabelDecl);
DoDeclarationStat(s *DeclarationStat);
DoExpressionStat(s *ExpressionStat);
DoIfStat(s *IfStat);
DoForStat(s *ForStat);
DoSwitchStat(s *SwitchStat);
DoSelectStat(s *SelectStat);
DoControlFlowStat(s *ControlFlowStat);
}
// 各ステートメント構造体がStatインターフェースを実装
func (s *BadStat) Visit(v StatVisitor) { v.DoBadStat(s); }
func (s *LabelDecl) Visit(v StatVisitor) { v.DoLabelDecl(s); }
// ... 他のステートメントも同様にVisitメソッドを実装
コアとなるコードの解説
上記のコードは、Go言語のASTにおけるステートメントの表現方法を根本的に変更しています。
-
Stat
からStatImpl
へのリネーム:- 元の
Stat
構造体は、StatImpl
という名前に変更されました。これは、既存のコードベースとの互換性を一時的に保ちつつ、新しいインターフェースベースの設計への段階的な移行を可能にするための措置です。StatImpl
は、古いパーサーやプリンターが引き続き参照する「古いスタイル」のステートメント表現として機能します。
- 元の
-
Stat
インターフェースの導入:- 新たに定義された
Stat
インターフェースは、Visit(v StatVisitor)
という単一のメソッドを要求します。このメソッドは、Visitorパターンの中核をなすもので、ステートメント自身がStatVisitor
を受け入れ、そのVisitorの適切なDoXxx
メソッドを呼び出す役割を担います。これにより、ASTの走査ロジックがASTノードの構造から分離され、よりクリーンな設計が実現されます。
- 新たに定義された
-
StatVisitor
インターフェースの導入:StatVisitor
インターフェースは、ASTの各ステートメントタイプに対応するDoXxx
メソッド(例:DoIfStat
,DoForStat
)を定義します。このインターフェースを実装する具体的なVisitor(例:Printer
)は、それぞれのDoXxx
メソッド内で、特定のステートメントタイプに対する処理ロジックを記述します。これにより、ASTの走査と操作のロジックが明確に分離され、新しい操作を追加する際に既存のAST構造を変更する必要がなくなります。
-
新しいステートメント構造体の定義と
Stat
インターフェースの実装:BadStat
,LabelDecl
,DeclarationStat
,ExpressionStat
,IfStat
,ForStat
,SwitchStat
,SelectStat
,ControlFlowStat
といった、Go言語の様々な種類のステートメントを表す新しい構造体が定義されました。- これらの新しい構造体はすべて、
Stat
インターフェースのVisit
メソッドを実装しています。各Visit
メソッドは、自身がどのタイプのステートメントであるかに応じて、渡されたStatVisitor
の対応するDoXxx
メソッドを呼び出します。例えば、IfStat
のVisit
メソッドはv.DoIfStat(s)
を呼び出します。
この変更は、GoコンパイラのAST処理において、静的な型チェックと動的なディスパッチを組み合わせることで、コードの柔軟性、拡張性、および保守性を大幅に向上させることを目指しています。特に、Visitorパターンの採用は、コンパイラのフロントエンドにおける様々な解析(型チェック、コード最適化、コード生成など)を、AST構造に影響を与えることなく追加・変更できる強力な基盤を提供します。
関連リンク
- Go言語の公式リポジトリ: https://github.com/golang/go
- 抽象構文木 (AST) について: https://en.wikipedia.org/wiki/Abstract_syntax_tree (Wikipedia)
- Go言語のインターフェースについて: https://go.dev/tour/methods/10 (Go Tour)
- Visitorパターンについて: https://en.wikipedia.org/wiki/Visitor_pattern (Wikipedia)
参考にした情報源リンク
- Go言語のソースコード (特に
src/go/ast
パッケージ): https://github.com/golang/go/tree/master/src/go/ast - Go言語のコンパイラ設計に関するドキュメントやブログ記事 (一般的な情報源)
- デザインパターンに関する書籍やオンラインリソース (Visitorパターンに関する一般的な情報)
- コミットメッセージと差分情報 (
git diff
の出力)