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

[インデックス 13868] ファイルの概要

このコミットは、Go言語のドキュメンテーションツール (go/doc パッケージ) において、Goのコード例 (Example) をスタンドアロンで実行可能なプログラムとして合成する機能を追加するものです。具体的には、Example 関数から package mainfunc main を持つ完全なGoプログラムを生成し、Go Playgroundなどで実行できるようにするための変更が含まれています。

コミット

  • コミットハッシュ: 7e525928d33bdd48142a2664dc03664c0034354b
  • 作者: Andrew Gerrand adg@golang.org
  • コミット日時: 2012年9月18日 火曜日 14:13:34 -0700

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/7e525928d33bdd48142a2664dc03664c0034354b

元コミット内容

go/doc: synthesize "package main" for examples

R=gri
CC=golang-dev
https://golang.org/cl/6525046

変更の背景

Go言語のドキュメンテーションシステムは、コード例 (Example functions) をドキュメントに含めることで、コードの動作を具体的に示す強力な機能を提供しています。これらのExample関数は通常、テストファイル (_test.go サフィックスを持つファイル) 内に記述され、go test コマンドによって実行・検証されます。

しかし、これらのExample関数は、それ単体では完全な実行可能プログラムではありません。Go Playgroundのようなオンラインツールや、ユーザーがローカルでExampleコードを試す際には、package mainfunc main() が必要となります。

このコミットの背景には、go/doc パッケージが生成するドキュメントから、Exampleコードをよりインタラクティブに、そして独立して実行できるようにするという目的があります。これにより、ユーザーはドキュメントを読みながら、その場でコード例を試すことが容易になり、学習体験が向上します。

前提知識の解説

このコミットを理解するためには、以下のGo言語および関連ツールの概念を理解しておく必要があります。

  1. Go言語のExample関数: Go言語では、Example というプレフィックスを持つ関数を記述することで、パッケージのドキュメンテーションにコード例を含めることができます。これらの関数は通常、_test.go ファイル内に定義され、go test コマンドによって実行され、その出力が期待される出力 (// Output:) と一致するかどうかが検証されます。これにより、ドキュメントのコード例が常に最新かつ正確であることが保証されます。

    例:

    package mypackage
    
    import "fmt"
    
    func ExampleHello() {
        fmt.Println("Hello")
        // Output: Hello
    }
    
  2. package mainfunc main(): Go言語において、実行可能なプログラムは必ず package main パッケージに属し、エントリポイントとして func main() 関数を持っていなければなりません。go run コマンドや go build コマンドで実行されるのは、この main パッケージの main 関数です。

  3. go/doc パッケージ: Go標準ライブラリの一部であり、Goのソースコードからドキュメンテーションを抽出・生成するためのパッケージです。go doc コマンドや godoc ツールはこのパッケージを利用して、GoのコードからコメントやExample関数を解析し、整形されたドキュメントを生成します。

  4. go/ast パッケージ: Go標準ライブラリの一部であり、Goのソースコードの抽象構文木 (Abstract Syntax Tree: AST) を表現するためのパッケージです。go/parser パッケージでソースコードを解析すると、ASTが生成されます。このASTを操作することで、プログラムの構造を分析したり、コードを変換したりすることができます。

    • ast.File: Goの単一のソースファイルを表すASTノード。
    • ast.FuncDecl: 関数宣言を表すASTノード。
    • ast.BlockStmt: コードブロック (中括弧 {} で囲まれた部分) を表すASTノード。
    • ast.ImportSpec: インポート宣言を表すASTノード。
    • ast.GenDecl: 一般的な宣言 (import, const, type, var) を表すASTノード。
    • ast.Ident: 識別子 (変数名、関数名など) を表すASTノード。
    • ast.SelectorExpr: セレクタ式 (例: fmt.Printlnfmt.Println) を表すASTノード。
    • ast.Inspect: ASTを再帰的に走査するためのヘルパー関数。
  5. Go Playground: Go言語の公式ウェブサイトで提供されているオンラインツールで、Goのコードをブラウザ上で記述・実行し、その結果を確認できます。Go Playgroundで実行されるコードは、完全な package main プログラムである必要があります。

技術的詳細

このコミットの主要な変更は、go/doc/example.go ファイルに playExample という新しい関数が追加されたことです。この関数は、既存のExample関数のASTを受け取り、それを package mainfunc main() を持つ完全なGoプログラムのASTに変換します。

playExample 関数の主な処理は以下の通りです。

  1. Example関数の識別: Examples 関数内で、各Example関数 (ast.FuncDecl) が処理される際に、新しく追加された Play フィールドに playExample 関数の結果が格納されます。Play フィールドは *ast.File 型であり、合成された完全なプログラムのASTを保持します。

  2. テストファイルであることの確認: playExample 関数は、まずExample関数が定義されているファイルがテストファイル (_test.go で終わるファイル名) であることを確認します。これは、Example関数が通常テストファイル内に記述されるためであり、現時点ではパッケージの一部であるExampleはサポートしていません。

  3. 未解決の識別子の特定とインポートの解決: Example関数の本体 (body *ast.BlockStmt) を走査し、未解決の識別子 (例: fmt.Printlnfmt のようなパッケージ修飾子) を特定します。 その後、元のファイルに存在するインポート宣言を調べ、特定された未解決の識別子に対応するインポートパスを解決します。この際、パッケージ名がインポートパスのベース名と一致するというヒューリスティックが使用されます。ドットインポート (. "path/to/pkg") は現時点ではサポートされていません。

  4. 新しいインポート宣言の合成: 解決されたインポートパスに基づいて、新しい ast.GenDecl (一般的な宣言) を作成し、token.IMPORT 型の宣言として、必要なインポートスペック (ast.ImportSpec) を追加します。これにより、合成されたプログラムに必要なパッケージがインポートされます。

  5. main 関数の合成: Example関数の本体 (body) をそのまま新しい main 関数の本体として使用し、ast.FuncDecl (関数宣言) を作成します。関数名は main、型は引数なし・戻り値なしの func() となります。

  6. コメントのフィルタリング: 元のファイルに存在するコメントのうち、Example関数の本体の範囲外にあるコメントは除外されます。これにより、合成されたプログラムにはExample関数に関連するコメントのみが含まれるようになります。

  7. 新しいファイルのASTの合成: 最終的に、package main を表す ast.Ident、合成されたインポート宣言 (importDecl)、合成された main 関数宣言 (funcDecl)、およびフィルタリングされたコメント (comments) を含む新しい *ast.File が作成され、返されます。

このプロセスにより、Example関数はGo Playgroundなどで実行可能な独立したGoプログラムとして表現できるようになります。

コアとなるコードの変更箇所

このコミットによって変更された主要なファイルは以下の通りです。

  • src/pkg/go/doc/example.go

このファイルに92行の追加が行われています。

コアとなるコードの解説

src/pkg/go/doc/example.go の変更点に焦点を当てて解説します。

  1. Example 構造体の変更: Example 構造体に新しいフィールド Play *ast.File が追加されました。

    type Example struct {
        Name     string // name of the item being exemplified
        Doc      string // example function doc string
        Code     ast.Node
        Play     *ast.File // a whole program version of the example
        Comments []*ast.CommentGroup
        Output   string // expected output
    }
    

    この Play フィールドは、Example関数から合成された package main プログラムの抽象構文木 (AST) を保持します。これにより、go/doc パッケージの利用者は、Exampleの元のコード (Code) だけでなく、実行可能な完全なプログラム (Play) にもアクセスできるようになります。

  2. Examples 関数内の Play フィールドへの値の割り当て: Examples 関数内でExample関数が解析される際に、新しく追加された playExample 関数が呼び出され、その結果が Example 構造体の Play フィールドに割り当てられます。

    // ...
    			Examples = append(Examples, &Example{
    				Name:     name[len("Example"):],
    				Doc:      doc,
    				Code:     f.Body,
    				Play:     playExample(file, f.Body), // ここでplayExampleが呼び出される
    				Comments: file.Comments,
    				Output:   exampleOutput(f, file.Comments),
    			})
    // ...
    
  3. playExample 関数の追加: これがこのコミットの核心となる新しい関数です。

    // playExample synthesizes a new *ast.File based on the provided
    // file with the provided function body as the body of main.
    func playExample(file *ast.File, body *ast.BlockStmt) *ast.File {
        // ... (上記「技術的詳細」で説明したロジック)
    }
    

    この関数は、Example関数の本体 (body) と、それが含まれる元のファイル (file) のASTを受け取ります。そして、このExample関数の本体を main 関数として、必要なインポート文を合成し、最終的に package main を持つ完全なGoプログラムのAST (*ast.File) を生成して返します。

    • インポートの解決ロジック: ast.Inspect を使用してExample関数の本体を走査し、fmt.Printlnfmt のようなセレクタ式 (ast.SelectorExpr) の中で、未解決の識別子 (id.Obj == nil) を探します。 その後、元のファイルのインポート宣言 (file.Imports) を調べ、未解決の識別子名 (id.Name) とインポートパスのベース名 (path.Base(p)) が一致するかどうかで、必要なインポートパスを特定します。

    • ASTの合成: ast.GenDecl (import宣言用) と ast.FuncDecl (main関数用) を手動で構築し、それらを新しい ast.FileDecls (宣言リスト) に追加します。 LparenRparen を非ゼロに設定することで、go/printer パッケージがこのインポート宣言をファクタリングされた形式 (複数のインポートが括弧で囲まれる形式) で出力するようにします。

    • コメントの処理: Example関数の本体の範囲外にあるコメントは、合成されるASTから除外されます。これは、合成されたプログラムがExample関数に直接関連するコードのみを含むようにするためです。

この変更により、go/doc パッケージは、Example関数を単なるコードスニペットとしてだけでなく、Go Playgroundなどで直接実行可能な完全なプログラムとして提供できるようになりました。

関連リンク

  • Gerrit Change-ID: https://golang.org/cl/6525046 これはGoプロジェクトのコードレビューシステムであるGerritの変更リストへのリンクです。このリンクを辿ることで、このコミットがマージされるまでの議論や、関連する変更履歴を確認できます。

参考にした情報源リンク

  • Go言語のExample関数に関する公式ドキュメントやチュートリアル (一般的な知識として)
  • Go言語のgo/astパッケージのドキュメント (AST操作の理解のため)
  • Go Playgroundの動作原理 (合成されたコードの利用目的の理解のため)
  • Go言語のgo/docパッケージのドキュメント (パッケージの役割の理解のため)
  • Go言語のpackage mainfunc mainに関する基本概念 (一般的な知識として)
  • strconv.Unquote および strconv.Quote のGoドキュメント (文字列リテラルの処理のため)
  • path.Base のGoドキュメント (インポートパスのベース名取得のため)