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

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

このコミットは、Go言語の初期のコードベースにおける、コード整形ツール(prettyパッケージ)の機能改善とバグ修正に関するものです。具体的には、整形時のアラインメント(整列)において、空白文字の代わりにタブ文字を使用するオプションの追加と、構文エラーを含むテストケースの除外が行われています。

コミット

commit 34533f06eb30308849ca46a920134d30f2a89de5
Author: Robert Griesemer <gri@golang.org>
Date:   Tue Nov 18 19:25:43 2008 -0800

    - support for alignment via tabs instead of blanks
    - exclude a test due to syntax errors
    
    R=r
    OCL=19563
    CL=19565

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

https://github.com/golang/go/commit/34533f06eb30308849ca46a920134d30f2a89de5

元コミット内容

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

  • 空白の代わりにタブによるアラインメントのサポート
  • 構文エラーによるテストの除外

変更の背景

このコミットが行われた2008年11月は、Go言語がまだ一般に公開される前の、開発初期段階にあたります。Go言語の設計目標の一つに「読みやすさ」と「フォーマットの統一」がありました。go fmtのようなツールが後に登場するわけですが、このコミットはその前身となるコード整形機能の一部を改善するものです。

当時のコード整形ツールは、コードのアラインメントに空白文字を使用していたと考えられます。しかし、開発者の好みやプロジェクトのコーディング規約によっては、タブ文字によるアラインメントが好まれる場合があります。この変更は、そのような柔軟性を提供し、より多くの開発者がGo言語のコード整形ツールを快適に利用できるようにすることを目的としています。

また、test.shの変更は、開発中のGo言語の構文がまだ安定しておらず、一部のテストファイルが最新の構文に追従できていなかったために発生した一時的な対応と考えられます。これは、言語自体が活発に進化していた時期の典型的な状況です。

前提知識の解説

コード整形(Code Formatting)

コード整形とは、プログラムのソースコードを一定の規則に従って自動的にレイアウトし直すプロセスです。これには、インデント、スペース、改行、括弧の配置などが含まれます。コード整形ツールを使用することで、チーム内でのコードスタイルの統一が図られ、コードの可読性が向上し、レビューが容易になります。Go言語では、go fmtという標準ツールがこの役割を担っています。

タブとスペースによるインデント/アラインメント

プログラミングにおいて、コードブロックの階層を示すためにインデントが用いられます。インデントには主にタブ文字(\t)とスペース文字( )が使用されます。

  • タブ: 多くのエディタでタブ幅を自由に設定できるため、個々の開発者が好みの表示幅でコードを見ることができます。しかし、異なるタブ幅設定のエディタで開くと、コードの見た目が崩れる可能性があります。
  • スペース: スペースは常に同じ幅で表示されるため、どのエディタで開いてもコードの見た目が一貫しています。しかし、インデントの深さに応じて多くのスペース文字が必要となり、ファイルサイズが若干大きくなることがあります。

このコミットは、アラインメント(特定の列に要素を揃えること)において、空白だけでなくタブも選択できるようにするものです。

tabwriter

tabwriterは、Go言語の標準ライブラリ(text/tabwriterパッケージ)に存在する、テキストを整形して表形式で出力するためのユーティリティです。このツールは、タブ文字(\t)を区切り文字として使用し、各列の幅を自動的に調整して、きれいに揃った出力を生成します。このコミットが行われた時点では、usr/gri/pretty/tabwriter.goとしてGo言語の初期のコードベースに存在していました。

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

抽象構文木(AST)は、ソースコードの抽象的な構文構造を木構造で表現したものです。コンパイラやインタプリタがソースコードを解析する際に生成されます。ASTは、プログラムの構造を意味的に表現するため、コードの静的解析、最適化、変換、そしてコード整形ツールなどで利用されます。このコミットのコードでは、AST.DeclAST.Programといった型が使用されており、これはGo言語のソースコードの宣言やプログラム全体をASTとして扱っていることを示しています。

技術的詳細

このコミットの主要な技術的変更点は、tabwriterパッケージとprinterパッケージにおけるアラインメントの挙動の変更です。

printer.goの変更

printer.goは、Go言語のソースコードを整形して出力する役割を担うファイルです。 変更前は、TabWriter.MakeTabWriter関数を呼び出す際に、タブ幅(tabwidth)のみを引数として渡していました。これは、アラインメントに空白文字を使用することを前提としていたことを示唆しています。

-var tabwith = Flag.Int("tabwidth", 4, nil, "tab width");
-var comments = Flag.Bool("comments", false, nil, "enable printing of comments");
+var (
+	usetabs = Flag.Bool("usetabs", false, nil, "align with tabs instead of blanks");
+	tabwidth = Flag.Int("tabwidth", 4, nil, "tab width");
+	comments = Flag.Bool("comments", false, nil, "enable printing of comments");
+)

この変更により、usetabsという新しいフラグが追加されました。これは、アラインメントにタブを使用するかどうかを制御するブーリアン値です。

-	P.writer = TabWriter.MakeTabWriter(OS.Stdout, 4);
+	P.writer = TabWriter.MakeTabWriter(OS.Stdout, usetabs.BVal(), int(tabwidth.IVal()));

MakeTabWriterの呼び出しが変更され、usetabs.BVal()usetabsフラグのブーリアン値)が新しい引数として渡されるようになりました。これにより、printertabwriterに対して、タブによるアラインメントを有効にするかどうかを指示できるようになります。

tabwriter.goの変更

tabwriter.goは、実際にテキストのアラインメント処理を行うロジックを含んでいます。

 export type TabWriter struct {
 	// configuration
 	writer IO.Write;
+	usetabs bool;
 	tabwidth int;

TabWriter構造体にusetabsフィールドが追加されました。これは、このTabWriterインスタンスがタブによるアラインメントを使用するかどうかを内部的に保持するためのものです。

-func (b *TabWriter) Init(writer IO.Write, tabwidth int) {
+func (b *TabWriter) Init(writer IO.Write, usetabs bool, tabwidth int) {
 	b.writer = writer;
+	b.usetabs = usetabs;
 	b.tabwidth = tabwidth;

Initメソッドのシグネチャが変更され、usetabs引数が追加されました。これにより、TabWriterの初期化時にタブアラインメントの有効/無効を設定できるようになります。

最も重要な変更は、WriteBlanks関数がPadding関数に置き換えられた点です。

-func (b *TabWriter) WriteBlanks(n int) {
-...
-}
+func (b *TabWriter) Padding(textwidth, cellwidth int) {
+	n := cellwidth - textwidth;
+	if n < 0 {
+		panic("internal error");
+	}
+	if b.usetabs {
+		if cellwidth % b.tabwidth != 0 {
+			panic("internal error");  // cellwidth should be a multiple of tabwidth
+		}
+		n = (n + b.tabwidth - 1) / b.tabwidth;
+		for n > len(Tabs) {
+			m, err := b.writer.Write(Tabs);
+			n -= len(Tabs);
+		}
+		m, err := b.writer.Write(Tabs[0 : n]);
+	} else {
+		for n > len(Blanks) {
+			m, err := b.writer.Write(Blanks);
+			n -= len(Blanks);
+		}
+		m, err := b.writer.Write(Blanks[0 : n]);
+	}
+}
  • WriteBlanksは単に指定された数の空白を書き出すだけでしたが、Paddingtextwidth(現在のテキストの幅)とcellwidth(目標のセルの幅)を受け取り、その差分を埋めるためのパディングを生成します。
  • Padding関数内でb.usetabsの値がチェックされます。
    • usetabstrueの場合、必要なパディング量をタブ文字で埋めます。ここで、cellwidthtabwidthの倍数であることを確認するロジックが含まれています。これは、タブによるアラインメントの整合性を保つために重要です。
    • usetabsfalseの場合、以前と同様に空白文字でパディングを行います。

さらに、Format関数内でもusetabsのチェックが追加されています。

 	if b.usetabs {
 		// make width a multiple of the tab width
 		width = ((width + b.tabwidth - 1) / b.tabwidth) * b.tabwidth;
 	}

これは、タブアラインメントが有効な場合、計算された列の幅をタブ幅の倍数に調整するためのものです。これにより、タブ文字で正確にアラインメントできるようになります。

test.shの変更

test.shは、Go言語のテストスクリプトです。

-\tbug068.go | bug088.go | bug083.go | bug106.go ) ;;  # skip - files contain syntax errors
+\tbug068.go | bug088.go | bug083.go | bug106.go | bug125.go ) ;;  # skip - files contain syntax errors

bug125.goというファイルが、構文エラーのためにテストから除外されるリストに追加されました。これは、Go言語の構文がまだ流動的であった初期段階において、特定のテストファイルが最新の言語仕様に追従できていなかったことを示しています。

untab.goの変更

untab.goは、タブを空白に変換するユーティリティであると考えられます。

 var (
+	usetabs = Flag.Bool("usetabs", false, nil, "align with tabs instead of blanks");
 	tabwidth = Flag.Int("tabwidth", 4, nil, "tab width");
 )

printer.goと同様に、usetabsフラグが追加されています。

-\tdst := TabWriter.MakeTabWriter(OS.Stdout, int(tabwidth.IVal()));
+\tdst := TabWriter.MakeTabWriter(OS.Stdout, usetabs.BVal(), int(tabwidth.IVal()));

MakeTabWriterの呼び出しも更新され、usetabsフラグの値が渡されるようになりました。これにより、untabツールもタブによるアラインメントの挙動を制御できるようになります。

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

このコミットのコアとなるコードの変更箇所は以下のファイルに集中しています。

  • usr/gri/pretty/printer.go: コード整形ツールのメインロジックの一部。TabWriterの初期化時にusetabsフラグを渡すように変更。
  • usr/gri/pretty/tabwriter.go: 実際にテキストのアラインメント処理を行う部分。TabWriter構造体にusetabsフィールドを追加し、Initメソッドのシグネチャを変更。WriteBlanks関数をPadding関数に置き換え、タブと空白のどちらでパディングを行うかをusetabsに基づいて分岐するロジックを実装。また、タブアラインメント時に列幅をタブ幅の倍数に調整するロジックを追加。
  • usr/gri/pretty/test.sh: テストスクリプト。構文エラーのあるbug125.goをテスト対象から除外。
  • usr/gri/pretty/untab.go: タブを空白に変換するユーティリティ。printer.goと同様にusetabsフラグを追加し、MakeTabWriterの呼び出しを更新。

コアとなるコードの解説

このコミットの核心は、tabwriter.goにおけるPadding関数の導入と、それに関連するusetabsフラグの伝播です。

以前は、TabWriterは固定的に空白文字を使用してアラインメントを行っていました。しかし、この変更により、TabWriterusetabsというブーリアン値を受け取るようになり、この値に基づいてパディングの生成方法を動的に切り替えることができるようになりました。

Padding関数は、現在のテキストの幅(textwidth)と目標とするセルの幅(cellwidth)を受け取り、その差分を埋めるためのパディングを計算します。

  • if b.usetabs ブロック:

    • cellwidth % b.tabwidth != 0 のチェックは、タブによるアラインメントの際に、セルの幅がタブ幅の正確な倍数であることを保証するためのものです。もし倍数でなければ、タブで正確に揃えることができないため、内部エラーとしてパニックを起こします。
    • n = (n + b.tabwidth - 1) / b.tabwidth; の計算は、必要なパディング量(n)をタブ文字の数に変換しています。これは、例えばタブ幅が4の場合、1〜4文字のパディングは1つのタブ、5〜8文字のパディングは2つのタブ、といった具合に切り上げるための一般的なテクニックです。
    • その後、計算された数のタブ文字をb.writerに書き出します。
  • else ブロック:

    • usetabsfalseの場合、以前と同様に空白文字を書き出します。for n > len(Blanks) のループは、大量の空白が必要な場合に、Blanksスライスを繰り返し書き出すことで効率的に処理するためのものです。

この変更により、Go言語のコード整形ツールは、ユーザーの好みに応じてタブまたは空白のどちらでアラインメントを行うかを選択できるようになり、より柔軟な整形機能を提供できるようになりました。これは、Go言語が初期段階から開発者の使いやすさを重視していたことを示す一例と言えるでしょう。

test.shの変更は、開発の進行に伴う一時的な対応であり、言語仕様の安定化とともに解消される性質のものです。

関連リンク

参考にした情報源リンク