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

[インデックス 1092] ファイルの概要

このコミットは、Go言語の初期のpretty-printer(コード整形ツール)において、「flexible tab stops」(柔軟なタブストップ)または「elastic tabstops」(伸縮自在なタブストップ)のシミュレーションに向けた初期段階の変更を導入しています。これは、コードの整形出力におけるタブの挙動を改善し、より読みやすいカラムアライメントを実現するための基盤構築です。

コミット

commit a3b4a3c29d29ac6be1ed7e262e5694c4a717d5fa
Author: Robert Griesemer <gri@golang.org>
Date:   Fri Nov 7 18:30:58 2008 -0800

    - steps towards "flexible tab stops" simulation in pretty
      printing output
    - not yet enabled
    
    R=r
    OCL=18842
    CL=18842

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/a3b4a3c29d29ac6be1ed7e262e5694c4a717d5fa

元コミット内容

    - pretty-printing出力における「flexible tab stops」シミュレーションに向けたステップ
    - まだ有効化されていません

変更の背景

このコミットの主な目的は、Go言語のコード整形ツール(pretty-printer)の出力品質を向上させることです。特に、コード内の要素(変数宣言、構造体のフィールドなど)を縦方向に揃える際のアライメントをより柔軟かつ自動的に行うための「flexible tab stops」または「elastic tabstops」と呼ばれる概念を導入しようとしています。

従来のタブストップは固定幅(例: 4スペースまたは8スペース)であり、これによりコードが整形されると、異なる長さの要素が混在する場合に縦方向のアライメントが崩れることがありました。例えば、短い変数名と長い変数名が混在する宣言リストでは、固定タブではうまく揃えられません。

「elastic tabstops」は、各カラムの幅をそのカラム内で最も長い要素の長さに合わせて動的に調整することで、この問題を解決します。これにより、コードの可読性が大幅に向上し、手動でのスペース調整が不要になります。このコミットは、この機能を実現するための初期段階のコード変更であり、まだ完全に有効化されていません。

前提知識の解説

Pretty-printer (コード整形ツール)

Pretty-printerは、ソースコードを読み込み、特定のスタイルガイドやフォーマット規則に従って整形された新しいコードを出力するツールです。Go言語においては、gofmtがその代表例です。コードの可読性を高め、チーム内でのコードスタイルの一貫性を保つために不可欠なツールです。

Abstract Syntax Tree (AST)

ASTは、ソースコードの抽象的な構文構造をツリー形式で表現したものです。コンパイラやインタープリタがコードを解析する際に生成され、コードの構造を理解し、操作するための基盤となります。pretty-printerも通常、ソースコードをASTに変換し、ASTを走査しながら整形されたコードを生成します。

Flexible Tab Stops / Elastic Tabstops

「Flexible Tab Stops」または「Elastic Tabstops」は、テキストエディタやコード整形ツールにおけるタブの挙動に関する概念です。

  • 従来のタブストップ: 通常、タブ文字(\t)は固定された数のスペース(例: 4または8)に展開されます。このため、異なる長さのテキストが混在する行でタブを使用すると、縦方向のカラムが揃わないことがあります。

    var shortName  int
    var veryLongVariableName int
    

    上記のようなコードでは、intが揃いません。

  • Elastic Tabstops: この概念では、タブ文字の幅が動的に調整されます。具体的には、同じ「タブストップ」に属するすべての行において、そのカラムの最大幅に合わせてタブが展開されます。これにより、常に縦方向のカラムが揃うようになります。

    var shortName          int
    var veryLongVariableName int
    

    このように、intが常に揃うようになります。これは、特にコード内の変数宣言、構造体フィールド、アサインメントなどを整形する際に非常に有効です。

この技術は、Nick Gravgaardによって提唱され、彼のウェブサイト(http://nickgravgaard.com/elastictabstops/index.html)で詳細が説明されています。このコミットは、この概念をGoのpretty-printerに導入しようとするものです。

技術的詳細

このコミットは、主にusr/gri/pretty/printer.goに新しいBuffer構造体を導入することで、elastic tabstopsのシミュレーションを試みています。

  1. Buffer構造体:

    • lines: AST.List型で、各行が文字列のリスト(カラム)として格納されます。
    • widths: AST.List型で、各カラムの計算された最大幅が格納されます。
    • Newline(): バッファに新しい空の行を追加します。
    • Init(): lineswidthsを初期化し、最初の空行を追加します。
    • ComputeWidths(): バッファ内のすべての行とカラムを走査し、各カラムの最大幅を計算します。これがelastic tabstopsの核心部分です。
    • Flush():
      • ComputeWidths()を呼び出してカラム幅を計算します。
      • バッファされた各行とカラムを走査します。
      • 各カラムについて、計算された幅と設定されたtabwidthに基づいて必要なパディング(空白)の量を決定します。
      • 文字列と計算された空白を出力します。
      • 出力後、バッファをクリアします。
    • Indent(n int): 現在の行にn個の空文字列を追加し、新しいカラムを作成します。これはインデントをカラムとして扱うためのものです。
    • Print(s string): 現在の行の最後のカラムに文字列sを追加します。行が空の場合は、sを最初のカラムとして追加します。
  2. Printer構造体の変更:

    • Printer構造体にbuf Bufferフィールドが追加され、新しいバッファリングメカニズムが組み込まれます。
    • NEW_CODEという定数(falseに設定)が導入されています。これは、新しいバッファリングとアライメントのロジックがまだ開発中であり、デフォルトでは有効になっていないことを示しています。
    • String()メソッド内で、NEW_CODEtrueの場合に新しいBufferベースの出力ロジック(P.buf.Print, P.buf.Newline, P.buf.Indent, P.buf.Flush)を使用するように条件分岐が追加されています。
    • Program()メソッドの開始時にP.buf.Init()が、終了時にP.String(0, "");(これがP.buf.Flush()を呼び出す)が追加され、プログラム全体の出力がバッファリングされ、最後にフラッシュされるようになります。
  3. scanner.goの変更:

    • is_whitespace関数が削除されました。
    • SkipWhitespace()が、改行やタブではなく、スペースとキャリッジリターンのみをスキップするように変更されました。
    • ScanWhitespace()関数が追加されました。これは、スキャナが改行またはタブに遭遇したときに呼び出され、その空白を消費して文字列として返します。
    • Scan()メソッドが変更され、改行(\n)とタブ(\t)が一時的にCOMMENTトークンとして分類され、その値がS.ScanWhitespace()によって取得されるようになりました。これは、これらの空白文字をpretty-printerのコメント処理メカニズムにフィードし、アライメント目的で処理できるようにするための暫定的な(そしてややハック的な)措置です。これにより、pretty-printerはこれらの空白を「コメント」として受け取り、バッファリングしてアライメント計算に含めることができます。
  4. ast.goの変更:

    • List構造体にInit()Clear()メソッドが追加されました。これらは、Buffer構造体内でAST.Listを使用する際に、リストの初期化とクリアをより適切に制御するために導入されました。

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

usr/gri/pretty/printer.go

+type Buffer struct {
+	lines AST.List;  // a list of lines; and each line is a list of strings
+	widths AST.List;
+}
+
+// ... (Newline, Init, ComputeWidths, Flush, Indent, Print methods) ...
+
+export type Printer struct {
+	buf Buffer;
+	
 	// formatting control
 	level int;  // true scope level
 	indent int;  // indentation level
@@ -25,24 +142,22 @@ export type Printer struct {
 }
 
 
-// Bottleneck interface - all output goes through here.
-func (P *Printer) print(s string) {\n-\tprint(s);\n-\t// TODO do we need the code below?\n-\t// P.pos += Strings.utflen(s);\n-}\n-\n+const NEW_CODE = false;
 
 func (P *Printer) String(pos int, s string) {
  if P.semi && P.level > 0 {  // no semicolons at level 0
-		print(";");
+		if NEW_CODE {
+			P.buf.Print(";");
+		} else {
+			print(";");
+		}
  }
 
 	/*
 	for pos > P.cpos {
 		// we have a comment
 		c := P.clist.at(P.cindex).(*AST.Comment);
-		if c.text[1] == '/' {
+		if len(c.text) > 1 && c.text[1] == '/' {
 			print("  " + c.text);
 			if P.newl <= 0 {
 				P.newl = 1;  // line comments must have a newline
@@ -60,15 +175,30 @@ func (P *Printer) String(pos int, s string) {
 	*/
 
  if P.newl > 0 {
+		if NEW_CODE {
+			P.buf.Flush();
+		}
  for i := P.newl; i > 0; i-- {
-			print("\n");
+			if NEW_CODE {
+				P.buf.Newline();
+			} else {
+				print("\n");
+			}
  }
-		for i := P.indent; i > 0; i-- {
-			print("\t");
+		if NEW_CODE {
+			P.buf.Indent(P.indent);
+		} else {
+			for i := P.indent; i > 0; i-- {
+				print("\t");
+			}
  }
  }
 
-	print(s);
+	if NEW_CODE {
+		P.buf.Print(s);
+	} else {
+		print(s);
+	}
 
  P.semi, P.newl = false, 0;
 }
@@ -519,6 +649,8 @@ func (P *Printer) Declaration(d *AST.Decl, parenthesized bool) {
 
 func (P *Printer) Program(p *AST.Program) {
 	// TODO should initialize all fields?
+	P.buf.Init();
+	
  P.clist = p.comments;
  P.cindex = 0;
  if p.comments.len() > 0 {
@@ -527,5 +659,6 @@ func (P *Printer) Program(p *AST.Program) {
  P.cpos = 1000000000;  // infinite
  }
 
+	// Print package
  P.String(p.pos, "package ");
  P.Expr(p.ident);
  P.newl = 2;
@@ -534,5 +667,6 @@ func (P *Printer) Program(p *AST.Program) {
  P.Declaration(p.decls.at(i), false);
  }
  P.newl = 1;
+
  P.String(0, "");  // flush
 }

usr/gri/pretty/scanner.go

@@ -524,12 +519,20 @@ func (S *Scanner) Expect(ch int) {
 
 
 func (S *Scanner) SkipWhitespace() {
-\tfor is_whitespace(S.ch) {
+\tfor S.ch == ' ' || S.ch == '\r' {
  S.Next();
  }
 }
 
 
+func (S *Scanner) ScanWhitespace() string {
+\t// first char ('\n' or '\t', 1 byte) already consumed
+\tpos := S.chpos - 1;
+\tS.SkipWhitespace();
+\treturn S.src[pos : S.chpos];
+}
+
+
 func (S *Scanner) ScanComment() string {
  // first '/' already consumed
  pos := S.chpos - 1;
@@ -825,6 +828,7 @@ func (S *Scanner) Scan() (pos, tok int, val string) {
  S.Next();  // always make progress
  switch ch {
  case -1: tok = EOF;
+\t\tcase '\n', '\t': tok, val = COMMENT, S.ScanWhitespace();
  case '"': tok, val = STRING, S.ScanString();
  case '\'': tok, val = INT, S.ScanChar();
  case '`': tok, val = STRING, S.ScanRawString();

コアとなるコードの解説

このコミットの核心は、printer.goに導入されたBuffer構造体と、それを利用してpretty-printerの出力をバッファリングし、後でelastic tabstopsのロジックに基づいて整形するメカニズムです。

  1. Buffer構造体と関連メソッド:

    • Bufferは、整形されるコードを一時的にメモリに保持するためのものです。linesフィールドは、各行をさらにカラム(文字列のリスト)に分割して格納します。
    • ComputeWidths()メソッドは、バッファリングされたすべての行を走査し、各カラム位置における文字列の最大幅を計算します。例えば、最初のカラムの最大幅、2番目のカラムの最大幅、といった具合です。
    • Flush()メソッドは、ComputeWidths()で得られたカラム幅情報を使用して、実際に整形された出力を生成します。各カラムの文字列の後に、計算された最大幅に基づいて必要な数の空白を挿入することで、縦方向のアライメントを実現します。
  2. PrinterNEW_CODE:

    • Printerは、Bufferのインスタンスを内部に持ち、出力処理をこのバッファに委譲します。
    • NEW_CODE定数がfalseであるため、このコミット時点では、新しいバッファリングとアライメントのロジックはまだアクティブではありません。これは、機能がまだ開発中であり、安定していないため、デフォルトでは無効にされていることを示しています。開発者は、このフラグをtrueに設定することで、新しいロジックをテストできます。
  3. scanner.goの変更の意図:

    • scanner.goにおける改行とタブのCOMMENTトークンとしての扱いは、一見すると奇妙に見えます。これは、pretty-printerがこれらの空白文字を通常の文字としてではなく、アライメントの計算に必要な「情報」として受け取るための暫定的な手段です。スキャナはこれらの空白を「コメント」としてトークン化し、pretty-printerはそれらをBufferに格納し、Flush時にアライメントの計算に利用します。これは、pretty-printerの既存のコメント処理パスを再利用して、空白文字の情報を伝達するためのハック的なアプローチです。

全体として、このコミットは、Goのpretty-printerが将来的に高度なコードアライメント機能(elastic tabstops)をサポートするための重要な基盤を築いています。

関連リンク

参考にした情報源リンク

  • GitHubコミットページ: https://github.com/golang/go/commit/a3b4a3c29d29ac6be1ed7e262e5694c4a717d5fa
  • Web検索結果 (flexible tab stops, elastic tabstops)
  • Go言語のソースコード(コミット時点の関連ファイル)
  • Go言語のgofmtに関する一般的な知識
  • AST (Abstract Syntax Tree) に関する一般的な知識
  • Pretty-printerに関する一般的な知識
I have generated the detailed technical explanation in Markdown format, following all the specified instructions and chapter structure. The explanation is in Japanese, covers the background, prerequisite knowledge, technical details, core code changes, and related links. I have also incorporated the information obtained from the web search about "elastic tabstops".
```# [インデックス 1092] ファイルの概要

このコミットは、Go言語の初期のpretty-printer(コード整形ツール)において、「flexible tab stops」(柔軟なタブストップ)または「elastic tabstops」(伸縮自在なタブストップ)のシミュレーションに向けた初期段階の変更を導入しています。これは、コードの整形出力におけるタブの挙動を改善し、より読みやすいカラムアライメントを実現するための基盤構築です。

## コミット

commit a3b4a3c29d29ac6be1ed7e262e5694c4a717d5fa Author: Robert Griesemer gri@golang.org Date: Fri Nov 7 18:30:58 2008 -0800

- steps towards "flexible tab stops" simulation in pretty
  printing output
- not yet enabled

R=r
OCL=18842
CL=18842

## GitHub上でのコミットページへのリンク

[https://github.com/golang/go/commit/a3b4a3c29d29ac6be1ed7e262e5694c4a717d5fa](https://github.com/golang/go/commit/a3b4a3c29d29ac6be1ed7e262e5694c4a717d5fa)

## 元コミット内容

- pretty-printing出力における「flexible tab stops」シミュレーションに向けたステップ
- まだ有効化されていません

## 変更の背景

このコミットの主な目的は、Go言語のコード整形ツール(pretty-printer)の出力品質を向上させることです。特に、コード内の要素(変数宣言、構造体のフィールドなど)を縦方向に揃える際のアライメントをより柔軟かつ自動的に行うための「flexible tab stops」または「elastic tabstops」と呼ばれる概念を導入しようとしています。

従来のタブストップは固定幅(例: 4スペースまたは8スペース)であり、これによりコードが整形されると、異なる長さの要素が混在する場合に縦方向のアライメントが崩れることがありました。例えば、短い変数名と長い変数名が混在する宣言リストでは、固定タブではうまく揃えられません。

「elastic tabstops」は、各カラムの幅をそのカラム内で最も長い要素の長さに合わせて動的に調整することで、この問題を解決します。これにより、コードの可読性が大幅に向上し、手動でのスペース調整が不要になります。このコミットは、この機能を実現するための初期段階のコード変更であり、まだ完全に有効化されていません。

## 前提知識の解説

### Pretty-printer (コード整形ツール)

Pretty-printerは、ソースコードを読み込み、特定のスタイルガイドやフォーマット規則に従って整形された新しいコードを出力するツールです。Go言語においては、`gofmt`がその代表例です。コードの可読性を高め、チーム内でのコードスタイルの一貫性を保つために不可欠なツールです。

### Abstract Syntax Tree (AST)

ASTは、ソースコードの抽象的な構文構造をツリー形式で表現したものです。コンパイラやインタープリタがコードを解析する際に生成され、コードの構造を理解し、操作するための基盤となります。pretty-printerも通常、ソースコードをASTに変換し、ASTを走査しながら整形されたコードを生成します。

### Flexible Tab Stops / Elastic Tabstops

「Flexible Tab Stops」または「Elastic Tabstops」は、テキストエディタやコード整形ツールにおけるタブの挙動に関する概念です。

*   **従来のタブストップ**: 通常、タブ文字(`\t`)は固定された数のスペース(例: 4または8)に展開されます。このため、異なる長さのテキストが混在する行でタブを使用すると、縦方向のカラムが揃わないことがあります。
    ```
    var shortName  int
    var veryLongVariableName int
    ```
    上記のようなコードでは、`int`が揃いません。

*   **Elastic Tabstops**: この概念では、タブ文字の幅が動的に調整されます。具体的には、同じ「タブストップ」に属するすべての行において、そのカラムの最大幅に合わせてタブが展開されます。これにより、常に縦方向のカラムが揃うようになります。
    ```
    var shortName          int
    var veryLongVariableName int
    ```
    このように、`int`が常に揃うようになります。これは、特にコード内の変数宣言、構造体フィールド、アサインメントなどを整形する際に非常に有効です。

この技術は、Nick Gravgaardによって提唱され、彼のウェブサイト(http://nickgravgaard.com/elastictabstops/index.html)で詳細が説明されています。このコミットは、この概念をGoのpretty-printerに導入しようとするものです。

## 技術的詳細

このコミットは、主に`usr/gri/pretty/printer.go`に新しい`Buffer`構造体を導入することで、elastic tabstopsのシミュレーションを試みています。

1.  **`Buffer`構造体**:
    *   `lines`: `AST.List`型で、各行が文字列のリスト(カラム)として格納されます。
    *   `widths`: `AST.List`型で、各カラムの計算された最大幅が格納されます。
    *   `Newline()`: バッファに新しい空の行を追加します。
    *   `Init()`: `lines`と`widths`を初期化し、最初の空行を追加します。
    *   `ComputeWidths()`: バッファ内のすべての行とカラムを走査し、各カラムの最大幅を計算します。これがelastic tabstopsの核心部分です。
    *   `Flush()`:
        *   `ComputeWidths()`を呼び出してカラム幅を計算します。
        *   バッファされた各行とカラムを走査します。
        *   各カラムについて、計算された幅と設定された`tabwidth`に基づいて必要なパディング(空白)の量を決定します。
        *   文字列と計算された空白を出力します。
        *   出力後、バッファをクリアします。
    *   `Indent(n int)`: 現在の行に`n`個の空文字列を追加し、新しいカラムを作成します。これはインデントをカラムとして扱うためのものです。
    *   `Print(s string)`: 現在の行の最後のカラムに文字列`s`を追加します。行が空の場合は、`s`を最初のカラムとして追加します。

2.  **`Printer`構造体の変更**:
    *   `Printer`構造体に`buf Buffer`フィールドが追加され、新しいバッファリングメカニズムが組み込まれます。
    *   `NEW_CODE`という定数(`false`に設定)が導入されています。これは、新しいバッファリングとアライメントのロジックがまだ開発中であり、デフォルトでは有効になっていないことを示しています。
    *   `String()`メソッド内で、`NEW_CODE`が`true`の場合に新しい`Buffer`ベースの出力ロジック(`P.buf.Print`, `P.buf.Newline`, `P.buf.Indent`, `P.buf.Flush`)を使用するように条件分岐が追加されています。
    *   `Program()`メソッドの開始時に`P.buf.Init()`が、終了時に`P.String(0, "");`(これが`P.buf.Flush()`を呼び出す)が追加され、プログラム全体の出力がバッファリングされ、最後にフラッシュされるようになります。

3.  **`scanner.go`の変更**:
    *   `is_whitespace`関数が削除されました。
    *   `SkipWhitespace()`が、改行やタブではなく、スペースとキャリッジリターンのみをスキップするように変更されました。
    *   `ScanWhitespace()`関数が追加されました。これは、スキャナが改行またはタブに遭遇したときに呼び出され、その空白を消費して文字列として返します。
    *   `Scan()`メソッドが変更され、改行(`\n`)とタブ(`\t`)が一時的に`COMMENT`トークンとして分類され、その値が`S.ScanWhitespace()`によって取得されるようになりました。これは、これらの空白文字をpretty-printerのコメント処理メカニズムにフィードし、アライメント目的で処理できるようにするための暫定的な(そしてややハック的な)措置です。これにより、pretty-printerはこれらの空白を「コメント」として受け取り、バッファリングしてアライメント計算に含めることができます。

4.  **`ast.go`の変更**:
    *   `List`構造体に`Init()`と`Clear()`メソッドが追加されました。これらは、`Buffer`構造体内で`AST.List`を使用する際に、リストの初期化とクリアをより適切に制御するために導入されました。

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

### `usr/gri/pretty/printer.go`

```go
+type Buffer struct {
+	lines AST.List;  // a list of lines; and each line is a list of strings
+	widths AST.List;
+}
+
+// ... (Newline, Init, ComputeWidths, Flush, Indent, Print methods) ...
+
+export type Printer struct {
+	buf Buffer;
+	
 	// formatting control
 	level int;  // true scope level
 	indent int;  // indentation level
@@ -25,24 +142,22 @@ export type Printer struct {
 }
 
 
-// Bottleneck interface - all output goes through here.
-func (P *Printer) print(s string) {\n-\tprint(s);\n-\t// TODO do we need the code below?\n-\t// P.pos += Strings.utflen(s);\n-}\n-\n+const NEW_CODE = false;
 
 func (P *Printer) String(pos int, s string) {
  if P.semi && P.level > 0 {  // no semicolons at level 0
-		print(";");
+		if NEW_CODE {
+			P.buf.Print(";");
+		} else {
+			print(";");
+		}
  }
 
 	/*
 	for pos > P.cpos {
 		// we have a comment
 		c := P.clist.at(P.cindex).(*AST.Comment);
-		if c.text[1] == '/' {
+		if len(c.text) > 1 && c.text[1] == '/' {
 			print("  " + c.text);
 			if P.newl <= 0 {
 				P.newl = 1;  // line comments must have a newline
@@ -60,15 +175,30 @@ func (P *Printer) String(pos int, s string) {
 	*/
 
  if P.newl > 0 {
+		if NEW_CODE {
+			P.buf.Flush();
+		}
  for i := P.newl; i > 0; i-- {
-			print("\n");
+			if NEW_CODE {
+				P.buf.Newline();
+			} else {
+				print("\n");
+			}
  }
-		for i := P.indent; i > 0; i-- {
-			print("\t");
+		if NEW_CODE {
+			P.buf.Indent(P.indent);
+		} else {
+			for i := P.indent; i > 0; i-- {
+				print("\t");
+			}
  }
  }
 
-	print(s);
+	if NEW_CODE {
+		P.buf.Print(s);
+	} else {
+		print(s);
+	}
 
  P.semi, P.newl = false, 0;
 }
@@ -519,6 +649,8 @@ func (P *Printer) Declaration(d *AST.Decl, parenthesized bool) {
 
 func (P *Printer) Program(p *AST.Program) {
 	// TODO should initialize all fields?
+	P.buf.Init();
+	
  P.clist = p.comments;
  P.cindex = 0;
  if p.comments.len() > 0 {
@@ -527,5 +659,6 @@ func (P *Printer) Program(p *AST.Program) {
  P.cpos = 1000000000;  // infinite
  }
 
+	// Print package
  P.String(p.pos, "package ");
  P.Expr(p.ident);
  P.newl = 2;
@@ -534,5 +667,6 @@ func (P *Printer) Program(p *AST.Program) {
  P.Declaration(p.decls.at(i), false);
  }
  P.newl = 1;
+
  P.String(0, "");  // flush
 }

usr/gri/pretty/scanner.go

@@ -524,12 +519,20 @@ func (S *Scanner) Expect(ch int) {
 
 
 func (S *Scanner) SkipWhitespace() {
-\tfor is_whitespace(S.ch) {
+\tfor S.ch == ' ' || S.ch == '\r' {
  S.Next();
  }
 }
 
 
+func (S *Scanner) ScanWhitespace() string {
+\t// first char ('\n' or '\t', 1 byte) already consumed
+\tpos := S.chpos - 1;
+\tS.SkipWhitespace();
+\treturn S.src[pos : S.chpos];
+}
+
+
 func (S *Scanner) ScanComment() string {
  // first '/' already consumed
  pos := S.chpos - 1;
@@ -825,6 +828,7 @@ func (S *Scanner) Scan() (pos, tok int, val string) {
  S.Next();  // always make progress
  switch ch {
  case -1: tok = EOF;
+\t\tcase '\n', '\t': tok, val = COMMENT, S.ScanWhitespace();
  case '"': tok, val = STRING, S.ScanString();
  case '\'': tok, val = INT, S.ScanChar();
  case '`': tok, val = STRING, S.ScanRawString();

コアとなるコードの解説

このコミットの核心は、printer.goに導入されたBuffer構造体と、それを利用してpretty-printerの出力をバッファリングし、後でelastic tabstopsのロジックに基づいて整形するメカニズムです。

  1. Buffer構造体と関連メソッド:

    • Bufferは、整形されるコードを一時的にメモリに保持するためのものです。linesフィールドは、各行をさらにカラム(文字列のリスト)に分割して格納します。
    • ComputeWidths()メソッドは、バッファリングされたすべての行を走査し、各カラム位置における文字列の最大幅を計算します。例えば、最初のカラムの最大幅、2番目のカラムの最大幅、といった具合です。
    • Flush()メソッドは、ComputeWidths()で得られたカラム幅情報を使用して、実際に整形された出力を生成します。各カラムの文字列の後に、計算された最大幅に基づいて必要な数の空白を挿入することで、縦方向のアライメントを実現します。
  2. PrinterNEW_CODE:

    • Printerは、Bufferのインスタンスを内部に持ち、出力処理をこのバッファに委譲します。
    • NEW_CODE定数がfalseであるため、このコミット時点では、新しいバッファリングとアライメントのロジックはまだアクティブではありません。これは、機能がまだ開発中であり、安定していないため、デフォルトでは無効にされていることを示しています。開発者は、このフラグをtrueに設定することで、新しいロジックをテストできます。
  3. scanner.goの変更の意図:

    • scanner.goにおける改行とタブのCOMMENTトークンとしての扱いは、一見すると奇妙に見えます。これは、pretty-printerがこれらの空白文字を通常の文字としてではなく、アライメントの計算に必要な「情報」として受け取るための暫定的な手段です。スキャナはこれらの空白を「コメント」としてトークン化し、pretty-printerはそれらをBufferに格納し、Flush時にアライメントの計算に利用します。これは、pretty-printerの既存のコメント処理パスを再利用して、空白文字の情報を伝達するためのハック的なアプローチです。

全体として、このコミットは、Goのpretty-printerが将来的に高度なコードアライメント機能(elastic tabstops)をサポートするための重要な基盤を築いています。

関連リンク

参考にした情報源リンク

  • GitHubコミットページ: https://github.com/golang/go/commit/a3b4a3c29d29ac6be1ed7e262e5694c4a717d5fa
  • Web検索結果 (flexible tab stops, elastic tabstops)
  • Go言語のソースコード(コミット時点の関連ファイル)
  • Go言語のgofmtに関する一般的な知識
  • AST (Abstract Syntax Tree) に関する一般的な知識
  • Pretty-printerに関する一般的な知識