Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 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)に依存しており、新しいステートメントタイプを追加する際や、ステートメントに対する共通の操作を定義する際に、柔軟性に欠ける可能性がありました。

インターフェースを導入することで、以下の利点が得られます。

  1. 拡張性: 新しいステートメントタイプを容易に追加できるようになります。新しい構造体は既存のStatインターフェースを実装するだけでよく、既存のコードの大部分を変更する必要がなくなります。
  2. ポリモーフィズム: ASTを走査する際に、具体的な型に依存せず、Statインターフェースとして統一的に扱うことができるようになります。これにより、コードの重複を減らし、より汎用的な処理ロジックを記述できます。
  3. 保守性: コードベースが大きくなるにつれて、特定のASTノードに対する変更が他の部分に与える影響を局所化しやすくなります。
  4. 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パターンは通常、以下の要素で構成されます。

  1. Element (要素): 訪問されるオブジェクト(ASTノード)。各要素はAcceptメソッドを持ち、Visitorオブジェクトを受け入れます。
  2. Visitor (訪問者): 要素に対して実行される操作を定義するインターフェース。各要素タイプに対応するVisitメソッド(例: VisitIfStatement, VisitForStatement)を持ちます。
  3. ConcreteElement (具象要素): Elementインターフェースを実装する具体的なクラス(例: IfStat, ForStat)。Acceptメソッド内で、対応するVisitorのVisitメソッドを呼び出します。
  4. 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メソッド内で対応するStatVisitorDoXxxメソッドを呼び出します。これはVisitorパターンの「ConcreteElement」部分です。
  • ExprVisitorへのリネーム:
    • 式(Expression)のVisitorインターフェースも、VisitorからExprVisitorへとリネームされ、命名の一貫性が保たれています。

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におけるステートメントの表現方法を根本的に変更しています。

  1. StatからStatImplへのリネーム:

    • 元のStat構造体は、StatImplという名前に変更されました。これは、既存のコードベースとの互換性を一時的に保ちつつ、新しいインターフェースベースの設計への段階的な移行を可能にするための措置です。StatImplは、古いパーサーやプリンターが引き続き参照する「古いスタイル」のステートメント表現として機能します。
  2. Statインターフェースの導入:

    • 新たに定義されたStatインターフェースは、Visit(v StatVisitor)という単一のメソッドを要求します。このメソッドは、Visitorパターンの中核をなすもので、ステートメント自身がStatVisitorを受け入れ、そのVisitorの適切なDoXxxメソッドを呼び出す役割を担います。これにより、ASTの走査ロジックがASTノードの構造から分離され、よりクリーンな設計が実現されます。
  3. StatVisitorインターフェースの導入:

    • StatVisitorインターフェースは、ASTの各ステートメントタイプに対応するDoXxxメソッド(例: DoIfStat, DoForStat)を定義します。このインターフェースを実装する具体的なVisitor(例: Printer)は、それぞれのDoXxxメソッド内で、特定のステートメントタイプに対する処理ロジックを記述します。これにより、ASTの走査と操作のロジックが明確に分離され、新しい操作を追加する際に既存のAST構造を変更する必要がなくなります。
  4. 新しいステートメント構造体の定義とStatインターフェースの実装:

    • BadStat, LabelDecl, DeclarationStat, ExpressionStat, IfStat, ForStat, SwitchStat, SelectStat, ControlFlowStatといった、Go言語の様々な種類のステートメントを表す新しい構造体が定義されました。
    • これらの新しい構造体はすべて、StatインターフェースのVisitメソッドを実装しています。各Visitメソッドは、自身がどのタイプのステートメントであるかに応じて、渡されたStatVisitorの対応するDoXxxメソッドを呼び出します。例えば、IfStatVisitメソッドはv.DoIfStat(s)を呼び出します。

この変更は、GoコンパイラのAST処理において、静的な型チェックと動的なディスパッチを組み合わせることで、コードの柔軟性、拡張性、および保守性を大幅に向上させることを目指しています。特に、Visitorパターンの採用は、コンパイラのフロントエンドにおける様々な解析(型チェック、コード最適化、コード生成など)を、AST構造に影響を与えることなく追加・変更できる強力な基盤を提供します。

関連リンク

参考にした情報源リンク

  • Go言語のソースコード (特にsrc/go/astパッケージ): https://github.com/golang/go/tree/master/src/go/ast
  • Go言語のコンパイラ設計に関するドキュメントやブログ記事 (一般的な情報源)
  • デザインパターンに関する書籍やオンラインリソース (Visitorパターンに関する一般的な情報)
  • コミットメッセージと差分情報 (git diffの出力)