[インデックス 12545] ファイルの概要
このコミットは、Go言語の標準ライブラリ go/printer パッケージに Fprint 関数の使用例を追加するものです。具体的には、src/pkg/go/printer/example_test.go という新しいテストファイルが追加され、ExampleFprint という関数が実装されています。この例は、go/printer.Fprint を使用してGoの抽象構文木 (AST) を整形し、標準出力に出力するプロセスを示しています。これにより、go/printer の機能と使い方をより明確に理解できるようになります。
コミット
commit ece0d0e7d2b70539b16b2a0c9ad0fa9afd68a92d
Author: Robert Griesemer <gri@golang.org>
Date: Fri Mar 9 13:53:25 2012 -0800
go/printer: example for Fprint
R=golang-dev, dsymonds, rsc
CC=golang-dev
https://golang.org/cl/5785057
---
src/pkg/go/printer/example_test.go | 67 ++++++++++++++++++++++++++++++++++++++
1 file changed, 67 insertions(+)
diff --git a/src/pkg/go/printer/example_test.go b/src/pkg/go/printer/example_test.go
new file mode 100644
index 0000000000..e570040ba1
--- /dev/null
+++ b/src/pkg/go/printer/example_test.go
@@ -0,0 +1,67 @@
+// Copyright 2012 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package printer_test
+
+import (
+ "bytes"
+ "fmt"
+ "go/ast"
+ "go/parser"
+ "go/printer"
+ "go/token"
+ "strings"
+ "testing"
+)
+
+// Dummy test function so that godoc does not use the entire file as example.
+func Test(*testing.T) {}
+
+func parseFunc(filename, functionname string) (fun *ast.FuncDecl, fset *token.FileSet) {
+ fset = token.NewFileSet()
+ if file, err := parser.ParseFile(fset, filename, nil, 0); err == nil {
+ for _, d := range file.Decls {
+ if f, ok := d.(*ast.FuncDecl); ok && f.Name.Name == functionname {
+ fun = f
+ return
+ }
+ }
+ }
+ panic("function not found")
+}
+
+func ExampleFprint() {
+ // Parse source file and extract the AST without comments for
+ // this function, with position information referring to the
+ // file set fset.
+ funcAST, fset := parseFunc("example_test.go", "ExampleFprint")
+
+ // Print the function body into buffer buf.
+ // The file set is provided to the printer so that it knows
+ // about the original source formatting and can add additional
+ // line breaks where they were present in the source.
+ var buf bytes.Buffer
+ printer.Fprint(&buf, fset, funcAST.Body)
+
+ // Remove braces {} enclosing the function body, unindent,
+ // and trim leading and trailing white space.
+ s := buf.String()
+ s = s[1 : len(s)-1]
+ s = strings.TrimSpace(strings.Replace(s, "\\n\\t", "\\n", -1))
+
+ // Print the cleaned-up body text to stdout.
+ fmt.Println(s)
+
+ // output:
+ // funcAST, fset := parseFunc("example_test.go", "ExampleFprint")
+ //
+ // var buf bytes.Buffer
+ // printer.Fprint(&buf, fset, funcAST.Body)
+ //
+ // s := buf.String()
+ // s = s[1 : len(s)-1]
+ // s = strings.TrimSpace(strings.Replace(s, "\\n\\t", "\\n", -1))
+ //
+ // fmt.Println(s)
+}
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/ece0d0e7d2b70539b16b2a0c9ad0fa9afd68a92d
元コミット内容
go/printer: example for Fprint
変更の背景
Go言語の公式ドキュメントツールである godoc は、_test.go ファイル内の Example 関数を自動的に検出し、その出力をドキュメントに含める機能を持っています。これにより、コードの動作例を直接ドキュメントとして提供することができ、ユーザーがライブラリの使い方を理解する上で非常に役立ちます。
このコミットの背景には、go/printer パッケージの Fprint 関数が提供する強力なコード整形機能の利用方法を、具体的なコード例を通じて示すという目的があります。Fprint はGoのASTを整形して出力するための中心的な関数ですが、その柔軟性ゆえに、どのように使用すれば良いか初心者には分かりにくい場合があります。ExampleFprint の追加により、開発者は Fprint を使ってGoコードをパースし、ASTを操作し、そして整形されたコードとして出力する一連のワークフローを、実際に動作するコードとして参照できるようになります。
また、godoc がファイル全体を例として扱わないようにするためのダミーの Test 関数 (Test(*testing.T) {}) が追加されている点も重要です。これは、godoc の挙動を考慮した慣用的なプラクティスであり、example_test.go ファイルが意図しない形でドキュメントに表示されるのを防ぎます。
前提知識の解説
このコミットを理解するためには、以下のGo言語の標準ライブラリと概念に関する知識が必要です。
go/ast(Abstract Syntax Tree): Go言語のソースコードを解析して生成される抽象構文木(AST)を表現するためのパッケージです。ソースコードの構造を木構造で表現し、プログラムの要素(関数、変数、式など)をノードとして扱います。go/parserによって生成され、go/printerによって整形されます。go/parser: Go言語のソースコードを解析し、go/astパッケージのASTを生成するためのパッケージです。ファイルパス、文字列、またはio.Readerからソースコードを読み込み、ASTを構築します。go/token: Go言語のソースコード内のトークン(キーワード、識別子、演算子など)や、ファイル内の位置情報(行番号、列番号、オフセット)を扱うためのパッケージです。token.FileSetは、複数のファイルにまたがる位置情報を一元的に管理するために使用されます。go/printer:go/astパッケージで表現されたASTを、Go言語のソースコードとして整形して出力するためのパッケージです。Goの公式フォーマッタであるgofmtの基盤となっています。Fprint関数は、ASTを指定されたio.Writerに出力します。bytes.Buffer:bytesパッケージに含まれる型で、可変長のバイトシーケンスをメモリ上で効率的に操作するためのバッファです。io.Writerインターフェースを実装しているため、printer.Fprintの出力先として利用できます。fmt.Println:fmtパッケージの関数で、引数を標準出力に改行付きで出力します。デバッグや簡単な出力によく使われます。strings.TrimSpace:stringsパッケージの関数で、文字列の先頭と末尾にある空白文字(スペース、タブ、改行など)を削除します。strings.Replace:stringsパッケージの関数で、文字列内の指定された部分文字列を別の文字列に置換します。_test.goファイルとExample関数: Go言語では、_test.goで終わるファイルはテストファイルとして扱われます。このファイル内でExampleというプレフィックスを持つ関数(例:ExampleFprint)を定義すると、go testコマンド実行時にその関数が実行され、その標準出力が// output:コメントと比較されます。また、godocコマンドで生成されるドキュメントには、これらのExample関数のコードと出力が自動的に含まれます。これは、ライブラリの利用例をドキュメントに組み込むためのGoの慣用的な方法です。godoc: Go言語のソースコードからドキュメントを生成するツールです。Example関数を認識し、そのコードと出力をドキュメントに含めることができます。
技術的詳細
このコミットで追加された ExampleFprint 関数は、go/printer パッケージの Fprint 関数がどのようにGoのASTを整形し、出力するかを具体的に示しています。
-
ソースコードのパースとASTの抽出:
parseFuncヘルパー関数は、現在のファイル (example_test.go) からExampleFprint関数自身のソースコードをパースし、そのAST (*ast.FuncDecl) を抽出します。- この際、
go/token.NewFileSet()で新しいFileSetが作成され、go/parser.ParseFileでファイルがパースされます。FileSetは、ソースコード内の位置情報(行番号、列番号など)を正確に追跡するために重要です。 parser.ParseFileの第4引数に0を渡すことで、コメントを含まないASTが生成されます。これは、printer.Fprintが通常、コメントを自動的に処理するため、例では純粋なコード整形に焦点を当てるためです。
-
printer.FprintによるASTの整形:var buf bytes.Bufferを使用して、整形されたコードを一時的に保持するためのバッファを作成します。bytes.Bufferはio.Writerインターフェースを実装しているため、Fprintの出力先として直接渡すことができます。printer.Fprint(&buf, fset, funcAST.Body)がこの例の核心です。- 第一引数
&bufは、整形されたコードの出力先となるio.Writerです。 - 第二引数
fsetは、パース時に使用されたtoken.FileSetです。printerはこのFileSetを参照することで、元のソースコードのフォーマット(特に改行位置)に関する情報を利用し、より自然な整形を行います。これにより、元のコードの意図を尊重した整形が可能になります。 - 第三引数
funcAST.Bodyは、整形対象となるASTノードです。ここではExampleFprint関数の本体(中括弧{}内のコード)のASTが渡されています。
- 第一引数
-
出力の整形と表示:
s := buf.String()で、bytes.Bufferに書き込まれた整形済みコードを文字列として取得します。s = s[1 : len(s)-1]は、関数の本体を囲む中括弧{}を削除しています。FprintはASTノード全体を整形するため、funcAST.Bodyを渡すと中括弧も含まれて出力されますが、この例では中身のコードのみを表示したいという意図があります。s = strings.TrimSpace(strings.Replace(s, "\\n\\t", "\\n", -1))は、整形された文字列から不要なインデントと空白を削除しています。Fprintはデフォルトでインデントを追加しますが、Example関数の出力として簡潔に表示するために、この後処理が行われています。具体的には、\n\t(改行とタブ)を\n(改行)に置換することでインデントを削除し、TrimSpaceで先頭と末尾の空白を削除しています。fmt.Println(s)で、最終的に整形されたコードが標準出力に表示されます。
-
// output:コメント:Example関数には、期待される出力を// output:コメントとして記述する慣習があります。go testはこのコメントと実際の出力を比較し、一致しない場合はテスト失敗とします。これにより、例が常に正しい出力を生成することを保証します。
この一連の処理は、Goのツールチェインがどのようにソースコードを解析し、操作し、そして再生成するかという基本的な流れを示しており、go/printer がその中でどのような役割を果たすかを明確にしています。
コアとなるコードの変更箇所
diff --git a/src/pkg/go/printer/example_test.go b/src/pkg/go/printer/example_test.go
new file mode 100644
index 0000000000..e570040ba1
--- /dev/null
+++ b/src/pkg/go/printer/example_test.go
@@ -0,0 +1,67 @@
+// Copyright 2012 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package printer_test
+
+import (
+ "bytes"
+ "fmt"
+ "go/ast"
+ "go/parser"
+ "go/printer"
+ "go/token"
+ "strings"
+ "testing"
+)
+
+// Dummy test function so that godoc does not use the entire file as example.
+func Test(*testing.T) {}
+
+func parseFunc(filename, functionname string) (fun *ast.FuncDecl, fset *token.FileSet) {
+ fset = token.NewFileSet()
+ if file, err := parser.ParseFile(fset, filename, nil, 0); err == nil {
+ for _, d := range file.Decls {
+ if f, ok := d.(*ast.FuncDecl); ok && f.Name.Name == functionname {
+ fun = f
+ return
+ }
+ }
+ }
+ panic("function not found")
+}
+
+func ExampleFprint() {
+ // Parse source file and extract the AST without comments for
+ // this function, with position information referring to the
+ // file set fset.
+ funcAST, fset := parseFunc("example_test.go", "ExampleFprint")
+
+ // Print the function body into buffer buf.
+ // The file set is provided to the printer so that it knows
+ // about the original source formatting and can add additional
+ // line breaks where they were present in the source.
+ var buf bytes.Buffer
+ printer.Fprint(&buf, fset, funcAST.Body)
+
+ // Remove braces {} enclosing the function body, unindent,
+ // and trim leading and trailing white space.
+ s := buf.String()
+ s = s[1 : len(s)-1]
+ s = strings.TrimSpace(strings.Replace(s, "\\n\\t", "\\n", -1))
+
+ // Print the cleaned-up body text to stdout.
+ fmt.Println(s)
+
+ // output:
+ // funcAST, fset := parseFunc("example_test.go", "ExampleFprint")
+ //
+ // var buf bytes.Buffer
+ // printer.Fprint(&buf, fset, funcAST.Body)
+ //
+ // s := buf.String()
+ // s = s[1 : len(s)-1]
+ // s = strings.TrimSpace(strings.Replace(s, "\\n\\t", "\\n", -1))
+ //
+ // fmt.Println(s)
+}
コアとなるコードの解説
追加された src/pkg/go/printer/example_test.go ファイルは、go/printer パッケージの Fprint 関数の使用方法を示すためのものです。
-
パッケージ宣言とインポート:
package printer_test import ( "bytes" "fmt" "go/ast" "go/parser" "go/printer" "go/token" "strings" "testing" )printer_testパッケージとして宣言されており、これはgo/printerパッケージの外部テストであることを示します。必要な標準ライブラリパッケージ(bytes,fmt,go/ast,go/parser,go/printer,go/token,strings,testing)がインポートされています。 -
Test(*testing.T) {}ダミー関数:// Dummy test function so that godoc does not use the entire file as example. func Test(*testing.T) {}この空のテスト関数は、
godocがexample_test.goファイル全体をExampleとして扱ってしまうのを防ぐための慣用的な記述です。これにより、godocはファイル内の明示的なExample関数のみをドキュメントに含めるようになります。 -
parseFuncヘルパー関数:func parseFunc(filename, functionname string) (fun *ast.FuncDecl, fset *token.FileSet) { fset = token.NewFileSet() if file, err := parser.ParseFile(fset, filename, nil, 0); err == nil { for _, d := range file.Decls { if f, ok := d.(*ast.FuncDecl); ok && f.Name.Name == functionname { fun = f return } } } panic("function not found") }この関数は、指定されたファイル (
filename) から特定の関数 (functionname) のAST (*ast.FuncDecl) と、そのASTが属するFileSetを抽出するためのヘルパーです。token.NewFileSet()で新しいFileSetを作成します。parser.ParseFile(fset, filename, nil, 0)でファイルをパースし、ASTを生成します。nilはソースコードの読み込み元(ここではファイル名で指定)、0はパースモード(コメントを含まない)を示します。- パースされたファイルの宣言 (
file.Decls) をループし、*ast.FuncDecl型にキャストできる(つまり関数宣言である)かつ、名前がfunctionnameと一致する関数を見つけたら、そのASTとFileSetを返します。 - 関数が見つからない場合は
panicします。
-
ExampleFprint関数:func ExampleFprint() { // Parse source file and extract the AST without comments for // this function, with position information referring to the // file set fset. funcAST, fset := parseFunc("example_test.go", "ExampleFprint") // Print the function body into buffer buf. // The file set is provided to the printer so that it knows // about the original source formatting and can add additional // line breaks where they were present in the source. var buf bytes.Buffer printer.Fprint(&buf, fset, funcAST.Body) // Remove braces {} enclosing the function body, unindent, // and trim leading and trailing white space. s := buf.String() s = s[1 : len(s)-1] s = strings.TrimSpace(strings.Replace(s, "\\n\\t", "\\n", -1)) // Print the cleaned-up body text to stdout. fmt.Println(s) // output: // funcAST, fset := parseFunc("example_test.go", "ExampleFprint") // // var buf bytes.Buffer // printer.Fprint(&buf, fset, funcAST.Body) // // s := buf.String() // s = s[1 : len(s)-1] // s = strings.TrimSpace(strings.Replace(s, "\\n\\t", "\\n", -1)) // // fmt.Println(s) }この関数は、
go/printer.Fprintの具体的な使用例を提供します。funcAST, fset := parseFunc("example_test.go", "ExampleFprint"):自身のソースコードをパースし、ExampleFprint関数のASTとFileSetを取得します。var buf bytes.Buffer:整形されたコードを格納するためのバッファを初期化します。printer.Fprint(&buf, fset, funcAST.Body):Fprint関数を呼び出し、ExampleFprint関数の本体 (funcAST.Body) のASTをbufに整形して出力します。fsetを渡すことで、元のソースコードのフォーマット情報が利用されます。s := buf.String():バッファの内容を文字列として取得します。s = s[1 : len(s)-1]:関数の本体を囲む中括弧{}を削除します。s = strings.TrimSpace(strings.Replace(s, "\\n\\t", "\\n", -1)):整形された文字列から不要なインデント(\n\tを\nに置換)と、先頭・末尾の空白を削除します。これは、Example関数の出力として簡潔に表示するための後処理です。fmt.Println(s):整形・加工されたコードを標準出力に出力します。// output:コメント:このコメントブロックは、go testが実際の出力と比較する期待される出力を定義しています。これにより、例が常に正しい出力を生成することを保証します。
この ExampleFprint 関数は、GoのAST操作、整形、そして godoc との連携という、Go言語のツールチェインの重要な側面を簡潔かつ効果的に示しています。
関連リンク
- Go Gerrit Change-ID: https://golang.org/cl/5785057
参考にした情報源リンク
- GoDoc:
go/printerpackage: https://pkg.go.dev/go/printer - GoDoc:
go/astpackage: https://pkg.go.dev/go/ast - GoDoc:
go/parserpackage: https://pkg.go.dev/go/parser - GoDoc:
go/tokenpackage: https://pkg.go.dev/go/token - GoDoc:
bytespackage: https://pkg.go.dev/bytes - GoDoc:
fmtpackage: https://pkg.go.dev/fmt - GoDoc:
stringspackage: https://pkg.go.dev/strings - GoDoc:
testingpackage (Examples): https://pkg.go.dev/testing#hdr-Examples - Effective Go - Examples: https://go.dev/doc/effective_go#examples
- Go: The Good Parts - go/ast, go/parser, go/token, go/printer: https://medium.com/@matryer/go-the-good-parts-go-ast-go-parser-go-token-go-printer-2d2d2d2d2d2d (Note: This is a general article, not specific to this commit, but provides good context on the packages.)