[インデックス 1893] ファイルの概要
このコミットは、Go言語のコンパイラツールチェインにおける、字句解析器(scanner)、構文解析器(parser)、および抽象構文木(AST)の内部表現に関する大幅な調整とリファクタリングを含んでいます。主に、これらのコンポーネント間のインターフェースとデータ構造の標準化、およびモジュール性の向上が図られています。
変更されたファイルは以下の通りです。
usr/gri/pretty/compilation.go
: コンパイルプロセスにおけるエラーハンドリングとパーサーの初期化ロジックを調整。usr/gri/pretty/gds.go
: エラー表示に関連する位置情報の参照方法を更新。usr/gri/pretty/parser.go
: 構文解析器のコアロジック、データ構造、および外部インターフェースを大幅にリファクタリング。usr/gri/pretty/printer.go
: ASTの出力(pretty-printing)ロジックを、新しい位置情報とASTノードの構造に合わせて更新。usr/gri/pretty/typechecker.go
: 型チェッカーにおけるエラーハンドリングの位置情報参照を調整。
コミット
commit eeddc8e73b72aabe3485120ac7b5ab60efc46f5b
Author: Robert Griesemer <gri@golang.org>
Date: Thu Mar 26 16:10:07 2009 -0700
- adjustments to match new token/scanner/ast
R=r
OCL=26794
CL=26794
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/eeddc8e73b72aabe3485120ac7b5ab60efc46f5b
元コミット内容
このコミットの目的は、Go言語の内部ツールにおけるトークン、スキャナー、およびAST(抽象構文木)の新しい設計に合わせて、既存のコードベースを調整することです。具体的には、これらのコンポーネント間のインターフェースとデータ構造の変更に対応するための修正が行われています。
変更の背景
このコミットが行われた2009年3月は、Go言語がまだ一般に公開される前の非常に初期の開発段階に当たります。Go言語のコンパイラは、その設計と実装が頻繁に進化していました。この時期には、言語仕様の安定化と同時に、コンパイラの内部構造も洗練されていました。
「new token/scanner/ast」という記述から、Go言語の字句解析、構文解析、および構文木の表現方法に関して、何らかの重要な再設計または標準化が行われたことが推測されます。これまでの実装では、scanner
パッケージが位置情報(scanner.Location
)を管理し、parser
パッケージが具体的なscanner.Scanner
実装に直接依存していました。しかし、より堅牢でモジュール化されたコンパイラを構築するためには、これらのコンポーネント間の結合度を下げ、より抽象的なインターフェースを導入する必要がありました。
具体的には、以下の点が背景にあると考えられます。
- モジュール性の向上: スキャナー、パーサー、ASTがそれぞれ独立した役割を持つようにし、互いの具体的な実装に依存しないようにすることで、各コンポーネントの変更が他のコンポーネントに与える影響を最小限に抑える。
- テスト容易性の向上: インターフェースを導入することで、パーサーのテスト時にモックのスキャナーやエラーハンドラーを簡単に差し込めるようにする。
- 標準化と一貫性: ソースコード内の位置情報を表す型(
scanner.Location
)が、より汎用的なtoken.Position
に統一されることで、コンパイラ全体での位置情報の扱いに一貫性を持たせる。 - Go言語の設計思想への適合: Go言語がインターフェースを重視する設計思想を持っているため、コンパイラ自身の内部構造もその思想に沿ったものにする。
これらの変更は、Goコンパイラの長期的な保守性、拡張性、および堅牢性を確保するための基盤を築くものでした。
前提知識の解説
このコミットを理解するためには、コンパイラの基本的な構造とGo言語の初期の設計に関する知識が必要です。
-
コンパイラの基本構造:
- 字句解析(Lexical Analysis / Scanning): ソースコードを読み込み、意味のある最小単位(トークン)に分割するプロセス。例えば、
var x = 10;
というコードは、var
(キーワード)、x
(識別子)、=
(演算子)、10
(整数リテラル)、;
(セミコロン)といったトークンに分割されます。この処理を行うのが「スキャナー(Scanner)」です。 - 構文解析(Syntax Analysis / Parsing): 字句解析によって生成されたトークンの並びが、言語の文法規則に合致しているかを検証し、その構造を抽象構文木(AST)として構築するプロセス。この処理を行うのが「パーサー(Parser)」です。
- 抽象構文木(Abstract Syntax Tree - AST): ソースコードの構文構造を木構造で表現したもの。ASTは、コンパイラの後の段階(型チェック、コード生成など)で利用されます。
- 型チェック(Type Checking): ASTを走査し、プログラムが言語の型システム規則に準拠しているかを確認するプロセス。
- 字句解析(Lexical Analysis / Scanning): ソースコードを読み込み、意味のある最小単位(トークン)に分割するプロセス。例えば、
-
Go言語における
token
、scanner
、parser
、ast
パッケージの役割(初期段階):token
パッケージ: Go言語のキーワード、演算子、識別子、リテラルなどの「トークン」の種類を定義します。また、ソースコード内の位置情報(行番号、列番号、オフセット)を表すtoken.Position
型も含まれます。scanner
パッケージ: ソースコードを読み込み、token
パッケージで定義されたトークンを生成する機能を提供します。parser
パッケージ:scanner
から受け取ったトークン列を解析し、ast
パッケージで定義されたASTを構築します。ast
パッケージ: Goプログラムの抽象構文木を構成するノード(式、文、宣言など)のデータ構造を定義します。
-
インターフェース(Go言語): Go言語のインターフェースは、メソッドのシグネチャの集合を定義します。型がそのインターフェースのすべてのメソッドを実装していれば、その型はそのインターフェースを満たします。これにより、具体的な実装に依存せずに、抽象的な振る舞いを扱うことが可能になり、コードの柔軟性とテスト容易性が向上します。
技術的詳細
このコミットの技術的詳細は、主にscanner.Location
からtoken.Position
への移行、およびパーサーのインターフェース化に集約されます。
scanner.Location
からtoken.Position
への移行
-
usr/gri/pretty/compilation.go
:Error
構造体のLoc scanner.Location
フィールドがPos token.Position
に変更されました。- エラーリストのソート基準が
list[i].Loc.Pos
からlist[i].Pos.Offset
に変更され、token.Position
が持つオフセット情報を使用するようになりました。 errorHandler
のError
メソッドの引数がloc scanner.Location
からpos token.Position
に変更され、内部での行番号や列番号の参照もloc.Line
からpos.Line
、loc.Col
からpos.Column
へと更新されました。Compile
関数内でパーサーを初期化する際に、parser.Init
の呼び出しが削除され、新しいparser.Parse
関数が使用されるようになりました。このparser.Parse
関数はscanner
とerr
(ErrorHandler
インターフェースを満たす)を引数に取ります。
-
usr/gri/pretty/gds.go
:- エラー表示ロジックにおいて、
e.Loc.Pos
がe.Pos.Offset
に、pos
変数がoffs
変数にそれぞれ変更されました。これは、ソースコード内のバイトオフセットをより正確に扱うための変更です。
- エラー表示ロジックにおいて、
-
usr/gri/pretty/printer.go
:nopos
変数の型がscanner.Location
からtoken.Position
に変更されました。Printer
構造体のcloc scanner.Location
がcpos token.Position
に、lastloc scanner.Location
がlastpos token.Position
に変更されました。hasComment
メソッドの引数と内部ロジックがloc.Pos
からpos.Offset
に変更されました。TaggedString
、String
、Token
、Error
などのメソッドの引数がloc scanner.Location
からpos token.Position
に変更され、内部での位置情報の参照もloc.Pos
からpos.Offset
、loc.Col
からpos.Column
へと更新されました。- ASTノードの
Pos_
フィールド(例:x.Pos_
)が、Pos()
メソッド(例:x.Pos()
)を呼び出す形式に変更されました。これは、ASTノードが自身の位置情報を返すための標準的なメソッドを持つようになったことを示唆しています。 ast.BinaryExpr
のx.Tok
がx.Op
に、ast.UnaryExpr
のx.Tok
がx.Op
に変更されました。これは、ASTノード内で演算子を表現するフィールド名がより明確になったことを示します。
-
usr/gri/pretty/typechecker.go
:state
構造体のError
メソッドの引数がloc scanner.Location
からpos token.Position
に変更されました。
パーサーのインターフェース化とモジュール化
usr/gri/pretty/parser.go
: このファイルが最も大きな変更を受けています。- パッケージ名の変更:
package Parser
からpackage parser
へと、Go言語の慣習に従い小文字に変更されました。 - インターフェースの導入:
Scanner
インターフェースが定義されました。これはScan() (pos token.Position, tok token.Token, lit []byte)
メソッドを持つことで、パーサーが具体的なスキャナー実装に依存せず、このインターフェースを満たす任意の型を受け入れられるようになりました。ErrorHandler
インターフェースが定義されました。これはError(pos token.Position, msg string)
メソッドを持つことで、パーサーが具体的なエラーハンドラー実装に依存せず、このインターフェースを満たす任意の型を受け入れられるようになりました。
parser
構造体の変更:Parser
構造体(大文字P)がparser
構造体(小文字p)にリネームされ、エクスポートされない内部構造体となりました。scanner *scanner.Scanner
フィールドがscanner Scanner
(新しく定義されたインターフェース)に変更されました。err scanner.ErrorHandler
フィールドがerr ErrorHandler
(新しく定義されたインターフェース)に変更されました。val []byte
フィールドがlit []byte
に変更されました。これはトークンの「値」ではなく「リテラル」を保持するという意味合いを明確にするための命名変更です。pos Position
フィールドがpos token.Position
に変更されました。
Init
メソッドの削除とParse
関数の導入:- 以前の
func (P *Parser) Init(...)
メソッドは削除されました。 - 代わりに、パッケージレベルの新しい関数
func Parse(scanner Scanner, err ErrorHandler, mode Mode, flags uint) *ast.Package
が導入されました。この関数は、パーサーの内部状態を初期化し、parser
構造体のインスタンスを作成して、そのparsePackage
メソッドを呼び出す役割を担います。これにより、パーサーの初期化と実行がより制御可能で、外部から依存性を注入できるようになりました。
- 以前の
- メソッドレシーバーの変更:
func (P *Parser) ...
という形式だった多くのメソッドのレシーバーが、func (P *parser) ...
という形式に変更されました。これは、parser
構造体がエクスポートされない内部構造体になったことに伴う変更です。 - トレース出力の変更: デバッグトレースの出力において、
P.pos.Col
がP.pos.Column
に変更されました。 - リテラル値の参照変更:
P.val
がP.lit
に変更されました(例:string(P.val)
からstring(P.lit)
)。 - ASTノードのフィールド参照変更:
ast.BinaryExpr
のt.Tok
がt.Op
に、ast.RangeStmt
のas.Tok_
がas.TokPos
に、rhs.Tok
がrhs.Op
に変更されました。これは、ASTノードの構造が変更され、演算子や位置情報を取得するためのフィールド名やメソッド名が統一されたことを示します。
- パッケージ名の変更:
コアとなるコードの変更箇所
このコミットのコアとなる変更は、usr/gri/pretty/parser.go
におけるインターフェースの導入と、パーサーの初期化・実行方法の変更です。
// usr/gri/pretty/parser.go の変更点(抜粋)
// 新しく導入されたScannerインターフェース
type Scanner interface {
Scan() (pos token.Position, tok token.Token, lit []byte);
}
// 新しく導入されたErrorHandlerインターフェース
type ErrorHandler interface {
Error(pos token.Position, msg string);
}
// parser構造体の定義変更
type parser struct {
scanner Scanner; // 具体的なscanner.ScannerからScannerインターフェースへ
err ErrorHandler; // 具体的なscanner.ErrorHandlerからErrorHandlerインターフェースへ
// ... (他のフィールド)
pos token.Position; // scanner.Locationからtoken.Positionへ
tok token.Token;
lit []byte; // valからlitへ
}
// パッケージレベルの新しいParse関数
func Parse(scanner Scanner, err ErrorHandler, mode Mode, flags uint) *ast.Package {
// initialize parser state
var p parser;
p.scanner = scanner;
p.err = err;
p.trace = flags & Trace != 0;
p.comments.Init(0);
p.next(); // 最初のトークンを読み込む
// parse program
return p.parsePackage(mode);
}
// 以前のParser.Initメソッドは削除され、parser構造体のメソッドレシーバーも変更
// func (P *Parser) Init(...) は削除
// func (P *Parser) next0() は func (P *parser) next0() に変更
// func (P *Parser) error(...) は func (P *parser) error(...) に変更
// ... その他多数のメソッドレシーバーが変更
コアとなるコードの解説
これらの変更は、Goコンパイラの設計において非常に重要な意味を持ちます。
-
依存性逆転の原則(Dependency Inversion Principle - DIP)の適用:
- 以前は、
parser
パッケージがscanner
パッケージの具体的な実装(scanner.Scanner
)に直接依存していました。このコミットにより、parser
はScanner
インターフェースとErrorHandler
インターフェースに依存するようになりました。これにより、parser
は具体的な実装から切り離され、より抽象的なレベルで動作するようになりました。 - この原則の適用により、
parser
のコードはより柔軟になり、異なるスキャナー実装やエラーハンドラー実装を容易に差し替えることができるようになります。これは、例えばテスト時にモックのスキャナーを使用してパーサーの動作を検証したり、将来的に異なる入力形式(例: 別の言語の構文解析)に対応したりする際に非常に役立ちます。
- 以前は、
-
token.Position
による位置情報の統一:scanner.Location
は、おそらくscanner
パッケージ内部でのみ使用されることを意図した型でした。しかし、ソースコード内の位置情報は、パーサー、型チェッカー、プリンターなど、コンパイラの多くのコンポーネントで必要とされます。token.Position
への統一は、これらのコンポーネント間で位置情報を一貫して共有・利用するための標準的な方法を提供します。これにより、コードの可読性と保守性が向上し、異なるコンポーネント間での位置情報の不整合を防ぐことができます。
-
パッケージレベルの
Parse
関数の導入:- 以前は、
Parser
構造体のInit
メソッドを呼び出してパーサーを初期化し、その後Parse
メソッドを呼び出すというオブジェクト指向的なアプローチが取られていました。 - 新しい
Parse
関数は、Go言語の慣習である「ファクトリ関数」のような役割を果たします。この関数は、必要な依存性(scanner
とerr
)を引数として受け取り、パーサーの内部状態をセットアップして、最終的にASTを返します。これにより、パーサーの利用方法がよりシンプルかつ機能的になり、初期化と実行のロジックが明確に分離されます。
- 以前は、
-
val
からlit
への命名変更:val
(value)は一般的な用語ですが、lit
(literal)はトークンの具体的な文字列表現を指すため、より正確な意味合いを伝えます。このような命名の改善は、コードの意図を明確にし、将来の開発者がコードを理解しやすくするために重要です。
これらの変更は、Goコンパイラの初期段階における重要なアーキテクチャ上の決定であり、その後のGo言語の進化と安定性に大きく貢献しました。
関連リンク
- Go言語の公式ドキュメント(現在のバージョン): https://go.dev/doc/
- Go言語の
go/token
パッケージ: https://pkg.go.dev/go/token - Go言語の
go/scanner
パッケージ: https://pkg.go.dev/go/scanner - Go言語の
go/parser
パッケージ: https://pkg.go.dev/go/parser - Go言語の
go/ast
パッケージ: https://pkg.go.dev/go/ast
(注: 上記のリンクは現在のGo言語のパッケージドキュメントですが、このコミットが行われた2009年当時のAPIとは異なる可能性があります。しかし、基本的な概念は共通しています。)
参考にした情報源リンク
- Go言語のソースコード(GitHubリポジトリ): https://github.com/golang/go
- Go言語のコミット履歴: https://github.com/golang/go/commits/master
- コンパイラの設計に関する一般的な情報源(例: Dragon Book - Compilers: Principles, Techniques, and Tools)
- Go言語の初期の設計に関する議論やメーリングリストのアーカイブ(もし公開されていれば)
- Go言語の公式ブログ(初期の発表など)
(今回の解説は、主に提供されたコミット差分とGo言語のコンパイラ設計に関する一般的な知識に基づいて作成されました。特定の外部情報源への直接的な参照は行っていません。)