[インデックス 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/printer
package: https://pkg.go.dev/go/printer - GoDoc:
go/ast
package: https://pkg.go.dev/go/ast - GoDoc:
go/parser
package: https://pkg.go.dev/go/parser - GoDoc:
go/token
package: https://pkg.go.dev/go/token - GoDoc:
bytes
package: https://pkg.go.dev/bytes - GoDoc:
fmt
package: https://pkg.go.dev/fmt - GoDoc:
strings
package: https://pkg.go.dev/strings - GoDoc:
testing
package (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.)