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

[インデックス 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コードの整形だけでなく、パッケージの公開インターフェース(エクスポートされた型、関数、変数など)を抽出して表示する機能も持っていました。この機能もまだ初期段階であり、より洗練された出力と、コメント情報との連携が求められていました。

前提知識の解説

このコミットの変更内容を理解するためには、以下の概念についての知識が役立ちます。

  1. 字句解析(Lexical Analysis)と構文解析(Parsing):

    • 字句解析: ソースコードをトークン(キーワード、識別子、演算子、リテラルなど)のストリームに変換するプロセスです。コメントは通常、この段階で識別されます。
    • 構文解析: トークンのストリームを解析し、言語の文法規則に従って抽象構文木(AST)を構築するプロセスです。
  2. 抽象構文木(Abstract Syntax Tree, AST):

    • ソースコードの構造を木構造で表現したものです。各ノードは、宣言、式、文などのプログラムの構成要素に対応します。
    • ASTは、コンパイラ、リンター、コード整形ツール、IDEなど、多くの開発ツールでコードを理解し操作するための中心的なデータ構造として使用されます。
  3. Go言語のコメント規則:

    • Go言語では、//(行コメント)と/* ... */(ブロックコメント)の2種類のコメントがあります。
    • 特に、宣言(変数、定数、型、関数など)の直前にあるコメントは、その宣言のドキュメントとして扱われることが一般的です。go docコマンドやgodocツールは、この慣習に基づいてドキュメントを生成します。
  4. vectorパッケージ:

    • Go言語の初期には、標準ライブラリにジェネリックなデータ構造が少なかったため、vectorのような動的配列を模倣したパッケージが使われることがありました。これは、現在の[]T(スライス)が提供する機能に相当します。
  5. scannerパッケージ:

    • Go言語の字句解析器(スキャナー)を提供するパッケージです。ソースコードを読み込み、トークンとそれらの位置情報(行番号、列番号、オフセット)を生成します。
  6. 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.Vectorlast_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.CommentsP.Initに渡されるようになりました。

HTMLテンプレートの変更 (usr/gri/pretty/template.html)

  • 新しいプレースホルダー(PACKAGE_NAME, PACKAGE_COMMENT, PACKAGE_INTERFACE, PACKAGE_BODY)が導入され、プリンターが生成するHTML出力の構造がより柔軟になりました。これにより、パッケージ名、パッケージコメント、インターフェース定義、完全なソースコード本体をそれぞれ独立したセクションとして埋め込むことが可能になりました。

これらの変更は、Go言語のツールがソースコードのセマンティックな情報をより深く理解し、それに基づいてより高品質なコード整形やドキュメント生成を行うための重要なステップでした。

コアとなるコードの変更箇所

このコミットのコアとなる変更は、主に以下のファイルに集中しています。

  1. usr/gri/pretty/ast.go:

    • CommentCommentGroup構造体の定義。
    • 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; // 変更
    }
    
  2. 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 { /* ... */ } // 新規導入
    
  3. 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)

CommentCommentGroupの導入は、コメントを単なる文字列としてではなく、構造化されたデータとして扱うための基盤を築きます。特に、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言語のツールがソースコードの構造だけでなく、その意味論的な側面(特にドキュメンテーションコメント)をより深く理解し、利用するための重要な一歩を示しています。

関連リンク

参考にした情報源リンク