[インデックス 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言語がまだ一般公開される前の初期開発段階にありました。この時期は、言語の設計が固まりつつあり、それに伴いツールチェイン(コンパイラ、フォーマッタなど)も活発に開発されていました。
変更の背景には、以下の点が挙げられます。
- モジュール性の向上と標準ライブラリへの準備:
tabwriter
は、コードの整形において、特に表形式のデータをきれいに揃えるために重要な機能です。これをprinter.go
という特定のツールの内部ロジックから切り離し、独立したパッケージとして提供することで、再利用性を高め、将来的にGoの標準ライブラリ(text/tabwriter
パッケージとして実際に組み込まれることになります)に含めるための準備を進めていました。これは、Go言語が提供するツールの基盤を堅牢にするための重要なステップでした。 - パフォーマンスの最適化: 初期の実装では文字列操作が多用されていた
tabwriter
の内部を、バイトバッファベースに書き換えることで、パフォーマンスの改善を図りました。Go言語では、文字列は不変であり、文字列操作は新しい文字列の割り当てを伴うことが多いため、大量のテキスト処理においてはバイトスライス([]byte
)を直接操作する方が効率的です。この変更は、Goの設計思想である「効率性」を反映したものです。 - 言語構文の進化への追従: Go言語は開発初期段階であり、構文が頻繁に変更されていました。
pretty
パッケージのようなコード整形ツールは、常に最新の構文に対応している必要があります。このコミットには、そうした構文変更への対応も含まれており、ツールの実用性を維持するための継続的な努力が伺えます。 - コード整形ルールの洗練: 関数定義におけるスペースの扱いなど、細かい整形ルールの調整は、Go言語の公式なコードスタイル(
gofmt
によって確立されるもの)を形成する過程の一部です。これにより、Goコードの一貫した見た目を保証しようとしていました。
前提知識の解説
このコミットを理解するためには、以下の概念について知っておく必要があります。
- Go言語の初期開発: Go言語は、Googleで2007年から開発が始まり、2009年に一般公開されました。このコミットは、その一般公開前の活発な開発期間に行われたものであり、言語仕様や標準ライブラリの基盤が形成されていく過程を反映しています。
- Pretty Printer: プログラミング言語のソースコードを読みやすく整形するツールのことです。Go言語においては、
gofmt
がその代表例です。このコミットで変更されているusr/gri/pretty
パッケージは、まさにその整形機能を提供するものでした。 - Abstract Syntax Tree (AST): ソースコードの抽象的な構文構造を木構造で表現したものです。コンパイラやコード整形ツールは、ソースコードをASTに変換し、そのASTを操作することで、コードの解析や変換を行います。
AST
パッケージは、GoコードのASTを定義しています。 - Scanner: ソースコードをトークン(キーワード、識別子、演算子など)のストリームに分解する字句解析器です。
Scanner
パッケージは、この機能を提供します。 - Elastic Tabstops (柔軟なタブストップ): Nick Gravgaardによって提唱された、タブ文字とスペースを組み合わせて、表形式のテキストを自動的に整形するアルゴリズムです。各列の幅が、その列の最長の要素に合わせて動的に調整されるため、コードの可読性が向上します。
tabwriter
パッケージは、このアルゴリズムを実装しています。 - バイトバッファ (
[]byte
) と文字列 (string
): Go言語において、string
型は不変なバイトのシーケンスであり、UTF-8エンコードされたテキストを表します。一方、[]byte
は可変なバイトスライスです。I/O操作やバイナリデータの処理では、[]byte
を直接扱う方が効率的であり、メモリ割り当てのオーバーヘッドを減らすことができます。 io.Writer
インターフェース: Goの標準ライブラリio
パッケージで定義されているインターフェースで、Write([]byte) (n int, err error)
メソッドを持つ型が実装します。これにより、様々な出力先(ファイル、ネットワーク接続、メモリバッファなど)に対して統一的な書き込み操作が可能になります。tabwriter
がio.Writer
を実装することで、任意のio.Writer
に整形されたテキストを出力できるようになります。
技術的詳細
このコミットの技術的な詳細は以下の通りです。
-
tabwriter
の分離と再設計:- 以前は
printer.go
内にBuffer
という型として存在していた柔軟なタブストップのロジックが、tabwriter.go
という新しいファイルにTabWriter
という型として完全に分離されました。 TabWriter
は、io.Writer
インターフェースを実装しています。これにより、TabWriter
は任意のio.Writer
(例えばos.Stdout
)をラップし、その出力に対してタブ整形を適用できるようになりました。これはGoのインターフェースの強力な活用例です。- 内部的には、
TabWriter
はByteArray
というカスタムのバイトスライス管理構造体と、vector
パッケージ(Goの初期のコレクション型)を使用して、入力されたバイト列と各セルの幅を管理します。 - 整形処理は、入力されたテキストをタブや改行で区切り、各セルの幅を計算し、最終的な出力時に適切なパディング(空白)を追加することで行われます。
- 以前は
-
文字列からバイトバッファへの移行:
- 旧
Buffer
型では、cell string
のように文字列を直接扱っていましたが、新TabWriter
ではbuf ByteArray
のようにバイトバッファ([]byte
のラッパー)を使用しています。 Write
メソッドは、入力された[]byte
を解析し、タブや改行を検出すると、それまでの内容を内部のバイトバッファに追加し、セルの幅を記録します。PrintLines
やWriteBlanks
といった出力関連のヘルパー関数も、[]byte
を直接操作し、b.writer.Write()
を通じて最終的な出力を行います。これにより、不要な文字列変換やメモリ割り当てが削減され、パフォーマンスが向上します。
- 旧
-
printer.go
の変更:Printer
構造体からbuf Buffer
フィールドが削除され、代わりにwriter IO.Write
フィールドが追加されました。Program
メソッド内で、P.writer = TabWriter.MakeTabWriter(OS.Stdout, 4);
という行が追加され、Printer
がTabWriter
をラップして出力を行うようになりました。これにより、Printer
は整形ロジック自体を持つのではなく、整形された出力をTabWriter
に委譲する形になりました。P.buf.Print
やP.buf.Newline
といった旧Buffer
への呼び出しは、P.Printf
やP.writer.Write
といった新しいIO.Write
インターフェースを通じた呼び出しに置き換えられました。特に、\t
や\n
といったタブや改行文字を直接Printf
に渡すことで、TabWriter
がそれらを解釈し、適切な整形を行うようになっています。
-
構文変更への対応:
parser.go
において、ParseMethodSpec
関数内でP.ParseIdent()
がP.ParseIdentList()
に変更され、さらにP.ParseType()
とP.ParseFunctionType()
の分岐が追加されています。これは、Go言語のメソッドや関数のシグネチャに関する構文が進化し、より柔軟な型指定が可能になったことへの対応と考えられます。ParseDeclaration
関数では、Scanner.EXPORT
だけでなくScanner.PACKAGE
もエクスポートされた宣言として扱うように変更されています。これは、Goのパッケージシステムにおける可視性ルールの初期の調整を示唆しています。
-
整形ルールの調整:
printer.go
のDeclaration
メソッド内で、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
フィールドに設定していることを示しています。これにより、Printer
がwriter
に書き込むすべてのデータは、TabWriter
によって整形されてから最終的な出力先に送られます。Printf
ヘルパーの導入と既存メソッドの変更:Printf
という新しいヘルパーメソッドが追加され、Fmt.fprintf(P.writer, fmt, s);
を通じてP.writer
(つまりTabWriter
)に整形済み文字列を書き込むようになりました。これにより、以前P.buf.Print
やP.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言語の公式ドキュメント: https://go.dev/
text/tabwriter
パッケージのドキュメント: https://pkg.go.dev/text/tabwriter (このコミットの後のバージョン)- Elastic Tabstopsの概念: http://nickgravgaard.com/elastictabstops/index.html
参考にした情報源リンク
- Go言語の歴史に関する情報 (Google検索)
text/tabwriter
パッケージの現在の実装とドキュメント (Go公式ドキュメント)- Go言語における文字列とバイトスライスの違いに関する一般的な知識
io.Writer
インターフェースに関する一般的な知識- Elastic Tabstopsに関する情報 (Nick Gravgaardのウェブサイト)
- Go言語の初期のコミット履歴 (GitHub)