[インデックス 1898] ファイルの概要
このコミットは、Go言語の初期のコンパイラツールチェーンの一部であるprettyパッケージ内のcompilation.goとparser.goファイルに対する変更を含んでいます。parser.goはGoソースコードを解析して抽象構文木(AST)を構築するパーサーの実装であり、compilation.goはそのパーサーを利用してコンパイルプロセスの一部を担うファイルです。
コミット
このコミットは、Go言語のパーサーとスキャナーのインターフェース調整、およびパーサーコードのコメント追加とクリーンアップを目的としています。
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/34050ca8de7c6ef84fc1f829a3bb7a473723429c
元コミット内容
- adjustments to match slightly changed scanner interface
- more comments on parser, various cleanups
TBR=r
OCL=26813
CL=26813
変更の背景
このコミットが行われた2009年3月は、Go言語がまだ一般に公開される前の開発初期段階にあたります。この時期は、言語仕様やツールチェーンの設計が活発に行われており、頻繁にインターフェースの変更やコードのリファクタリングが行われていました。
このコミットの主な背景は以下の2点です。
- スキャナーインターフェースの変更への対応: Go言語の字句解析器(スキャナー)のインターフェースがわずかに変更されたため、それに合わせてパーサーがスキャナーと連携する方法を調整する必要がありました。これは、字句解析の効率化や、より柔軟なトークン処理を可能にするための変更であったと推測されます。
- パーサーの可読性向上とクリーンアップ: パーサーはコンパイラの非常に複雑な部分であり、その理解と保守を容易にするために、より詳細なコメントを追加し、コードベース全体をクリーンアップする必要がありました。これは、開発の初期段階において、コードの品質と将来の拡張性を確保するための重要なステップです。
これらの変更は、Goコンパイラの基盤を固め、より堅牢で保守しやすいコードベースを構築するための継続的な取り組みの一環として行われました。
前提知識の解説
このコミットを理解するためには、以下の概念について基本的な知識が必要です。
コンパイラの基本構造
コンパイラは、ソースコードを機械が実行できる形式に変換するプログラムです。一般的なコンパイラは、以下の主要な段階で構成されます。
- 字句解析 (Lexical Analysis): ソースコードをトークン(意味を持つ最小単位、例: 識別子、キーワード、演算子)のストリームに変換します。この処理を行うのが「スキャナー(またはレキサー)」です。
- 構文解析 (Syntax Analysis): トークンのストリームが言語の文法規則に従っているかを検証し、ソースコードの構造を抽象構文木(AST)として表現します。この処理を行うのが「パーサー」です。
- 意味解析 (Semantic Analysis): ASTに対して型チェックや名前解決などの意味的な検証を行います。
- 中間コード生成 (Intermediate Code Generation): ターゲットマシンに依存しない中間表現を生成します。
- コード最適化 (Code Optimization): 中間コードを最適化し、実行効率を向上させます。
- コード生成 (Code Generation): ターゲットマシンのアセンブリコードや機械語を生成します。
このコミットは、主に字句解析と構文解析の段階に関わっています。
Go言語のパーサーとAST (Abstract Syntax Tree)
- パーサー: Go言語のパーサーは、Goのソースコードを読み込み、その構文構造を解析します。解析の結果は、プログラムの階層的な構造を表現するデータ構造である抽象構文木(AST)として構築されます。
- AST (Abstract Syntax Tree): ASTは、ソースコードの構文構造を抽象的に表現した木構造です。各ノードは、式、文、宣言などの言語要素に対応します。ASTは、コンパイラの後の段階(意味解析、コード生成など)で利用されます。Go言語のASTは、
go/astパッケージで定義されています。
Go言語のトークンと位置情報
token.Token: Go言語の字句解析器(スキャナー)によって識別される各トークンの種類を表す型です。例えば、token.IDENT(識別子)、token.INT(整数リテラル)、token.ADD(+演算子)などがあります。token.Position: ソースコード内の位置(ファイル名、行番号、列番号)を表す構造体です。エラー報告やデバッグに利用されます。token.EOF: ファイルの終端(End Of File)を表す特別なトークンです。スキャナーがこれ以上トークンを生成できないことを示します。
Go言語のASTノードの例
このコミットのコードには、Go言語のASTを構成する様々なノードが登場します。
ast.Package: パッケージ全体を表すノード。ast.Expr: 式を表すインターフェース。ast.BadExpr: 構文エラーのある式を表す。ast.Ident: 識別子(変数名、関数名など)を表す。ast.SelectorExpr: セレクタ式(例:obj.Field)を表す。ast.TypeAssertExpr: 型アサーション(例:x.(type))を表す。ast.IndexExpr: インデックス式(例:array[index])を表す。ast.SliceExpr: スライス式(例:array[low:high])を表す。ast.CallExpr: 関数呼び出しを表す。ast.CompositeLit: 複合リテラル(例:[]int{1, 2, 3})を表す。ast.UnaryExpr: 単項演算子式(例:-x,!b)を表す。ast.BinaryExpr: 二項演算子式(例:a + b)を表す。ast.ParenExpr: 括弧で囲まれた式(例:(a + b))を表す。ast.FunctionLit: 関数リテラル(匿名関数)を表す。ast.StringLit,ast.IntLit,ast.FloatLit,ast.CharLit: 各種リテラルを表す。ast.StringList: 複数の文字列リテラルが連結されたもの。ast.Ellipsis: 可変長引数(...)を表す。ast.ArrayType: 配列型を表す。ast.ChannelType: チャネル型を表す。ast.InterfaceType: インターフェース型を表す。ast.MapType: マップ型を表す。ast.StructType: 構造体型を表す。ast.StarExpr: ポインタ型またはポインタのデリファレンスを表す。ast.FunctionType: 関数型を表す。
ast.Stmt: 文を表すインターフェース。ast.BadStmt: 構文エラーのある文を表す。ast.DeclStmt: 宣言文を表す。ast.LabeledStmt: ラベル付き文を表す。ast.AssignStmt: 代入文を表す。ast.IncDecStmt: インクリメント/デクリメント文を表す。ast.GoStmt:go文(ゴルーチン起動)を表す。ast.DeferStmt:defer文を表す。ast.ReturnStmt:return文を表す。ast.BranchStmt:break,continue,goto,fallthrough文を表す。ast.BlockStmt: ブロック文({ ... })を表す。ast.IfStmt:if文を表す。ast.SwitchStmt:switch文を表す。ast.TypeSwitchStmt: 型スイッチ文を表す。ast.SelectStmt:select文を表す。ast.ForStmt:for文を表す。ast.RangeStmt:for ... range文を表す。
ast.Decl: 宣言を表すインターフェース。ast.Field: 構造体フィールド、インターフェースメソッド、関数パラメータ/結果を表す。ast.Comment: コメントを表す。vector.Vector: Go言語の初期に利用されていた、動的配列のようなデータ構造。現在のGoの[]typeスライスに相当します。
その他の概念
ErrorHandler: パーサーが構文エラーを検出した際に呼び出されるインターフェース。エラーの報告方法を抽象化します。Traceフラグ: デバッグ目的で、パーサーの処理過程をトレース出力するためのフラグ。
技術的詳細
このコミットの技術的詳細は、主にparser.goにおけるパーサーの実装変更と、それに伴うcompilation.goの呼び出し側の修正に集約されます。
parser.goの変更点
-
parser構造体の変更:err ErrorHandler;のコメントが// nil if no handler installedに変更され、エラーハンドラがオプションであることを明示しています。errorCount int;フィールドが追加されました。これは、パーサーが検出したエラーの数を追跡するために使用されます。これにより、エラーハンドラがインストールされていない場合でも、エラーの総数を把握できるようになります。comments vector.Vector;のコメントが// list of collected, unassociated commentsに変更され、コメントがまだASTノードに関連付けられていない状態であることを示しています。pos token.Position;のコメントが// token locationから// token positionに変更され、より正確な用語を使用しています。
-
Scannerインターフェースのコメント変更:Scanメソッドのlit引数に関する説明がより明確になりました。以前は「対応するトークンリテラル文字列lit」とされていましたが、変更後は「対応するトークンリテラル文字列lit;litはトークンがリテラルである場合(tok.IsLiteral()がtrueの場合)を除き、未定義/nilでもよい」と追記され、リテラルでないトークンではlitが必須ではないことが明示されました。
-
ErrorHandlerインターフェースのコメント変更:- エラーハンドラが「提供されるべき」から「提供されてもよい」というニュアンスに変わり、エラーハンドラがオプションであることを強調しています。
Errorメソッドが「エラーハンドラがインストールされている場合」にのみ呼び出されることが明記されました。
-
Trace定数のコメント追加:Trace = 1 << iota;に// print a trace of parsed productionsというコメントが追加され、このフラグの目的が明確になりました。
-
interval構造体の移動:interval構造体の定義が、parser構造体の定義の直前に移動しました。これは、関連するデータ構造を近くに配置することで、コードの可読性を向上させるためのクリーンアップです。
-
レシーバー名の変更 (
Pからpへ):parser構造体のメソッドのレシーバー名が、慣習に従いPからpに変更されました。これはGo言語のコーディングスタイルガイドラインに沿った変更であり、コードの一貫性と可読性を向上させます。この変更は、parser.go内のほぼ全てのメソッドに影響を与えています。
-
エラー処理の改善:
errorメソッド内で、p.err != nilのチェックが追加されました。これにより、エラーハンドラがインストールされていない場合(p.errがnilの場合)でも、nilポインタ参照エラーを発生させることなく、errorCountをインクリメントできるようになりました。これは、エラーハンドラがオプションになったことと整合性を保つための重要な変更です。
-
コメント収集ロジックの改善:
getDocメソッド内で、収集したコメントをparserのコメントリストから削除するロジックが変更されました。以前は個々のコメントをnilに設定していましたが、p.comments.Cut(doc.beg, doc.end);を使用して、指定された範囲のコメントを一括で削除するように変更されました。これにより、コメント管理がより効率的かつクリーンになりました。
-
parseVarTypeメソッドの削除:parseVarTypeメソッドが削除され、その呼び出し箇所が直接parseType()に置き換えられました。これは、parseVarTypeが単にparseTypeを呼び出すだけの冗長なメソッドであったため、コードの簡素化と重複の排除を目的とした変更です。
-
parseParameterDeclとparseParameterListの修正:- これらのメソッド内で、パラメータの型を解析する際に
parseParameterType()が呼び出されるようになりました。以前はparseType()やtryParameterType()が混在していましたが、より一貫した呼び出しになりました。
- これらのメソッド内で、パラメータの型を解析する際に
compilation.goの変更点
Compile関数内で、parser.Parseの呼び出しが変更されました。- 変更前:
prog := parser.Parse(&scanner, &err, parser.ParseEntirePackage, pflags); - 変更後:
prog, nerrs := parser.Parse(&scanner, &err, parser.ParseEntirePackage, pflags); - これは、
parser.Parse関数が、解析されたプログラム(prog)に加えて、検出されたエラーの数(nerrs)を返すようにインターフェースが変更されたことを示しています。これにより、コンパイルプロセスにおいて、パーサーが返したエラーの数を直接利用できるようになりました。
- 変更前:
これらの変更は、Go言語のパーサーの堅牢性、可読性、およびエラー報告能力を向上させることを目的としています。特に、エラーハンドラがオプションになったことと、エラーカウントが明示的に返されるようになったことは、コンパイラの柔軟性と診断能力を高める上で重要です。
コアとなるコードの変更箇所
このコミットにおけるコアとなるコードの変更箇所は、主にparser.go内の以下の部分です。
-
parser構造体の定義変更:--- a/usr/gri/pretty/parser.go +++ b/usr/gri/pretty/parser.go @@ -46,24 +43,31 @@ type ErrorHandler interface { // flags (or 0) must be provided as a parameter to the Parse function. // const ( - Trace = 1 << iota; + Trace = 1 << iota; // print a trace of parsed productions ) +type interval struct { + beg, end int; +} + + // The parser structure holds the parser's internal state. type parser struct { scanner Scanner; - err ErrorHandler; + err ErrorHandler; // nil if no handler installed + errorCount int; // Tracing/debugging trace bool; indent uint; + // Comments comments vector.Vector; // list of collected, unassociated comments last_doc interval; // last comments interval of consecutive comments // The next token - pos token.Position; // token location + pos token.Position; // token position tok token.Token; // one token look-ahead lit []byte; // token literalこの変更は、
parser構造体にerrorCountフィールドを追加し、errフィールドのコメントを更新し、posフィールドのコメントを修正しています。また、interval構造体の定義が移動しています。 -
errorメソッドの変更:--- a/usr/gri/pretty/parser.go +++ b/usr/gri/pretty/parser.go @@ -195,7 +199,9 @@ func (P *parser) next() { func (P *parser) error(pos token.Position, msg string) { - P.err.Error(pos, msg); + if p.err != nil { + p.err.Error(pos, msg); + } + p.errorCount++; }この変更は、
errorメソッド内でp.errがnilでないことを確認してからエラーハンドラを呼び出すようにし、p.errorCountをインクリメントするようにしています。 -
コメント収集ロジックの変更 (
getDocメソッド):--- a/usr/gri/pretty/parser.go +++ b/usr/gri/pretty/parser.go @@ -214,10 +221,12 @@ func (P *parser) getDoc() ast.Comments { // use as documentation c := make(ast.Comments, n); for i := 0; i < n; i++ { - c[i] = P.comments.At(doc.beg + i).(*ast.Comment); - // TODO find a better way to do this - P.comments.Set(doc.beg + i, nil); // remove the comment from the general list + c[i] = p.comments.At(doc.beg + i).(*ast.Comment); } + + // remove comments from the general list + p.comments.Cut(doc.beg, doc.end); + return c; }この変更は、コメントを
vector.Vectorから削除する方法を、個別にnilを設定するのではなく、Cutメソッドを使用して一括で削除するように変更しています。 -
parseVarTypeメソッドの削除と呼び出し箇所の修正:--- a/usr/gri/pretty/parser.go +++ b/usr/gri/pretty/parser.go @@ -297,149 +306,140 @@ func (P *parser) parseExpressionList() []ast.Expr { // ---------------------------------------------------------------------------- // Types -func (P *parser) parseType() ast.Expr { - if P.trace { - defer un(trace(P, "Type")); - } - - typ := P.tryType(); - if typ == nil { - P.error(P.pos, "type expected"); - typ = &ast.BadExpr{P.pos}; - } - - return typ; -} -
- -func (P *parser) parseVarType() ast.Expr {
- if P.trace {
-
defer un(trace(P, "VarType")); - }
-
- return P.parseType(); -}
- // ... (parseChannelType, parseMapType, parseFieldDecl, parseParameterDecl, parseParameterList, parseResult, parseFunctionType, parseMethodSpec, parseInterfaceType, parseStructType, parsePointerType, tryType, parsePrimaryExpr, parseUnaryExpr, parseBinaryExpr, parseExpression, parseSimpleStmt, parseCallExpr, parseGoStmt, parseDeferStmt, parseReturnStmt, parseBranchStmt, isExpr, asExpr, parseControlClause, parseIfStmt, parseCaseClause, parseTypeCaseClause, parseSwitchStmt, parseCommClause, parseSelectStmt, parseForStmt, parseStatement, parseDeclaration, parseImportSpec, parseImportDecl, parseConstSpec, parseConstDecl, parseTypeSpec, parseTypeDecl, parseVarSpec, parseVarDecl, parseFuncDecl, parseMethodDecl, parseTopLevelDecl, parseFile, Parse)
`parseVarType`メソッドが完全に削除され、その機能は`parseType`に統合されました。これにより、コードの重複が減り、簡潔になりました。
compilation.goでのparser.Parseの呼び出し変更:--- a/usr/gri/pretty/compilation.go +++ b/usr/gri/pretty/compilation.go @@ -101,7 +101,7 @@ func Compile(src_file string, flags *Flags) (*ast.Package, ErrorList) { if flags.Verbose { pflags |= parser.Trace; } - prog := parser.Parse(&scanner, &err, parser.ParseEntirePackage, pflags); + prog, nerrs := parser.Parse(&scanner, &err, parser.ParseEntirePackage, pflags); if err.errors.Len() == 0 { TypeChecker.CheckProgram(&err, prog);parser.Parseが返す値にnerrs(エラー数)が追加されたため、呼び出し側もそれを受け取るように変更されています。
コアとなるコードの解説
parser構造体の変更とエラー処理の改善
parser構造体にerrorCount intが追加されたことは、パーサーが構文エラーを検出した際に、その数を内部的に追跡できるようになったことを意味します。これは、エラーハンドラが設定されていない場合でも、パーサーがどれだけのエラーに遭遇したかを把握するために重要です。
errorメソッド内のif p.err != nilチェックは、エラーハンドラがオプションになったことと密接に関連しています。Go言語の初期段階では、エラーハンドラが常に提供されることを前提としていた可能性がありますが、この変更により、エラーハンドラがnilであっても安全にエラーを処理できるようになりました。これにより、パーサーの利用者は、エラーを詳細に処理する必要がない場合に、エラーハンドラを提供しないという選択肢を持つことができます。
コメント収集ロジックの改善
getDocメソッドにおけるコメント削除ロジックの変更は、vector.VectorのCutメソッドを利用することで、より効率的かつ意図が明確なコードになりました。以前はループ内で個々の要素をnilに設定していましたが、Cutを使用することで、指定された範囲の要素を一度に削除できます。これは、コードの簡潔さとパフォーマンスの向上に寄与します。
parseVarTypeの削除
parseVarTypeメソッドの削除は、コードの冗長性を排除し、パーサーのロジックを簡素化する良い例です。parseVarTypeがparseTypeの単なるラッパーであったため、この変更はコードベースをよりDRY(Don't Repeat Yourself)なものにしました。
parser.Parseの戻り値の変更
compilation.goにおけるparser.Parseの呼び出し変更は、パーサーのインターフェースが進化していることを示しています。nerrsというエラー数の戻り値が追加されたことで、Compile関数は、パーサーがどれだけのエラーを検出したかを直接知ることができるようになりました。これは、コンパイルの成功/失敗の判断や、エラー報告のロジックをより柔軟に構築するために役立ちます。
全体として、これらの変更は、Go言語のパーサーが開発の初期段階において、より堅牢で、柔軟性があり、保守しやすいように進化している過程を示しています。特に、エラー処理の改善とコードのクリーンアップは、長期的なプロジェクトの健全性にとって非常に重要です。
関連リンク
- Go言語の公式ドキュメント: https://go.dev/doc/
- Go言語のASTパッケージ (
go/ast): https://pkg.go.dev/go/ast - Go言語のトークンパッケージ (
go/token): https://pkg.go.dev/go/token
参考にした情報源リンク
- GitHub: golang/go commit 34050ca8de7c6ef84fc1f829a3bb7a473723429c: https://github.com/golang/go/commit/34050ca8de7c6ef84fc1f829a3bb7a473723429c
- Go言語の初期開発に関する一般的な情報(Go言語の歴史など)
- コンパイラ設計に関する一般的な知識