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

[インデックス 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点です。

  1. 宣言のために明示的なXSpecノード(ImportSpec, ValueSpec, TypeSpec)を持つようにする。
  2. 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表現を改善することに焦点を当てています。

  • 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インターフェースを実装する個々の宣言ノードのリストを持っていました。

変更後:

  1. Spec インターフェースの導入: Specという新しいインターフェースが導入されました。これは、単一の(括弧で囲まれていない)import、constant、type、またはvariable宣言を表すノードの共通の型となります。

    type (
        // The Spec type stands for any of *ImportSpec, *ValueSpec, and *TypeSpec.
        Spec interface {};
    
  2. 具体的な *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プロダクション)を表します。これにより、ConstDeclVarDeclValueSpecに統合されました。
      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ノードがその情報を持つためです。

  3. 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ノードで統一的に表現できるようになりました。LparenRparenフィールドは、括弧で囲まれた宣言の場合にその位置を記録します。

  4. DeclVisitor インターフェースの変更: ASTノードを巡回するためのDeclVisitorインターフェースも、新しいAST構造に合わせて変更されました。個別のDoImportDecl, DoConstDecl, DoTypeDecl, DoVarDecl, DoDeclListメソッドが削除され、代わりにDoGenDeclメソッドが追加されました。

  5. 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は、ConstDeclVarDeclの役割を統合しています。これにより、定数宣言と変数宣言がAST上で同じ構造で扱えるようになり、コードの重複が削減されます。

そして、DeclListの代わりにGenDeclが導入されました。GenDeclは、Tokフィールドで宣言の種類(IMPORT, CONST, TYPE, VAR)を保持し、SpecsフィールドにSpecインターフェースを実装するノードのリストを持ちます。これにより、単一の宣言(例: var x int)も、括弧で囲まれた複数の宣言(例: var (x int; y string))も、すべてGenDeclノードとして表現できるようになりました。LparenRparenフィールドは、括弧の有無とその位置を記録するために使用されます。

この変更により、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を呼び出すように変更され、宣言の解析フローが統一されました。

関連リンク

参考にした情報源リンク