[インデックス 1616] ファイルの概要
このコミットは、Go言語の初期開発段階におけるpretty
パッケージ(おそらくGoのコード整形ツールまたはAST処理ツールの一部)の重要なリファクタリングを示しています。主な目的は、抽象構文木(AST)におけるステートメントの表現方法を、よりGo言語のイディオムに沿ったインターフェースベースのアプローチに移行し、それに伴い不要になった古いコードを削除することです。
コミット
commit 5d571cc67ef0a8ef51a6a002858c40419a35ce43
Author: Robert Griesemer <gri@golang.org>
Date: Thu Feb 5 11:05:02 2009 -0800
snapshot:
- ast statements now use interfaces
- deleted old (now unused) code
R=r
OCL=24422
CL=24422
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/5d571cc67ef0a8ef51a6a002858c40419a35ce43
元コミット内容
snapshot:
- ast statements now use interfaces
- deleted old (now unused) code
R=r
OCL=24422
CL=24422
変更の背景
このコミットは、Go言語のコンパイラまたはツールチェインの初期段階における、AST(抽象構文木)の設計改善を目的としています。以前の設計では、ステートメント(if
文、for
文、switch
文など)を表現するために、おそらくStatImpl
のような単一の汎用的な構造体を使用し、その内部で型アサーションやタグフィールドを用いて異なる種類のステートメントを区別していたと考えられます。このようなアプローチは、新しいステートメントタイプが追加されるたびに、関連する処理ロジック(パーサー、プリンター、セマンティックアナライザーなど)に大規模な変更が必要となる「オープン・クローズドの原則」に反する問題がありました。
Go言語の設計思想は、インターフェースを用いたポリモーフィズムを重視します。このコミットは、その思想をASTのステートメント表現に適用し、より拡張性が高く、保守しやすい構造へと移行することを意図しています。具体的には、各ステートメントタイプが共通のインターフェースを実装することで、パーサーやプリンターなどのコンポーネントが特定のステートメントの実装詳細に依存することなく、統一的な方法でステートメントを処理できるようになります。これにより、将来的に新しいステートメントが追加された場合でも、既存のコードへの影響を最小限に抑えることが可能になります。
また、コミットメッセージにある「deleted old (now unused) code」は、このインターフェースベースの新設計への移行が完了し、以前の汎用的なStatImpl
構造体やそれに関連する古い解析ロジックが完全に不要になったことを示しています。これは、開発プロセスにおいて新旧のコードが一時的に共存していた期間があったことを示唆しており、このコミットでその過渡期が終了したことを意味します。
前提知識の解説
- 抽象構文木 (Abstract Syntax Tree, AST): プログラミング言語のソースコードを、その構文構造に基づいて抽象的に表現した木構造のデータ構造です。コンパイラやインタープリタは、ソースコードを字句解析(トークン化)し、構文解析(パース)してASTを構築します。ASTは、その後のセマンティック解析、最適化、コード生成などのフェーズで利用されます。各ノードは、変数宣言、関数呼び出し、条件分岐、ループなどの言語構造に対応します。
- Go言語のインターフェース: Go言語におけるインターフェースは、メソッドのシグネチャの集合を定義する型です。特定のインターフェースのすべてのメソッドを実装する任意の型は、そのインターフェースを満たすと見なされます。これにより、異なる具体的な型を統一的な方法で扱うポリモーフィズムを実現できます。Goのインターフェースは、JavaやC++のような明示的な
implements
キーワードを必要とせず、暗黙的に満たされます(Structural Typing)。 - Visitorパターン: オブジェクト指向デザインパターンの一つで、オブジェクト構造の要素に対して実行される操作を、その要素のクラスを変更することなく定義できるようにします。ASTの走査によく用いられ、ASTの各ノードタイプに対応する
Visit
メソッドを持つVisitorインターフェースを定義し、各ASTノードがそのVisit
メソッドを呼び出すことで、ノードの種類に応じた処理を実行します。これにより、ASTの構造と、その構造に対する操作(例:コード生成、型チェック、整形)を分離できます。 - Go言語のパーサー: ソースコードのトークン列を受け取り、言語の文法規則に従ってASTを構築するコンポーネントです。このコミットでは、ステートメントの解析ロジックが変更されています。
- Go言語のプリンター: ASTを受け取り、それを整形されたソースコードとして出力するコンポーネントです。このコミットでは、新しいAST構造に合わせて出力ロジックが変更されています。
技術的詳細
このコミットの核心は、Go言語のASTにおけるステートメントの表現を、StatImpl
という単一の構造体から、AST.Stat
インターフェースを介した多態的なアプローチへと変更した点にあります。
-
StatImpl
構造体の削除:usr/gri/pretty/ast.go
からStatImpl
構造体とその関連するNewStat
関数、OldBadStat
変数が完全に削除されました。これは、ステートメントの表現がこの汎用構造体に依存しなくなったことを意味します。usr/gri/pretty/parser.go
およびusr/gri/pretty/printer.go
から、StatImpl
を引数にとる、または返す多数のOldParse...
関数やStatImpl
を直接操作するロジックが削除されました。
-
インターフェースベースのステートメント表現への移行:
usr/gri/pretty/ast.go
のtype
ブロック内で、Stat
がインターフェースとして定義されていることが示唆されます(変更差分からは直接見えないが、Stat
がVisit
メソッドを持つことが示されているため)。- 新しい具体的なステートメント型として、
CompositeStat
とCaseClause
が導入されました。CompositeStat
は、Body *Block
を持ち、波括弧で囲まれたブロック(例:{ ... }
)を表現します。CaseClause
は、switch
文やselect
文のcase
節やdefault
節を表現し、Expr
(case
の条件式)とBody *Block
(節内のステートメントリスト)を持ちます。
StatVisitor
インターフェースが拡張され、新しく導入されたCompositeStat
とCaseClause
に対応するDoCompositeStat
とDoCaseClause
メソッドが追加されました。- 既存の各ステートメント構造体(
BadStat
,LabelDecl
,DeclarationStat
,ExpressionStat
,IfStat
,ForStat
,SwitchStat
,SelectStat
,ControlFlowStat
)に、StatVisitor
インターフェースのVisit
メソッドを実装するロジックが追加または修正されました。これにより、これらの構造体がStat
インターフェースを満たすようになります。
-
パーサーの簡素化:
usr/gri/pretty/parser.go
では、*newstat
フラグ(新しいステートメント解析を使用するかどうかを制御するフラグ)が削除されました。これは、新しいインターフェースベースの解析ロジックが標準となり、古いロジックが完全に廃止されたことを意味します。ParseStatementList
関数から、*newstat
フラグに基づく条件分岐が削除され、常にP.ParseStatement()
が呼び出されるようになりました。ParseStatement
関数は、各ステートメントタイプに対応する具体的な構造体を直接返すように変更されました。例えば、LBRACE
(波括弧)で始まるステートメントは&AST.CompositeStat{P.ParseBlock(nil, Scanner.LBRACE)}
を返すようになりました。ParseControlFlowStat
、ParseCaseClause
、ParseCommClause
などの関数も、StatImpl
ではなく、それぞれの具体的なAST構造体(*AST.ControlFlowStat
,*AST.CaseClause
)を直接構築して返すように変更されました。
-
プリンターの適応:
usr/gri/pretty/printer.go
では、StatImpl
を引数にとるStatImpl
メソッドが削除されました。StatementList
関数は、リスト内の各要素がAST.Stat
インターフェースを実装していることを前提とし、list.At(i).(AST.Stat).Visit(P)
を直接呼び出すように変更されました。これにより、Visitorパターンを通じて各ステートメントの具体的な型に応じた印刷ロジックが実行されます。OldControlClause
関数など、StatImpl
に依存していた古い印刷ロジックが削除されました。DoCompositeStat
やDoCaseClause
など、新しいAST構造体に対応するStatVisitor
インターフェースのメソッドが実装されました。
-
テストスクリプトの更新:
usr/gri/pretty/test.sh
では、./pretty
コマンドの呼び出しをCMD
変数に置き換えるという軽微な変更が行われました。これは機能的な変更ではなく、スクリプトの保守性を向上させるためのものです。
全体として、このコミットは、Go言語のAST設計を、よりGoらしいインターフェースとVisitorパターンを活用した、堅牢で拡張性の高いものへと進化させる重要なステップです。これにより、コンパイラやツールのコードベースの品質と保守性が大幅に向上しました。
コアとなるコードの変更箇所
usr/gri/pretty/ast.go
StatImpl
構造体、NewStat
関数、OldBadStat
変数の削除。CompositeStat
構造体の追加:type CompositeStat struct { Body *Block; };
CaseClause
構造体の追加:type CaseClause struct { Pos int; // position for "case" or "default" Expr Expr; // nil means default case Body *Block; };
StatVisitor
インターフェースにDoCompositeStat
とDoCaseClause
メソッドを追加。- 各ステートメント構造体(
ExpressionStat
,IfStat
,ForStat
,SwitchStat
,SelectStat
,ControlFlowStat
など)に、Visit
メソッドの実装を追加または修正し、対応するStatVisitor
メソッドを呼び出すように変更。
usr/gri/pretty/parser.go
OldParseStatement
、OldParseSimpleStat
、OldParseInvocationStat
、OldParseReturnStat
、OldParseControlClause
、OldParseIfStat
、OldParseForStat
、OldParseSwitchStat
、OldParseSelectStat
など、Old
プレフィックスを持つ多数の解析関数の削除。newstat
フラグの削除。ParseStatementList
関数から*newstat
フラグに基づく条件分岐を削除し、常にP.ParseStatement()
を呼び出すように変更。ParseStatement
関数内で、LBRACE
の場合に&AST.CompositeStat{P.ParseBlock(nil, Scanner.LBRACE)}
を直接返すように変更。ParseControlFlowStat
、ParseCaseClause
、ParseCommClause
などが、StatImpl
ではなく、具体的なAST構造体(*AST.ControlFlowStat
,*AST.CaseClause
)を返すように変更。
usr/gri/pretty/printer.go
StatImpl
を引数にとるStatImpl
メソッドの削除。StatementList
関数内で、list.At(i).(AST.Stat).Visit(P)
を直接呼び出すように変更。OldControlClause
関数など、StatImpl
に依存していた古い印刷ロジックの削除。DoCompositeStat
、DoCaseClause
など、新しいAST構造体に対応するStatVisitor
インターフェースのメソッドの実装。
コアとなるコードの解説
このコミットの最も重要な変更は、Go言語のASTにおけるステートメントの表現方法を根本的に変えた点にあります。
変更前(推測):
以前は、StatImpl
という単一の構造体が、すべての種類のステートメント(式ステートメント、宣言ステートメント、条件分岐、ループなど)を表現するために使われていたと考えられます。この構造体は、おそらくTok
フィールド(トークンタイプ)や他のフィールドを使って、どの種類のステートメントであるかを区別し、それに応じて異なるデータを保持していました。このような設計では、ステートメントの種類ごとに異なる処理を行う場合、switch
文や型アサーションを多用する必要があり、新しいステートメントタイプを追加するたびに、パーサー、プリンター、セマンティックアナライザーなど、多くの場所でコードの変更が必要になります。
変更後:
このコミットでは、StatImpl
を廃止し、GoのインターフェースとVisitorパターンを積極的に活用する設計に移行しました。
-
AST.Stat
インターフェース: GoのASTパッケージには、Stat
というインターフェースが存在し、すべてのステートメントノードがこのインターフェースを実装します。このインターフェースは、おそらくVisit(v StatVisitor)
のようなメソッドを定義しており、これにより各ステートメントノードが自身をVisitorに「訪問」させることができます。 -
具体的なステートメント構造体:
CompositeStat
やCaseClause
のように、各ステートメントの種類に対応する専用の構造体が定義されます。これらの構造体は、そのステートメントタイプに固有のデータ(例:CompositeStat
のBody
、CaseClause
のExpr
とBody
)のみを保持します。 -
StatVisitor
インターフェースとVisit
メソッド:StatVisitor
インターフェースは、各具体的なステートメントタイプに対応するDo...Stat
メソッド(例:DoCompositeStat
,DoCaseClause
)を定義します。 各具体的なステートメント構造体は、Stat
インターフェースのVisit
メソッドを実装します。このVisit
メソッドは、引数として受け取ったStatVisitor
の、自身の型に対応するDo...Stat
メソッドを呼び出します。例:
// ast.go type CompositeStat struct { Body *Block; } func (s *CompositeStat) Visit(v StatVisitor) { v.DoCompositeStat(s); } // printer.go (StatVisitorの実装例) type Printer struct { /* ... */ } func (P *Printer) DoCompositeStat(s *AST.CompositeStat) { P.Block(s.Body, true); // CompositeStatの印刷ロジック }
このパターンにより、パーサーは具体的なステートメント構造体を構築し、プリンターは
Stat
インターフェースを介してVisit
メソッドを呼び出すだけで、各ステートメントの具体的な印刷ロジックが自動的に実行されます。
この変更の利点:
- 拡張性: 新しいステートメントタイプを追加する場合、新しい構造体を定義し、
Stat
インターフェースを実装し、StatVisitor
インターフェースに新しいDo...Stat
メソッドを追加するだけで済みます。既存のパーサーやプリンターの主要なロジックを変更する必要がありません。 - 保守性: 各ステートメントタイプに固有のロジックが、そのステートメントの構造体とVisitorの実装にカプセル化されます。これにより、コードの理解とデバッグが容易になります。
- 型安全性:
StatImpl
のような汎用構造体で型アサーションを多用する代わりに、Goの型システムとインターフェースの恩恵を最大限に活用し、コンパイル時に型の一貫性を保証できます。 - コードの簡素化:
parser.go
からOldParse...
関数群が大量に削除されたことからもわかるように、新旧のロジックを共存させるための複雑な条件分岐が不要になり、コードベースが大幅に簡素化されました。
このコミットは、Go言語の設計原則である「シンプルさ」と「拡張性」をASTの構造に適用した、非常に重要なリファクタリングであると言えます。
関連リンク
- Go言語の公式ドキュメント: https://go.dev/doc/
- Go言語のASTパッケージに関する情報(現在のGoのASTパッケージは
go/ast
です。このコミット当時のpretty
パッケージとは異なりますが、概念は共通です): https://pkg.go.dev/go/ast - Visitorパターンに関する一般的な情報: https://ja.wikipedia.org/wiki/Visitor%E3%83%91%E3%82%BF%E3%83%BC%E3%83%B3
参考にした情報源リンク
- Go言語のソースコード(特に
go/ast
パッケージの歴史) - Go言語のインターフェースに関する一般的な知識
- コンパイラ設計におけるASTとVisitorパターンの利用に関する一般的な知識