[インデックス 1438] ファイルの概要
このコミットは、Go言語の初期の「pretty printer」またはコンパイラツールチェーンの一部における、識別子(ident)とリテラル(literal)の内部表現とスコープ管理に関する重要なリファクタリングを含んでいます。主に、識別子やリテラルを単なる文字列として扱うのではなく、よりリッチなセマンティック情報を持つObject構造体としてカプセル化し、追跡するための基盤を構築しています。
コミット
commit cb13c4d5525a0f924dec80229e023620a2eb8796
Author: Robert Griesemer <gri@golang.org>
Date: Wed Jan 7 16:54:03 2009 -0800
- more steps towards tracking idents in scopes
- snapshot of today
R=r
OCL=22247
CL=22247
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/cb13c4d5525a0f924dec80229e023620a2eb8796
元コミット内容
このコミットの元のメッセージは以下の通りです。
more steps towards tracking idents in scopes(スコープ内の識別子を追跡するためのさらなるステップ)snapshot of today(今日のスナップショット)
これは、Go言語のコンパイラまたは関連ツールにおいて、識別子のスコープ管理機能を強化するための継続的な作業の一部であることを示唆しています。
変更の背景
Go言語のコンパイラやツールチェーンは、プログラムのソースコードを解析し、抽象構文木(AST)を構築し、型チェックを行い、最終的に実行可能なバイナリを生成します。このプロセスにおいて、変数名、関数名、型名などの「識別子」がどのスコープで定義され、どのような意味を持つのかを正確に追跡することは極めて重要です。
このコミットが行われた2009年1月は、Go言語がまだ一般に公開される前の非常に初期の開発段階でした。当時のGoコンパイラ(またはこの場合は「pretty printer」という名称のツール)は、識別子やリテラルを単純な文字列として扱っていた可能性があります。しかし、より高度な型チェック、セマンティック解析、エラー検出、そして正確なコード生成を行うためには、識別子に付随するセマンティック情報を保持し、スコープに基づいて解決するメカニズムが不可欠です。
この変更は、識別子を単なる文字列から、その種類(定数、型、変数、関数など)、型情報、定義されたスコープレベルなどのメタデータを持つObject構造体へと昇格させることで、より堅牢なシンボルテーブル管理とスコープ解決の基盤を築くことを目的としています。これにより、コンパイラがコードの意味をより深く理解し、正確な処理を行えるようになります。
前提知識の解説
このコミットを理解するためには、以下の概念に関する基本的な知識が必要です。
-
コンパイラの基本構造:
- 字句解析 (Lexical Analysis): ソースコードをトークン(最小単位の単語)のストリームに変換するプロセス。
scanner.goがこれに該当します。 - 構文解析 (Syntactic Analysis / Parsing): トークンのストリームから抽象構文木(AST)を構築するプロセス。
parser.goがこれに該当します。 - 抽象構文木 (Abstract Syntax Tree - AST): ソースコードの構造を木構造で表現したもの。
ast.goで定義されるノードがこれにあたります。 - セマンティック解析 (Semantic Analysis): ASTを走査し、型チェック、スコープ解決、シンボル解決など、プログラムの意味的な正当性を検証するプロセス。
typechecker.goがこれに該当します。 - シンボルテーブル (Symbol Table): コンパイラがプログラム内の識別子(変数、関数、型など)に関する情報を格納するために使用するデータ構造。識別子の名前、型、スコープ、メモリ位置などの情報が含まれます。
- 字句解析 (Lexical Analysis): ソースコードをトークン(最小単位の単語)のストリームに変換するプロセス。
-
スコープ (Scope): プログラム内で識別子(変数名、関数名など)が参照可能である領域のこと。Go言語には、ブロックスコープ、パッケージスコープ、ファイルスコープなどがあります。コンパイラは、ある識別子がどのスコープで定義されているかを判断し、正しい定義に解決する必要があります。
-
識別子 (Identifier) と リテラル (Literal):
- 識別子: プログラム要素(変数、関数、型など)に付けられた名前。例:
myVariable,printHello,MyStruct。 - リテラル: ソースコードに直接記述された定数値。例:
123(整数リテラル),"hello"(文字列リテラル),3.14(浮動小数点リテラル)。
- 識別子: プログラム要素(変数、関数、型など)に付けられた名前。例:
-
Go言語のパッケージ構造: Go言語では、コードはパッケージに分割されます。
globals.goやobject.goといったファイルは、それぞれGlobalsパッケージやObjectパッケージを構成し、他のパッケージから参照される共通の定義を提供します。
技術的詳細
このコミットの核心は、Go言語のコンパイラ(またはprettyツール)が識別子とリテラルをどのように内部的に表現するかを変更した点にあります。
以前は、AST.Expr構造体内で識別子やリテラルの値が単にs stringフィールドとして保持されていました。これは、識別子の名前やリテラルの文字列値を直接格納するシンプルなアプローチです。しかし、この方法では、識別子に関連する追加のセマンティック情報(例えば、それが変数なのか、型なのか、関数なのか、どのスコープで定義されているのか、その型は何かなど)を効率的に管理することが困難です。
このコミットでは、以下の主要な変更が行われました。
-
Object構造体の一元化と拡張:ASTパッケージからObject構造体の定義が削除され、Globalsパッケージ(usr/gri/pretty/globals.go)に移動されました。これにより、Objectがより広範なコンパイラコンポーネントで共有される共通のエンティティとして扱われるようになります。Globals.Object構造体には、ident string(識別子の名前)、typ *Type(その識別子の型)、pnolev int(パッケージ番号または関数ネストレベル)などのフィールドが追加されました。- さらに、
block *array.Array; end int;フィールドが追加され、関数リテラルなどのブロックを持つ識別子に関連するステートメントのリストとブロックの終了位置をObject自体が保持できるようになりました。これは、関数リテラルが匿名オブジェクトとして扱われ、その本体がObjectに直接関連付けられることを示唆しています。
-
AST.Exprの変更:AST.Expr構造体からs stringフィールドが削除され、代わりにobj *Globals.Objectフィールドが導入されました。これにより、すべての識別子やリテラルがGlobals.Objectのインスタンスを介して参照されるようになります。AST.NewLit関数(リテラルを表すExprを作成する関数)のシグネチャが変更され、s stringの代わりにobj *Globals.Objectを受け取るようになりました。これは、リテラルもObjectとして表現されることを意味します。
-
パーサーとプリンターの適応:
parser.go内のParseIdent、ParseFunctionLit、ParseOperandなどの関数が、識別子やリテラルを解析する際にGlobals.NewObjectを呼び出し、その結果をAST.Exprのobjフィールドに格納するように変更されました。printer.go内のHtmlIdentifierやExpr1などの関数も、AST.Exprのsフィールドではなく、obj.identフィールドを参照するように更新されました。これにより、pretty printerが新しいObjectベースの表現に対応できるようになります。
-
型チェッカーの適応:
typechecker.go内のDeclareIdentやCheckDeclarationなどの関数が、識別子の宣言やルックアップを行う際にGlobals.NewObjectを使用し、ident.obj.identを参照するように変更されました。これは、型チェッカーがシンボルテーブルを構築し、識別子を解決する際に、Object構造体を利用するようになったことを示しています。
これらの変更により、コンパイラは識別子やリテラルを単なる文字列としてではなく、そのセマンティックな意味合いを内包したObjectとして扱うことができるようになり、より複雑な言語機能(例えば、クロージャ、スコープ解決、型推論など)の実装に向けた強固な基盤が構築されます。
コアとなるコードの変更箇所
このコミットにおけるコアとなるコードの変更箇所は、主に以下のファイルとセクションに集中しています。
-
usr/gri/pretty/ast.go:Object構造体の定義が削除され、Expr構造体内のs stringフィールドがobj *Globals.Objectに変更されました。NewLit関数のシグネチャが変更され、obj *Globals.Objectを受け取るようになりました。
// 削除された Object struct の定義 // export type Object struct { // Node; // lit string; // identifiers and literals // typ *Type; // val *Expr; // } // Expr struct の変更 // - s string; // identifiers and literals // + obj *Globals.Object; // NewLit 関数の変更 // -export func NewLit(pos, tok int, s string) *Expr { // +export func NewLit(pos, tok int, obj *Globals.Object) *Expr { // e := new(Expr); // - e.pos, e.tok, e.s = pos, tok, s; // + e.pos, e.tok, e.obj = pos, tok, obj; // return e; // } -
usr/gri/pretty/globals.go:Object構造体の定義が追加され、blockとendフィールドが追加されました。
package Globals import "array" // ... export type Object struct { // ... ident string; typ *Type; // nil for packages pnolev int; // >= 0: package no., <= 0: function nesting level, 0: global level // attached values block *array.Array; end int; // stats for function literals; end of block pos } -
usr/gri/pretty/parser.go:- 識別子やリテラルを解析する多くの箇所で、
AST.NewLitの呼び出しがGlobals.NewObjectを介してObjectインスタンスを渡すように変更されました。 - 特に
ParseIdent、ParseFunctionLit、ParseOperand、ParseImportSpecなどが影響を受けています。
// ParseIdent の変更例 // - x = AST.NewLit(P.pos, Scanner.IDENT, P.val); // + obj := Globals.NewObject(P.pos, Object.NONE, P.val); // + x = AST.NewLit(P.pos, Scanner.IDENT, obj); // ... // - print("Ident = \"", x.s, "\"\n"); // + print("Ident = \"", x.obj.ident, "\"\n"); // ParseFunctionLit の変更例 // - x := AST.NewLit(P.pos, Scanner.FUNC, ""); // + val := Globals.NewObject(P.pos, Object.NONE, ""); // + x := AST.NewLit(P.pos, Scanner.FUNC, val); // ... // - x.block, x.end = P.ParseBlock(); // + val.block, val.end = P.ParseBlock(); // ParseOperand (リテラル処理) の変更例 // - x = AST.NewLit(P.pos, P.tok, P.val); // + val := Globals.NewObject(P.pos, Object.NONE, P.val); // + x = AST.NewLit(P.pos, P.tok, val); // ... // - x.s += P.val; // + x.obj.ident += P.val; - 識別子やリテラルを解析する多くの箇所で、
-
usr/gri/pretty/printer.go:HtmlIdentifierやExpr1などの関数が、AST.Exprのobj.identフィールドを参照するように変更されました。
// HtmlIdentifier の変更例 // -func (P *Printer) HtmlIdentifier(pos int, ident string) { // +func (P *Printer) HtmlIdentifier(pos int, obj *Globals.Object) { // ... // - P.TaggedString(pos, `<a href="#` + ident + `">`, ident, `</a>`); // + P.TaggedString(pos, `<a href="#` + obj.ident + `">`, obj.ident, `</a>`); // - P.String(pos, ident); // + P.String(pos, obj.ident); // Expr1 の変更例 (Scanner.IDENT, Scanner.INT, Scanner.FUNC のケース) // case Scanner.IDENT: // - P.HtmlIdentifier(x.pos, x.s); // + P.HtmlIdentifier(x.pos, x.obj); // case Scanner.INT, Scanner.STRING, Scanner.FLOAT: // - P.String(x.pos, x.s); // + P.String(x.pos, x.obj.ident); // case Scanner.FUNC: // ... // - P.Block(0, x.block, x.end, true); // + P.Block(0, x.obj.block, x.obj.end, true); -
usr/gri/pretty/typechecker.go:DeclareIdentやCheckDeclarationがGlobals.NewObjectを使用し、ident.obj.identを参照するように変更されました。
// DeclareIdent の変更例 // - obj := Globals.NewObject(ident.pos, kind, ident.s); // + obj := Globals.NewObject(ident.pos, kind, ident.obj.ident); // CheckDeclaration の変更例 (Scanner.TYPE, Scanner.FUNC のケース) // - obj := s.Lookup(d.ident.s); // + obj := s.Lookup(d.ident.obj.ident);
コアとなるコードの解説
このコミットの最も重要な変更は、AST.Exprが識別子やリテラルを表現する方法を、直接文字列を持つs stringから、Globals.Objectへのポインタobj *Globals.Objectへと変更した点です。
-
AST.Exprのs stringからobj *Globals.Objectへの移行:- 以前は、
AST.Exprは識別子やリテラルの「値」を直接文字列sとして保持していました。例えば、x.sで識別子の名前や文字列リテラルの内容にアクセスしていました。 - この変更により、
AST.ExprはGlobals.Objectのインスタンスへのポインタobjを持つようになりました。Globals.Objectは、識別子の名前(identフィールド)だけでなく、その種類(kind)、型(typ)、定義されたスコープレベル(pnolev)、さらには関数リテラルの場合はそのコードブロック(block,end)など、より多くのセマンティック情報を保持できるようになります。 - これにより、ASTノードは単なる構文的な情報だけでなく、セマンティックな情報にもアクセスできるようになり、コンパイラの各フェーズ(パーサー、型チェッカー、プリンターなど)がよりリッチな情報に基づいて動作できるようになります。
- 以前は、
-
Globals.Objectの役割:Globals.Objectは、Goプログラム内の「名前付きエンティティ」(変数、定数、型、関数、パッケージなど)を抽象化して表現するための中心的なデータ構造となります。- この構造体が
globals.goに移動されたことで、コンパイラの様々な部分で共通して利用される「シンボル」の定義として機能します。 - 特に、
parser.goが識別子やリテラルを解析する際にGlobals.NewObjectを呼び出し、その結果をASTに組み込むことで、ソースコードの構文解析と同時にセマンティックな情報の紐付けが開始されます。 typechecker.goは、このObject構造体を利用してシンボルテーブルを構築し、識別子の参照をその定義に解決する(名前解決)プロセスを実行します。printer.goは、Objectから識別子の名前を取得して出力することで、整形されたコードを生成します。
この変更は、Goコンパイラがより複雑な言語機能やセマンティックな解析をサポートするための、シンボル管理の基盤を強化する重要な一歩と言えます。識別子を単なる文字列ではなく、その意味を内包するObjectとして扱うことで、コンパイラはプログラムの構造と意味をより深く理解し、正確な処理を行うことが可能になります。
関連リンク
- Go言語の公式リポジトリ: https://github.com/golang/go
- Go言語の初期のコミット履歴を辿ることで、コンパイラの進化の過程を理解できます。
参考にした情報源リンク
- コンパイラの構造に関する一般的な情報源(例: Dragon Book - Compilers: Principles, Techniques, & Tools)
- Go言語のASTパッケージに関するドキュメント(現在のGo言語のASTは、このコミット当時のものとは大きく異なりますが、概念は共通しています)
- Go言語の
go/typesパッケージに関するドキュメント(現在のGo言語の型システムとスコープ解決の仕組みを理解するのに役立ちます)