[インデックス 10375] Goコンパイラでの%+N書式における改行文字の修正
コミット
- コミットハッシュ: 40afe586920c0d5e4f81dbf46339790001cf30ae
- 作成者: Luuk van Dijk lvd@golang.org
- 日付: Mon Nov 14 10:08:04 2011 +0100
- コミットメッセージ: gc: fix newlines in %+N
- 修正対象: Issue #2442
- レビュー担当: rsc
- CC: golang-dev
- コードレビューURL: https://golang.org/cl/5370066
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/40afe586920c0d5e4f81dbf46339790001cf30ae
元コミット内容
このコミットは、Goコンパイラ(gc)のsrc/cmd/gc/fmt.c
ファイルにおける改行処理の修正を行っています。具体的には、%+N
書式指定子を使用したAST(抽象構文木)ノードのダンプ出力において、改行文字が適切に配置されていない問題を修正しています。
変更されたファイル:
src/cmd/gc/fmt.c
- 25行中13行の挿入、12行の削除
主な変更内容:
indent()
関数に改行文字の追加処理を挿入nodedump()
関数内の複数箇所で不要な改行文字を削除dump()
およびdumplist()
関数での最終改行文字の適切な配置
変更の背景
2011年当時、Goコンパイラはまだ C言語 で書かれており、Go自身が自己をコンパイルする前の時代でした。このコミットは、コンパイラのデバッグ機能における出力フォーマットの問題を修正するものです。
%+N
書式指定子は、Goコンパイラ内部でASTノードの詳細情報をダンプするために使用されるデバッグ用の特殊な書式でした。この書式は、構文解析やコンパイル処理の各段階でソースコードがどのような内部表現に変換されているかを視覚的に確認するために重要な役割を果たしていました。
Issue #2442は、この書式を使用した際に出力される改行文字の配置が不適切で、読みにくいデバッグ出力が生成される問題を報告していたものと推測されます。
前提知識の解説
Goコンパイラ(gc)の構造
2011年当時のGoコンパイラは以下の特徴を持っていました:
- C言語実装: Goコンパイラ自体がC言語で書かれていた時代
- 独自のAST表現: 内部的に独自のノード構造でソースコードを表現
- Plan 9由来の技術: Plan 9オペレーティングシステム由来のコンパイラ技術を基盤としていた
fmt.cファイルの役割
fmt.c
ファイルは、コンパイラ内部での書式化出力を担当する重要なモジュールでした:
- デバッグ出力: 開発者がコンパイラの内部動作を理解するための出力機能
- AST可視化: 抽象構文木の構造を人間が読める形式で出力
- 型情報表示: 型チェック後の型情報の詳細表示
%+N書式指定子
%+N
は、Goコンパイラ内部で使用される特殊な書式指定子でした:
- %N: 基本的なノード情報の出力
- %+N: 詳細モード(verbose mode)でのノード情報出力
- 階層表示: ASTの階層構造をインデントで表現
この書式は、現代のGoのfmt
パッケージの%+v
に相当する役割を果たしていました。
技術的詳細
indent()関数の変更
変更前:
indent(Fmt *fp)
{
int i;
for(i = 0; i < dumpdepth; ++i)
fmtstrcpy(fp, ". ");
}
変更後:
indent(Fmt *fp)
{
int i;
if(dumpdepth > 1)
fmtstrcpy(fp, "\n");
for(i = 0; i < dumpdepth; ++i)
fmtstrcpy(fp, ". ");
}
この変更により、深いレベルでのインデント時に自動的に改行が挿入されるようになりました。
nodedump()関数の改行処理統一
変更の方針:
- 一元化: 改行処理を
indent()
関数に集約 - 重複除去: 個別の改行出力コードを削除
- 統一性: 全てのノードタイプで一貫した出力フォーマット
具体的な変更箇所
- 初期化セクション:
%O-init\n%H
→%O-init%H
- 型情報セクション:
%O-ntype\n%N
→%O-ntype%N
- リストセクション:
%O-list\n%H
→%O-list%H
- 右リストセクション:
%O-rlist\n%H
→%O-rlist%H
- テストセクション:
%O-test\n%N
→%O-test%N
- ボディセクション:
%O-body\n%H
→%O-body%H
- elseセクション:
%O-else\n%H
→%O-else%H
- インクリメントセクション:
%O-incr\n%N
→%O-incr%N
コアとなるコードの変更箇所
src/cmd/gc/fmt.c:1269行目 - indent()関数
// 変更前
indent(Fmt *fp)
{
int i;
for(i = 0; i < dumpdepth; ++i)
fmtstrcpy(fp, ". ");
}
// 変更後
indent(Fmt *fp)
{
int i;
if(dumpdepth > 1)
fmtstrcpy(fp, "\n");
for(i = 0; i < dumpdepth; ++i)
fmtstrcpy(fp, ". ");
}
src/cmd/gc/fmt.c:1288行目 - nodedump()関数
深いネストの制限処理:
// 変更前
if(dumpdepth > 10)
return fmtstrcpy(fp, "...\n");
// 変更後
if(dumpdepth > 10)
return fmtstrcpy(fp, "...");
src/cmd/gc/fmt.c:1521-1525行目 - dumplist()とdump()関数
最終出力での改行追加:
// 変更前
void
dumplist(char *s, NodeList *l)
{
print("%s\n%+H", s, l);
}
void
dump(char *s, Node *n)
{
print("%s [%p]\n%+N", s, n, n);
}
// 変更後
void
dumplist(char *s, NodeList *l)
{
print("%s\n%+H\n", s, l);
}
void
dump(char *s, Node *n)
{
print("%s [%p]\n%+N\n", s, n, n);
}
コアとなるコードの解説
改行処理の設計思想
この修正の核心は、改行処理の責任の分離と一元化にあります:
-
責任の分離:
indent()
関数: インデントと改行の制御nodedump()
関数: ノード内容の出力- 各セクション: 情報の表示のみ
-
一元化の利点:
- 保守性: 改行ロジックが一箇所に集約
- 一貫性: 全ての出力で統一されたフォーマット
- 可読性: デバッグ出力の読みやすさ向上
dumpdepthによる階層管理
dumpdepth
変数は、AST走査の深さを管理する重要な変数です:
- 値が1: ルートレベル(改行不要)
- 値が2以上: 子ノードレベル(改行必要)
- 値が10超過: 深すぎるネスト("..."で省略)
この設計により、適切な階層表示が実現されています。
出力フォーマットの統一
変更により、以下の統一されたフォーマットが確立されました:
ノード情報
. 子ノード1
. . 孫ノード1-1
. . 孫ノード1-2
. 子ノード2
. . 孫ノード2-1
このフォーマットは、ASTの階層構造を視覚的に理解しやすくします。
関連リンク
- Go Packages - fmt - 現代のGoにおけるfmtパッケージの仕様
- Go Packages - go/ast - 現代のGoにおけるAST操作
- Go Compiler README - 現代のGoコンパイラの概要
- Go compiler internals - Goコンパイラの内部構造解説
参考にした情報源リンク
- コミット情報: commit_data/10375.txt
- Go言語公式ドキュメント: https://pkg.go.dev/fmt
- Goコンパイラ内部構造に関する技術記事: Eli Bendersky's website
- Go AST操作に関する解説: Medium - Cool Stuff With Go's AST Package
- Go書式設定チートシート: YourBasic Go - fmt.Printf formatting tutorial
このコミットは、Goコンパイラの開発初期における重要な改善の一つであり、開発者のデバッグ体験向上に大きく貢献しました。現代のGoにおいても、この時代に確立された設計思想はgo/ast
パッケージやfmt
パッケージの%+v
書式として受け継がれています。