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

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

このコミットは、Go言語の初期開発段階における抽象構文木(AST)、パーサー、およびプリンターの内部構造に対する重要なスナップショットと微調整を記録しています。特に、コードブロックの表現、ローカル変数とフィールドの収集、型チェックの改善に焦点が当てられています。

コミット

commit 9e3b0f444ae27629b3e93b2e8d0d8ba0f6f939ba
Author: Robert Griesemer <gri@golang.org>
Date:   Fri Jan 23 09:44:01 2009 -0800

    snapshot before making more changes:
    - fine-tuning of ast
    - more accurate block pos info (improved printing in some cases)
    - collecting local variables and fields
    - more work on type checking
    - lots of minor tweaks
    
    R=r
    OCL=23375
    CL=23375

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

https://github.com/golang/go/commit/9e3b0f444ae27629b3e93b2e8d0d8ba0f6f939ba

元コミット内容

このコミットは、さらなる変更を加える前のスナップショットとして記録されています。主な内容は以下の通りです。

  • AST(抽象構文木)の微調整。
  • ブロックの位置情報の精度向上(これにより、一部のケースで出力が改善)。
  • ローカル変数とフィールドの収集機能の追加。
  • 型チェック機能のさらなる開発。
  • その他多数の細かな修正。

変更の背景

このコミットは、Go言語のコンパイラまたはツールチェインの初期段階における、構文解析と意味解析の基盤を固めるための作業の一環として行われました。特に、コードの構造をより正確に表現し、型システムとスコープ規則を適切に処理するために、ASTの設計とパーサーの挙動を見直す必要がありました。

当時のGo言語はまだ公開されておらず、内部で活発な開発が行われていました。usr/gri/pretty/というパスから、これはRobert Griesemer氏が担当していた「pretty-printer」またはそれに類するコード整形・解析ツールの開発ブランチの一部であることが推測されます。このようなツールは、コンパイラのフロントエンド(字句解析、構文解析、意味解析)と密接に関連しており、ASTの正確な表現は、その後のコード生成や最適化、あるいは静的解析の品質に直結します。

具体的には、以下の課題に対処しようとしています。

  1. ASTの表現力向上: 従来のAST構造では、コードブロックやスコープ、型情報が十分に表現できていなかった可能性があります。これを改善し、よりセマンティックな情報をASTに持たせることで、後続の処理(型チェック、コード生成)を容易にすることを目指しています。
  2. パーサーの堅牢性: 構文解析の過程で、より正確な位置情報やエラーハンドリングを提供するために、パーサーのロジックを洗練させる必要がありました。
  3. 型チェックの基盤: Go言語の静的型付けを正確に実現するためには、ASTノードに型情報を付与し、スコープに基づいて変数の解決を行うメカニズムが不可欠です。このコミットは、そのための土台を築いています。

前提知識の解説

このコミットの変更内容を理解するためには、以下の概念に関する基本的な知識が必要です。

  • コンパイラの基本構造:

    • 字句解析 (Lexical Analysis / Scanning): ソースコードをトークン(最小単位の単語)のストリームに変換するプロセス。scanner.goがこれに該当します。
    • 構文解析 (Syntax Analysis / Parsing): トークンのストリームを文法規則に従って解析し、抽象構文木(AST)を構築するプロセス。parser.goがこれに該当します。
    • 抽象構文木 (Abstract Syntax Tree - AST): ソースコードの構文構造を木構造で表現したもの。コンパイラの各フェーズでコードの構造を操作・分析するための中心的なデータ構造です。ast.goがASTの定義を含みます。
    • 意味解析 (Semantic Analysis): ASTを走査し、型チェック、スコープ解決、名前解決などの意味的な検証を行うプロセス。このコミットの「collecting local variables and fields」「more work on type checking」がこれに該当します。
    • コード生成/整形 (Code Generation / Pretty Printing): ASTから実行可能なコードを生成したり、整形されたソースコードを出力したりするプロセス。printer.goがこれに該当します。
  • Go言語の基本:

    • ブロック (Block): 波括弧 {} で囲まれた一連のステートメント。Go言語では、関数本体、if文、for文、switch文などの制御構造がブロックを持ちます。
    • スコープ (Scope): プログラム内で識別子(変数名、関数名など)が有効な範囲。Go言語では、ブロックごとに新しいスコープが作成され、識別子の可視性が制御されます。
    • 型 (Type): 変数や式の種類を定義するもの。Go言語は静的型付け言語であり、コンパイル時に型の整合性がチェックされます。
    • ASTノード: ASTを構成する個々の要素。例えば、変数宣言、関数呼び出し、リテラルなどがそれぞれASTノードとして表現されます。
  • データ構造:

    • array.Array: Go言語の初期段階で使われていた、動的配列のようなデータ構造。現在のGoの[]Tスライスとは異なりますが、同様の目的で使用されています。

技術的詳細

このコミットの技術的な変更は、Go言語のコンパイラフロントエンドの基盤を強化するものです。

  1. Block型とASTの再構築:

    • ast.goBlockという新しい構造体が導入されました。これは、{ StatementList }: StatementList のような構文ブロックを明示的に表現するためのものです。
    • 以前はObjectStat構造体の中にBlock *array.ArrayEnd intといったフィールドでブロックの内容を表現していましたが、これがBody *Blockという形でBlock型へのポインタを持つように変更されました。これにより、ASTがよりセマンティックに正確になり、コードブロックの構造が明確に表現されるようになりました。
    • NewBlock関数が追加され、ブロックの作成時にScanner.LBRACEまたはScanner.COLONトークンを期待するようになりました。
  2. 型情報のASTへの統合:

    • ast.goExpr構造体にTyp *Typeフィールドが追加されました。これにより、式ノードが直接その式の型情報を持つことができるようになり、型チェックのロジックがAST上でより自然に表現できるようになります。
    • NewTypeExpr関数が変更され、Exprノードに直接型情報を設定するようになりました。
  3. スコープ管理の改善:

    • parser.goにおいて、ParseFunctionTypeParseStructType内でP.OpenScope()P.CloseScope()がより適切に呼び出されるようになりました。
    • 特に、ParseBlock関数が導入され、ブロックの解析時に新しいスコープを開き、関数パラメータやレシーバをそのスコープに宣言するロジックが追加されました。これにより、ローカル変数のスコープが正確に管理されるようになります。
    • DeclareInScope関数が改善され、識別子が既にスコープ内で宣言されている場合の重複宣言エラーをより正確に検出するようになりました。
  4. パーサーロジックの洗練:

    • parser.goParseStatementListが、ステートメントリストを返すのではなく、既存のarray.Arrayにステートメントを追加する形に変更されました。これは、Block構造体の導入と密接に関連しています。
    • ParseIfStat, ParseForStat, ParseSwitchStat, ParseSelectStatなどの制御フロー文の解析ロジックが、新しいParseBlock関数を使用するように変更されました。これにより、コードの重複が減り、一貫性が向上しました。
    • ParseSpec関数が、各宣言タイプ(IMPORT, CONST, TYPE, VAR)の具体的な解析ロジックを呼び出すように変更され、宣言のセマンティックチェック(例:変数と式の数の不一致)が追加されました。
  5. プリンターの更新:

    • printer.goBlock関数が、新しいAST.Block型を引数として受け取るように変更されました。これにより、ASTの変更がプリンターにも適切に反映され、整形された出力が生成されるようになります。
    • Expr1関数内で、Scanner.TYPEトークンを持つExprの型情報を正しく出力するように修正されました。
    • StatementListの処理が微調整され、改行の扱いが改善されました。
  6. スキャナーの微調整:

    • scanner.goTokenString関数が、LBRACERBRACEトークンに対して、デバッグ出力時に"{""}"という実際の文字を返すように変更されました。これは、ターミナルでのダブルクリック選択の利便性を考慮したものです。

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

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

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

    • Block構造体の新規追加。
    • Object構造体のBlockフィールドをBody *Blockに変更。
    • Expr構造体にTyp *Typeフィールドを追加。
    • Stat構造体のBlockフィールドをBody *Blockに変更。
    • assert関数の追加。
  2. usr/gri/pretty/parser.go:

    • ParseBlock関数の新規追加。
    • ParseFunctionLitParseIfStatParseForStatParseSwitchStatParseSelectStatなどの関数が、新しいParseBlockを使用するように変更。
    • DeclareInScope関数におけるスコープ内での重複宣言チェックのロジック改善。
    • ParseSpec関数における宣言のセマンティックチェックの追加。
    • ParseFunctionTypeおよびParseStructTypeにおけるスコープ管理の調整。
  3. usr/gri/pretty/printer.go:

    • Block関数のシグネチャがfunc (P *Printer) Block(b *AST.Block, indent bool)に変更され、AST.Block型を直接扱うように。
    • Expr1関数内でScanner.TYPEの処理がx.Typを使用するように変更。
    • Stat関数内で、LBRACE, IF, FOR, SWITCH, SELECTなどのステートメントがs.BodyAST.Block型)を使用するように変更。

コアとなるコードの解説

ast.goにおけるBlock構造体の導入

type Block struct {
	Node;
	List *array.Array;
	End int;  // position of closing "}" if present
}

func NewBlock(pos, tok int) *Block {
	assert(tok == Scanner.LBRACE || tok == Scanner.COLON);
	b := new(Block);
	b.Pos, b.Tok, b.List = pos, tok, array.New(0);
	return b;
}

この変更は、Go言語のAST設計における重要な進化を示しています。以前は、コードブロックの内容(ステートメントのリスト)と終了位置が、ObjectStatといった他のASTノードのフィールドとして直接埋め込まれていました。しかし、Blockという専用の構造体を導入することで、コードブロックがそれ自体で独立したASTノードとして扱われるようになります。これにより、ASTの構造がより明確になり、ブロックに関するセマンティックな情報(例:スコープ)をBlockノード自体に付与しやすくなります。NewBlock関数は、波括弧 { またはコロン : で始まるブロック(例:switch文のcase節)を生成するためのファクトリ関数です。

parser.goにおけるParseBlock関数の導入と利用

func (P *Parser) ParseBlock(ftyp *AST.Type, tok int) *AST.Block {
	P.Trace("Block");

	b := AST.NewBlock(P.pos, tok);
	P.Expect(tok);

	P.OpenScope();
	// enter recv and parameters into function scope
	if ftyp != nil {
		assert(ftyp.Form == AST.FUNCTION);
		if ftyp.Key != nil {
		}
		if ftyp.List != nil {
			for i, n := 0, ftyp.List.Len(); i < n; i++ {
				x := ftyp.List.At(i).(*AST.Expr);
				if x.Tok == Scanner.IDENT {
					P.DeclareInScope(P.top_scope, x, AST.VAR);
				}
			}
		}
	}

	P.ParseStatementList(b.List);
	P.CloseScope();

	if tok == Scanner.LBRACE {
		b.End = P.pos;
		P.Expect(Scanner.RBRACE);
		P.opt_semi = true;
	}

	P.Ecart();
	return b;
}

ParseBlock関数は、Go言語のパーサーにおける中心的な変更点の一つです。この関数は、{ または : で始まるコードブロックを解析し、新しいAST.Blockノードを構築します。 重要なのは、この関数がブロックの解析と同時にスコープ管理を行う点です。P.OpenScope()で新しいスコープを開き、関数型(ftyp)が与えられた場合は、その関数のレシーバやパラメータを新しいスコープに宣言します。これにより、ブロック内で宣言されたローカル変数やパラメータが正しくスコープ付けされ、後続の型チェックや名前解決フェーズで利用できるようになります。 このParseBlock関数は、ParseFunctionLitParseIfStatParseForStatParseSwitchStatParseSelectStatなど、様々な制御フロー文の解析で再利用されるようになり、パーサーのコードのモジュール性と保守性が向上しました。

Expr構造体へのTyp *Typeフィールドの追加

type Expr struct {
	Node;
	X, Y *Expr;  // binary (X, Y) and unary (Y) expressions
	Obj *Object;  // identifiers, literals
	Typ *Type;    // NEW: type of the expression
}

Expr(式)はASTの中で最も頻繁に登場するノードの一つです。このコミットでTyp *Typeフィールドが追加されたことは、Go言語の型チェックシステムがASTに深く統合され始めたことを示しています。これにより、各式のASTノードが自身の型情報を直接保持できるようになり、型チェックフェーズで式の型を推論したり、型の整合性を検証したりする際に、ASTを効率的に走査できるようになります。これは、コンパイラがコードの意味を理解し、エラーを検出するための基盤となります。

printer.goにおけるBlock関数の更新

func (P *Printer) Block(b *AST.Block, indent bool) {
	P.state = opening_scope;
	P.Token(b.Pos, b.Tok); // Prints "{" or ":"
	if !indent {
		P.indentation--;
	}
	P.StatementList(b.List); // Prints statements within the block
	if !indent {
		P.indentation++;
	}
	P.newlines = 1;
	if P.separator == none {
		P.separator = blank;
	}
	P.state = closing_scope;
	if b.Tok == Scanner.LBRACE {
		P.String(b.End, "}"); // Prints "}" for LBRACE blocks
	} else {
		P.String(0, "");  // process closing_scope state transition!
	}
}

プリンターのBlock関数が、新しいAST.Block型を引数として受け取るように変更されました。これは、ASTの構造変更にプリンターが追従していることを示しています。プリンターは、ASTの構造を走査して整形されたソースコードを出力する役割を担います。AST.Blockノードが持つListフィールド(ステートメントのリスト)とEndフィールド(ブロックの終了位置)を利用して、正確なインデントと波括弧の配置を行います。特に、switch文のcase節のようにコロンで始まるブロックの場合、閉じ波括弧は出力しないというロジックも含まれており、Go言語の構文規則に合わせた整形が行われるようになっています。

これらの変更は、Go言語のコンパイラがより堅牢で、正確な構文解析と意味解析を行うための重要なステップであり、後の型システムやコード生成の発展に不可欠な基盤を築いています。

関連リンク

参考にした情報源リンク

  • Go言語のGitHubリポジトリ: https://github.com/golang/go
  • コンパイラの構造に関する一般的な情報源 (例: Dragon Book - Compilers: Principles, Techniques, and Tools)
  • 抽象構文木 (AST) に関する一般的な情報源
  • スコープと名前解決に関する一般的な情報源
  • 型システムと型チェックに関する一般的な情報源
  • Robert Griesemer氏のGo言語における貢献に関する情報 (Go言語の初期開発者の一人)