[インデックス 1892] ファイルの概要
このコミットは、Go言語のコンパイラツールチェインにおける、ソースコード上の位置情報(Position)の管理方法に関する重要なリファクタリングです。具体的には、scanner
パッケージに存在していたLocation
構造体をtoken
パッケージに移動し、Position
と改名することで、位置情報の表現を一元化し、依存関係を整理しています。これにより、Go言語のパーサーや抽象構文木(AST)における位置情報の扱いがより明確かつ効率的になりました。
コミット
commit 5a72ca45fba1aad8ab08d66cd73fd40560407dbd
Author: Robert Griesemer <gri@golang.org>
Date: Thu Mar 26 16:08:44 2009 -0700
- renamed scanner.Location to token.Position
- by moving Position into token, scanner dependencies
are removed from several files
- clearer field names in token.Position, now possible to
have a Pos() accessor w/o naming conflicts
- added Pos() accessor
- use anonymous token.Position field in AST nodes
R=r
DELTA=244 (28 added, 55 deleted, 161 changed)
OCL=26786
CL=26793
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/5a72ca45fba1aad8ab08d66cd73fd40560407dbd
元コミット内容
このコミットの元の内容は以下の通りです。
scanner.Location
をtoken.Position
にリネームしました。Position
をtoken
パッケージに移動することで、いくつかのファイルからscanner
への依存関係が削除されました。token.Position
のフィールド名がより明確になり、名前の衝突なしにPos()
アクセサを持つことが可能になりました。Pos()
アクセサを追加しました。- ASTノードで匿名(埋め込み)の
token.Position
フィールドを使用しました。
変更の背景
Go言語のコンパイラは、ソースコードを解析する際に、各トークンや構文要素がソースコードのどの位置にあるかを正確に記録する必要があります。これは、エラーメッセージの表示、デバッグ情報の生成、コードフォーマットツール(go fmt
など)の動作に不可欠です。
このコミット以前は、ソースコード上の位置情報を表すLocation
構造体がscanner
パッケージ内に定義されていました。しかし、位置情報はスキャナーだけでなく、パーサーが構築する抽象構文木(AST)の各ノードにも必要とされます。Location
がscanner
パッケージに存在すると、ASTノードがscanner
パッケージに依存することになり、モジュール間の結合度が高まっていました。
この状況は、Goコンパイラの設計原則である「疎結合」に反し、将来的な変更や拡張の際に不必要な複雑さをもたらす可能性がありました。また、Location
構造体のフィールド名(Pos
, Line
, Col
)も、より汎用的な「位置」を表すには曖昧な部分がありました。
このコミットは、これらの問題を解決し、位置情報の管理をより体系的かつ効率的にするためのリファクタリングとして行われました。
前提知識の解説
このコミットを理解するためには、Go言語のコンパイラがソースコードを処理する際の基本的な段階と、関連するパッケージの役割について理解しておく必要があります。
-
スキャナー (Lexer/Tokenizer):
- ソースコードの文字列を読み込み、意味のある最小単位である「トークン」(キーワード、識別子、演算子、リテラルなど)に分割する役割を担います。
- Go言語では、
go/scanner
パッケージがこの機能を提供します。 - 各トークンがソースコードのどこから始まったか、という位置情報(行番号、列番号、バイトオフセット)を生成します。
-
トークン (Token):
- スキャナーによって識別された各トークンの種類(例:
IDENT
、ADD
、FOR
)を定義します。 - Go言語では、
go/token
パッケージがトークンの種類と、このコミットで導入されるPosition
構造体を定義します。
- スキャナーによって識別された各トークンの種類(例:
-
パーサー (Parser):
- スキャナーが生成したトークンのストリームを読み込み、Go言語の文法規則に従って、それらを構造化されたデータ表現である「抽象構文木(AST)」に変換します。
- Go言語では、
go/parser
パッケージがこの機能を提供します。 - ASTの各ノードは、対応するソースコードの断片の位置情報を持つ必要があります。
-
抽象構文木 (AST - Abstract Syntax Tree):
- ソースコードの構造を木構造で表現したものです。各ノードは、変数宣言、関数呼び出し、式などのGo言語の構文要素に対応します。
- Go言語では、
go/ast
パッケージがASTのノード構造を定義します。 - ASTは、コンパイラのセマンティック解析、コード生成、および各種ツール(
go fmt
,go vet
など)の入力として使用されます。
-
Go言語の埋め込み(Anonymous Field Embedding):
- Go言語のユニークな機能の一つで、構造体の中にフィールド名なしで別の構造体を埋め込むことができます。
- 埋め込まれた構造体のフィールドやメソッドは、外側の構造体のフィールドやメソッドであるかのように直接アクセスできます。これは、継承に似た振る舞いを実現しますが、より柔軟な「コンポジション(合成)」のメカニズムです。
- このコミットでは、ASTノードに
token.Position
を匿名で埋め込むことで、各ASTノードが自動的に位置情報とそのアクセサメソッドを持つようにしています。
技術的詳細
このコミットの技術的な核心は、ソースコード上の位置情報を表すデータ構造の定義と、その利用方法の変更にあります。
-
scanner.Location
からtoken.Position
への移動と改名:- 変更前:
scanner
パッケージ内にLocation
構造体がありました。// src/lib/go/scanner.go (変更前) type Location struct { Pos int; // byte position in source Line int; // line count, starting at 1 Col int; // column, starting at 1 (character count) }
- 変更後:
token
パッケージ内にPosition
構造体が定義されました。// src/lib/go/token.go (変更後) type Position struct { Offset int; // byte offset, starting at 0 Line int; // line number, starting at 1 Column int; // column number, starting at 1 (character count) }
- この変更により、位置情報がスキャナー固有のものではなく、Go言語のトークンと構文解析全体で共有される基本的な概念として昇格しました。
token
パッケージは、Go言語の構文解析における基本的な構成要素(トークン、位置)を定義する役割を担うため、Position
の配置場所としてより適切です。
- 変更前:
-
フィールド名の明確化:
Pos
->Offset
: ソースコードの先頭からのバイトオフセットを明確に示します。Col
->Column
: 列番号であることを明確にします。- これにより、各フィールドが表す意味がより直感的になりました。
-
Pos()
アクセサメソッドの追加:token.Position
にPos() Position
というメソッドが追加されました。// src/lib/go/token.go (変更後) func (pos *Position) Pos() Position { return *pos; }
- このメソッドは、
Position
構造体自体を返すシンプルなアクセサです。これは、後述する匿名フィールド埋め込みと組み合わせて、ASTノードから位置情報に統一的にアクセスするための重要な役割を果たします。
-
ASTノードでの匿名フィールド埋め込みの利用:
- 変更前は、ASTノードは
Pos_ Position
のような明示的なフィールドを持っていました。そして、各ASTノード型でPos() Position
メソッドを個別に実装し、そのPos_
フィールドを返していました。// usr/gri/pretty/ast.go (変更前 - 例) type BadExpr struct { Pos_ Position; // beginning position of bad expression }; func (x *BadExpr) Pos() Position { return x.Pos_; }
- 変更後、多くのASTノードで
token.Position
が匿名フィールドとして埋め込まれるようになりました。// usr/gri/pretty/ast.go (変更後 - 例) type BadExpr struct { token.Position; // beginning position of bad expression }; // BadExprは自動的にtoken.Positionのフィールドとメソッド(Pos()を含む)を持つ
- これにより、各ASTノード型で
Pos()
メソッドを個別に実装する必要がなくなりました。Goの埋め込み機能により、BadExpr
型のインスタンスは直接badExprInstance.Offset
、badExprInstance.Line
、badExprInstance.Column
、そしてbadExprInstance.Pos()
にアクセスできるようになります。これはコードの重複を大幅に削減し、ASTの定義を簡潔に保ちます。
- 変更前は、ASTノードは
これらの変更は、Goコンパイラの内部構造をよりクリーンで保守しやすいものにし、位置情報の扱いに関する設計を改善しました。
コアとなるコードの変更箇所
このコミットにおける主要なコード変更は、以下のファイルに集中しています。
-
src/lib/go/scanner.go
:Location
構造体の定義が削除され、token.Position
がインポートされるようになりました。Scanner
構造体内の位置情報フィールドがloc Location
からpos token.Position
に、pos int
からoffset int
に変更されました。ErrorHandler
インターフェースのError
メソッドのシグネチャがError(loc Location, msg string)
からError(pos token.Position, msg string)
に変更されました。next()
メソッド内で、S.loc.Pos
,S.loc.Col
,S.loc.Line
へのアクセスがS.pos.Offset
,S.pos.Column
,S.pos.Line
に更新されました。scanComment
,scanString
,scanRawString
などのメソッドの引数と内部での位置情報の参照がLocation
からtoken.Position
に更新されました。Scan()
メソッドの戻り値の型がloc Location
からpos token.Position
に変更されました。Tokenize
関数のシグネチャも同様にLocation
からtoken.Position
に変更されました。
-
src/lib/go/scanner_test.go
:- テストコード内で
scanner.Location
を使用していた箇所がtoken.Position
に更新されました。 eloc.Col
がeloc.Column
に、eloc.Pos
がeloc.Offset
に更新されました。
- テストコード内で
-
src/lib/go/token.go
:Position
構造体が新しく定義されました。Position
構造体にPos() Position
アクセサメソッドが追加されました。
-
usr/gri/pretty/ast.go
:import "scanner"
が削除され、import "token"
のみになりました。type Position scanner.Location
というエイリアスが削除されました。- ASTノードの定義において、
Pos_ Position
のような明示的な位置情報フィールドが、token.Position
という匿名フィールドに置き換えられました。- 例:
BadExpr struct { Pos_ Position; }
->BadExpr struct { token.Position; }
- 例:
- これにより、多くのASTノード型で個別に実装されていた
Pos()
メソッドが削除されました。匿名フィールドの埋め込みにより、これらのメソッドは自動的に提供されるためです。
コアとなるコードの解説
src/lib/go/token.go
における Position
構造体と Pos()
メソッドの定義
// src/lib/go/token.go
// Token source positions are represented by a Position value.
type Position struct {
Offset int; // byte offset, starting at 0
Line int; // line number, starting at 1
Column int; // column number, starting at 1 (character count)
}
// Pos is an accessor method for anonymous Position fields.
// It returns its receiver.
func (pos *Position) Pos() Position {
return *pos;
}
このコードは、ソースコード上の特定の位置をバイトオフセット、行番号、列番号で表現するPosition
構造体を定義しています。Pos()
メソッドは、このPosition
構造体へのポインタを受け取り、その値(Position
構造体自体)を返します。このシンプルなアクセサは、Goの匿名フィールド埋め込み機能と組み合わされることで、ASTノードから位置情報に統一的にアクセスするための重要な役割を担います。
usr/gri/pretty/ast.go
における匿名フィールド埋め込みの利用
// usr/gri/pretty/ast.go (変更後 - 例: BadExpr)
type BadExpr struct {
token.Position; // beginning position of bad expression
};
この変更は、ASTノードの定義において最も影響の大きい部分です。以前はPos_ Position;
のように明示的なフィールド名で位置情報を保持し、各ノード型でPos()
メソッドを実装してそのフィールドを返していました。しかし、token.Position;
とフィールド名なしで埋め込むことで、BadExpr
構造体は自動的にtoken.Position
のすべてのフィールド(Offset
, Line
, Column
)とメソッド(Pos()
)を「継承」したかのように振る舞います。
例えば、badExprInstance := &BadExpr{...}
というインスタンスがあった場合、badExprInstance.Offset
、badExprInstance.Line
、badExprInstance.Column
、そしてbadExprInstance.Pos()
のように直接アクセスできるようになります。これにより、ASTノードの定義が簡潔になり、位置情報へのアクセスが統一され、コードの重複が大幅に削減されました。
src/lib/go/scanner.go
における変更
// src/lib/go/scanner.go (変更後 - 抜粋)
type ErrorHandler interface {
Error(pos token.Position, msg string);
}
type Scanner struct {
// ...
pos token.Position; // previous reading position (position before ch)
offset int; // current reading offset (position after ch)
// ...
}
func (S *Scanner) next() {
if S.offset < len(S.src) {
S.pos.Offset = S.offset;
S.pos.Column++;
// ...
case r == '\n':
S.pos.Line++;
S.pos.Column = 0;
// ...
S.offset += w;
S.ch = r;
} else {
S.pos.Offset = len(S.src);
S.ch = -1; // eof
}
}
func (S *Scanner) Init(src []byte, err ErrorHandler, scan_comments bool) {
// ...
S.pos.Line = 1;
S.next();
}
func (S *Scanner) error(pos token.Position, msg string) {
S.err.Error(pos, msg);
}
func (S *Scanner) Scan() (pos token.Position, tok token.Token, lit []byte) {
// ...
pos, tok = S.pos, token.ILLEGAL;
// ...
return pos, tok, S.src[pos.Offset : S.pos.Offset];
}
func Tokenize(src []byte, err ErrorHandler, scan_comments bool, f func (pos token.Position, tok token.Token, lit []byte) bool) {
// ...
}
scanner.go
では、Location
構造体の削除に伴い、token.Position
をインポートし、すべての位置情報参照を新しいtoken.Position
型に切り替えています。Scanner
構造体の内部状態も更新され、next()
メソッドやInit()
メソッドでの位置情報の更新ロジックがS.pos.Offset
、S.pos.Line
、S.pos.Column
を使用するように変更されました。Scan()
メソッドやTokenize
関数のシグネチャも、Location
の代わりにtoken.Position
を返すように変更されています。これにより、スキャナーが生成する位置情報が、token
パッケージで定義された標準的な形式に統一されました。
これらの変更は、Goコンパイラのフロントエンドにおける位置情報の管理を大幅に改善し、コードの可読性、保守性、および拡張性を向上させました。
関連リンク
- Go言語の
go/token
パッケージのドキュメント (現在のバージョン): https://pkg.go.dev/go/token - Go言語の
go/scanner
パッケージのドキュメント (現在のバージョン): https://pkg.go.dev/go/scanner - Go言語の
go/ast
パッケージのドキュメント (現在のバージョン): https://pkg.go.dev/go/ast
参考にした情報源リンク
- Go言語の公式リポジトリ: https://github.com/golang/go
- Go言語の埋め込み(Embedding)に関する公式ドキュメントやチュートリアル(Go言語の基本的な概念として広く解説されています)
- A Tour of Go - Embedded fields: https://go.dev/tour/methods/15
- Effective Go - Embedding: https://go.dev/doc/effective_go#embedding
- コンパイラの構造に関する一般的な知識(Lexer, Parser, ASTなど)
- Compiler design principles (Wikipedia): https://en.wikipedia.org/wiki/Compiler#Design_principles
- Abstract syntax tree (Wikipedia): https://en.wikipedia.org/wiki/Abstract_syntax_tree