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

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

このコミットは、Go言語の初期のprettyパッケージにおける重要なリファクタリングと機能改善を含んでいます。主な変更点は、柔軟なタブストップ(elastic tabstops)を処理するロジックをprinter.goからtabwriter.goという独立したファイルに分離し、さらにその内部実装を文字列ベースからバイトバッファベースに書き換えたことです。これにより、コードのモジュール性が向上し、将来的な標準ライブラリへの組み込みが視野に入れられました。また、Go言語の構文変更への対応や、関数名と引数の間のスペースに関する整形ルールの調整も行われています。

コミット

commit 654bc2badc69ef4db50ed245db1f458af63b8d17
Author: Robert Griesemer <gri@golang.org>
Date:   Mon Nov 17 19:58:52 2008 -0800

    - factored out tabwriter a separate writer filter
      (to be moved into std lib eventually)
    - rewrote tabwriter to use byte buffers instead of strings
      (byte buffers to be moved into stdlib eventually)
    - support for recent syntax changes
    - no space printed after function name and before function parameters
    - comments still disabled due to a known bug
    
    R=r
    OCL=19430
    CL=19430

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

https://github.com/golang/go/commit/654bc2badc69ef4db50ed245db1f458af63b8d17

元コミット内容

このコミットの元の内容は以下の通りです。

  • tabwriterを独立したライターフィルターとして分離しました(最終的には標準ライブラリに移動予定)。
  • tabwriterを文字列ではなくバイトバッファを使用するように書き直しました(バイトバッファも最終的には標準ライブラリに移動予定)。
  • 最近の構文変更に対応しました。
  • 関数名の後と関数パラメータの前にスペースを印刷しないようにしました。
  • 既知のバグのため、コメントはまだ無効になっています。

変更の背景

このコミットが行われた2008年11月は、Go言語がまだ一般公開される前の初期開発段階にありました。この時期は、言語の設計が固まりつつあり、それに伴いツールチェイン(コンパイラ、フォーマッタなど)も活発に開発されていました。

変更の背景には、以下の点が挙げられます。

  1. モジュール性の向上と標準ライブラリへの準備: tabwriterは、コードの整形において、特に表形式のデータをきれいに揃えるために重要な機能です。これをprinter.goという特定のツールの内部ロジックから切り離し、独立したパッケージとして提供することで、再利用性を高め、将来的にGoの標準ライブラリ(text/tabwriterパッケージとして実際に組み込まれることになります)に含めるための準備を進めていました。これは、Go言語が提供するツールの基盤を堅牢にするための重要なステップでした。
  2. パフォーマンスの最適化: 初期の実装では文字列操作が多用されていたtabwriterの内部を、バイトバッファベースに書き換えることで、パフォーマンスの改善を図りました。Go言語では、文字列は不変であり、文字列操作は新しい文字列の割り当てを伴うことが多いため、大量のテキスト処理においてはバイトスライス([]byte)を直接操作する方が効率的です。この変更は、Goの設計思想である「効率性」を反映したものです。
  3. 言語構文の進化への追従: Go言語は開発初期段階であり、構文が頻繁に変更されていました。prettyパッケージのようなコード整形ツールは、常に最新の構文に対応している必要があります。このコミットには、そうした構文変更への対応も含まれており、ツールの実用性を維持するための継続的な努力が伺えます。
  4. コード整形ルールの洗練: 関数定義におけるスペースの扱いなど、細かい整形ルールの調整は、Go言語の公式なコードスタイル(gofmtによって確立されるもの)を形成する過程の一部です。これにより、Goコードの一貫した見た目を保証しようとしていました。

前提知識の解説

このコミットを理解するためには、以下の概念について知っておく必要があります。

  1. Go言語の初期開発: Go言語は、Googleで2007年から開発が始まり、2009年に一般公開されました。このコミットは、その一般公開前の活発な開発期間に行われたものであり、言語仕様や標準ライブラリの基盤が形成されていく過程を反映しています。
  2. Pretty Printer: プログラミング言語のソースコードを読みやすく整形するツールのことです。Go言語においては、gofmtがその代表例です。このコミットで変更されているusr/gri/prettyパッケージは、まさにその整形機能を提供するものでした。
  3. Abstract Syntax Tree (AST): ソースコードの抽象的な構文構造を木構造で表現したものです。コンパイラやコード整形ツールは、ソースコードをASTに変換し、そのASTを操作することで、コードの解析や変換を行います。ASTパッケージは、GoコードのASTを定義しています。
  4. Scanner: ソースコードをトークン(キーワード、識別子、演算子など)のストリームに分解する字句解析器です。Scannerパッケージは、この機能を提供します。
  5. Elastic Tabstops (柔軟なタブストップ): Nick Gravgaardによって提唱された、タブ文字とスペースを組み合わせて、表形式のテキストを自動的に整形するアルゴリズムです。各列の幅が、その列の最長の要素に合わせて動的に調整されるため、コードの可読性が向上します。tabwriterパッケージは、このアルゴリズムを実装しています。
  6. バイトバッファ ([]byte) と文字列 (string): Go言語において、string型は不変なバイトのシーケンスであり、UTF-8エンコードされたテキストを表します。一方、[]byteは可変なバイトスライスです。I/O操作やバイナリデータの処理では、[]byteを直接扱う方が効率的であり、メモリ割り当てのオーバーヘッドを減らすことができます。
  7. io.Writerインターフェース: Goの標準ライブラリioパッケージで定義されているインターフェースで、Write([]byte) (n int, err error)メソッドを持つ型が実装します。これにより、様々な出力先(ファイル、ネットワーク接続、メモリバッファなど)に対して統一的な書き込み操作が可能になります。tabwriterio.Writerを実装することで、任意のio.Writerに整形されたテキストを出力できるようになります。

技術的詳細

このコミットの技術的な詳細は以下の通りです。

  1. tabwriterの分離と再設計:

    • 以前はprinter.go内にBufferという型として存在していた柔軟なタブストップのロジックが、tabwriter.goという新しいファイルにTabWriterという型として完全に分離されました。
    • TabWriterは、io.Writerインターフェースを実装しています。これにより、TabWriterは任意のio.Writer(例えばos.Stdout)をラップし、その出力に対してタブ整形を適用できるようになりました。これはGoのインターフェースの強力な活用例です。
    • 内部的には、TabWriterByteArrayというカスタムのバイトスライス管理構造体と、vectorパッケージ(Goの初期のコレクション型)を使用して、入力されたバイト列と各セルの幅を管理します。
    • 整形処理は、入力されたテキストをタブや改行で区切り、各セルの幅を計算し、最終的な出力時に適切なパディング(空白)を追加することで行われます。
  2. 文字列からバイトバッファへの移行:

    • Buffer型では、cell stringのように文字列を直接扱っていましたが、新TabWriterではbuf ByteArrayのようにバイトバッファ([]byteのラッパー)を使用しています。
    • Writeメソッドは、入力された[]byteを解析し、タブや改行を検出すると、それまでの内容を内部のバイトバッファに追加し、セルの幅を記録します。
    • PrintLinesWriteBlanksといった出力関連のヘルパー関数も、[]byteを直接操作し、b.writer.Write()を通じて最終的な出力を行います。これにより、不要な文字列変換やメモリ割り当てが削減され、パフォーマンスが向上します。
  3. printer.goの変更:

    • Printer構造体からbuf Bufferフィールドが削除され、代わりにwriter IO.Writeフィールドが追加されました。
    • Programメソッド内で、P.writer = TabWriter.MakeTabWriter(OS.Stdout, 4);という行が追加され、PrinterTabWriterをラップして出力を行うようになりました。これにより、Printerは整形ロジック自体を持つのではなく、整形された出力をTabWriterに委譲する形になりました。
    • P.buf.PrintP.buf.Newlineといった旧Bufferへの呼び出しは、P.PrintfP.writer.Writeといった新しいIO.Writeインターフェースを通じた呼び出しに置き換えられました。特に、\t\nといったタブや改行文字を直接Printfに渡すことで、TabWriterがそれらを解釈し、適切な整形を行うようになっています。
  4. 構文変更への対応:

    • parser.goにおいて、ParseMethodSpec関数内でP.ParseIdent()P.ParseIdentList()に変更され、さらにP.ParseType()P.ParseFunctionType()の分岐が追加されています。これは、Go言語のメソッドや関数のシグネチャに関する構文が進化し、より柔軟な型指定が可能になったことへの対応と考えられます。
    • ParseDeclaration関数では、Scanner.EXPORTだけでなくScanner.PACKAGEもエクスポートされた宣言として扱うように変更されています。これは、Goのパッケージシステムにおける可視性ルールの初期の調整を示唆しています。
  5. 整形ルールの調整:

    • printer.goDeclarationメソッド内で、d.tok != Scanner.FUNCという条件が追加され、関数宣言の場合には型名の前に空白を入れないというルールが明示されました。これは、func (P *Printer) String(...)のように、関数名と引数リストの間にスペースを入れないというGoの標準的な整形スタイルを確立するための一歩です。

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

usr/gri/pretty/printer.go (変更)

// 変更前:
// type Printer struct {
// 	buf Buffer;
// 	// ...
// }
// func (P *Printer) Program(p *AST.Program) {
// 	P.buf.Init();
// 	// ...
// }
// func (P *Printer) Print(s string) {
// 	P.buf.Print(s);
// }
// func (P *Printer) Newline() {
// 	P.buf.Newline();
// }
// func (P *Printer) Tab() {
// 	P.buf.Tab();
// }

// 変更後:
import IO "io"
import OS "os"
import TabWriter "tabwriter" // 新しくインポート

export type Printer struct {
	writer IO.Write; // BufferからIO.Writeへ変更
	// ...
}

func (P *Printer) Printf(fmt string, s ...) {
	Fmt.fprintf(P.writer, fmt, s); // 新しいPrintfヘルパー
}

func (P *Printer) String(pos int, s string) {
	// ...
	// P.buf.Print(";") -> P.Printf(";")
	// P.buf.Print(" ") -> P.Printf(" %s ", text)
	// P.buf.Tab(); P.buf.Print(text); -> P.Printf("\t%s", text)
	// P.buf.Newline(); -> P.Printf("\n");
	// P.buf.Tab(); -> P.Printf("\t");
	// P.buf.Print(s); -> P.Printf("%s", s);
	// ...
}

func (P *Printer) Tab() {
	P.String(0, "\t"); // 直接タブ文字を渡す
}

func (P *Printer) Declaration(d *AST.Decl, parenthesized bool) {
	// ...
	if d.typ != nil {
		if d.tok != Scanner.FUNC { // 関数宣言の場合のスペース制御
			P.Blank();
		}
		P.Type(d.typ);
	}
	// ...
}

func (P *Printer) Program(p *AST.Program) {
	P.writer = TabWriter.MakeTabWriter(OS.Stdout, 4); // TabWriterの初期化
	// ...
}

usr/gri/pretty/tabwriter.go (新規ファイル)

// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package tabwriter

import (
	OS "os";
	IO "io";
	Vector "vector"; // Go初期のコレクション型
)

// ByteArray: []byteをラップし、動的なサイズ変更をサポートするカスタムバッファ
type ByteArray struct {
	a *[]byte;
}
// ... Init, Clear, Len, At, Set, Slice, Append メソッド ...

// TabWriter: 柔軟なタブストップを実装するメインの構造体
type TabWriter struct {
	// configuration
	writer IO.Write; // 出力先
	tabwidth int;    // タブ幅

	// current state
	buf ByteArray;  // タブや改行を含まない収集されたテキスト
	width int;      // 最後の不完全なセルの幅
	lines Vector.Vector; // 各行のセル幅のリスト
	widths Vector.Vector; // 列幅のリスト (整形時に使用)
}

// ... AddLine, Init, Line, LastLine, Dump, WriteBlanks, PrintLines, Format, EmptyLine, Tab, Newline メソッド ...

// Write: io.Writerインターフェースの実装。入力バイト列を処理し、タブや改行を検出して内部バッファに格納
func (b *TabWriter) Write(buf *[]byte) (i int, err *OS.Error) {
	// ... タブと改行を検出してセルの幅を計算し、buf.Appendで内部バッファにデータを追加 ...
}

// MakeTabWriter: TabWriterのコンストラクタ関数
export func MakeTabWriter(writer IO.Write, tabwidth int) IO.Write {
	b := new(TabWriter);
	b.Init(writer, tabwidth);
	return b;
}

コアとなるコードの解説

printer.goの変更点

printer.goの最も重要な変更は、コード整形ロジックの中心がPrinter自身からTabWriterへと移譲されたことです。

  • Printer構造体の変更: buf Bufferフィールドが削除され、writer IO.Writeフィールドが追加されました。これは、Printerが直接バッファを管理するのではなく、io.Writerインターフェースを介して任意の出力先に書き込むように設計変更されたことを意味します。
  • ProgramメソッドでのTabWriterの初期化: P.writer = TabWriter.MakeTabWriter(OS.Stdout, 4);という行は、Printerが標準出力(OS.Stdout)をラップするTabWriterインスタンスを生成し、それを自身のwriterフィールドに設定していることを示しています。これにより、Printerwriterに書き込むすべてのデータは、TabWriterによって整形されてから最終的な出力先に送られます。
  • Printfヘルパーの導入と既存メソッドの変更: Printfという新しいヘルパーメソッドが追加され、Fmt.fprintf(P.writer, fmt, s);を通じてP.writer(つまりTabWriter)に整形済み文字列を書き込むようになりました。これにより、以前P.buf.PrintP.buf.Newlineなどで行っていた文字列の直接操作が、P.Printfを介したTabWriterへの委譲に置き換えられました。例えば、P.buf.Print(";")P.Printf(";")に、P.buf.Newline()P.Printf("\n")に、P.buf.Tab()P.String(0, "\t")(最終的にP.Printf("\t")を呼び出す)に変更されています。これは、TabWriterがタブ文字(\t)や改行文字(\n)を特別に解釈し、柔軟なタブストップの整形ロジックを適用するためです。
  • 関数宣言の整形ルール: Declarationメソッド内のif d.tok != Scanner.FUNCという条件は、Go言語の関数宣言の特定の整形ルールを適用するためのものです。これにより、func (P *Printer) String(...)のように、関数名と引数リストの間にスペースが入らないように制御されます。

tabwriter.goの新規実装

tabwriter.goは、Goの標準ライブラリtext/tabwriterの原型となる重要なコンポーネントです。

  • ByteArray構造体: これは、Goの初期段階で動的なバイトスライスを扱うためのカスタム実装です。現在のGoでは、組み込みのappend関数やスライス操作が非常に強力であるため、このようなカスタム構造体は通常必要ありません。しかし、当時はまだ言語機能が成熟していなかったため、このようなヘルパーが必要でした。Appendメソッドは、必要に応じて基盤となるバイトスライスの容量を増やし、データを効率的に追加します。
  • TabWriter構造体:
    • writer IO.Write: 整形されたテキストの最終的な出力先です。
    • tabwidth int: タブの幅(通常は4または8)を設定します。
    • buf ByteArray: タブや改行文字を含まない、入力されたすべてのテキストデータを保持します。
    • width int: 現在処理中のセルの幅を追跡します。
    • lines Vector.Vector: 各行のセルの幅を記録します。VectorはGo初期の動的配列実装です。
    • widths Vector.Vector: 整形時に各列の最大幅を計算し、パディングのために使用します。
  • Writeメソッド: io.Writerインターフェースの核心となるメソッドです。
    • 入力されたバイトスライスbufを走査し、タブ文字(\t)や改行文字(\n)を検出します。
    • タブや改行が見つかるまでのテキストは、b.buf.Appendを使って内部のByteArrayに追加されます。
    • タブが検出されると、それまでのセルの幅(b.width)が計算され、現在の行のセル幅リスト(b.LastLine().Append(b.width))に追加されます。
    • 改行が検出されると、同様に最後のセルの幅が追加され、新しい行が開始されます。
    • 特に重要なのは、if b.LastLine().Len() == 1の条件です。これは、現在の行に1つのセルしかない場合(つまり、その行が他の行の整形に影響を与えない場合)、それまでに収集されたバッファの内容をFormatメソッドを使って最終的なb.writerに出力し、内部バッファをリセットするという最適化です。これにより、不必要なバッファリングを防ぎ、効率的なストリーム処理を可能にしています。
  • Formatメソッド: 収集された行とセルの幅情報に基づいて、実際に柔軟なタブストップの整形を行う再帰的な関数です。各列の最大幅を計算し、それに基づいて空白を挿入してテキストを揃えます。
  • MakeTabWriter関数: TabWriterのインスタンスを生成し、初期化を行うファクトリ関数です。

このコミットは、Go言語のツールチェインがどのように設計され、パフォーマンスとモジュール性を追求していったかを示す良い例であり、現在のtext/tabwriterパッケージの基礎を築いたものです。

関連リンク

参考にした情報源リンク

  • Go言語の歴史に関する情報 (Google検索)
  • text/tabwriterパッケージの現在の実装とドキュメント (Go公式ドキュメント)
  • Go言語における文字列とバイトスライスの違いに関する一般的な知識
  • io.Writerインターフェースに関する一般的な知識
  • Elastic Tabstopsに関する情報 (Nick Gravgaardのウェブサイト)
  • Go言語の初期のコミット履歴 (GitHub)