[インデックス 1819] ファイルの概要
このコミットは、Go言語のパーサーとプリティプリンター(コード整形ツール)におけるコメントの取り扱いと、抽象構文木(AST)へのコメントの関連付けを大幅に改善するものです。特に、宣言にコメントを正確に紐付け、それらをASTを通じて利用可能にし、インターフェースの出力機能を強化することに焦点を当てています。
コミット
このコミットは、Go言語のツールチェインの一部であるpretty
パッケージ(Goソースコードを整形・表示するための実験的なツール)に対する大規模な変更を含んでいます。主な目的は、ソースコード内のコメントをより正確に解析し、ASTノードに結びつけ、その情報を利用して整形された出力を生成することです。これにより、コードの可読性を高め、自動生成されるドキュメントの品質を向上させる基盤が築かれています。
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/e06a654ce17c3e03acadc220cfda908581bee00f
元コミット内容
daily snapshot:
- correctly associate comments with declarations
(available through AST)
- very raw printing of interface
- much more functionality, now needs some formatting, sorting, etc.
R=r
OCL=26213
CL=26213
変更の背景
Go言語の初期開発段階において、コンパイラやツールは急速に進化していました。このコミットが行われた2009年3月時点では、Go言語の仕様はまだ固まっておらず、ツールの機能も発展途上でした。特に、ソースコードの解析においてコメントを単なる無視すべき要素としてではなく、意味のある情報として扱う必要性が認識され始めていました。
従来のパーサーでは、コメントは通常、字句解析(lexing)の段階で破棄されるか、ASTとは独立した形で収集されることが多かったです。しかし、Go言語では、宣言の直前にあるコメントがその宣言のドキュメントとして機能するという慣習があり、これをツールが理解し、利用できるようにすることが重要でした。このコミットは、そのための基盤を構築し、コメントをASTの一部として扱うことで、よりリッチなコード分析やドキュメント生成を可能にすることを目的としています。
また、pretty
パッケージは、Goコードの整形だけでなく、パッケージの公開インターフェース(エクスポートされた型、関数、変数など)を抽出して表示する機能も持っていました。この機能もまだ初期段階であり、より洗練された出力と、コメント情報との連携が求められていました。
前提知識の解説
このコミットの変更内容を理解するためには、以下の概念についての知識が役立ちます。
-
字句解析(Lexical Analysis)と構文解析(Parsing):
- 字句解析: ソースコードをトークン(キーワード、識別子、演算子、リテラルなど)のストリームに変換するプロセスです。コメントは通常、この段階で識別されます。
- 構文解析: トークンのストリームを解析し、言語の文法規則に従って抽象構文木(AST)を構築するプロセスです。
-
抽象構文木(Abstract Syntax Tree, AST):
- ソースコードの構造を木構造で表現したものです。各ノードは、宣言、式、文などのプログラムの構成要素に対応します。
- ASTは、コンパイラ、リンター、コード整形ツール、IDEなど、多くの開発ツールでコードを理解し操作するための中心的なデータ構造として使用されます。
-
Go言語のコメント規則:
- Go言語では、
//
(行コメント)と/* ... */
(ブロックコメント)の2種類のコメントがあります。 - 特に、宣言(変数、定数、型、関数など)の直前にあるコメントは、その宣言のドキュメントとして扱われることが一般的です。
go doc
コマンドやgodoc
ツールは、この慣習に基づいてドキュメントを生成します。
- Go言語では、
-
vector
パッケージ:- Go言語の初期には、標準ライブラリにジェネリックなデータ構造が少なかったため、
vector
のような動的配列を模倣したパッケージが使われることがありました。これは、現在の[]T
(スライス)が提供する機能に相当します。
- Go言語の初期には、標準ライブラリにジェネリックなデータ構造が少なかったため、
-
scanner
パッケージ:- Go言語の字句解析器(スキャナー)を提供するパッケージです。ソースコードを読み込み、トークンとそれらの位置情報(行番号、列番号、オフセット)を生成します。
-
tabwriter
パッケージ:- Go言語の標準ライブラリの一部で、テキストをタブで整形して出力するためのライターです。列を揃える際に便利です。
技術的詳細
このコミットの技術的な核心は、コメントをASTに統合し、その情報をパーサーとプリンターの両方で活用することにあります。
ASTの変更 (usr/gri/pretty/ast.go
)
Comment
構造体の導入: コメントのテキストと、そのコメントが終了する行番号(複数行コメントの場合に重要)を保持するComment
構造体が定義されました。CommentGroup
構造体の導入: 連続するコメントのシーケンスを表すCommentGroup
が導入されました。これは、間に他のトークンや空行がないコメントのまとまりを意味します。Go言語のドキュメンテーションコメントは、しばしばこのようなグループとして扱われます。- 宣言への
CommentGroup
の追加:Field
(構造体フィールド、関数パラメータ/結果)ConstDecl
(定数宣言)TypeDecl
(型宣言)VarDecl
(変数宣言)FuncDecl
(関数宣言) これらのASTノードにCommentGroup
型のフィールドが追加されました。これにより、パーサーはコメントを読み取った際に、そのコメントがどの宣言に属するかを判断し、ASTノードに直接関連付けることができるようになります。
Program
構造体の変更: パッケージ全体のコメントを保持するために、Program
構造体もCommentGroup
と[]CommentGroup
を持つように変更されました。
パーサーの変更 (usr/gri/pretty/parser.go
)
- コメント収集ロジックの改善:
Parser
構造体にcomments vector.Vector
とlast_comment ast.CommentGroup
が追加され、コメントの収集と一時的な保持がより構造化されました。getComment()
,getCommentGroup()
,getLastComment()
といったヘルパー関数が導入され、個々のコメントやコメントグループを効率的に取得・処理できるようになりました。特にgetCommentGroup()
は、連続するコメントを一つのグループとしてまとめるロジックを実装しています。next()
関数が変更され、トークンを読み進める際にコメントを検出し、last_comment
に格納するようになりました。これにより、次に現れる宣言にコメントを関連付ける準備ができます。
- 宣言へのコメントの関連付け:
parseFieldDecl
,parseConstSpec
,parseTypeSpec
,parseVarSpec
,parseDecl
,parseFunctionDecl
といった宣言を解析する関数が、getLastComment()
を呼び出して直前のコメントグループを取得し、それを対応するASTノードのComment
フィールドに設定するように変更されました。
- 柔軟なパースモードの導入:
ParseProgram()
関数が削除され、代わりにParse(mode int)
という新しい関数が導入されました。この関数は、ParseEntirePackage
,ParseImportDeclsOnly
,ParsePackageClauseOnly
という定数で指定されるモードに応じて、ソースコードの異なる部分を解析できるようになりました。これにより、パーサーの再利用性と柔軟性が向上しました。
プリンターの変更 (usr/gri/pretty/printer.go
)
- コメント出力の統合:
Printer
構造体が[]ast.CommentGroup
を保持するように変更され、コメントグループ単位で処理を行うようになりました。printComment()
関数が追加され、CommentGroup
の内容をHTML形式で整形して出力するロジックが実装されました。これは、Goのドキュメンテーションコメントの慣習(空行で段落を区切るなど)を考慮しています。TaggedString
関数が大幅に修正され、コメントを文字列の出力と適切にインターリーブ(挿入)するようになりました。特に、コメントがコードのどの位置に現れるかに応じて、改行やインデントを調整するロジックが含まれています。
- エクスポートされたシンボルのフィルタリング:
isExported()
とhasExportedNames()
というヘルパー関数が追加されました。これらは、識別子(名前)がエクスポートされているか(つまり、大文字で始まるか)を判断するために使用されます。Idents()
関数がfull
引数を受け取るようになり、エクスポートされた識別子のみを印刷するオプションが追加されました。
- インターフェース出力の強化:
Interface()
関数が大幅に拡張され、ConstDecl
,TypeDecl
,VarDecl
,FuncDecl
といった様々な宣言タイプを処理できるようになりました。この関数は、エクスポートされたシンボルのみを抽出し、それらの宣言と関連するコメントをHTML形式で出力します。これにより、パッケージの公開APIドキュメントを自動生成する機能が強化されました。Program()
関数は、P.full = true
を設定し、完全なソースコードを出力するモードであることを示します。
Print
関数の変更:Print
関数の引数順序が変更され、prog.Comments
がP.Init
に渡されるようになりました。
HTMLテンプレートの変更 (usr/gri/pretty/template.html
)
- 新しいプレースホルダー(
PACKAGE_NAME
,PACKAGE_COMMENT
,PACKAGE_INTERFACE
,PACKAGE_BODY
)が導入され、プリンターが生成するHTML出力の構造がより柔軟になりました。これにより、パッケージ名、パッケージコメント、インターフェース定義、完全なソースコード本体をそれぞれ独立したセクションとして埋め込むことが可能になりました。
これらの変更は、Go言語のツールがソースコードのセマンティックな情報をより深く理解し、それに基づいてより高品質なコード整形やドキュメント生成を行うための重要なステップでした。
コアとなるコードの変更箇所
このコミットのコアとなる変更は、主に以下のファイルに集中しています。
-
usr/gri/pretty/ast.go
:Comment
とCommentGroup
構造体の定義。Field
,ConstDecl
,TypeDecl
,VarDecl
,FuncDecl
,Program
といったASTノードにCommentGroup
フィールドを追加。
// usr/gri/pretty/ast.go (抜粋) type Comment struct { Loc scanner.Location; EndLine int; // the line where the comment ends Text []byte; } type CommentGroup []*Comment // ... type ( Field struct { Idents []*Ident; Typ Expr; Tag Expr; // nil = no tag Comment CommentGroup; // 追加 }; // ... 他のDecl型にもCommentGroupが追加 ) // ... type Program struct { Loc scanner.Location; // tok is token.PACKAGE Ident *Ident; Decls []Decl; Comment CommentGroup; // 変更 Comments []CommentGroup; // 変更 }
-
usr/gri/pretty/parser.go
:- コメント収集とASTノードへの関連付けロジックの追加・変更。
Parse
関数の導入と、ParseProgram
の削除。
// usr/gri/pretty/parser.go (抜粋) type Parser struct { // ... comments vector.Vector; // 変更 last_comment ast.CommentGroup; // 追加 // ... }; // ... func (P *Parser) getComment() *ast.Comment { /* ... */ } func (P *Parser) getCommentGroup() ast.CommentGroup { /* ... */ } func (P *Parser) getLastComment() ast.CommentGroup { /* ... */ } func (P *Parser) next() { P.next0(); P.last_comment = nil; for P.tok == token.COMMENT { P.last_comment = P.getCommentGroup(); P.comments.Push(P.last_comment); } } // ... parseFieldDecl, parseConstSpec, parseTypeSpec, parseVarSpec, parseDecl, parseFunctionDecl // これらの関数内で P.getLastComment() を呼び出し、ASTノードのCommentフィールドに設定 // ... const ( ParseEntirePackage = iota; ParseImportDeclsOnly; ParsePackageClauseOnly; ) func (P *Parser) Parse(mode int) *ast.Program { /* ... */ } // 新規導入
-
usr/gri/pretty/printer.go
:- コメントの整形と出力ロジックの追加・変更。
- エクスポートされたシンボルのフィルタリングとインターフェース出力の強化。
// usr/gri/pretty/printer.go (抜粋) type Printer struct { // ... html bool; full bool; // if false, print interface only; print all otherwise // 追加 comments []ast.CommentGroup; // the list of all comments groups // 変更 cindex int; // the current comment group index cloc scanner.Location; // the position of the next comment group // 変更 // ... }; // ... func isExported(name *ast.Ident) bool { /* ... */ } // 追加 func hasExportedNames(names []*ast.Ident) bool { /* ... */ } // 追加 func stripWhiteSpace(s []byte) []byte { /* ... */ } // 追加 func cleanComment(s []byte) []byte { /* ... */ } // 追加 func (P *Printer) printComment(comment ast.CommentGroup) { /* ... */ } // 追加 func (P *Printer) Interface(p *ast.Program) { /* ... */ } // 大幅変更 func (P *Printer) Program(p *ast.Program) { /* ... */ } // 変更 func Print(writer io.Write, prog *ast.Program, html bool) { /* ... */ } // 引数順序変更、内部ロジック変更
コアとなるコードの解説
ASTの変更 (usr/gri/pretty/ast.go
)
Comment
とCommentGroup
の導入は、コメントを単なる文字列としてではなく、構造化されたデータとして扱うための基盤を築きます。特に、CommentGroup
を宣言のASTノードに直接埋め込むことで、パーサーがコメントを読み取った際に、そのコメントがどのコード要素に属するのかというセマンティックな関連付けをASTレベルで表現できるようになります。これは、後続のツール(例えば、ドキュメント生成ツールやリンター)がコメント情報を利用する上で非常に重要です。
パーサーの変更 (usr/gri/pretty/parser.go
)
パーサーの変更は、このコミットの最も複雑な部分です。
getCommentGroup()
は、Goの慣習に従い、連続するコメントを一つの論理的なグループとして扱います。これにより、例えば、複数行にわたるパッケージコメントや、関数宣言の直前にあるドキュメンテーションコメントを正しく識別できます。next()
関数内でlast_comment
を更新するロジックは、パーサーが次に処理するトークンの直前にあるコメントを「記憶」し、そのコメントを後続の宣言に紐付けるためのメカニズムです。parse*Decl
系の関数がgetLastComment()
を呼び出すことで、コメントがASTノードに「注入」されます。Parse(mode int)
の導入は、パーサーがソースコード全体を解析するだけでなく、インポート宣言のみ、あるいはパッケージ句のみといった、より限定的な情報を抽出できるようになったことを意味します。これは、依存関係解析やシンボル情報の高速な取得など、様々な用途でパーサーを再利用するための設計変更です。
プリンターの変更 (usr/gri/pretty/printer.go
)
プリンターの変更は、ASTに格納されたコメント情報を実際に利用して、整形された出力を生成する部分です。
printComment()
関数は、Goのドキュメンテーションコメントの慣習(例えば、空行が段落の区切りとなること)をHTML出力に反映させるためのロジックを含んでいます。これにより、godoc
のようなツールが生成するドキュメントに近い形式でコメントが表示されるようになります。isExported()
とhasExportedNames()
は、Go言語における「エクスポートされた(公開された)シンボル」の概念をコードで表現したものです。Goでは、識別子が大文字で始まる場合、それはパッケージ外からアクセス可能(エクスポートされている)と見なされます。Interface()
関数は、これらのエクスポートチェック関数とprintComment()
を組み合わせて、パッケージの公開APIのみを抽出し、それらの宣言と関連するドキュメンテーションコメントをHTML形式で出力します。これは、Goのドキュメント生成ツールの原型とも言える機能です。TaggedString
におけるコメントのインターリーブ処理は、コードとコメントの相対的な位置関係を保ちながら、適切に整形された出力を生成するための複雑なロジックです。これにより、コメントがコードの途中に挿入されても、その位置が維持されるようになります。
これらの変更は、Go言語のツールがソースコードの構造だけでなく、その意味論的な側面(特にドキュメンテーションコメント)をより深く理解し、利用するための重要な一歩を示しています。
関連リンク
- Go言語のASTパッケージのドキュメント (現代のGo): https://pkg.go.dev/go/ast
- Go言語のパーサーパッケージのドキュメント (現代のGo): https://pkg.go.dev/go/parser
- Go言語のプリンターパッケージのドキュメント (現代のGo): https://pkg.go.dev/go/printer
- Go言語の
go doc
コマンドについて: https://go.dev/blog/godoc
参考にした情報源リンク
- Go言語の公式リポジトリ (GitHub): https://github.com/golang/go
- Go言語の初期のコミット履歴 (GitHub): https://github.com/golang/go/commits/master?after=e06a654ce17c3e03acadc220cfda908581bee00f+34&branch=master
- Go言語の設計に関する初期の議論(Go Wikiなど、当時の情報源)
- Go Wiki: https://go.dev/wiki
- Go Blog: https://go.dev/blog
- 抽象構文木 (AST) に関する一般的な情報源 (例: Wikipedia)
- 字句解析と構文解析に関する一般的な情報源 (例: コンパイラ理論の教科書)
- Go言語のコードスタイルガイドライン (コメントの慣習について): https://go.dev/doc/effective_go#commentary