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

[インデックス 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を整形し、出力するかを具体的に示しています。

  1. ソースコードのパースとASTの抽出:

    • parseFunc ヘルパー関数は、現在のファイル (example_test.go) から ExampleFprint 関数自身のソースコードをパースし、そのAST (*ast.FuncDecl) を抽出します。
    • この際、go/token.NewFileSet() で新しい FileSet が作成され、go/parser.ParseFile でファイルがパースされます。FileSet は、ソースコード内の位置情報(行番号、列番号など)を正確に追跡するために重要です。
    • parser.ParseFile の第4引数に 0 を渡すことで、コメントを含まないASTが生成されます。これは、printer.Fprint が通常、コメントを自動的に処理するため、例では純粋なコード整形に焦点を当てるためです。
  2. printer.Fprint によるASTの整形:

    • var buf bytes.Buffer を使用して、整形されたコードを一時的に保持するためのバッファを作成します。bytes.Bufferio.Writer インターフェースを実装しているため、Fprint の出力先として直接渡すことができます。
    • printer.Fprint(&buf, fset, funcAST.Body) がこの例の核心です。
      • 第一引数 &buf は、整形されたコードの出力先となる io.Writer です。
      • 第二引数 fset は、パース時に使用された token.FileSet です。printer はこの FileSet を参照することで、元のソースコードのフォーマット(特に改行位置)に関する情報を利用し、より自然な整形を行います。これにより、元のコードの意図を尊重した整形が可能になります。
      • 第三引数 funcAST.Body は、整形対象となるASTノードです。ここでは ExampleFprint 関数の本体(中括弧 {} 内のコード)のASTが渡されています。
  3. 出力の整形と表示:

    • 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) で、最終的に整形されたコードが標準出力に表示されます。
  4. // 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 関数の使用方法を示すためのものです。

  1. パッケージ宣言とインポート:

    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)がインポートされています。

  2. Test(*testing.T) {} ダミー関数:

    // Dummy test function so that godoc does not use the entire file as example.
    func Test(*testing.T) {}
    

    この空のテスト関数は、godocexample_test.go ファイル全体を Example として扱ってしまうのを防ぐための慣用的な記述です。これにより、godoc はファイル内の明示的な Example 関数のみをドキュメントに含めるようになります。

  3. 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 します。
  4. 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言語のツールチェインの重要な側面を簡潔かつ効果的に示しています。

関連リンク

参考にした情報源リンク