[インデックス 1253] ファイルの概要
このコミットは、Go言語の初期のpretty-printer(コード整形ツール)において、コメントの出力精度と全体的なコード整形品質を向上させるための重要な変更を導入しています。具体的には、AST(抽象構文木)ノードに宣言、ブロック、パラメータリストなどの「終端位置」情報を追加し、その情報を用いてコメントをより正確な位置に配置できるようにパーサーとプリンターを改良しています。また、低レベルの出力ルーチンが微調整され、デバッグサポートも強化されています。
コミット
commit d79f687ed8a94dae7d15c4e4622a770eb0373fad
Author: Robert Griesemer <gri@golang.org>
Date: Wed Nov 26 17:07:45 2008 -0800
- collect addition source position information in parser
for end of declarations, blocks, parameter lists, etc.
- use extra src positions to more accurately print comments
- fine-tuned low-level printing routine for comments
- added better debugging support
Status:
- comments now appear at the right place (inbetween the right tokens)
- newline control needs improvement (not very hard)
- comment printing disabled for now because pretty is not idempotent
with it; to enable: -comments
R=r
OCL=20079
CL=20079
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/d79f687ed8a94dae7d15c4e4622a770eb0373fad
元コミット内容
- collect addition source position information in parser
for end of declarations, blocks, parameter lists, etc.
- use extra src positions to more accurately print comments
- fine-tuned low-level printing routine for comments
- added better debugging support
Status:
- comments now appear at the right place (inbetween the right tokens)
- newline control needs improvement (not very hard)
- comment printing disabled for now because pretty is not idempotent
with it; to enable: -comments
変更の背景
このコミットが行われた2008年11月は、Go言語がまだ一般に公開される前の非常に初期の段階でした。コンパイラやツールチェーンの開発が進む中で、ソースコードの「整形(pretty printing)」は重要な機能の一つです。特に、ソースコードに含まれるコメントは、プログラムの動作には影響しませんが、人間がコードを理解する上で不可欠な要素です。
従来のpretty-printerでは、コメントが元のソースコードのどこに位置していたかという情報が不足しているか、あるいは不正確であったため、整形後のコードでコメントが意図しない場所に移動したり、失われたりする問題がありました。これは、コードの可読性を著しく損なうだけでなく、開発者がコメントを記述するモチベーションを低下させる要因にもなります。
このコミットの主な背景は、以下の課題を解決することにありました。
- コメントの正確な配置: コメントを元のソースコードにおける意味的な位置(例えば、特定の変数宣言の直後、ブロックの開始前など)に正確に再配置すること。
- 整形品質の向上: コメントだけでなく、宣言、ブロック、パラメータリストなどの構文要素の終端位置を正確に把握することで、より自然で読みやすいコード整形を実現すること。
- デバッグの容易化: 整形処理自体のデバッグを容易にするためのサポートを追加すること。
これらの改善は、Go言語のツールチェーンが提供する開発体験の質を高める上で不可欠なステップでした。
前提知識の解説
このコミットを理解するためには、以下の概念について基本的な知識が必要です。
-
抽象構文木 (Abstract Syntax Tree, AST):
- ソースコードを解析(パース)した結果として生成される、プログラムの構造を木構造で表現したものです。
- ASTは、コンパイラやインタープリタがコードの意味を理解し、最適化やコード生成を行うための中心的なデータ構造となります。
- 各ノードは、変数宣言、関数呼び出し、演算子などのプログラムの構成要素を表します。
-
パーサー (Parser):
- ソースコードを読み込み、その文法構造を解析してASTを構築するコンポーネントです。
- 字句解析器(Lexer/Scanner)が生成したトークン列を入力として受け取り、文法規則に従ってASTノードを組み立てます。
- このコミットでは、パーサーがASTノードを構築する際に、各構文要素の「終端位置」という追加の情報を収集するように変更されています。
-
プリティプリンター (Pretty Printer):
- ASTを入力として受け取り、整形されたソースコードを出力するツールです。
- 単にコードを再出力するだけでなく、一貫したインデント、空白、改行、そしてコメントの配置など、読みやすい形式に整形する役割を担います。
- コメントはプログラムの実行には影響しないため、多くのパーサーはコメントをASTに含めないか、含めてもその位置情報が曖昧な場合があります。そのため、コメントを正確に再配置することはpretty-printerにとって難しい課題の一つです。
-
ソース位置情報 (Source Position Information):
- ソースコード内の特定の要素(トークン、式、宣言など)が、元のファイル内のどの行、どの列から始まり、どこで終わるかを示す情報です。
- エラーメッセージの表示、デバッグ、そしてこのコミットのようにコード整形において、コメントや空白を元のコードの意図に近い形で再現するために非常に重要です。
-
冪等性 (Idempotence):
- ある操作を複数回適用しても、1回適用した場合と同じ結果が得られる性質を指します。
- pretty-printerの文脈では、「整形されたコードを再度整形しても、結果が変わらない」という性質が理想的です。このコミットの
Status
セクションには、「コメントの出力はまだ冪等ではない」と明記されており、今後の課題として認識されています。
-
Go言語の初期の構文:
- このコミットは2008年のものであり、現在のGo言語の構文とは異なる部分が見られます(例:
export type
,array.Array
の使用)。これは、Go言語がまだ活発に開発され、言語仕様が固まる前の段階であったことを示しています。
- このコミットは2008年のものであり、現在のGo言語の構文とは異なる部分が見られます(例:
技術的詳細
このコミットは、主にast.go
、parser.go
、printer.go
の3つのファイルにわたる変更を通じて、ソースコードの整形、特にコメントの扱いを改善しています。
1. ASTノードへの終端位置情報の追加 (ast.go
)
usr/gri/pretty/ast.go
では、ASTの主要な構造体(Expr
, Type
, Stat
, Decl
)にend int
フィールドが追加されました。このend
フィールドは、対応する構文要素がソースコード内で終了する位置(バイトオフセットまたは文字オフセット)を記録するために使用されます。
Expr
(式):block *array.Array; end int;
Type
(型):list *array.Array; end int;
Stat
(文):block *array.Array; end int;
Decl
(宣言):list *array.Array; end int;
これにより、パーサーが構文要素を解析する際に、その開始位置だけでなく終了位置も正確にASTに格納できるようになります。この終端位置情報は、コメントを適切な位置に挿入するために不可欠です。
2. パーサーでの終端位置情報の収集 (parser.go
)
usr/gri/pretty/parser.go
では、様々な構文要素を解析するメソッドが変更され、新しく追加されたend
フィールドに終端位置を代入するようになりました。P.pos
は現在のパーサーの読み取り位置を示しており、構文要素の閉じ括弧やキーワードの直後などに設定されます。
ParseParameters
,ParseResult
,ParseFunctionType
,ParseInterfaceType
,ParseStructType
: 型定義や関数シグネチャの終端にt.end = P.pos;
を追加。ParseBlock
: ブロックの解析結果として、ステートメントリストだけでなく、ブロックの終端位置も返すように変更 (return slist, end;
)。これに伴い、ParseFunctionLit
,ParseIfStat
,ParseForStat
,ParseRangeStat
,ParseStatement
などのブロックを扱うメソッドも、x.block, x.end = P.ParseBlock();
のように終端位置を受け取るように変更。ParseDecl
: 宣言の終端にd.end = P.pos;
を追加。
これらの変更により、ASTはより豊富な位置情報を持つようになり、pretty-printerがコメントや空白をより正確に扱うための基盤が整いました。
3. プリティプリンターの抜本的な改良 (printer.go
)
usr/gri/pretty/printer.go
は、このコミットで最も大きく変更されたファイルです。コメントの出力ロジックが全面的に見直され、より柔軟な整形制御のための新しい状態管理が導入されました。
-
デバッグフラグの追加:
var (debug = flag.Bool("debug", false, nil, "print debugging information"); ...)
debug
フラグが追加され、整形処理中にデバッグ情報を出力できるようになりました。これにより、整形ロジックの挙動を詳細に追跡し、問題の特定が容易になります。
-
セパレータと状態の導入:
const (none = iota; blank; tab; comma; semicolon;)
:separator
という新しいフィールドがPrinter
構造体に追加され、出力すべき保留中の区切り文字(空白、タブ、カンマ、セミコロン)を管理します。これにより、区切り文字の出力タイミングをより細かく制御できます。const (inline = iota; lineend; funcend;)
:state
という新しいフィールドがPrinter
構造体に追加され、現在の出力状態(インライン、行末、関数末尾)を管理します。これにより、改行や追加の空白の挿入を、文脈に応じて調整できるようになります。
-
String
メソッドの刷新:String(pos int, s string)
メソッドは、文字列s
を出力する前に、保留中のセパレータとコメントを処理するようになりました。- セパレータの処理:
P.separator
の値に応じて、空白、タブ、カンマ、セミコロンを適切に出力します。 - コメントのインターリーブ:
P.cpos
(現在のコメント位置)とpos
(出力しようとしているトークンの位置)を比較し、その間に存在するコメントを挿入するロジックが大幅に強化されました。- 改行コメント(
//
スタイル)やブロックコメント(/* */
スタイル)を、元のソースコードでの位置関係を考慮して出力します。 - コメントの前に改行が必要か、インデントが必要か、空白が必要かなどを、
nlcount
(ソースコード中の改行数)やtrailing_blank
,trailing_tab
(直前の出力が空白/タブだったか)などの情報に基づいて判断します。 debug.BVal()
が有効な場合、コメントやトークンの出力時にそのソース位置を表示するようになりました。
- 改行コメント(
-
Newline
メソッドの追加:Newline()
メソッドが追加され、改行と現在のインデントレベルに応じたタブを出力する処理をカプセル化しました。
-
Separator
メソッドの追加:Separator(separator int)
メソッドが追加され、保留中のセパレータを設定し、必要に応じてString
メソッドを呼び出して即座に処理します。
-
スコープ関連メソッドの変更:
OpenScope
はP.state = lineend;
を設定し、スコープ開始後に改行を促します。CloseScope(pos int, paren string)
は、閉じ括弧の出力時に終端位置pos
を受け取るようになり、より正確なコメント配置に寄与します。
-
その他のメソッドの変更:
Fields
,Type
,Expr1
,Block
,ControlClause
,Stat
,Declaration
,Program
など、多くの整形関連メソッドが、新しいseparator
とstate
フィールドを活用するように変更されました。これにより、各構文要素の出力前後に適切な空白や改行が挿入されるよう、より細かく制御できるようになりました。- 特に、
P.semi
(セミコロンの保留)やP.newl
(改行の保留)といった古い状態管理が、より汎用的なP.separator
とP.state
に置き換えられています。
4. テストファイルの変更 (selftest2.go
)
usr/gri/pretty/selftest2.go
は、pretty-printerのテストケースとして使用されるファイルです。
- 新しい関数
f0
が追加され、if
文とコメントを含む簡単なコードがテスト対象として追加されました。 - 既存の
var x int;
の行に// declare x
というコメントが追加され、コメントの整形が正しく行われるかを確認するためのテストケースが強化されました。
これらの変更は、Go言語のpretty-printerが、単にコードを整形するだけでなく、コメントという非コード要素を元の意図通りに保持し、出力する能力を大幅に向上させるための重要なステップでした。ただし、コミットメッセージにあるように、コメントの出力がまだ「冪等ではない」という課題も残されています。
コアとなるコードの変更箇所
このコミットのコアとなる変更は、主に以下のファイルとセクションに集中しています。
-
usr/gri/pretty/ast.go
:Expr
構造体: 39行目- block *array.Array;
->+ block *array.Array; end int;
Type
構造体: 91行目- list *array.Array;
->+ list *array.Array; end int;
Stat
構造体: 139行目- block *array.Array;
->+ block *array.Array; end int;
Decl
構造体: 165行目- list *array.Array;
->+ list *array.Array; end int;
-
usr/gri/pretty/parser.go
:ParseParameters
関数: 402行目+ t.end = P.pos;
ParseResult
関数: 437行目+ t.end = P.pos;
ParseFunctionType
関数: 456行目+ t.end = P.pos;
ParseInterfaceType
関数: 494行目+ t.end = P.pos;
ParseStructType
関数: 539行目+ t.end = P.pos;
ParseBlock
関数: 609-616行目、戻り値と内部ロジックの変更- func (P *Parser) ParseBlock() *array.Array {
+ func (P *Parser) ParseBlock() (slist *array.Array, end int) {
+ slist = P.ParseStatementList();
+ end = P.pos;
- return s;
+ return slist, end;
ParseFunctionLit
,ParseIfStat
,ParseForStat
,ParseRangeStat
,ParseStatement
関数:ParseBlock()
の呼び出し箇所で、終端位置を受け取るように変更。ParseDecl
関数: 1420行目+ d.end = P.pos;
ParseFunctionDecl
関数: 1469行目+ d.list, d.end = P.ParseBlock();
-
usr/gri/pretty/printer.go
:debug
フラグの追加: 17行目+ var (debug = flag.Bool("debug", false, nil, "print debugging information");
Printer
構造体の変更: 44-45行目- semi bool; // pending ";"
- newl int; // pending "\n"'s
+ separator int; // pending separator
+ state int; // state info
Newline
関数の追加: 70-74行目String
関数の大幅な変更: 77-190行目。特にコメントとセパレータの処理ロジック。Separator
関数の追加: 193-196行目CloseScope
関数の変更: 210行目- func (P *Printer) CloseScope(paren string) {
->+ func (P *Printer) CloseScope(pos int, paren string) {
および内部ロジックの変更。Fields
関数の変更: 260行目- func (P *Printer) Fields(list *array.Array) {
->+ func (P *Printer) Fields(list *array.Array, end int) {
および内部ロジックの変更。Block
関数の変更: 339行目- func (P *Printer) Block(list *array.Array, indent bool);
->+ func (P *Printer) Block(list *array.Array, end int, indent bool);
および内部ロジックの変更。ControlClause
,Stat
,Declaration
,Program
など、多くの関数でP.semi
,P.newl
の使用をP.separator
,P.state
に置き換え、空白や改行の挿入ロジックを調整。
コアとなるコードの解説
ASTノードへの終端位置情報の追加 (ast.go
)
ASTノードにend int
フィールドを追加することは、この変更の根本的な基盤です。これにより、パーサーがソースコードを読み込む際に、各構文要素がどこで終わるかという情報をASTに直接埋め込むことができるようになります。この情報は、特にコメントのように、特定の構文要素の「直後」や「内部」に位置する非コード要素を正確に再配置するために不可欠です。例えば、var x int; // declare x
というコードがあった場合、var x int;
という宣言のASTノードが、その終端位置を知ることで、コメント// declare x
がその宣言に付随するものとして扱えるようになります。
パーサーでの終端位置情報の収集 (parser.go
)
パーサーの変更は、ASTに終端位置情報を「供給」する役割を担います。P.pos
はパーサーが現在処理しているトークンの開始位置を示すため、構文要素の閉じ括弧やセミコロン、ブロックの終端など、その要素が完了したと判断できる位置でP.pos
をend
フィールドに代入します。
例えば、ParseBlock()
関数がslist *array.Array, end int
を返すように変更されたことで、ブロックの開始位置だけでなく、閉じ波括弧}
の直後の位置も正確に取得できるようになりました。これにより、pretty-printerはブロックの内部や直後に存在するコメントを、より正確な文脈で処理できるようになります。
プリティプリンターの抜本的な改良 (printer.go
)
printer.go
の変更は、収集された終端位置情報を実際に活用して、整形されたコードを生成する部分です。
-
Printer
構造体の状態管理:semi
とnewl
という単純なフラグから、separator
とstate
というより洗練された状態管理への移行は、整形ロジックの柔軟性を大幅に向上させました。separator
は、次に空白、タブ、カンマ、セミコロンのいずれかを出力すべきかを保留します。これにより、例えばa, b
のようにカンマの後に空白が必要な場合や、a; b
のようにセミコロンの後に改行が必要な場合など、文脈に応じた区切り文字の挿入が可能になります。state
は、現在の出力がインライン(行の途中)、行末、関数末尾のいずれであるかを示します。これにより、例えば関数宣言の後に2つの改行を挿入する(funcend
状態)など、より複雑な改行ルールを適用できます。
-
String
メソッドのコメント処理: このメソッドは、pretty-printerの心臓部とも言える部分です。P.cpos < pos
という条件でループを回し、現在出力しようとしているトークン(s
)の開始位置(pos
)よりも前に存在するコメントをすべて処理します。- コメントがソースコード中の改行を伴っていた場合(
ctext == "\n"
)、nlcount
を増やして、その改行数を記録します。これにより、元のコードの改行の意図を反映した改行数を整形後のコードに適用できます。 - コメントの種類(
//
スタイルか/* */
スタイルか)や、そのコメントが新しい行で始まるべきか、既存の行の末尾に続くべきかなどを判断し、適切なインデントや空白を挿入してコメントを出力します。 - 特に、
//
スタイルのコメントは通常、行の残りを占めるため、その後に改行が必要であることをP.state = lineend;
で示します。
これらの変更により、pretty-printerはASTから得られる豊富な位置情報と、新しい状態管理メカニズムを組み合わせて、より正確で視覚的に優れたコード整形を実現できるようになりました。コメントが「正しい場所(トークンの間)」に表示されるようになったのは、この改良の直接的な成果です。
関連リンク
- Go言語公式ウェブサイト: https://go.dev/
- Go言語の初期のコミット履歴 (GitHub): https://github.com/golang/go/commits/master?after=d79f687ed8a94dae7d15c4e4622a770eb0373fad+1 (このコミットの直後の履歴から辿れます)
- コンパイラ設計に関する一般的な情報 (Wikipedia): https://ja.wikipedia.org/wiki/%E3%82%B3%E3%83%B3%E3%83%91%E3%82%A4%E3%83%A9
- 抽象構文木に関する一般的な情報 (Wikipedia): https://ja.wikipedia.org/wiki/%E6%8A%BD%E8%B1%A1%E6%A7%8B%E6%96%87%E6%9C%A8
参考にした情報源リンク
- Go言語のソースコード (GitHub): https://github.com/golang/go
- コンパイラ理論に関する一般的な知識
- コード整形ツール(pretty-printer)の設計に関する一般的な知識