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

[インデックス 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実装に直接依存していました。しかし、より堅牢でモジュール化されたコンパイラを構築するためには、これらのコンポーネント間の結合度を下げ、より抽象的なインターフェースを導入する必要がありました。

具体的には、以下の点が背景にあると考えられます。

  1. モジュール性の向上: スキャナー、パーサー、ASTがそれぞれ独立した役割を持つようにし、互いの具体的な実装に依存しないようにすることで、各コンポーネントの変更が他のコンポーネントに与える影響を最小限に抑える。
  2. テスト容易性の向上: インターフェースを導入することで、パーサーのテスト時にモックのスキャナーやエラーハンドラーを簡単に差し込めるようにする。
  3. 標準化と一貫性: ソースコード内の位置情報を表す型(scanner.Location)が、より汎用的なtoken.Positionに統一されることで、コンパイラ全体での位置情報の扱いに一貫性を持たせる。
  4. Go言語の設計思想への適合: Go言語がインターフェースを重視する設計思想を持っているため、コンパイラ自身の内部構造もその思想に沿ったものにする。

これらの変更は、Goコンパイラの長期的な保守性、拡張性、および堅牢性を確保するための基盤を築くものでした。

前提知識の解説

このコミットを理解するためには、コンパイラの基本的な構造とGo言語の初期の設計に関する知識が必要です。

  1. コンパイラの基本構造:

    • 字句解析(Lexical Analysis / Scanning): ソースコードを読み込み、意味のある最小単位(トークン)に分割するプロセス。例えば、var x = 10;というコードは、var(キーワード)、x(識別子)、=(演算子)、10(整数リテラル)、;(セミコロン)といったトークンに分割されます。この処理を行うのが「スキャナー(Scanner)」です。
    • 構文解析(Syntax Analysis / Parsing): 字句解析によって生成されたトークンの並びが、言語の文法規則に合致しているかを検証し、その構造を抽象構文木(AST)として構築するプロセス。この処理を行うのが「パーサー(Parser)」です。
    • 抽象構文木(Abstract Syntax Tree - AST): ソースコードの構文構造を木構造で表現したもの。ASTは、コンパイラの後の段階(型チェック、コード生成など)で利用されます。
    • 型チェック(Type Checking): ASTを走査し、プログラムが言語の型システム規則に準拠しているかを確認するプロセス。
  2. Go言語におけるtokenscannerparserastパッケージの役割(初期段階):

    • tokenパッケージ: Go言語のキーワード、演算子、識別子、リテラルなどの「トークン」の種類を定義します。また、ソースコード内の位置情報(行番号、列番号、オフセット)を表すtoken.Position型も含まれます。
    • scannerパッケージ: ソースコードを読み込み、tokenパッケージで定義されたトークンを生成する機能を提供します。
    • parserパッケージ: scannerから受け取ったトークン列を解析し、astパッケージで定義されたASTを構築します。
    • astパッケージ: Goプログラムの抽象構文木を構成するノード(式、文、宣言など)のデータ構造を定義します。
  3. インターフェース(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が持つオフセット情報を使用するようになりました。
    • errorHandlerErrorメソッドの引数がloc scanner.Locationからpos token.Positionに変更され、内部での行番号や列番号の参照もloc.Lineからpos.Lineloc.Colからpos.Columnへと更新されました。
    • Compile関数内でパーサーを初期化する際に、parser.Initの呼び出しが削除され、新しいparser.Parse関数が使用されるようになりました。このparser.Parse関数はscannererrErrorHandlerインターフェースを満たす)を引数に取ります。
  • usr/gri/pretty/gds.go:

    • エラー表示ロジックにおいて、e.Loc.Pose.Pos.Offsetに、pos変数がoffs変数にそれぞれ変更されました。これは、ソースコード内のバイトオフセットをより正確に扱うための変更です。
  • usr/gri/pretty/printer.go:

    • nopos変数の型がscanner.Locationからtoken.Positionに変更されました。
    • Printer構造体のcloc scanner.Locationcpos token.Positionに、lastloc scanner.Locationlastpos token.Positionに変更されました。
    • hasCommentメソッドの引数と内部ロジックがloc.Posからpos.Offsetに変更されました。
    • TaggedStringStringTokenErrorなどのメソッドの引数がloc scanner.Locationからpos token.Positionに変更され、内部での位置情報の参照もloc.Posからpos.Offsetloc.Colからpos.Columnへと更新されました。
    • ASTノードのPos_フィールド(例: x.Pos_)が、Pos()メソッド(例: x.Pos())を呼び出す形式に変更されました。これは、ASTノードが自身の位置情報を返すための標準的なメソッドを持つようになったことを示唆しています。
    • ast.BinaryExprx.Tokx.Opに、ast.UnaryExprx.Tokx.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.ColP.pos.Columnに変更されました。
    • リテラル値の参照変更: P.valP.litに変更されました(例: string(P.val)からstring(P.lit))。
    • ASTノードのフィールド参照変更: ast.BinaryExprt.Tokt.Opに、ast.RangeStmtas.Tok_as.TokPosに、rhs.Tokrhs.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コンパイラの設計において非常に重要な意味を持ちます。

  1. 依存性逆転の原則(Dependency Inversion Principle - DIP)の適用:

    • 以前は、parserパッケージがscannerパッケージの具体的な実装(scanner.Scanner)に直接依存していました。このコミットにより、parserScannerインターフェースとErrorHandlerインターフェースに依存するようになりました。これにより、parserは具体的な実装から切り離され、より抽象的なレベルで動作するようになりました。
    • この原則の適用により、parserのコードはより柔軟になり、異なるスキャナー実装やエラーハンドラー実装を容易に差し替えることができるようになります。これは、例えばテスト時にモックのスキャナーを使用してパーサーの動作を検証したり、将来的に異なる入力形式(例: 別の言語の構文解析)に対応したりする際に非常に役立ちます。
  2. token.Positionによる位置情報の統一:

    • scanner.Locationは、おそらくscannerパッケージ内部でのみ使用されることを意図した型でした。しかし、ソースコード内の位置情報は、パーサー、型チェッカー、プリンターなど、コンパイラの多くのコンポーネントで必要とされます。
    • token.Positionへの統一は、これらのコンポーネント間で位置情報を一貫して共有・利用するための標準的な方法を提供します。これにより、コードの可読性と保守性が向上し、異なるコンポーネント間での位置情報の不整合を防ぐことができます。
  3. パッケージレベルのParse関数の導入:

    • 以前は、Parser構造体のInitメソッドを呼び出してパーサーを初期化し、その後Parseメソッドを呼び出すというオブジェクト指向的なアプローチが取られていました。
    • 新しいParse関数は、Go言語の慣習である「ファクトリ関数」のような役割を果たします。この関数は、必要な依存性(scannererr)を引数として受け取り、パーサーの内部状態をセットアップして、最終的にASTを返します。これにより、パーサーの利用方法がよりシンプルかつ機能的になり、初期化と実行のロジックが明確に分離されます。
  4. valからlitへの命名変更:

    • val(value)は一般的な用語ですが、lit(literal)はトークンの具体的な文字列表現を指すため、より正確な意味合いを伝えます。このような命名の改善は、コードの意図を明確にし、将来の開発者がコードを理解しやすくするために重要です。

これらの変更は、Goコンパイラの初期段階における重要なアーキテクチャ上の決定であり、その後のGo言語の進化と安定性に大きく貢献しました。

関連リンク

(注: 上記のリンクは現在の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言語のコンパイラ設計に関する一般的な知識に基づいて作成されました。特定の外部情報源への直接的な参照は行っていません。)