[インデックス 18064] ファイルの概要
このコミットは、Go言語のcmd/yaccツールにおいて、生成されるGoコード(通常はy.goというファイル名)がGoの標準フォーマッタであるgofmtによって自動的に整形されるようにする変更を導入しています。これにより、cmd/yaccが生成するコードの可読性と一貫性が向上し、Goコミュニティのコーディング規約に準拠するようになります。
コミット
このコミットは、Go言語のパーサジェネレータであるcmd/yaccが生成するGoソースコードに対して、自動的にgofmtを適用するように修正しました。具体的には、src/cmd/yacc/yacc.goファイルにgo/formatパッケージとio/ioutilパッケージのインポートが追加され、生成処理の終了時にgofmtを実行する新しい関数gofmt()が導入されました。このgofmt()関数は、生成された出力ファイルを読み込み、go/format.Sourceで整形し、元のファイルに上書き保存します。
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/3d2c4df983a1594616da94f166b16d019a487456
元コミット内容
commit 3d2c4df983a1594616da94f166b16d019a487456
Author: ChaiShushan <chaishushan@gmail.com>
Date: Wed Dec 18 15:17:08 2013 -0500
cmd/yacc: gofmt y.go
R=golang-dev, adg, rsc
CC=golang-dev
https://golang.org/cl/36950043
変更の背景
Go言語では、コードの可読性と一貫性を保つために、公式のコードフォーマッタであるgofmtの使用が強く推奨されています。gofmtは、インデント、スペース、改行などのスタイルに関する問題を自動的に修正し、Goコードベース全体で統一されたスタイルを強制します。
cmd/yaccのようなコードジェネレータは、Goのソースコードを生成します。この生成されたコードがgofmtの規約に従っていない場合、以下のような問題が発生する可能性があります。
- 可読性の低下: 生成されたコードが手書きのコードと異なるフォーマットであると、開発者がコードを読み解く際に混乱を招く可能性があります。
- メンテナンス性の低下: フォーマットが不統一だと、コードレビューの際にスタイルの指摘に時間が割かれたり、手動での修正が必要になったりして、メンテナンスのオーバーヘッドが増加します。
- ツールとの非互換性:
gofmtを前提とする他のGoツール(例:goimports、IDEの自動フォーマット機能)が、生成されたコードに対して正しく動作しない可能性があります。
このコミットの背景には、cmd/yaccが生成するパーサコードもGoの標準的なコーディングスタイルに準拠させることで、Goエコシステム全体の一貫性を高め、開発者の利便性を向上させるという目的があります。生成されたコードが最初からgofmtによって整形されていれば、ユーザーが手動でgofmtを実行する手間が省け、よりスムーズな開発ワークフローが実現されます。
前提知識の解説
Yacc (Yet Another Compiler Compiler)
Yaccは、コンパイラコンパイラ(またはパーサジェネレータ)の一種で、BNF(Backus-Naur Form)のような形式で記述された文法定義から、その文法を解析するためのパーサのソースコードを自動生成するツールです。主にC言語のコンパイラやインタプリタのフロントエンド(構文解析部分)の生成に利用されてきました。YaccはLALR(1)パーサを生成し、与えられた入力が文法に合致するかどうかを検証し、抽象構文木(AST)などの構造を構築するのに役立ちます。
Go言語
GoはGoogleによって開発された静的型付けのコンパイル型言語です。シンプルさ、効率性、並行処理のサポートを重視しており、システムプログラミング、Webサービス、CLIツールなど幅広い分野で利用されています。Goは厳格なコーディング規約と、それを自動的に強制するツール(gofmtなど)を持つことで知られています。
cmd/yacc
cmd/yaccは、Go言語で実装されたYacc互換のパーサジェネレータです。Goの標準ライブラリの一部として提供されており、Goプログラム内で使用するパーサを生成するために利用されます。Yaccと同様に、文法定義ファイル(通常は.y拡張子を持つ)を入力として受け取り、Go言語で記述されたパーサのソースコード(通常はy.go)を出力します。
gofmt
gofmtは、Go言語の公式なコードフォーマッタです。Goのソースコードを解析し、Goの標準的なスタイルガイドラインに従って自動的に整形します。これにより、Goプロジェクト全体で一貫したコードスタイルが保証され、コードの可読性が向上します。gofmtはGoツールチェーンに組み込まれており、go fmtコマンドを通じて簡単に実行できます。
go/formatパッケージ
go/formatパッケージは、gofmtツールが内部で使用しているGo標準ライブラリのパッケージです。このパッケージは、Goのソースコードをプログラム的に整形するための機能を提供します。format.Source()関数は、Goのソースコードをバイトスライスとして受け取り、整形されたバイトスライスを返します。これにより、開発者は独自のツールやコードジェネレータ内でgofmtの整形ロジックを再利用できます。
io/ioutilパッケージ
io/ioutilパッケージは、Go言語でファイルやI/O操作を行うためのユーティリティ関数を提供します。このコミットでは、主に以下の関数が使用されています。
ioutil.ReadFile(filename string) ([]byte, error): 指定されたファイルの内容をすべて読み込み、バイトスライスとして返します。ioutil.WriteFile(filename string, data []byte, perm os.FileMode) error: 指定されたファイルにバイトスライスを書き込みます。ファイルが存在しない場合は作成し、存在する場合は上書きします。perm引数でファイルのパーミッションを設定します。
技術的詳細
このコミットの技術的な核心は、cmd/yaccが生成したGoコードを、その生成プロセスの一部として自動的にgofmtで整形することです。これは、go/formatパッケージを利用して実現されています。
cmd/yaccは、文法定義に基づいてパーサのGoソースコードを生成し、通常はy.goというファイルに出力します。このコミット以前は、この生成されたコードはcmd/yacc自身の内部的なコード生成ロジックによってフォーマットされていましたが、それは必ずしもgofmtの厳密な規約に完全に準拠しているわけではありませんでした。
変更後のフローは以下のようになります。
cmd/yaccは通常通り、パーサのGoソースコードを生成し、一時ファイルまたは指定された出力ファイル(oflag変数で指定されるパス)に書き出します。- プログラムが終了する直前、
exit()関数が呼び出されます。 exit()関数内で、新しく追加されたgofmt()関数が呼び出されます。gofmt()関数は以下の処理を実行します。ioutil.ReadFile(oflag):oflagで指定された出力ファイル(例:y.go)の内容をバイトスライスとして読み込みます。format.Source(src): 読み込んだバイトスライス(Goソースコード)をgo/formatパッケージのSource関数に渡します。この関数がGoの標準的なフォーマットルールに従ってコードを整形します。整形中に構文エラーなどが発生した場合、エラーが返されますが、この実装ではエラーは無視されています。これは、cmd/yaccが生成するコードは通常は有効なGoコードであるため、フォーマットエラーが発生する可能性は低いと判断されたか、あるいはフォーマット失敗が致命的ではないと見なされたためと考えられます。ioutil.WriteFile(oflag, src, 0666): 整形されたバイトスライスを、元の出力ファイルに上書き保存します。これにより、cmd/yaccの出力が常にgofmtによって整形された状態になります。ファイルパーミッション0666は、所有者、グループ、その他のユーザーが読み書きできることを意味します。
このアプローチにより、cmd/yaccのユーザーは、生成されたパーサコードに対して別途gofmtを実行する必要がなくなります。これは、Goのツールチェーンにおける「自動化と一貫性」という哲学に合致する変更です。
コアとなるコードの変更箇所
変更はsrc/cmd/yacc/yacc.goファイルに集中しています。
--- a/src/cmd/yacc/yacc.go
+++ b/src/cmd/yacc/yacc.go
@@ -49,6 +49,8 @@ import (
"bytes"
"flag"
"fmt"
+ "go/format"
+ "io/ioutil"
"os"
"strings"
"unicode"
@@ -3212,6 +3214,7 @@ func exit(status int) {
if ftable != nil {
ftable.Flush()
ftable = nil
+ gofmt()
}
if foutput != nil {
foutput.Flush()
@@ -3224,6 +3227,18 @@ func exit(status int) {
os.Exit(status)
}
+func gofmt() {
+ src, err := ioutil.ReadFile(oflag)
+ if err != nil {
+ return
+ }
+ src, err = format.Source(src)
+ if err != nil {
+ return
+ }
+ ioutil.WriteFile(oflag, src, 0666)
+}
+
var yaccpar string // will be processed version of yaccpartext: s/$$/prefix/g
var yaccpartext = `
/* parser for yacc output */
コアとなるコードの解説
インポートの追加
import (
"bytes"
"flag"
"fmt"
+ "go/format" // Goコードのフォーマット機能を提供
+ "io/ioutil" // ファイルの読み書き機能を提供
"os"
"strings"
"unicode"
)
go/formatパッケージとio/ioutilパッケージが新しくインポートされています。これらはそれぞれ、Goソースコードの整形と、ファイルの読み書きを行うために必要です。
exit()関数内のgofmt()呼び出し
func exit(status int) {
if ftable != nil {
ftable.Flush()
ftable = nil
+ gofmt() // ここで新しく追加されたgofmt関数が呼び出される
}
if foutput != nil {
foutput.Flush()
foutput = nil
}
os.Exit(status)
}
exit()関数は、cmd/yaccプログラムが終了する際に呼び出されるクリーンアップ関数です。この関数内で、ftable(おそらく生成されたGoコードの出力ストリームを管理するオブジェクト)がフラッシュされた後にgofmt()関数が呼び出されています。これは、Goコードの生成が完了し、ファイルに書き込まれた後に、そのファイルを整形するという論理的な順序を保証します。
gofmt()関数の定義
func gofmt() {
src, err := ioutil.ReadFile(oflag) // oflagは出力ファイル名(例: y.go)
if err != nil {
return // ファイル読み込みエラーが発生した場合は何もしない
}
src, err = format.Source(src) // 読み込んだソースコードをgofmtのルールで整形
if err != nil {
return // フォーマットエラーが発生した場合は何もしない
}
ioutil.WriteFile(oflag, src, 0666) // 整形されたソースコードを元のファイルに上書き保存
}
このgofmt()関数が、実際の整形処理を実行します。
ioutil.ReadFile(oflag):oflagはcmd/yaccのコマンドライン引数で指定される出力ファイル名(デフォルトではy.go)を保持する変数です。この行は、生成されたGoコードが書き込まれたファイルを読み込みます。エラーが発生した場合(例: ファイルが見つからない、読み取り権限がないなど)、関数はそこで終了します。format.Source(src): 読み込んだファイルの内容(バイトスライスsrc)をgo/formatパッケージのSource関数に渡します。この関数がGoの標準的なフォーマットルールに従ってコードを整形し、整形後のバイトスライスを返します。ここでもエラーハンドリングが行われていますが、エラーが発生しても関数は終了するだけで、特にエラーメッセージは出力されません。これは、生成されたコードが通常は有効なGoコードであり、フォーマットエラーが発生する可能性が低いと見なされているためかもしれません。ioutil.WriteFile(oflag, src, 0666): 整形されたsrcバイトスライスを、元のファイルパスoflagに上書き保存します。0666はファイルのパーミッション設定で、所有者、グループ、その他のユーザーに読み書きの権限を与えます。
この一連の処理により、cmd/yaccが生成するGoコードは、常にgofmtによって整形された状態となり、Goプロジェクトにおけるコードスタイルの一貫性が保たれます。
関連リンク
- Go言語公式サイト: https://go.dev/
cmd/yaccのソースコード (Goリポジトリ内): https://github.com/golang/go/tree/master/src/cmd/yaccgofmtに関するGoのドキュメント: https://go.dev/blog/gofmtgo/formatパッケージのドキュメント: https://pkg.go.dev/go/formatio/ioutilパッケージのドキュメント: https://pkg.go.dev/io/ioutil (Go 1.16以降はioとosパッケージに移行されていますが、このコミット時点ではio/ioutilが使用されていました。)
参考にした情報源リンク
- Go言語の公式ドキュメント
gofmtに関するGoブログ記事- Goの標準ライブラリのドキュメント (
go/format,io/ioutil) - Yaccに関する一般的な情報源 (例: Wikipedia, コンパイラ理論の教科書)
- GitHubのコミットページ: https://github.com/golang/go/commit/3d2c4df983a1594616da94f166b16d019a487456
- Go CL (Change List) 36950043: https://golang.org/cl/36950043 (現在はGitHubにリダイレクトされます)