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

[インデックス 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では、コメントが元のソースコードのどこに位置していたかという情報が不足しているか、あるいは不正確であったため、整形後のコードでコメントが意図しない場所に移動したり、失われたりする問題がありました。これは、コードの可読性を著しく損なうだけでなく、開発者がコメントを記述するモチベーションを低下させる要因にもなります。

このコミットの主な背景は、以下の課題を解決することにありました。

  1. コメントの正確な配置: コメントを元のソースコードにおける意味的な位置(例えば、特定の変数宣言の直後、ブロックの開始前など)に正確に再配置すること。
  2. 整形品質の向上: コメントだけでなく、宣言、ブロック、パラメータリストなどの構文要素の終端位置を正確に把握することで、より自然で読みやすいコード整形を実現すること。
  3. デバッグの容易化: 整形処理自体のデバッグを容易にするためのサポートを追加すること。

これらの改善は、Go言語のツールチェーンが提供する開発体験の質を高める上で不可欠なステップでした。

前提知識の解説

このコミットを理解するためには、以下の概念について基本的な知識が必要です。

  1. 抽象構文木 (Abstract Syntax Tree, AST):

    • ソースコードを解析(パース)した結果として生成される、プログラムの構造を木構造で表現したものです。
    • ASTは、コンパイラやインタープリタがコードの意味を理解し、最適化やコード生成を行うための中心的なデータ構造となります。
    • 各ノードは、変数宣言、関数呼び出し、演算子などのプログラムの構成要素を表します。
  2. パーサー (Parser):

    • ソースコードを読み込み、その文法構造を解析してASTを構築するコンポーネントです。
    • 字句解析器(Lexer/Scanner)が生成したトークン列を入力として受け取り、文法規則に従ってASTノードを組み立てます。
    • このコミットでは、パーサーがASTノードを構築する際に、各構文要素の「終端位置」という追加の情報を収集するように変更されています。
  3. プリティプリンター (Pretty Printer):

    • ASTを入力として受け取り、整形されたソースコードを出力するツールです。
    • 単にコードを再出力するだけでなく、一貫したインデント、空白、改行、そしてコメントの配置など、読みやすい形式に整形する役割を担います。
    • コメントはプログラムの実行には影響しないため、多くのパーサーはコメントをASTに含めないか、含めてもその位置情報が曖昧な場合があります。そのため、コメントを正確に再配置することはpretty-printerにとって難しい課題の一つです。
  4. ソース位置情報 (Source Position Information):

    • ソースコード内の特定の要素(トークン、式、宣言など)が、元のファイル内のどの行、どの列から始まり、どこで終わるかを示す情報です。
    • エラーメッセージの表示、デバッグ、そしてこのコミットのようにコード整形において、コメントや空白を元のコードの意図に近い形で再現するために非常に重要です。
  5. 冪等性 (Idempotence):

    • ある操作を複数回適用しても、1回適用した場合と同じ結果が得られる性質を指します。
    • pretty-printerの文脈では、「整形されたコードを再度整形しても、結果が変わらない」という性質が理想的です。このコミットのStatusセクションには、「コメントの出力はまだ冪等ではない」と明記されており、今後の課題として認識されています。
  6. Go言語の初期の構文:

    • このコミットは2008年のものであり、現在のGo言語の構文とは異なる部分が見られます(例: export type, array.Arrayの使用)。これは、Go言語がまだ活発に開発され、言語仕様が固まる前の段階であったことを示しています。

技術的詳細

このコミットは、主にast.goparser.goprinter.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メソッドを呼び出して即座に処理します。
  • スコープ関連メソッドの変更:

    • OpenScopeP.state = lineend;を設定し、スコープ開始後に改行を促します。
    • CloseScope(pos int, paren string)は、閉じ括弧の出力時に終端位置posを受け取るようになり、より正確なコメント配置に寄与します。
  • その他のメソッドの変更:

    • Fields, Type, Expr1, Block, ControlClause, Stat, Declaration, Programなど、多くの整形関連メソッドが、新しいseparatorstateフィールドを活用するように変更されました。これにより、各構文要素の出力前後に適切な空白や改行が挿入されるよう、より細かく制御できるようになりました。
    • 特に、P.semi(セミコロンの保留)やP.newl(改行の保留)といった古い状態管理が、より汎用的なP.separatorP.stateに置き換えられています。

4. テストファイルの変更 (selftest2.go)

usr/gri/pretty/selftest2.goは、pretty-printerのテストケースとして使用されるファイルです。

  • 新しい関数f0が追加され、if文とコメントを含む簡単なコードがテスト対象として追加されました。
  • 既存のvar x int;の行に// declare xというコメントが追加され、コメントの整形が正しく行われるかを確認するためのテストケースが強化されました。

これらの変更は、Go言語のpretty-printerが、単にコードを整形するだけでなく、コメントという非コード要素を元の意図通りに保持し、出力する能力を大幅に向上させるための重要なステップでした。ただし、コミットメッセージにあるように、コメントの出力がまだ「冪等ではない」という課題も残されています。

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

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

  1. 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;
  2. 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();
  3. 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.posendフィールドに代入します。 例えば、ParseBlock()関数がslist *array.Array, end intを返すように変更されたことで、ブロックの開始位置だけでなく、閉じ波括弧}の直後の位置も正確に取得できるようになりました。これにより、pretty-printerはブロックの内部や直後に存在するコメントを、より正確な文脈で処理できるようになります。

プリティプリンターの抜本的な改良 (printer.go)

printer.goの変更は、収集された終端位置情報を実際に活用して、整形されたコードを生成する部分です。

  • Printer構造体の状態管理: seminewlという単純なフラグから、separatorstateというより洗練された状態管理への移行は、整形ロジックの柔軟性を大幅に向上させました。

    • 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言語のソースコード (GitHub): https://github.com/golang/go
  • コンパイラ理論に関する一般的な知識
  • コード整形ツール(pretty-printer)の設計に関する一般的な知識