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

[インデックス 1628] ファイルの概要

このコミットは、Go言語の初期のコンパイラ/ツールチェインの一部であるprettyツールにおける、シンボル管理と型システムに関する重要なリファクタリングとクリーンアップを含んでいます。特に、抽象構文木(AST)からObjectTypeScopeといった概念を分離し、symboltableパッケージに集約することで、コンパイラの構造をより明確にし、役割分担を整理しています。また、スキャナーとパーサー間のトークンチャネル接続の削除や、依存関係計算の修正も行われています。

コミット

commit 187cf78a7c2fff4c1f606dacc412d5dda84f45b2
Author: Robert Griesemer <gri@golang.org>
Date:   Fri Feb 6 11:10:25 2009 -0800

    - preparation for setting up types
      - moved Object, Type, Scope  out of AST into symboltable
      - moved universe into symboltable
      - removed dead code
    - fixed dependency computation (pretty -d filename.go)
    - lots of cleanups
    - removed tocken channel connection between parser and scanner
      (was cute, but not really needed)
    
    R=r
    OCL=24545
    CL=24545

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/187cf78a7c2fff4c1f606dacc412d5dda84f45b2

元コミット内容

  • 型設定のための準備
    • ObjectTypeScopeをASTからsymboltableへ移動
    • universesymboltableへ移動
    • 不要なコードの削除
  • 依存関係計算の修正 (pretty -d filename.go)
  • 多数のクリーンアップ
  • パーサーとスキャナー間のトークンチャネル接続の削除(可愛かったが、実際には不要だった)

変更の背景

このコミットは、Go言語のコンパイラ(特にprettyツール、おそらく初期のGoコード整形ツールや解析ツール)の初期開発段階における重要な構造変更を示しています。当時のGoコンパイラはまだ発展途上にあり、コンポーネント間の責任分担や設計原則が固まっていく過程でした。

主な背景としては、以下の点が挙げられます。

  1. コンパイラ設計の洗練: コンパイラは通常、複数のフェーズ(字句解析、構文解析、意味解析、コード生成など)に分かれています。それぞれのフェーズが明確な役割を持つことで、コードの保守性、拡張性、理解しやすさが向上します。このコミットは、意味解析の基盤となるシンボル管理と型システムを、構文解析の結果であるASTから分離しようとする意図が見られます。
  2. ASTとシンボルテーブルの役割分担の明確化:
    • AST (Abstract Syntax Tree): ソースコードの構文構造を木構造で表現したものです。ASTはプログラムの「形」を表現しますが、識別子が何を指すのか(変数、関数、型など)や、その型が何であるかといった「意味」の情報は直接持ちません。
    • シンボルテーブル (Symbol Table): プログラム内で宣言された識別子(シンボル)に関する情報を格納するデータ構造です。これには、識別子の名前、種類(変数、関数、型など)、スコープ、型情報などが含まれます。シンボルテーブルは、意味解析フェーズで識別子の解決や型チェックを行うために不可欠です。 このコミット以前は、Object(識別子の情報)、Type(型情報)、Scope(スコープ情報)がASTパッケージ内に混在していたと考えられます。これは、ASTが構文情報だけでなく意味情報も持ちすぎていたことを意味し、単一責任の原則に反していました。これらをsymboltableパッケージに移動することで、ASTは純粋な構文表現に徹し、シンボルテーブルが意味情報の管理を一手に担うという、よりクリーンな設計に移行しています。
  3. universeの分離: universe(ユニバーススコープ)は、Go言語における組み込みの識別子(int, string, true, false, lenなどの組み込み関数)が定義されている最上位のスコープです。これもASTパッケージ内にあったものをsymboltableに移動することで、組み込みシンボルの管理もシンボルテーブルの責任範囲として明確化されています。
  4. スキャナーとパーサー間の通信改善: 初期Goでは、スキャナー(字句解析器)とパーサー(構文解析器)がGoのチャネルを使って通信する設計が試みられていたようです。これはGoの並行処理機能の活用例として「可愛かった」と表現されていますが、コンパイラのフロントエンドにおいては、スキャナーがトークンを生成し、パーサーがそれを消費するという、より直接的で同期的なモデルの方がシンプルで効率的であると判断された可能性があります。チャネルによる通信はオーバーヘッドがあり、この文脈では不要と判断されたのでしょう。
  5. 依存関係計算の修正: pretty -d filename.goは、Goソースファイルの依存関係を計算する機能を示唆しています。コンパイラやビルドシステムにとって、ファイルの依存関係を正確に把握することは、効率的なコンパイルや再ビルドのために非常に重要です。この修正は、その依存関係計算ロジックの改善を目的としています。

これらの変更は、Goコンパイラの基盤をより堅牢でスケーラブルなものにするための、初期段階における重要なアーキテクチャ決定の一部と言えます。

前提知識の解説

このコミットを理解するためには、以下のコンピュータサイエンスおよびプログラミング言語の概念に関する知識が役立ちます。

  1. コンパイラの基本構造:

    • 字句解析 (Lexical Analysis / Scanning): ソースコードをトークン(意味を持つ最小単位、例: 識別子、キーワード、演算子、リテラル)のストリームに変換するフェーズ。担当は「スキャナー」または「字句解析器」。
    • 構文解析 (Syntax Analysis / Parsing): トークンのストリームが言語の文法規則に従っているかを検証し、抽象構文木(AST)を構築するフェーズ。担当は「パーサー」または「構文解析器」。
    • 意味解析 (Semantic Analysis): ASTを走査し、プログラムの意味的な正当性を検証するフェーズ。これには、型チェック、識別子の解決(どの宣言に対応するか)、スコープ規則の適用などが含まれます。このフェーズでシンボルテーブルが活用されます。
    • 中間コード生成 (Intermediate Code Generation): 意味解析の結果を基に、機械独立な中間表現を生成するフェーズ。
    • コード最適化 (Code Optimization): 中間コードをより効率的な形に変換するフェーズ。
    • コード生成 (Code Generation): ターゲットマシンコードを生成するフェーズ。
  2. 抽象構文木 (Abstract Syntax Tree - AST): ソースコードの構文構造を抽象的に表現した木構造のデータ構造です。各ノードはソースコードの構成要素(式、文、宣言など)を表し、子ノードはそれらの構成要素の内部構造を表します。ASTは、コンパイラの構文解析フェーズで生成され、意味解析、最適化、コード生成などの後続フェーズで利用されます。

  3. シンボルテーブル (Symbol Table): コンパイラがプログラム内の識別子(変数名、関数名、型名など)に関する情報を格納するために使用するデータ構造です。シンボルテーブルには、識別子の名前、その識別子の種類(変数、関数、型など)、データ型、スコープ(識別子が有効な範囲)、メモリ上のアドレスなどの情報が記録されます。意味解析フェーズで、識別子の参照がどの宣言に対応するかを解決したり、型チェックを行ったりするために不可欠です。

  4. スコープ (Scope): プログラム内で識別子が有効な範囲を指します。Go言語には、ファイルスコープ、パッケージスコープ、ブロックスコープなどがあります。シンボルテーブルは通常、これらのスコープ階層を反映した構造(例: ネストされたハッシュマップやリンクリスト)を持ち、識別子の解決(名前解決)は現在のスコープから親スコープへと順に探索することで行われます。

  5. 型システム (Type System): プログラミング言語における、値や式の種類(型)を定義し、それらの型がどのように相互作用できるかを規定する規則の集合です。型システムは、プログラムの安全性(例: 整数と文字列の加算を防ぐ)と正確性(例: 関数が期待する型の引数を受け取ることを保証する)を保証する上で重要な役割を果たします。コンパイラは型チェックを通じて、プログラムが型システムの規則に違反していないかを確認します。

  6. Go言語のチャネル (Channels): Go言語におけるゴルーチン間の通信メカニズムです。チャネルを通じて値を送受信することで、ゴルーチンは安全にデータを共有し、同期を取ることができます。このコミットでは、スキャナーとパーサー間の通信にチャネルが使用されていたが、後に削除されたことが示されており、初期のGoコンパイラ設計における並行処理の試みと、その後の設計変更の背景を理解する上で重要です。

技術的詳細

このコミットの技術的詳細は、主にGoコンパイラのフロントエンドにおけるデータ構造とコンポーネント間の相互作用の再構築に焦点を当てています。

1. Object, Type, ScopeAST から symboltable への移動

  • 変更前: usr/gri/pretty/ast.goObjectTypeScope の定義が含まれていました。これは、構文解析の結果であるASTが、識別子の意味情報(種類、型、スコープ)まで直接管理していたことを意味します。
  • 変更後: usr/gri/pretty/symboltable.go という新しいファイルが作成され、ObjectTypeScope の定義がそこへ移動されました。
    • Object 構造体は、識別子の種類 (Kind)、名前 (Ident)、ソースコード上の位置 (Pos)、関連する型 (Typ)、スコープレベル (Pnolev) などの情報を持つようになりました。
    • Type 構造体は、型の形式 (Form)、サイズ (Size)、関連するシンボルテーブルのスコープ (Scope) などの情報を持つようになりました。
    • Scope 構造体は、親スコープ (Parent) と、識別子名から Object へのマップ (entries) を持つようになりました。LookupLocalLookupInsert などのメソッドを通じて、スコープ内での識別子の検索と挿入を管理します。
  • 影響:
    • ast.go は、純粋に構文構造を表現するASTノードの定義に特化しました。これにより、ASTの役割が明確になり、コードの理解と保守が容易になります。
    • parser.goprinter.go など、以前 AST パッケージの ObjectScope を直接参照していた箇所は、新しく導入された SymbolTable パッケージを参照するように変更されました。例えば、parser.gotop_scope フィールドの型が *AST.Scope から *SymbolTable.Scope に変更されています。
    • AST.NewObjectAST.NewScope の呼び出しは、それぞれ SymbolTable.NewObjectSymbolTable.NewScope に置き換えられました。

2. universesymboltable への移動と universe.go の削除

  • 変更前: usr/gri/pretty/universe.go というファイルが存在し、Go言語の組み込み型や組み込み関数、定数(true, false, nil など)が定義されている「ユニバーススコープ」を管理していました。このファイルは AST パッケージの ObjectType を利用していました。
  • 変更後: universe.go ファイルが削除され、その内容は symboltable.go に統合されました。
    • symboltable.go 内に Universe というグローバルな Scope 変数が定義され、init() 関数内でGoの組み込み型、定数、組み込み関数がこの Universe スコープに登録されるようになりました。
    • AST.Universe_void_typ のようなASTパッケージ内のユニバース関連の参照も、SymbolTable パッケージ内の対応する型に更新されました。
  • 影響: ユニバーススコープの管理もシンボルテーブルの責任範囲として明確化され、関連するコードが一箇所に集約されました。

3. スキャナーとパーサー間のトークンチャネル接続の削除

  • 変更前: usr/gri/pretty/scanner.goToken 構造体と TokenStream() メソッドが存在し、usr/gri/pretty/parser.go はこのチャネル (tokchan <-chan *Scanner.Token) を介してトークンを受け取っていました。これは、スキャナーがトークンを生成し、パーサーがそれをチャネルから読み取るという、並行処理的なアプローチを試みていたことを示唆しています。
  • 変更後:
    • scanner.go から Token 構造体と TokenStream() メソッドが削除されました。
    • parser.go から tokchan フィールドが削除され、Open メソッドの引数からも tokchan が取り除かれました。
    • parser.gonext0() メソッド内で、チャネルからの読み取り (<-P.tokchan) のロジックが削除され、直接 P.scanner.Scan() を呼び出すようになりました。
  • 影響: スキャナーとパーサー間の通信が、チャネルを介した並行処理モデルから、より直接的で同期的な関数呼び出しモデルに切り替わりました。これは、コンパイラのフロントエンドにおいては、シンプルさと効率性を優先した結果と考えられます。チャネルのオーバーヘッドや、この特定の文脈での並行処理のメリットが小さいと判断されたのでしょう。

4. 依存関係計算の修正 (pretty -d filename.go)

  • usr/gri/pretty/compilation.go 内の依存関係計算ロジック (addDeps 関数など) が修正されました。
  • 特に、fmt パッケージのインポートが追加され、print 関数が fmt.Printf に置き換えられています。これは、出力形式の改善や、より標準的なI/Oライブラリへの移行を示唆しています。
  • src_file から .go 拡張子を削除するロジックが追加され、依存関係のファイル名処理がより堅牢になりました。
  • flags.Deps フラグが正しく機能するように、pretty.go のコメントアウトされていた部分が有効化されました。これにより、pretty -d コマンドが実際に依存関係情報を出力するようになりました。

5. その他のクリーンアップとデッドコードの削除

  • ast.go から utf8 および unicode パッケージのインポートが削除されました。これは、Object.IsExported() メソッドが symboltable.go に移動したため、ASTパッケージではこれらのパッケージが不要になったためです。
  • scanner.go から EXPRSTAT というトークン定数が削除されました。これは、おそらくASTの表現方法の変更に伴い、不要になったデッドコードの一部です。
  • Makefile の依存関係が更新され、symboltable.6 が新しい依存関係として追加され、universe.6 が削除されました。これは、パッケージ構造の変更をビルドシステムに反映したものです。

これらの変更は、Goコンパイラの初期段階における設計の進化と成熟を示しており、コンポーネント間の責任分担を明確にし、より効率的で保守しやすいコードベースを構築するための重要なステップでした。

コアとなるコードの変更箇所

このコミットのコアとなる変更は、主に以下のファイルに集中しています。

  1. usr/gri/pretty/ast.go:

    • Object, Type, Scope 構造体とその関連メソッド(KindStr, IsExported, String, NewObject, NewScope, LookupLocal, Lookup, add, Insert, InsertImport, Print)が完全に削除されました。
    • Type 構造体の定義から Ref フィールドと Obj フィールドが削除され、Scope フィールドの型が *SymbolTable.Scope に変更されました。
    • Ident 構造体の Obj フィールドの型が *AST.Object から *SymbolTable.Object に変更されました。
    • import 文から utf8unicode が削除されました。
  2. usr/gri/pretty/symboltable.go (新規ファイル):

    • Object, Type, Scope 構造体とその関連メソッドが、ast.go から移動されてここに定義されました。
    • universe.go の内容(組み込み型、定数、組み込み関数の定義と初期化ロジック)がこのファイルに統合されました。init() 関数内で Universe スコープが初期化され、各種組み込みシンボルが登録されます。
  3. usr/gri/pretty/universe.go:

    • ファイル全体が削除されました。その内容は symboltable.go に移動されました。
  4. usr/gri/pretty/parser.go:

    • tokchan <-chan *Scanner.Token フィールドが削除されました。
    • Open メソッドのシグネチャから tokchan 引数が削除されました。
    • next0() メソッド内のチャネルからのトークン読み取りロジックが削除され、直接 P.scanner.Scan() を呼び出すようになりました。
    • top_scope フィールドの型が *AST.Scope から *SymbolTable.Scope に変更されました。
    • openScope() メソッド内で AST.NewScope の代わりに SymbolTable.NewScope が呼び出されるようになりました。
    • declareInScope および parseIdent メソッド内で、AST.ObjectAST.Scope の代わりに SymbolTable.ObjectSymbolTable.Scope が使用されるようになりました。また、AST.NONE, AST.TYPE, AST.FUNC, AST.FIELD, AST.VAR, AST.CONST, AST.PACKAGE といった定数が SymbolTable パッケージの対応する定数に置き換えられました。
  5. usr/gri/pretty/scanner.go:

    • Token 構造体と TokenStream() メソッドが削除されました。
    • EXPRSTAT トークン定数が削除されました。
  6. usr/gri/pretty/compilation.go:

    • flags.Tokenchan フィールドが削除されました。
    • Compile 関数内で flags.Tokenchan に基づくトークンチャネルの初期化ロジックが削除されました。
    • addDeps 関数内の依存関係計算ロジックが修正され、fmt パッケージがインポートされ、printfmt.Printf に置き換えられました。
  7. usr/gri/pretty/pretty.go:

    • flags.TokenchanFlag.BoolVar の呼び出しが削除されました。
    • flags.Deps のコメントアウトが解除され、依存関係計算機能が有効になりました。
  8. usr/gri/pretty/printer.go:

    • AST.Object の代わりに SymbolTable.Object を参照するように変更されました。
  9. usr/gri/pretty/Makefile:

    • ast.6 の依存関係から scanner.6 が削除され、symboltable.6 が追加されました。
    • typechecker.6 の依存関係から universe.6 が削除されました。
    • parser.6 の依存関係に symboltable.6 が追加されました。
    • printer.6 の依存関係に symboltable.6 が追加されました。
    • universe.6 のビルドルールが削除されました。

これらの変更は、Goコンパイラの内部構造におけるシンボル管理と型システムの責任をASTから分離し、symboltableパッケージに集約するという、根本的なアーキテクチャ変更を反映しています。

コアとなるコードの解説

このコミットの核心は、コンパイラの意味解析フェーズで中心的な役割を果たすObjectTypeScopeといった概念を、構文解析の結果であるASTから独立したsymboltableパッケージに分離した点にあります。

symboltable パッケージの導入

  • Object 構造体:

    type Object struct {
        Id int;  // unique id
        Pos int;  // source position (< 0 if unknown position)
        Kind int;  // object kind (CONST, TYPE, VAR, FUNC, etc.)
        Ident string;
        Typ *Type;  // nil for packages
        Pnolev int;  // >= 0: package no., <= 0: function nesting level, 0: global level
    }
    

    これは、Goプログラム内の各識別子(変数、関数、型、定数など)に関するメタデータを保持します。Kind フィールドは識別子の種類を示し、Typ フィールドはそれが持つ型へのポインタです。Pnolev はスコープのネストレベルを示し、識別子の可視性を判断するのに使われます。

  • Scope 構造体:

    type Scope struct {
        Parent *Scope;
        entries map[string] *Object;
    }
    

    Scope は、識別子の有効範囲を管理します。Parent フィールドは親スコープへのポインタであり、これによりスコープの階層構造が形成されます。entries マップは、そのスコープ内で宣言された識別子名と、それに対応する Object のマッピングを保持します。LookupLocal は現在のスコープ内のみを検索し、Lookup は親スコープを辿って検索します。Insert はスコープに新しい Object を追加します。

  • Type 構造体:

    type Type struct {
        Id int;  // unique id
        Ref int;  // for exporting only: >= 0 means already exported
        Form int;  // type form (ARRAY, STRUCT, INTERFACE, FUNCTION, etc.)
        Size int;  // size in bytes
        Obj *Object;  // primary type object or nil
        Scope *Scope;  // locals, fields & methods
        // ... その他の型に関する詳細情報
    }
    

    Type は、Go言語の型に関する情報を保持します。Form フィールドは型の種類(配列、構造体、インターフェース、関数など)を示します。Scope フィールドは、構造体のフィールドやインターフェースのメソッドなど、型に関連するスコープを管理するために使用されます。

universe スコープの統合

symboltable.goinit() 関数内で、Go言語の組み込み型(int, string, bool など)、組み込み定数(true, false, nil, iota)、組み込み関数(len, new, panic, print など)が Universe というグローバルスコープに登録されます。これにより、これらの組み込みシンボルがコンパイラの意味解析フェーズで正しく解決されるようになります。

スキャナーとパーサー間の通信モデルの変更

以前は、スキャナーがトークンをGoのチャネルに送信し、パーサーがそのチャネルからトークンを受信するという並行処理モデルが採用されていました。このコミットでは、このチャネル接続が削除され、パーサーがスキャナーの Scan() メソッドを直接呼び出してトークンを取得する、より伝統的な同期的なモデルに移行しました。

// parser.go の変更前 (抜粋)
func (P *Parser) next0() {
    if P.tokchan == nil {
        P.pos, P.tok, P.val = P.scanner.Scan();
    } else {
        t := <-P.tokchan; // チャネルから読み込み
        P.tok, P.pos, P.val = t.Tok, t.Pos, t.Val;
    }
    // ...
}

// parser.go の変更後 (抜粋)
func (P *Parser) next0() {
    P.pos, P.tok, P.val = P.scanner.Scan(); // 直接 Scan() を呼び出し
    // ...
}

この変更は、コンパイラのフロントエンドにおけるトークン生成と消費のプロセスが、並行処理のメリットよりも同期的なシンプルさを優先した結果と考えられます。

依存関係計算の改善

compilation.go 内の addDeps 関数は、Goソースファイルの依存関係を再帰的に解決し、ビルドシステムが利用できる形式で出力します。このコミットでは、fmt パッケージの使用や、ファイルパス処理の改善により、この依存関係計算の堅牢性と出力形式が向上しました。

これらの変更は、Goコンパイラの設計において、意味解析の基盤となるデータ構造とロジックをASTから分離し、よりモジュール化されたクリーンなアーキテクチャへと進化させるための重要なステップでした。これにより、コンパイラの各フェーズの責任が明確になり、将来的な機能追加や改善が容易になる基盤が築かれました。

関連リンク

  • Go言語の公式リポジトリ: https://github.com/golang/go
  • Go言語の初期の設計に関する議論やドキュメント(当時のメーリングリストやデザインドキュメントを探すと、より深い背景情報が見つかる可能性があります)

参考にした情報源リンク

  • Go言語のコンパイラに関する一般的な知識(AST, シンボルテーブル, スコープなど)
  • Go言語のチャネルに関するドキュメント
  • Go言語の歴史に関する情報(特に初期の設計決定について)
  • GitHubのコミット履歴とファイル変更差分
  • Go言語のコンパイラ設計に関する書籍やオンラインリソース(例: "Compilers: Principles, Techniques, and Tools" by Aho, Lam, Sethi, Ullman)
  • Go言語のgo/ast, go/token, go/types パッケージの現在のドキュメント(当時の設計との比較のため)
  • Robert Griesemer氏のGo言語に関する講演や記事(もしあれば)

(注:2009年当時のGo言語の内部実装に関する詳細な公開ドキュメントは限られているため、上記の解説は一般的なコンパイラ設計の原則と、コミットメッセージおよびコード変更から推測される内容に基づいています。)