[インデックス 1927] ファイルの概要
このコミットは、Go言語の初期のコンパイラ(またはそれに類するツール)の一部である usr/gri/pretty/compilation.go
と usr/gri/pretty/parser.go
の2つのファイルに影響を与えています。
usr/gri/pretty/compilation.go
: コンパイルプロセスを管理するファイルで、ソースファイルの読み込み、エラーハンドリング、スキャナーとパーサーの初期化、AST(抽象構文木)の生成に関わるロジックが含まれています。usr/gri/pretty/parser.go
: Go言語のソースコードを解析し、ASTを構築するパーサーの主要なロジックが含まれています。特に、入力ソースの扱い方やスキャナーとの連携部分が変更されています。
コミット
commit 3f42f44227db4bdcfd5848c5089ab96795ffc06e
Author: Robert Griesemer <gri@golang.org>
Date: Tue Mar 31 13:28:01 2009 -0700
- incorporation of suggestions by rsc
R=rsc
OCL=26959
CL=26959
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/3f42f44227db4bdcfd5848c5089ab96795ffc06e
元コミット内容
- incorporation of suggestions by rsc
R=rsc
OCL=26959
CL=26959
変更の背景
このコミットの背景には、rsc
(Russ Cox) からの提案の取り込みがあります。具体的な提案内容はコミットメッセージからは読み取れませんが、コードの変更内容から推測すると、Go言語のパーサーとスキャナーの連携、およびソースコードの入力処理に関する改善が目的であったと考えられます。
主な変更点は以下の通りです。
- ソースコード入力の柔軟性向上: これまでファイルパスやバイトスライスで直接ソースコードを扱っていた部分が、
io.Reader
インターフェースを介した汎用的な入力処理に移行しています。これにより、ファイルだけでなく、ネットワークストリームやメモリ上のデータなど、様々なソースからコードを読み込めるようになります。 - スキャナーインターフェースの廃止と具体的な実装の利用: 以前は
parser
パッケージ内で抽象的なScanner
インターフェースを定義していましたが、このコミットでそれが廃止され、Go標準ライブラリのscanner
パッケージにある具体的なscanner.Scanner
型を直接利用するようになりました。これにより、コードの複雑性が減り、標準ライブラリの機能との整合性が向上します。 - エラーハンドリングの改善:
Compile
関数やParse
関数のエラー報告の仕組みが、よりGoらしいブール値による成功/失敗の通知に変わっています。
これらの変更は、Goコンパイラの初期段階における設計の洗練と、より堅牢で柔軟な基盤の構築を目指したものと推測されます。
前提知識の解説
このコミットを理解するためには、以下のGo言語の基本的な概念と、コンパイラの構成要素に関する知識が必要です。
1. Go言語の io.Reader
インターフェース
io.Reader
はGo言語の標準ライブラリ io
パッケージで定義されている最も基本的なインターフェースの一つです。
type Reader interface {
Read(p []byte) (n int, err error)
}
このインターフェースは、データを読み込むための単一の Read
メソッドを定義しています。このメソッドは、バイトスライス p
にデータを読み込み、読み込んだバイト数 n
とエラー err
を返します。
io.Reader
を実装する型は、ファイル、ネットワーク接続、メモリ上のバッファなど、様々なデータソースを表すことができます。これにより、データの読み込み元に依存しない汎用的なコードを書くことが可能になります。このコミットでは、ソースコードの読み込みを io.Reader
に抽象化することで、入力の柔軟性を高めています。
2. Go言語の scanner
パッケージ
Go言語の標準ライブラリには、字句解析(Lexical Analysis)を行うための text/scanner
パッケージ(初期のGoでは scanner
パッケージとして存在)が存在します。字句解析とは、ソースコードをトークン(キーワード、識別子、演算子、リテラルなど)のストリームに分解するプロセスです。
scanner.Scanner
は、この字句解析を行うための構造体です。通常、Init
メソッドで入力ソース(io.Reader
など)とエラーハンドラを設定し、Scan
メソッドを繰り返し呼び出すことで次のトークンを取得します。
3. AST (Abstract Syntax Tree)
AST(抽象構文木)は、プログラミング言語のソースコードの抽象的な構文構造を木構造で表現したものです。コンパイラやインタプリタにおいて、パーサーがソースコードを解析した結果として生成されます。ASTは、ソースコードの意味を表現しており、その後のセマンティック解析、最適化、コード生成などのフェーズで利用されます。Go言語では、go/ast
パッケージでASTのノードが定義されています。
4. コンパイラの構成要素(スキャナーとパーサー)
コンパイラは通常、いくつかのフェーズに分かれています。
- 字句解析 (Lexical Analysis): ソースコードをトークンに分解します。この役割を担うのが**スキャナー(またはレキサー)**です。
- 構文解析 (Syntax Analysis): トークンのストリームが言語の文法規則に従っているかを検証し、ASTを構築します。この役割を担うのがパーサーです。
このコミットは、特にこれらの初期フェーズにおける入力処理と連携の改善に焦点を当てています。
5. エラーハンドリング
Go言語では、エラーは戻り値として明示的に返されるのが一般的です。このコミットでは、Parse
関数の戻り値がエラーカウントからブール値(成功/失敗)に変更されており、よりGoらしいエラー通知のパターンに近づいています。また、errorHandler
構造体を通じて、エラーの位置情報(行番号、列番号)とメッセージを管理しています。
技術的詳細
このコミットで行われた技術的な変更は多岐にわたりますが、主要なポイントは以下の通りです。
usr/gri/pretty/compilation.go
の変更点
-
errorHandler.Init
関数のシグネチャ変更:- 変更前:
func (h *errorHandler) Init(filename string, src []byte, columns bool)
- 変更後:
func (h *errorHandler) Init(filename string, columns bool)
src []byte
パラメータが削除されました。これは、ソースコードのバイトスライスをerrorHandler
が直接保持する必要がなくなったことを意味します。エラーハンドリングはファイル名と列情報にのみ依存するようになりました。
- 変更前:
-
Compile
関数の変更:- 入力ソースの読み込み方法の変更:
- 変更前:
src, ok := Platform.ReadSourceFile(src_file);
Platform.ReadSourceFile
というプラットフォーム依存の関数でソースファイルを読み込んでいました。
- 変更後:
src, os_err := os.Open(filename, os.O_RDONLY, 0); defer src.Close();
- Go標準ライブラリの
os.Open
を使用してファイルをオープンし、io.Reader
として扱えるようになりました。これにより、プラットフォーム依存性が解消され、より標準的なGoのI/O処理に準拠します。
- Go標準ライブラリの
- 変更前:
errorHandler.Init
の呼び出し変更:- 変更前:
err.Init(src_file, src, flags.Columns);
- 変更後:
err.Init(filename, flags.Columns);
src
パラメータが削除されたことに伴う変更です。
- 変更前:
- スキャナーの初期化とパーサーへの渡し方の変更:
- 変更前:
var scanner scanner.Scanner; scanner.Init(src, &err, true); // ... prog, nerrs := parser.Parse(&scanner, &err, mode);
scanner.Scanner
を明示的に初期化し、そのポインタをparser.Parse
に渡していました。
- 変更後:
// ... prog, ok2 := parser.Parse(src, &err, mode);
parser.Parse
関数が直接io.Reader
(ここではos.Open
で開いたファイル) を受け取るように変更されました。スキャナーの初期化はparser.Parse
の内部で行われるようになりました。
- 変更前:
parser.Parse
の戻り値の変更:- 変更前:
(*ast.Package, int)
(ASTとエラー数) - 変更後:
(*ast.Package, bool)
(ASTと成功/失敗を示すブール値)- エラー数を直接返すのではなく、処理が成功したかどうかをブール値で示すようになりました。エラーの詳細は
errorHandler
を通じて取得されます。
- エラー数を直接返すのではなく、処理が成功したかどうかをブール値で示すようになりました。エラーの詳細は
- 変更前:
- 入力ソースの読み込み方法の変更:
usr/gri/pretty/parser.go
の変更点
-
Scanner
インターフェースの削除:- 変更前は
parser
パッケージ内に独自のScanner
インターフェースが定義されていましたが、これが完全に削除されました。 - これにより、
parser
はGo標準ライブラリのtext/scanner.Scanner
(またはその前身) に直接依存するようになりました。
- 変更前は
-
parser
構造体の変更:- 変更前:
scanner Scanner;
(独自のScanner
インターフェース型) - 変更後:
scanner scanner.Scanner;
(標準ライブラリのscanner.Scanner
型) - パーサーが使用するスキャナーの型が、抽象的なインターフェースから具体的な構造体に変更されました。
- 変更前:
-
Parse
関数のシグネチャと実装の変更:- 入力ソースの型変更:
- 変更前:
func Parse(scanner Scanner, err ErrorHandler, mode uint) (*ast.Package, int)
- 変更後:
func Parse(src interface{}, err ErrorHandler, mode uint) (*ast.Package, bool)
scanner Scanner
からsrc interface{}
に変更されました。これにより、Parse
関数は文字列、バイトスライス、io.Reader
など、様々な形式のソース入力を受け入れられるようになりました。
- 変更前:
readSource
ヘルパー関数の導入:Parse
関数の内部で、新しいreadSource
ヘルパー関数が導入されました。この関数はinterface{}
型のsrc
を受け取り、それをバイトスライス[]byte
に変換します。string
,[]byte
,*io.ByteBuffer
,io.Read
の各型に対応し、それぞれの形式からバイトスライスを効率的に読み込むロジックが実装されています。これにより、Parse
関数の入力の柔軟性が大幅に向上しました。
- スキャナーの内部初期化:
p.scanner.Init(data, err, mode & ParseComments != 0);
Parse
関数の内部で、readSource
で読み込んだdata
を使ってp.scanner
(標準ライブラリのscanner.Scanner
) が初期化されるようになりました。これにより、Compile
関数のような呼び出し元はスキャナーの初期化を意識する必要がなくなりました。
ParseComments
フラグの追加:mode
パラメータにParseComments
という新しいフラグが追加されました。これは、コメントをASTに含めるかどうかを制御するためのものです。
- 戻り値の変更:
- 変更前:
return pak, p.errorCount;
- 変更後:
return prog, p.scanner.ErrorCount == 0 && p.errorCount == 0;
- パーサー自身のエラー数とスキャナーのエラー数の両方が0である場合にのみ
true
を返すようになりました。これにより、より厳密な成功判定が可能になります。
- パーサー自身のエラー数とスキャナーのエラー数の両方が0である場合にのみ
- 変更前:
- 入力ソースの型変更:
その他の変更点
parser.go
のprintTrace
関数内で、デバッグ出力の際に使用されるdots
の表示ロジックが簡略化されました。parser.go
の定数定義で、PackageClauseOnly
にuint
型が明示的に指定されました。
これらの変更は、Go言語のコンパイラがよりモジュール化され、標準ライブラリの機能と密接に連携し、より柔軟な入力処理と堅牢なエラーハンドリングを実現するための重要なステップであったと言えます。特に、io.Reader
の導入は、Goの設計思想である「インターフェースによる抽象化」をコンパイラ内部にも適用した好例です。
コアとなるコードの変更箇所
usr/gri/pretty/compilation.go
--- a/usr/gri/pretty/compilation.go
+++ b/usr/gri/pretty/compilation.go
@@ -49,16 +49,14 @@ func (list ErrorList) Swap(i, j int) { list[i], list[j] = list[j], list[i]; }\n \n type errorHandler struct {\n \tfilename string;\n-\tsrc []byte;\n \tcolumns bool;\n \terrline int;\n \terrors vector.Vector;\n }\n \n \n-func (h *errorHandler) Init(filename string, src []byte, columns bool) {\n+func (h *errorHandler) Init(filename string, columns bool) {\n \th.filename = filename;\n-\th.src = src;\n \th.columns = columns;\n \th.errors.Init(0);\n }\n@@ -84,26 +82,24 @@ func (h *errorHandler) Error(pos token.Position, msg string) {\n }\n \n \n-func Compile(src_file string, flags *Flags) (*ast.Package, ErrorList) {\n-\tsrc, ok := Platform.ReadSourceFile(src_file);\n-\tif !ok {\n-\t\tprint(\"cannot open \", src_file, \"\\n\");\n+func Compile(filename string, flags *Flags) (*ast.Package, ErrorList) {\n+\tsrc, os_err := os.Open(filename, os.O_RDONLY, 0);\n+\tdefer src.Close();\n+\tif os_err != nil {\n+\t\tfmt.Printf(\"cannot open %s (%s)\\n\", filename, os_err.String());\n \t\treturn nil, nil;\n \t}\n \n \tvar err errorHandler;\n-\terr.Init(src_file, src, flags.Columns);\n+\terr.Init(filename, flags.Columns);\n \n-\tvar scanner scanner.Scanner;\n-\tscanner.Init(src, &err, true);\n-\n-\tmode := uint(0);\n+\tmode := parser.ParseComments;\n \tif flags.Verbose {\n \t\tmode |= parser.Trace;\n \t}\n-\tprog, nerrs := parser.Parse(&scanner, &err, mode);\n+\tprog, ok2 := parser.Parse(src, &err, mode);\n \n-\tif err.errors.Len() == 0 {\n+\tif ok2 {\n \t\tTypeChecker.CheckProgram(&err, prog);\n \t}\n \t
usr/gri/pretty/parser.go
--- a/usr/gri/pretty/parser.go
+++ b/usr/gri/pretty/parser.go
@@ -10,24 +10,15 @@\n package parser\n \n import (\n+\t\"ast\";\n \t\"fmt\";\n-\t\"vector\";\n+\t\"io\";\n+\t\"scanner\";\n \t\"token\";\n-\t\"ast\";\n+\t\"vector\";\n )\n \n \n-// An implementation of a Scanner must be provided to the Parser.\n-// The parser calls Scan() repeatedly until token.EOF is returned.\n-// Scan must return the current token position pos, the token value\n-// tok, and the corresponding token literal string lit; lit can be\n-// undefined/nil unless the token is a literal (tok.IsLiteral() == true).\n-//\n-type Scanner interface {\n-\tScan() (pos token.Position, tok token.Token, lit []byte);\n-}\n-\n-\n // An implementation of an ErrorHandler may be provided to the parser.\n // If a syntax error is encountered and a handler was installed, Error\n // is called with a position and an error message. The position points\n@@ -45,7 +36,7 @@ type interval struct {\n \n // The parser structure holds the parser\'s internal state.\n type parser struct {\n-\tscanner Scanner;\n+\tscanner scanner.Scanner;\n \terr ErrorHandler; // nil if no handler installed\n \terrorCount int;\n \n@@ -1847,13 +1837,14 @@ func (p *parser) parseDeclaration() ast.Decl {\n // ----------------------------------------------------------------------------\n // Packages\n \n-// A set of flags (or 0) must be provided via the mode parameter to\n-// the Parse function. They control the amount of source code parsed\n-// and other optional parser functionality.\n+// The mode parameter to the Parse function is a set of flags (or 0).\n+// They control the amount of source code parsed and other optional\n+// parser functionality.\n //\n const (\n-\tPackageClauseOnly = 1 << iota; // parsing stops after package clause\n+\tPackageClauseOnly uint = 1 << iota; // parsing stops after package clause\n \tImportsOnly; // parsing stops after import declarations\n+\tParseComments; // parse comments and add them to AST\n \tTrace; // print a trace of parsed productions\n )\n \n@@ -1914,29 +1905,57 @@ func (p *parser) parsePackage() *ast.Package {\n // ----------------------------------------------------------------------------\n // Parsing of entire programs.\n \n-// Parse invokes the Go parser. It calls the scanner\'s Scan method repeatedly\n-// to obtain a token sequence which is parsed according to Go syntax. If an\n-// error handler is provided (err != nil), it is invoked for each syntax error\n-// encountered.\n+// Parse parses a Go program.\n //\n-// Parse returns an AST and the number of syntax errors encountered. If the\n-// error count is 0, the result is the correct AST for the token sequence\n-// returned by the scanner (*). If the error count is > 0, the AST may only\n-// be constructed partially, with ast.BadX nodes representing the fragments\n-// of source code that contained syntax errors.\n+// The program source src may be provided in a variety of formats. At the\n+// moment the following types are supported: string, []byte, and io.Read.\n //\n-// The mode parameter controls the amount of source text parsed and other\n-// optional parser functionality.\n+// The ErrorHandler err, if not nil, is invoked if src cannot be read and\n+// for each syntax error found. The mode parameter controls the amount of\n+// source text parsed and other optional parser functionality.\n //\n-// (*) Note that a scanner may find lexical syntax errors but still return\n-// a legal token sequence. To be sure there are no syntax errors in the\n-// source (and not just the token sequence corresponding to the source)\n-// both the parser and scanner error count must be 0.\n+// Parse returns an AST and the boolean value true if no errors occured;\n+// it returns a partial AST (or nil if the source couldn\'t be read) and\n+// the boolean value false to indicate failure.\n+// \n+// If syntax errors were found, the AST may only be constructed partially,\n+// with ast.BadX nodes representing the fragments of erroneous source code.\n //\n-func Parse(scanner Scanner, err ErrorHandler, mode uint) (*ast.Package, int) {\n+func Parse(src interface{}, err ErrorHandler, mode uint) (*ast.Package, bool) {\n+\tdata := readSource(src, err);\n+\n \t// initialize parser state\n \tvar p parser;\n-\tp.scanner = scanner;\n+\tp.scanner.Init(data, err, mode & ParseComments != 0);\n \tp.err = err;\n \tp.mode = mode;\n \tp.trace = mode & Trace != 0; // for convenience (p.trace is used frequently)\n@@ -1944,6 +1963,6 @@ func Parse(scanner Scanner, err ErrorHandler, mode uint) (*ast.Package, int) {\n \tp.next();\n \n \t// parse program\n-\tpak := p.parsePackage();\n-\treturn pak, p.errorCount;\n+\tprog := p.parsePackage();\n+\treturn prog, p.scanner.ErrorCount == 0 && p.errorCount == 0;\n }\n```
## コアとなるコードの解説
### `usr/gri/pretty/compilation.go` の変更点
- **`errorHandler` 構造体と `Init` メソッドの変更**:
- `errorHandler` 構造体から `src []byte` フィールドが削除されました。これは、エラーハンドラがソースコードのバイトスライスを直接保持する必要がなくなったことを示します。エラー報告はファイル名と位置情報に基づいて行われるため、ソースコードの内容自体は不要になったと判断されたのでしょう。
- `Init` メソッドのシグネチャもこれに合わせて変更され、`src []byte` パラメータが削除されました。
- **`Compile` 関数の変更**:
- **ソースファイルの読み込みの標準化**: 以前は `Platform.ReadSourceFile` という抽象化された(おそらくプラットフォーム依存の)関数を使用していましたが、これをGo標準ライブラリの `os.Open` と `io.Copy` を使用する形に変更しました。これにより、Goの標準的なI/Oメカニズムに準拠し、コードの移植性と理解しやすさが向上しました。`defer src.Close()` を使用して確実にファイルを閉じるようにしています。
- **パーサーへの入力の変更**: `parser.Parse` に直接 `os.Open` で開いたファイル(`io.Reader` の一種)を渡すようになりました。これにより、`Compile` 関数内で明示的にスキャナーを初期化する必要がなくなり、パーサーが入力ソースの読み込みとスキャナーの初期化を内部で処理するようになりました。これは関心の分離とコードの簡素化に貢献します。
- **エラーチェックの変更**: `parser.Parse` の戻り値がエラー数からブール値(成功/失敗)に変わったため、`if ok2` という形で成功をチェックするようになりました。
### `usr/gri/pretty/parser.go` の変更点
- **`Scanner` インターフェースの廃止**:
- `parser` パッケージ内で独自に定義されていた `Scanner` インターフェースが削除されました。これは、Go標準ライブラリの `text/scanner` パッケージ(またはその前身)が提供する `scanner.Scanner` 型を直接利用することにしたためです。これにより、カスタムインターフェースのオーバーヘッドが削減され、標準ライブラリとの整合性が高まります。
- **`parser` 構造体の `scanner` フィールドの型変更**:
- `parser` 構造体の `scanner` フィールドの型が、独自の `Scanner` インターフェースから `scanner.Scanner` (標準ライブラリの型) に変更されました。これは上記のインターフェース廃止に伴う直接的な変更です。
- **`Parse` 関数の大幅な変更**:
- **入力ソースの汎用化 (`src interface{}` の導入)**: `Parse` 関数が `scanner Scanner` を直接受け取る代わりに、`src interface{}` を受け取るようになりました。これにより、`Parse` 関数はファイルパス、バイトスライス、`io.Reader` など、様々な形式のソースコードを柔軟に処理できるようになりました。これは、パーサーの再利用性と汎用性を大きく向上させます。
- **`readSource` ヘルパー関数の導入**: `Parse` 関数の内部で、`readSource` という新しいヘルパー関数が導入されました。この関数は `interface{}` 型の `src` を受け取り、それをバイトスライス `[]byte` に変換します。この関数が、異なる入力タイプ(`string`, `[]byte`, `*io.ByteBuffer`, `io.Read`)からの読み込みロジックをカプセル化しています。特に `io.Read` からの読み込みには `io.Copy` を使用しており、効率的なデータ転送を実現しています。
- **スキャナーの内部初期化**: `Parse` 関数内で `p.scanner.Init(data, err, mode & ParseComments != 0);` を呼び出すことで、スキャナーが内部で初期化されるようになりました。これにより、`Parse` 関数の呼び出し元は、入力ソースを渡すだけでよく、スキャナーの具体的な初期化方法を知る必要がなくなりました。
- **`ParseComments` フラグの追加**: `mode` パラメータに `ParseComments` という新しいフラグが追加されました。これは、パーサーがコメントを解析し、ASTに含めるかどうかを制御するためのものです。これにより、コメントを保持したASTを生成する機能が追加されました。
- **戻り値の変更**: `Parse` 関数の戻り値が `(*ast.Package, int)` から `(*ast.Package, bool)` に変更されました。成功を示すブール値は、パーサー自身のエラーカウントとスキャナーのエラーカウントの両方が0である場合にのみ `true` となります。これにより、より厳密なエラーチェックが可能になり、呼び出し元は単一のブール値で解析の成否を判断できるようになりました。
これらの変更は、Go言語のコンパイラがよりモジュール化され、標準ライブラリの機能と密接に連携し、より柔軟な入力処理と堅牢なエラーハンドリングを実現するための重要なステップであったと言えます。特に、`io.Reader` の導入は、Goの設計思想である「インターフェースによる抽象化」をコンパイラ内部にも適用した好例です。
## 関連リンク
- Go言語の `io` パッケージ: [https://pkg.go.dev/io](https://pkg.go.dev/io)
- Go言語の `text/scanner` パッケージ: [https://pkg.go.dev/text/scanner](https://pkg.go.dev/text/scanner)
- Go言語の `go/ast` パッケージ: [https://pkg.go.dev/go/ast](https://pkg.go.dev/go/ast)
- Go言語の `go/token` パッケージ: [https://pkg.go.dev/go/token](https://pkg.go.dev/go/token)
## 参考にした情報源リンク
- Go言語公式ドキュメント (pkg.go.dev)
- Go言語のコンパイラ/パーサーに関する一般的な知識
- コミットの差分情報 (`git diff`)
- Go言語の初期の設計に関する議論 (一般的な知識として)
- Go言語の `os` パッケージ: [https://pkg.go.dev/os](https://pkg.go.dev/os)
- Go言語の `fmt` パッケージ: [https://pkg.go.dev/fmt](https://pkg.go.dev/fmt)
- Go言語の `vector` パッケージ (初期のGoで使われていたデータ構造): [https://pkg.go.dev/container/vector](https://pkg.go.dev/container/vector) (現在は `container/list` やスライスが一般的)
- Go言語の `bytes` パッケージ (io.ByteBufferの代替): [https://pkg.go.dev/bytes](https://pkg.go.dev/bytes)
# [インデックス 1927] ファイルの概要
このコミットは、Go言語の初期のコンパイラ(またはそれに類するツール)の一部である `usr/gri/pretty/compilation.go` と `usr/gri/pretty/parser.go` の2つのファイルに影響を与えています。
- `usr/gri/pretty/compilation.go`: コンパイルプロセスを管理するファイルで、ソースファイルの読み込み、エラーハンドリング、スキャナーとパーサーの初期化、AST(抽象構文木)の生成に関わるロジックが含まれています。
- `usr/gri/pretty/parser.go`: Go言語のソースコードを解析し、ASTを構築するパーサーの主要なロジックが含まれています。特に、入力ソースの扱い方やスキャナーとの連携部分が変更されています。
## コミット
commit 3f42f44227db4bdcfd5848c5089ab96795ffc06e Author: Robert Griesemer gri@golang.org Date: Tue Mar 31 13:28:01 2009 -0700
- incorporation of suggestions by rsc
R=rsc
OCL=26959
CL=26959
## GitHub上でのコミットページへのリンク
[https://github.com/golang/go/commit/3f42f44227db4bdcfd5848c5089ab96795ffc06e](https://github.com/golang/go/commit/3f42f44227db4bdcfd5848c5089ab96795ffc06e)
## 元コミット内容
- incorporation of suggestions by rsc
R=rsc OCL=26959 CL=26959
## 変更の背景
このコミットの背景には、`rsc` (Russ Cox) からの提案の取り込みがあります。具体的な提案内容はコミットメッセージからは読み取れませんが、コードの変更内容から推測すると、Go言語のパーサーとスキャナーの連携、およびソースコードの入力処理に関する改善が目的であったと考えられます。
主な変更点は以下の通りです。
1. **ソースコード入力の柔軟性向上**: これまでファイルパスやバイトスライスで直接ソースコードを扱っていた部分が、`io.Reader` インターフェースを介した汎用的な入力処理に移行しています。これにより、ファイルだけでなく、ネットワークストリームやメモリ上のデータなど、様々なソースからコードを読み込めるようになります。
2. **スキャナーインターフェースの廃止と具体的な実装の利用**: 以前は `parser` パッケージ内で抽象的な `Scanner` インターフェースを定義していましたが、このコミットでそれが廃止され、Go標準ライブラリの `scanner` パッケージにある具体的な `scanner.Scanner` 型を直接利用するようになりました。これにより、コードの複雑性が減り、標準ライブラリの機能との整合性が向上します。
3. **エラーハンドリングの改善**: `Compile` 関数や `Parse` 関数のエラー報告の仕組みが、よりGoらしいブール値による成功/失敗の通知に変わっています。
これらの変更は、Goコンパイラの初期段階における設計の洗練と、より堅牢で柔軟な基盤の構築を目指したものと推測されます。
## 前提知識の解説
このコミットを理解するためには、以下のGo言語の基本的な概念と、コンパイラの構成要素に関する知識が必要です。
### 1. Go言語の `io.Reader` インターフェース
`io.Reader` はGo言語の標準ライブラリ `io` パッケージで定義されている最も基本的なインターフェースの一つです。
```go
type Reader interface {
Read(p []byte) (n int, err error)
}
このインターフェースは、データを読み込むための単一の Read
メソッドを定義しています。このメソッドは、バイトスライス p
にデータを読み込み、読み込んだバイト数 n
とエラー err
を返します。
io.Reader
を実装する型は、ファイル、ネットワーク接続、メモリ上のバッファなど、様々なデータソースを表すことができます。これにより、データの読み込み元に依存しない汎用的なコードを書くことが可能になります。このコミットでは、ソースコードの読み込みを io.Reader
に抽象化することで、入力の柔軟性を高めています。
2. Go言語の scanner
パッケージ
Go言語の標準ライブラリには、字句解析(Lexical Analysis)を行うための text/scanner
パッケージ(初期のGoでは scanner
パッケージとして存在)が存在します。字句解析とは、ソースコードをトークン(キーワード、識別子、演算子、リテラルなど)のストリームに分解するプロセスです。
scanner.Scanner
は、この字句解析を行うための構造体です。通常、Init
メソッドで入力ソース(io.Reader
など)とエラーハンドラを設定し、Scan
メソッドを繰り返し呼び出すことで次のトークンを取得します。
3. AST (Abstract Syntax Tree)
AST(抽象構文木)は、プログラミング言語のソースコードの抽象的な構文構造を木構造で表現したものです。コンパイラやインタプリタにおいて、パーサーがソースコードを解析した結果として生成されます。ASTは、ソースコードの意味を表現しており、その後のセマンティック解析、最適化、コード生成などのフェーズで利用されます。Go言語では、go/ast
パッケージでASTのノードが定義されています。
4. コンパイラの構成要素(スキャナーとパーサー)
コンパイラは通常、いくつかのフェーズに分かれています。
- 字句解析 (Lexical Analysis): ソースコードをトークンに分解します。この役割を担うのが**スキャナー(またはレキサー)**です。
- 構文解析 (Syntax Analysis): トークンのストリームが言語の文法規則に従っているかを検証し、ASTを構築します。この役割を担うのがパーサーです。
このコミットは、特にこれらの初期フェーズにおける入力処理と連携の改善に焦点を当てています。
5. エラーハンドリング
Go言語では、エラーは戻り値として明示的に返されるのが一般的です。このコミットでは、Parse
関数の戻り値がエラーカウントからブール値(成功/失敗)に変更されており、よりGoらしいエラー通知のパターンに近づいています。また、errorHandler
構造体を通じて、エラーの位置情報(行番号、列番号)とメッセージを管理しています。
技術的詳細
このコミットで行われた技術的な変更は多岐にわたりますが、主要なポイントは以下の通りです。
usr/gri/pretty/compilation.go
の変更点
-
errorHandler.Init
関数のシグネチャ変更:- 変更前:
func (h *errorHandler) Init(filename string, src []byte, columns bool)
- 変更後:
func (h *errorHandler) Init(filename string, columns bool)
src []byte
パラメータが削除されました。これは、ソースコードのバイトスライスをerrorHandler
が直接保持する必要がなくなったことを意味します。エラーハンドリングはファイル名と列情報にのみ依存するようになりました。
- 変更前:
-
Compile
関数の変更:- 入力ソースの読み込み方法の変更:
- 変更前:
src, ok := Platform.ReadSourceFile(src_file);
Platform.ReadSourceFile
というプラットフォーム依存の関数でソースファイルを読み込んでいました。
- 変更後:
src, os_err := os.Open(filename, os.O_RDONLY, 0); defer src.Close();
- Go標準ライブラリの
os.Open
を使用してファイルをオープンし、io.Reader
として扱えるようになりました。これにより、プラットフォーム依存性が解消され、より標準的なGoのI/O処理に準拠します。
- Go標準ライブラリの
- 変更前:
errorHandler.Init
の呼び出し変更:- 変更前:
err.Init(src_file, src, flags.Columns);
- 変更後:
err.Init(filename, flags.Columns);
src
パラメータが削除されたことに伴う変更です。
- 変更前:
- スキャナーの初期化とパーサーへの渡し方の変更:
- 変更前:
var scanner scanner.Scanner; scanner.Init(src, &err, true); // ... prog, nerrs := parser.Parse(&scanner, &err, mode);
scanner.Scanner
を明示的に初期化し、そのポインタをparser.Parse
に渡していました。
- 変更後:
// ... prog, ok2 := parser.Parse(src, &err, mode);
parser.Parse
関数が直接io.Reader
(ここではos.Open
で開いたファイル) を受け取るように変更されました。スキャナーの初期化はparser.Parse
の内部で行われるようになりました。
- 変更前:
parser.Parse
の戻り値の変更:- 変更前:
(*ast.Package, int)
(ASTとエラー数) - 変更後:
(*ast.Package, bool)
(ASTと成功/失敗を示すブール値)- エラー数を直接返すのではなく、処理が成功したかどうかをブール値で示すようになりました。エラーの詳細は
errorHandler
を通じて取得されます。
- エラー数を直接返すのではなく、処理が成功したかどうかをブール値で示すようになりました。エラーの詳細は
- 変更前:
- 入力ソースの読み込み方法の変更:
usr/gri/pretty/parser.go
の変更点
-
Scanner
インターフェースの削除:- 変更前は
parser
パッケージ内に独自のScanner
インターフェースが定義されていましたが、これが完全に削除されました。 - これにより、
parser
はGo標準ライブラリのtext/scanner
(またはその前身) に直接依存するようになりました。
- 変更前は
-
parser
構造体の変更:- 変更前:
scanner Scanner;
(独自のScanner
インターフェース型) - 変更後:
scanner scanner.Scanner;
(標準ライブラリのscanner.Scanner
型) - パーサーが使用するスキャナーの型が、抽象的なインターフェースから具体的な構造体に変更されました。
- 変更前:
-
Parse
関数のシグネチャと実装の変更:- 入力ソースの型変更:
- 変更前:
func Parse(scanner Scanner, err ErrorHandler, mode uint) (*ast.Package, int)
- 変更後:
func Parse(src interface{}, err ErrorHandler, mode uint) (*ast.Package, bool)
scanner Scanner
からsrc interface{}
に変更されました。これにより、Parse
関数は文字列、バイトスライス、io.Reader
など、様々な形式のソース入力を受け入れられるようになりました。
- 変更前:
readSource
ヘルパー関数の導入:Parse
関数の内部で、新しいreadSource
ヘルパー関数が導入されました。この関数はinterface{}
型のsrc
を受け取り、それをバイトスライス[]byte
に変換します。string
,[]byte
,*io.ByteBuffer
,io.Read
の各型に対応し、それぞれの形式からバイトスライスを効率的に読み込むロジックが実装されています。これにより、Parse
関数の入力の柔軟性が大幅に向上しました。
- スキャナーの内部初期化:
p.scanner.Init(data, err, mode & ParseComments != 0);
Parse
関数の内部で、readSource
で読み込んだdata
を使ってp.scanner
(標準ライブラリのscanner.Scanner
) が初期化されるようになりました。これにより、Parse
関数の呼び出し元は、入力ソースを渡すだけでよく、スキャナーの具体的な初期化方法を知る必要がなくなりました。
ParseComments
フラグの追加:mode
パラメータにParseComments
という新しいフラグが追加されました。これは、コメントを解析し、ASTに含めるかどうかを制御するためのものです。
- 戻り値の変更:
- 変更前:
return pak, p.errorCount;
- 変更後:
return prog, p.scanner.ErrorCount == 0 && p.errorCount == 0;
- パーサー自身のエラー数とスキャナーのエラー数の両方が0である場合にのみ
true
を返すようになりました。これにより、より厳密な成功判定が可能になります。
- パーサー自身のエラー数とスキャナーのエラー数の両方が0である場合にのみ
- 変更前:
- 入力ソースの型変更:
その他の変更点
parser.go
のprintTrace
関数内で、デバッグ出力の際に使用されるdots
の表示ロジックが簡略化されました。parser.go
の定数定義で、PackageClauseOnly
にuint
型が明示的に指定されました。
これらの変更は、Go言語のコンパイラがよりモジュール化され、標準ライブラリの機能と密接に連携し、より柔軟な入力処理と堅牢なエラーハンドリングを実現するための重要なステップであったと言えます。特に、io.Reader
の導入は、Goの設計思想である「インターフェースによる抽象化」をコンパイラ内部にも適用した好例です。
コアとなるコードの変更箇所
usr/gri/pretty/compilation.go
--- a/usr/gri/pretty/compilation.go
+++ b/usr/gri/pretty/compilation.go
@@ -49,16 +49,14 @@ func (list ErrorList) Swap(i, j int) { list[i], list[j] = list[j], list[i]; }\n
type errorHandler struct {
filename string;
-\tsrc []byte;
columns bool;
errline int;
errors vector.Vector;
}\n
-func (h *errorHandler) Init(filename string, src []byte, columns bool) {
+func (h *errorHandler) Init(filename string, columns bool) {
h.filename = filename;
-\th.src = src;
h.columns = columns;
h.errors.Init(0);
}\n@@ -84,26 +82,24 @@ func (h *errorHandler) Error(pos token.Position, msg string) {\n }\n
-func Compile(src_file string, flags *Flags) (*ast.Package, ErrorList) {
-\tsrc, ok := Platform.ReadSourceFile(src_file);\n-\tif !ok {\n-\t\tprint(\"cannot open \", src_file, \"\\n\");
+func Compile(filename string, flags *Flags) (*ast.Package, ErrorList) {
+\tsrc, os_err := os.Open(filename, os.O_RDONLY, 0);\n+\tdefer src.Close();\n+\tif os_err != nil {\n+\t\tfmt.Printf(\"cannot open %s (%s)\\n\", filename, os_err.String());\n \t\treturn nil, nil;\n \t}\n
var err errorHandler;\n-\terr.Init(src_file, src, flags.Columns);\n+\terr.Init(filename, flags.Columns);\n
-\tvar scanner scanner.Scanner;\n-\tscanner.Init(src, &err, true);\n-\n-\tmode := uint(0);\n+\tmode := parser.ParseComments;\n \tif flags.Verbose {\n \t\tmode |= parser.Trace;\n \t}\n-\tprog, nerrs := parser.Parse(&scanner, &err, mode);\n+\tprog, ok2 := parser.Parse(src, &err, mode);\n
-\tif err.errors.Len() == 0 {\n+\tif ok2 {\n \t\tTypeChecker.CheckProgram(&err, prog);\n \t}\n \t
usr/gri/pretty/parser.go
--- a/usr/gri/pretty/parser.go
+++ b/usr/gri/pretty/parser.go
@@ -10,24 +10,15 @@\n package parser\n
import (\n+\t\"ast\";\n \t\"fmt\";\n-\t\"vector\";\n+\t\"io\";\n+\t\"scanner\";\n \t\"token\";\n-\t\"ast\";\n+\t\"vector\";\n )\n
-
-// An implementation of a Scanner must be provided to the Parser.\n-// The parser calls Scan() repeatedly until token.EOF is returned.\n-// Scan must return the current token position pos, the token value\n-// tok, and the corresponding token literal string lit; lit can be\n-// undefined/nil unless the token is a literal (tok.IsLiteral() == true).\n-//\n-type Scanner interface {\n-\tScan() (pos token.Position, tok token.Token, lit []byte);\n-}\n-\n-\n // An implementation of an ErrorHandler may be provided to the parser.\n // If a syntax error is encountered and a handler was installed, Error\n // is called with a position and an error message. The position points\n@@ -45,7 +36,7 @@ type interval struct {\n
// The parser structure holds the parser\'s internal state.\n type parser struct {\n-\tscanner Scanner;\n+\tscanner scanner.Scanner;\n \terr ErrorHandler; // nil if no handler installed\n \terrorCount int;\n \n@@ -1847,13 +1837,14 @@ func (p *parser) parseDeclaration() ast.Decl {\n // ----------------------------------------------------------------------------\n // Packages\n \n-// A set of flags (or 0) must be provided via the mode parameter to\n-// the Parse function. They control the amount of source code parsed\n-// and other optional parser functionality.\n+// The mode parameter to the Parse function is a set of flags (or 0).\n+// They control the amount of source code parsed and other optional\n+// parser functionality.\n //\n const (\n-\tPackageClauseOnly = 1 << iota; // parsing stops after package clause\n+\tPackageClauseOnly uint = 1 << iota; // parsing stops after package clause\n \tImportsOnly; // parsing stops after import declarations\n+\tParseComments; // parse comments and add them to AST\n \tTrace; // print a trace of parsed productions\n )\n \n@@ -1914,29 +1905,57 @@ func (p *parser) parsePackage() *ast.Package {\n // ----------------------------------------------------------------------------\n // Parsing of entire programs.\n \n-// Parse invokes the Go parser. It calls the scanner\'s Scan method repeatedly\n-// to obtain a token sequence which is parsed according to Go syntax. If an\n-// error handler is provided (err != nil), it is invoked for each syntax error\n-// encountered.\n+// Parse parses a Go program.\n //\n-// Parse returns an AST and the number of syntax errors encountered. If the\n-// error count is 0, the result is the correct AST for the token sequence\n-// returned by the scanner (*). If the error count is > 0, the AST may only\n-// be constructed partially, with ast.BadX nodes representing the fragments\n-// of source code that contained syntax errors.\n+// The program source src may be provided in a variety of formats. At the\n+// moment the following types are supported: string, []byte, and io.Read.\n //\n-// The mode parameter controls the amount of source text parsed and other\n-// optional parser functionality.\n+// The ErrorHandler err, if not nil, is invoked if src cannot be read and\n+// for each syntax error found. The mode parameter controls the amount of\n+// source text parsed and other optional parser functionality.\n //\n-// (*) Note that a scanner may find lexical syntax errors but still return\n-// a legal token sequence. To be sure there are no syntax errors in the\n-// source (and not just the token sequence corresponding to the source)\n-// both the parser and scanner error count must be 0.\n+// Parse returns an AST and the boolean value true if no errors occured;\n+// it returns a partial AST (or nil if the source couldn\'t be read) and\n+// the boolean value false to indicate failure.\n+// \n+// If syntax errors were found, the AST may only be constructed partially,\n+// with ast.BadX nodes representing the fragments of erroneous source code.\n //\n-func Parse(scanner Scanner, err ErrorHandler, mode uint) (*ast.Package, int) {\n+func Parse(src interface{}, err ErrorHandler, mode uint) (*ast.Package, bool) {\n+\tdata := readSource(src, err);\n+\n \t// initialize parser state\n \tvar p parser;\n-\tp.scanner = scanner;\n+\tp.scanner.Init(data, err, mode & ParseComments != 0);\n \tp.err = err;\n \tp.mode = mode;\n \tp.trace = mode & Trace != 0; // for convenience (p.trace is used frequently)\n@@ -1944,6 +1963,6 @@ func Parse(scanner Scanner, err ErrorHandler, mode uint) (*ast.Package, int) {\n \tp.next();\n \n \t// parse program\n-\tpak := p.parsePackage();\n-\treturn pak, p.errorCount;\n+\tprog := p.parsePackage();\n+\treturn prog, p.scanner.ErrorCount == 0 && p.errorCount == 0;\n }\n```
## コアとなるコードの解説
### `usr/gri/pretty/compilation.go` の変更点
- **`errorHandler` 構造体と `Init` メソッドの変更**:
- `errorHandler` 構造体から `src []byte` フィールドが削除されました。これは、エラーハンドラがソースコードのバイトスライスを直接保持する必要がなくなったことを示します。エラー報告はファイル名と位置情報に基づいて行われるため、ソースコードの内容自体は不要になったと判断されたのでしょう。
- `Init` メソッドのシグネチャもこれに合わせて変更され、`src []byte` パラメータが削除されました。
- **`Compile` 関数の変更**:
- **ソースファイルの読み込みの標準化**: 以前は `Platform.ReadSourceFile` という抽象化された(おそらくプラットフォーム依存の)関数を使用していましたが、これをGo標準ライブラリの `os.Open` と `io.Copy` を使用する形に変更しました。これにより、Goの標準的なI/Oメカニズムに準拠し、コードの移植性と理解しやすさが向上しました。`defer src.Close()` を使用して確実にファイルを閉じるようにしています。
- **パーサーへの入力の変更**: `parser.Parse` に直接 `os.Open` で開いたファイル(`io.Reader` の一種)を渡すようになりました。これにより、`Compile` 関数内で明示的にスキャナーを初期化する必要がなくなり、パーサーが入力ソースの読み込みとスキャナーの初期化を内部で処理するようになりました。これは関心の分離とコードの簡素化に貢献します。
- **エラーチェックの変更**: `parser.Parse` の戻り値がエラー数からブール値(成功/失敗)に変わったため、`if ok2` という形で成功をチェックするようになりました。
### `usr/gri/pretty/parser.go` の変更点
- **`Scanner` インターフェースの廃止**:
- `parser` パッケージ内で独自に定義されていた `Scanner` インターフェースが削除されました。これは、Go標準ライブラリの `text/scanner` パッケージ(またはその前身)が提供する `scanner.Scanner` 型を直接利用することにしたためです。これにより、カスタムインターフェースのオーバーヘッドが削減され、標準ライブラリとの整合性が高まります。
- **`parser` 構造体の `scanner` フィールドの型変更**:
- `parser` 構造体の `scanner` フィールドの型が、独自の `Scanner` インターフェースから `scanner.Scanner` (標準ライブラリの型) に変更されました。これは上記のインターフェース廃止に伴う直接的な変更です。
- **`Parse` 関数の大幅な変更**:
- **入力ソースの汎用化 (`src interface{}` の導入)**: `Parse` 関数が `scanner Scanner` を直接受け取る代わりに、`src interface{}` を受け取るようになりました。これにより、`Parse` 関数はファイルパス、バイトスライス、`io.Reader` など、様々な形式のソースコードを柔軟に処理できるようになりました。これは、パーサーの再利用性と汎用性を大きく向上させます。
- **`readSource` ヘルパー関数の導入**: `Parse` 関数の内部で、`readSource` という新しいヘルパー関数が導入されました。この関数は `interface{}` 型の `src` を受け取り、それをバイトスライス `[]byte` に変換します。この関数が、異なる入力タイプ(`string`, `[]byte`, `*io.ByteBuffer`, `io.Read`)からの読み込みロジックをカプセル化しています。特に `io.Read` からの読み込みには `io.Copy` を使用しており、効率的なデータ転送を実現しています。
- **スキャナーの内部初期化**: `Parse` 関数内で `p.scanner.Init(data, err, mode & ParseComments != 0);` を呼び出すことで、スキャナーが内部で初期化されるようになりました。これにより、`Parse` 関数の呼び出し元は、入力ソースを渡すだけでよく、スキャナーの具体的な初期化方法を知る必要がなくなりました。
- **`ParseComments` フラグの追加**: `mode` パラメータに `ParseComments` という新しいフラグが追加されました。これは、パーサーがコメントを解析し、ASTに含めるかどうかを制御するためのものです。これにより、コメントを保持したASTを生成する機能が追加されました。
- **戻り値の変更**: `Parse` 関数の戻り値が `(*ast.Package, int)` から `(*ast.Package, bool)` に変更されました。成功を示すブール値は、パーサー自身のエラーカウントとスキャナーのエラーカウントの両方が0である場合にのみ `true` となります。これにより、より厳密なエラーチェックが可能になり、呼び出し元は単一のブール値で解析の成否を判断できるようになりました。
これらの変更は、Go言語のコンパイラがよりモジュール化され、標準ライブラリの機能と密接に連携し、より柔軟な入力処理と堅牢なエラーハンドリングを実現するための重要なステップであったと言えます。特に、`io.Reader` の導入は、Goの設計思想である「インターフェースによる抽象化」をコンパイラ内部にも適用した好例です。
## 関連リンク
- Go言語の `io` パッケージ: [https://pkg.go.dev/io](https://pkg.go.dev/io)
- Go言語の `text/scanner` パッケージ: [https://pkg.go.dev/text/scanner](https://pkg.go.dev/text/scanner)
- Go言語の `go/ast` パッケージ: [https://pkg.go.dev/go/ast](https://pkg.go.dev/go/ast)
- Go言語の `go/token` パッケージ: [https://pkg.go.dev/go/token](https://pkg.go.dev/go/token)
## 参考にした情報源リンク
- Go言語公式ドキュメント (pkg.go.dev)
- Go言語のコンパイラ/パーサーに関する一般的な知識
- コミットの差分情報 (`git diff`)
- Go言語の初期の設計に関する議論 (一般的な知識として)
- Go言語の `os` パッケージ: [https://pkg.go.dev/os](https://pkg.go.dev/os)
- Go言語の `fmt` パッケージ: [https://pkg.go.dev/fmt](https://pkg.go.dev/fmt)
- Go言語の `vector` パッケージ (初期のGoで使われていたデータ構造): [https://pkg.go.dev/container/vector](https://pkg.go.dev/container/vector) (現在は `container/list` やスライスが一般的)
- Go言語の `bytes` パッケージ (io.ByteBufferの代替): [https://pkg.go.dev/bytes](https://pkg.go.dev/bytes)