[インデックス 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の正確な表現は、その後のコード生成や最適化、あるいは静的解析の品質に直結します。
具体的には、以下の課題に対処しようとしています。
- ASTの表現力向上: 従来のAST構造では、コードブロックやスコープ、型情報が十分に表現できていなかった可能性があります。これを改善し、よりセマンティックな情報をASTに持たせることで、後続の処理(型チェック、コード生成)を容易にすることを目指しています。
- パーサーの堅牢性: 構文解析の過程で、より正確な位置情報やエラーハンドリングを提供するために、パーサーのロジックを洗練させる必要がありました。
- 型チェックの基盤: 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
がこれに該当します。
- 字句解析 (Lexical Analysis / Scanning): ソースコードをトークン(最小単位の単語)のストリームに変換するプロセス。
-
Go言語の基本:
- ブロック (Block): 波括弧
{}
で囲まれた一連のステートメント。Go言語では、関数本体、if
文、for
文、switch
文などの制御構造がブロックを持ちます。 - スコープ (Scope): プログラム内で識別子(変数名、関数名など)が有効な範囲。Go言語では、ブロックごとに新しいスコープが作成され、識別子の可視性が制御されます。
- 型 (Type): 変数や式の種類を定義するもの。Go言語は静的型付け言語であり、コンパイル時に型の整合性がチェックされます。
- ASTノード: ASTを構成する個々の要素。例えば、変数宣言、関数呼び出し、リテラルなどがそれぞれASTノードとして表現されます。
- ブロック (Block): 波括弧
-
データ構造:
array.Array
: Go言語の初期段階で使われていた、動的配列のようなデータ構造。現在のGoの[]T
スライスとは異なりますが、同様の目的で使用されています。
技術的詳細
このコミットの技術的な変更は、Go言語のコンパイラフロントエンドの基盤を強化するものです。
-
Block
型とASTの再構築:ast.go
にBlock
という新しい構造体が導入されました。これは、{ StatementList }
や: StatementList
のような構文ブロックを明示的に表現するためのものです。- 以前は
Object
やStat
構造体の中にBlock *array.Array
やEnd int
といったフィールドでブロックの内容を表現していましたが、これがBody *Block
という形でBlock
型へのポインタを持つように変更されました。これにより、ASTがよりセマンティックに正確になり、コードブロックの構造が明確に表現されるようになりました。 NewBlock
関数が追加され、ブロックの作成時にScanner.LBRACE
またはScanner.COLON
トークンを期待するようになりました。
-
型情報のASTへの統合:
ast.go
のExpr
構造体にTyp *Type
フィールドが追加されました。これにより、式ノードが直接その式の型情報を持つことができるようになり、型チェックのロジックがAST上でより自然に表現できるようになります。NewTypeExpr
関数が変更され、Expr
ノードに直接型情報を設定するようになりました。
-
スコープ管理の改善:
parser.go
において、ParseFunctionType
やParseStructType
内でP.OpenScope()
やP.CloseScope()
がより適切に呼び出されるようになりました。- 特に、
ParseBlock
関数が導入され、ブロックの解析時に新しいスコープを開き、関数パラメータやレシーバをそのスコープに宣言するロジックが追加されました。これにより、ローカル変数のスコープが正確に管理されるようになります。 DeclareInScope
関数が改善され、識別子が既にスコープ内で宣言されている場合の重複宣言エラーをより正確に検出するようになりました。
-
パーサーロジックの洗練:
parser.go
のParseStatementList
が、ステートメントリストを返すのではなく、既存のarray.Array
にステートメントを追加する形に変更されました。これは、Block
構造体の導入と密接に関連しています。ParseIfStat
,ParseForStat
,ParseSwitchStat
,ParseSelectStat
などの制御フロー文の解析ロジックが、新しいParseBlock
関数を使用するように変更されました。これにより、コードの重複が減り、一貫性が向上しました。ParseSpec
関数が、各宣言タイプ(IMPORT
,CONST
,TYPE
,VAR
)の具体的な解析ロジックを呼び出すように変更され、宣言のセマンティックチェック(例:変数と式の数の不一致)が追加されました。
-
プリンターの更新:
printer.go
のBlock
関数が、新しいAST.Block
型を引数として受け取るように変更されました。これにより、ASTの変更がプリンターにも適切に反映され、整形された出力が生成されるようになります。Expr1
関数内で、Scanner.TYPE
トークンを持つExpr
の型情報を正しく出力するように修正されました。StatementList
の処理が微調整され、改行の扱いが改善されました。
-
スキャナーの微調整:
scanner.go
のTokenString
関数が、LBRACE
とRBRACE
トークンに対して、デバッグ出力時に"{"
と"}"
という実際の文字を返すように変更されました。これは、ターミナルでのダブルクリック選択の利便性を考慮したものです。
コアとなるコードの変更箇所
このコミットにおけるコアとなるコードの変更は、主に以下のファイルに集中しています。
-
usr/gri/pretty/ast.go
:Block
構造体の新規追加。Object
構造体のBlock
フィールドをBody *Block
に変更。Expr
構造体にTyp *Type
フィールドを追加。Stat
構造体のBlock
フィールドをBody *Block
に変更。assert
関数の追加。
-
usr/gri/pretty/parser.go
:ParseBlock
関数の新規追加。ParseFunctionLit
、ParseIfStat
、ParseForStat
、ParseSwitchStat
、ParseSelectStat
などの関数が、新しいParseBlock
を使用するように変更。DeclareInScope
関数におけるスコープ内での重複宣言チェックのロジック改善。ParseSpec
関数における宣言のセマンティックチェックの追加。ParseFunctionType
およびParseStructType
におけるスコープ管理の調整。
-
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.Body
(AST.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設計における重要な進化を示しています。以前は、コードブロックの内容(ステートメントのリスト)と終了位置が、Object
やStat
といった他の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
関数は、ParseFunctionLit
、ParseIfStat
、ParseForStat
、ParseSwitchStat
、ParseSelectStat
など、様々な制御フロー文の解析で再利用されるようになり、パーサーのコードのモジュール性と保守性が向上しました。
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言語の公式ウェブサイト: https://go.dev/
- Go言語のASTパッケージに関するドキュメント (現在のバージョン): https://pkg.go.dev/go/ast
- Go言語のパーサーパッケージに関するドキュメント (現在のバージョン): https://pkg.go.dev/go/parser
- Go言語のプリンターパッケージに関するドキュメント (現在のバージョン): https://pkg.go.dev/go/printer
- Go言語のトークンパッケージに関するドキュメント (現在のバージョン): https://pkg.go.dev/go/token
参考にした情報源リンク
- Go言語のGitHubリポジトリ: https://github.com/golang/go
- コンパイラの構造に関する一般的な情報源 (例: Dragon Book - Compilers: Principles, Techniques, and Tools)
- 抽象構文木 (AST) に関する一般的な情報源
- スコープと名前解決に関する一般的な情報源
- 型システムと型チェックに関する一般的な情報源
- Robert Griesemer氏のGo言語における貢献に関する情報 (Go言語の初期開発者の一人)