[インデックス 1100] ファイルの概要
このコミットは、Go言語の初期のコード整形ツールにおける「エラスティックタブストップ(elastic tabstops)」機能に関するさらなる作業を反映しています。具体的には、エラスティックタブストップの新しい実装コードが有効化されましたが、その効果を視覚的に確認するためのコメント出力はまだ実装されていません。これは、Go言語の公式フォーマッタであるgo fmt
の基礎となる、コードの自動整形機能の開発初期段階における重要な一歩です。
コミット
commit 2a58e7d7a02064f8c5a95c98e7c3e30a26e1fa55
Author: Robert Griesemer <gri@golang.org>
Date: Mon Nov 10 17:56:46 2008 -0800
more work on elastic tabs:
- new code enabled, but no comments printed yet (so the effect
of the elastic tabs is not seen yet)
TBR=r
DELTA=200 (93 added, 69 deleted, 38 changed)
OCL=18951
CL=18951
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/2a58e7d7a02064f8c5a95c98e7c3e30a26e1fa55
元コミット内容
more work on elastic tabs:
- new code enabled, but no comments printed yet (so the effect
of the elastic tabs is not seen yet)
変更の背景
このコミットは、Go言語のコードフォーマッタ(後のgo fmt
)の初期開発の一環として行われました。当時のGo言語はまだ公開されておらず、開発チームはコードの可読性と一貫性を高めるための自動整形メカニズムを模索していました。その中で、「エラスティックタブストップ」という概念が、異なる環境や個人の設定に依存せずにコードの列をきれいに揃えるための有望なアプローチとして検討されていました。
この変更の目的は、エラスティックタブストップの新しい実装を有効にし、その動作を内部的にテストすることでした。コミットメッセージにある「no comments printed yet」という記述は、この機能がまだ開発途上であり、ユーザー(または開発者自身)が整形結果を視覚的に確認するためのデバッグ出力やコメント挿入機能が未実装であることを示唆しています。これは、機能のコアロジックの実装に焦点を当て、その後の段階でユーザーインターフェースやデバッグ機能を改善していくという、典型的なソフトウェア開発のアプローチです。
前提知識の解説
エラスティックタブストップ (Elastic Tabstops)
エラスティックタブストップは、コードのインデントとアライメントを扱うための革新的なアプローチです。従来のタブ(固定幅またはユーザー設定幅)やスペースによるインデントとは異なり、エラスティックタブストップでは、タブ文字がその後のテキストの列を揃えるように動的に幅を調整します。
- 従来のタブ/スペースの問題点:
- 固定幅タブ: エディタのタブ幅設定が異なると、コードの見た目が崩れる可能性があります。
- スペースインデント: 異なるインデントレベルで列を揃える場合、手動で多数のスペースを挿入する必要があり、編集が煩雑になります。また、列の途中に文字が挿入されると、それ以降の列全体を再調整する必要があります。
- エラスティックタブストップの利点:
- 動的なアライメント: タブ文字が、同じ列にある他の行のテキストと揃うように自身の幅を調整します。これにより、コードの見た目が常にきれいに保たれます。
- 編集の容易さ: 列の途中に文字を挿入しても、エラスティックタブストップが自動的に幅を調整するため、手動での再調整が不要になります。
- 個人の好みに対応: 各開発者が自分のエディタで好きなタブ幅を設定しても、コードの列は常に正しく揃います。
この概念は、Nick Gravgaardによって提唱され、特にコードの整形において高い可読性とメンテナンス性を提供すると期待されました。Go言語のgo fmt
は、最終的にはエラスティックタブストップとは異なるアプローチ(固定のタブ幅と、必要に応じたスペースによるアライメント)を採用しましたが、初期の検討段階ではこのアイデアが重要な役割を果たしました。
Go言語のコード整形 (go fmt
)
go fmt
は、Go言語のソースコードを自動的に整形するためのツールです。Go言語の設計哲学の一つに「一貫性」があり、go fmt
はその哲学を具現化する中心的なツールです。go fmt
を使用することで、Goコミュニティ全体で統一されたコードスタイルが強制され、コードの可読性が向上し、レビュープロセスが簡素化されます。
go fmt
は、インデント、スペース、改行、括弧の配置など、多くのスタイルガイドラインを自動的に適用します。このコミットが行われた2008年当時、go fmt
はまだ開発の初期段階にあり、どのような整形ルールを採用するか、特にインデントとアライメントのメカニズムについて様々な試行錯誤が行われていました。エラスティックタブストップの検討もその一環でした。
技術的詳細
このコミットは、usr/gri/pretty/printer.go
ファイルに対する大幅な変更を含んでいます。このファイルは、Go言語のコードを整形するための「プリティプリンタ」のロジックを実装していると考えられます。
主な変更点は以下の通りです。
Buffer
構造体の変更:segment
フィールドが追加されました。これは、現在の行セグメント(タブで区切られる前の文字列)を保持するために使用されます。widths
フィールドが削除されました。これは、エラスティックタブストップの新しいアプローチにおいて、列幅の計算方法が変更されたことを示唆しています。
Buffer
メソッドの追加と変更:Line(i int) *AST.List
: 指定されたインデックスの行を取得するヘルパーメソッド。Tab()
: 現在のsegment
を現在の行に追加し、segment
をリセットします。これは、タブ文字が検出されたときに新しい列セグメントを開始する役割を担います。Print(s string)
: 文字列s
を現在のsegment
に追加します。これは、整形対象のコードをバッファに書き込む主要なメソッドとなります。Newline()
:Tab()
を呼び出して現在のセグメントを確定した後、新しい行を開始します。Init()
:Buffer
の初期化ロジックが変更され、widths
の初期化が削除されました。ComputeWidths()
メソッドが完全に削除されました。これは、以前の列幅計算ロジックが新しいエラスティックタブストップの実装に置き換えられたことを意味します。Flush()
メソッドが大幅に書き換えられました。以前はComputeWidths()
を呼び出して列幅を計算し、その後行を直接出力していましたが、新しい実装ではTab()
を呼び出して最後のセグメントを確定し、Format()
メソッドを呼び出して整形処理を行います。PrintLines(line0, line1 int, widths *AST.List)
: 指定された行範囲を、与えられたwidths
(列幅)に基づいて出力する新しいヘルパーメソッド。Format(line0, line1 int, widths *AST.List)
: エラスティックタブストップの核心となる整形ロジックを実装する新しいメソッド。このメソッドは再帰的に呼び出され、コードの列を分析し、適切なタブ幅を計算して適用します。Dump()
: デバッグ目的でバッファの内容をダンプする新しいメソッド。
Printer
構造体の変更:NEW_CODE
定数が削除されました。以前はfalse
に設定されており、新しいエラスティックタブストップのコードパスが有効になっていなかったことを示唆しています。この定数の削除は、新しいコードがデフォルトで有効になったことを意味します。String(pos int, s string)
メソッドが変更され、直接print
関数を呼び出す代わりに、P.buf.Print(s)
やP.buf.Newline()
といったBuffer
のメソッドを使用するようになりました。これにより、出力ロジックがBuffer
内にカプセル化され、エラスティックタブストップのロジックが適用されるようになりました。- コメントの処理ロジックも変更され、
CountNewlinesAndTabs
という新しいヘルパー関数が導入されました。 Program(p *AST.Program)
メソッドの最後にあったP.String(0, "")
がP.buf.Flush()
に変更されました。これは、整形処理の最終段階でBuffer
のFlush
メソッドを呼び出すことで、エラスティックタブストップの整形ロジックを適用することを意味します。
これらの変更は、Go言語のコード整形において、従来の固定幅タブや単純なスペースインデントから、より高度なエラスティックタブストップベースのアライメントシステムへの移行を試みたことを明確に示しています。
コアとなるコードの変更箇所
変更はすべて /usr/gri/pretty/printer.go
ファイル内で行われています。
Buffer
構造体の定義変更:--- a/usr/gri/pretty/printer.go +++ b/usr/gri/pretty/printer.go @@ -36,96 +36,117 @@ func PrintBlanks(n int) { // (http://nickgravgaard.com/elastictabstops/index.html) type Buffer struct { + segment string; // current line segment lines AST.List; // a list of lines; and each line is a list of strings - widths AST.List; }
Buffer
メソッドの追加と変更:Line
,Tab
,Print
メソッドの追加。Newline
,Init
メソッドの変更。ComputeWidths
メソッドの削除。PrintLines
,Format
,Dump
メソッドの追加。Flush
メソッドの大幅な変更。
Printer
構造体のString
メソッドの変更:--- a/usr/gri/pretty/printer.go +++ b/usr/gri/pretty/printer.go @@ -142,28 +163,42 @@ export type Printer struct { } -const NEW_CODE = false; +func CountNewlinesAndTabs(s string) (int, int, string) { + nls, tabs := 0, 0; + for i := 0; i < len(s); i++ { + switch ch := s[i]; ch { + case '\n': nls++; + case '\t': tabs++; + case ' ': + default: + // non-whitespace char + assert(ch == '/'); + return nls, tabs, s[i : len(s)]; + } + } + return nls, tabs, ""; +} + func (P *Printer) String(pos int, s string) { if P.semi && P.level > 0 { // no semicolons at level 0 - if NEW_CODE { - P.buf.Print(";"); - } else { - print(";"); - } + P.buf.Print(";"); } /* for pos > P.cpos { // we have a comment - c := P.clist.at(P.cindex).(*AST.Comment); - if len(c.text) > 1 && c.text[1] == '/' { - print(" " + c.text); + comment := P.clist.at(P.cindex).(*AST.Comment); + nls, tabs, text := CountNewlinesAndTabs(comment.text); + + if nls == 0 && len(text) > 1 && text[1] == '/' { + P.buf.Tab(); + P.buf.Print(text); if P.newl <= 0 { - P.newl = 1; // line comments must have a newline + //P.newl = 1; // line comments must have a newline } } else { - print(c.text); + P.buf.Print(text); } P.cindex++; if P.cindex < P.clist.len() { @@ -175,30 +210,19 @@ 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-- { - if NEW_CODE { + P.buf.Newline(); + if P.newl > 1 { + for i := P.newl; i > 1; i-- { + //P.buf.Flush(); + P.buf.Newline(); + } + } + for i := P.indent; i > 0; i-- { + P.buf.Tab(); + } + } + + P.buf.Print(s); P.semi, P.newl = false, 0; } @@ -668,5 +692,5 @@ func (P *Printer) Program(p *AST.Program) { } P.newl = 1; - P.String(0, ""); // flush + P.buf.Flush(); // TODO should not access P.buf directly here }
コアとなるコードの解説
このコミットの核心は、Buffer
構造体とそれに関連するメソッド群の再設計にあります。
Buffer
構造体:- 以前は
lines
とwidths
という2つのAST.List
を持っていましたが、新しい実装ではsegment
という文字列フィールドが追加され、widths
が削除されました。これは、整形処理の単位が「行全体」から「行セグメント」(タブで区切られた部分)に変わり、列幅の計算が動的に行われるようになったことを示しています。
- 以前は
Tab()
とPrint()
メソッド:Print(s string)
は、入力された文字列を一時的にsegment
に蓄積します。Tab()
は、segment
に蓄積された文字列を現在の行のAST.List
に追加し、segment
をクリアします。これにより、タブ文字が検出されるたびに新しい列が開始されるというエラスティックタブストップの動作が実現されます。
Format()
メソッド:- このメソッドは、エラスティックタブストップの整形ロジックの主要な部分を担っています。コードの行を走査し、各列の最大幅を計算します。そして、その計算された幅に基づいて、
PrintLines
を呼び出して空白を挿入し、列を揃えます。このメソッドは再帰的に呼び出されることで、ネストされた構造や複雑なアライメント要件にも対応できるように設計されています。
- このメソッドは、エラスティックタブストップの整形ロジックの主要な部分を担っています。コードの行を走査し、各列の最大幅を計算します。そして、その計算された幅に基づいて、
Printer.String()
の変更:- 以前は直接
print
関数を呼び出していましたが、変更後はP.buf.Print()
やP.buf.Newline()
といったBuffer
のメソッドを介して文字列を出力するようになりました。これにより、すべての出力がBuffer
の整形ロジック(エラスティックタブストップ)の制御下に置かれることになります。
- 以前は直接
NEW_CODE
定数の削除:- この定数が削除されたことで、以前は条件付きで有効になっていた新しいエラスティックタブストップのコードパスが、常に有効になるようになりました。これは、この機能が開発の次の段階に進んだことを示しています。
これらの変更により、Go言語のプリティプリンタは、より洗練されたアライメント機能を持つように進化しました。最終的にgo fmt
はエラスティックタブストップとは異なるアプローチを採用しましたが、このコミットはGo言語のコード整形における初期の重要な探求と試行錯誤の過程を示しています。
関連リンク
- Elastic Tabstops: http://nickgravgaard.com/elastictabstops/index.html
- Go言語の公式ドキュメント (go fmtについて): https://go.dev/blog/gofmt (これは現在の
go fmt
に関するもので、コミット当時の状況とは異なりますが、関連情報として有用です。)
参考にした情報源リンク
- コミットメッセージと差分 (
./commit_data/1100.txt
の内容) - エラスティックタブストップに関する一般的な情報源 (Nick Gravgaardのウェブサイトなど)
- Go言語の歴史と
go fmt
の進化に関する一般的な知識 - Go言語の
AST
(Abstract Syntax Tree) およびコード生成/整形に関する一般的なプログラミング知識 - GitHubのコミットページ: https://github.com/golang/go/commit/2a58e7d7a02064f8c5a95c98e7c3e30a26e1fa55