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

[インデックス 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の規約に従っていない場合、以下のような問題が発生する可能性があります。

  1. 可読性の低下: 生成されたコードが手書きのコードと異なるフォーマットであると、開発者がコードを読み解く際に混乱を招く可能性があります。
  2. メンテナンス性の低下: フォーマットが不統一だと、コードレビューの際にスタイルの指摘に時間が割かれたり、手動での修正が必要になったりして、メンテナンスのオーバーヘッドが増加します。
  3. ツールとの非互換性: 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の厳密な規約に完全に準拠しているわけではありませんでした。

変更後のフローは以下のようになります。

  1. cmd/yaccは通常通り、パーサのGoソースコードを生成し、一時ファイルまたは指定された出力ファイル(oflag変数で指定されるパス)に書き出します。
  2. プログラムが終了する直前、exit()関数が呼び出されます。
  3. exit()関数内で、新しく追加されたgofmt()関数が呼び出されます。
  4. 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()関数が、実際の整形処理を実行します。

  1. ioutil.ReadFile(oflag): oflagcmd/yaccのコマンドライン引数で指定される出力ファイル名(デフォルトではy.go)を保持する変数です。この行は、生成されたGoコードが書き込まれたファイルを読み込みます。エラーが発生した場合(例: ファイルが見つからない、読み取り権限がないなど)、関数はそこで終了します。
  2. format.Source(src): 読み込んだファイルの内容(バイトスライスsrc)をgo/formatパッケージのSource関数に渡します。この関数がGoの標準的なフォーマットルールに従ってコードを整形し、整形後のバイトスライスを返します。ここでもエラーハンドリングが行われていますが、エラーが発生しても関数は終了するだけで、特にエラーメッセージは出力されません。これは、生成されたコードが通常は有効なGoコードであり、フォーマットエラーが発生する可能性が低いと見なされているためかもしれません。
  3. ioutil.WriteFile(oflag, src, 0666): 整形されたsrcバイトスライスを、元のファイルパスoflagに上書き保存します。0666はファイルのパーミッション設定で、所有者、グループ、その他のユーザーに読み書きの権限を与えます。

この一連の処理により、cmd/yaccが生成するGoコードは、常にgofmtによって整形された状態となり、Goプロジェクトにおけるコードスタイルの一貫性が保たれます。

関連リンク

参考にした情報源リンク