[インデックス 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パターンの利用に関する一般的な知識