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

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

このコミットは、Go言語の初期開発段階におけるドキュメンテーションサーバーの重要な変更を記録しています。具体的には、既存のgds(Go Documentation Server)をgodocという名称に刷新し、内部的なAST(抽象構文木)の変更に対応させるとともに、パッケージの検索と提供機能を追加しています。これにより、Goのソースコードから自動的にドキュメントを生成し、Webブラウザを通じて閲覧できる現在のgodocツールの基礎が築かれました。

コミット

commit 695c90daa0d9408e5144bf94ad797b33fbdb5a8f
Author: Robert Griesemer <gri@golang.org>
Date:   Thu Apr 2 15:58:58 2009 -0700

    - adjustments for changed AST
    - renamed gds -> godoc
    - functionality to find and serve packages
      (to get a list of packages provide dir path + "?p")

    Next steps: cleanups, better formatting, fine-tuning of output

    R=r
    OCL=27037
    CL=27039

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

https://github.com/golang/go/commit/695c90daa0d9408e5144bf94ad797b33fbdb5a8f

元コミット内容

このコミットの元々の内容は以下の3点に集約されます。

  1. AST(抽象構文木)の変更への対応: Goコンパイラやツールチェインの内部で利用されるASTの構造が変更されたことに伴い、ドキュメンテーションサーバーもその変更に適応するための調整が行われました。
  2. gdsからgodocへの名称変更: 以前はgdsという名前であったドキュメンテーションサーバーがgodocに改名されました。これは、Goコミュニティにおけるこのツールの標準的な名称として定着することになります。
  3. パッケージ検索・提供機能の追加: ディレクトリパスに?pクエリパラメータを付与することで、そのディレクトリ内のパッケージリストを取得し、提供する機能が追加されました。これにより、Goのソースコードベースを探索し、ドキュメントを生成する能力が向上しました。

変更の背景

このコミットが行われた2009年4月は、Go言語がまだ一般に公開される前の初期開発段階でした。この時期は、言語仕様、コンパイラ、標準ライブラリ、そして開発ツールの基礎が精力的に構築されていたフェーズです。

変更の背景には、以下の要因が考えられます。

  • 言語仕様の進化とASTの安定化: Go言語の仕様は初期段階で頻繁に変更されており、それに伴いコンパイラが生成するASTの構造も進化していました。ドキュメンテーションツールはASTを解析して情報を抽出するため、ASTの変更に追随する必要がありました。このコミットは、ASTの特定の変更(例えば、識別子やリテラルの表現方法)に対応するためのものです。
  • ツールの体系化とブランド化: gdsという名称は汎用的であり、Go言語の公式ドキュメンテーションツールとしてのアイデンティティを確立するためには、よりGoらしい、覚えやすい名称が必要でした。godocという名称は、Go言語のドキュメントを扱うツールであることを明確に示し、その後のGoエコシステムにおける重要なツールとしてのブランドを確立しました。
  • 開発効率の向上: Go言語のプロジェクトが成長するにつれて、コードベースの理解とナビゲーションの重要性が増していました。ソースコードから直接ドキュメントを生成し、Webインターフェースで提供するgodocのようなツールは、開発者がGoの標準ライブラリやサードパーティパッケージのAPIを素早く参照し、理解する上で不可欠です。パッケージの検索・提供機能は、この開発効率向上に直接貢献します。

前提知識の解説

このコミットの理解には、以下の概念に関する前提知識が役立ちます。

  • AST (Abstract Syntax Tree - 抽象構文木): プログラミング言語のソースコードを解析して得られる、プログラムの構造を木構造で表現したものです。コンパイラやリンター、コード分析ツールなどは、このASTを利用してコードの意味を理解し、処理を行います。Go言語のツールチェインでは、go/astパッケージがASTの定義と操作を提供します。
  • Go言語のパッケージシステム: Go言語は、コードをpackageという単位で整理します。各Goファイルは必ずpackage宣言を持ち、関連する機能は同じパッケージにまとめられます。パッケージは、コードの再利用性、モジュール性、名前空間の管理に貢献します。
  • godocツール: Go言語の公式ドキュメンテーションツールです。Goのソースコード内のコメント(特にエクスポートされた識別子の直前のコメント)を解析し、HTML形式のドキュメントを自動生成します。これにより、開発者はコードとドキュメントを密接に連携させることができます。godocは、ローカルでドキュメントサーバーを起動してブラウザで閲覧することも、静的なHTMLファイルを生成することも可能です。
  • httpパッケージ (Go標準ライブラリ): Go言語の標準ライブラリに含まれるHTTPクライアントおよびサーバー機能を提供するパッケージです。このコミットでは、godocがWebサーバーとして機能するためにhttpパッケージが利用されています。
  • templateパッケージ (Go標準ライブラリ): Go言語の標準ライブラリに含まれる、HTMLなどのテキストテンプレートを扱うためのパッケージです。このコミットでは、godocがWebページを動的に生成するためにテンプレートエンジンを使用しています。
  • osパッケージ (Go標準ライブラリ): オペレーティングシステムと対話するための機能(ファイルシステム操作、環境変数など)を提供するパッケージです。godocがファイルシステムを走査してGoソースファイルやディレクトリを読み込むために使用されます。
  • utf8パッケージ (Go標準ライブラリ): UTF-8エンコーディングされたテキストを扱うためのユーティリティを提供するパッケージです。Goの識別子や文字列リテラルはUTF-8で表現されるため、文字のデコードに利用されます。
  • unicodeパッケージ (Go標準ライブラリ): Unicode文字のプロパティ(例えば、大文字であるか否か)を扱うためのパッケージです。Goのエクスポートされた識別子は大文字で始まるという規則があるため、このパッケージが利用されます。

技術的詳細

このコミットの技術的詳細は、主に以下の3つの側面から分析できます。

  1. AST構造の変更への対応 (ast.IdentLitからValueへの変更):

    • コミットの差分を見ると、astprinter.godocprinter.goにおいて、ast.Ident(識別子)のフィールドがLitからValueに変更されている箇所が多数見られます。
    • 例えば、name.Litname.Valueに、x.Litx.Valueに、string(t.Lit)string(t.Value)に変更されています。
    • これは、Go言語のAST表現において、識別子やリテラルの「文字列表現」を保持するフィールドの名称が変更されたことを意味します。初期のGoコンパイラでは、リテラル値がLit(Literalの略)というフィールドに格納されていた可能性がありますが、より汎用的なValueという名称に変更されたと考えられます。この変更は、ASTの設計がより洗練され、安定化に向かっていたことを示唆しています。
    • また、utf8.DecodeRune(name.Lit)utf8.DecodeRuneInString(name.Value, 0)に変更されています。これは、utf8.DecodeRune[]byteを受け取るのに対し、utf8.DecodeRuneInStringstringを受け取る関数であり、ast.Identのフィールドが[]byteからstringに変更されたことを示唆しています。これにより、文字列操作がより直接的かつ安全に行えるようになりました。
  2. gdsからgodocへのリファクタリングと機能拡張:

    • usr/gri/pretty/gds.goが削除され、代わりにusr/gri/pretty/godoc.goが新規作成されています。これは単なるファイル名の変更ではなく、既存のコードベースを基盤としつつ、大幅な機能追加と構造の改善が行われたことを示しています。
    • 新しいgodoc.goには、パッケージを検索し、そのドキュメントを提供するロジックが追加されています。
    • makePackageMap()関数が導入され、Goのソースツリーを走査してパッケージ情報を収集し、pakMap(パッケージ名からパッケージ記述子へのマップ)とpakList(ソートされたパッケージのリスト)を構築しています。これは、godocがGoのソースコードベース全体を理解し、効率的にパッケージを検索できるようにするための重要なステップです。
    • addDirectory()addFile()関数は、再帰的にディレクトリを走査し、Goファイルを解析してパッケージ情報を抽出する役割を担っています。
    • serveGoPackage()関数は、特定のGoパッケージのドキュメントを生成して提供します。
    • servePackageList()関数は、検索条件に合致するパッケージのリストをHTML形式で表示します。
    • regexpパッケージがインポートされ、パッケージ検索に正規表現が利用できるようになっています。これにより、ユーザーは柔軟な条件でパッケージを検索できるようになりました。
  3. Webサーバーとしての機能強化:

    • godocはHTTPサーバーとして動作し、http.Handle("/", http.HandlerFunc(serve))でルートパスに対するリクエストをserve関数で処理するように設定されています。
    • serve関数は、リクエストのURLパスとクエリパラメータを解析し、ディレクトリのリスト、個別のGoファイルのドキュメント、またはパッケージのリスト/ドキュメントを提供します。
    • 特に、req.Url.Queryが存在する場合(例: ?pクエリパラメータ)、servePackage関数が呼び出され、パッケージ関連の機能が提供されるようになっています。
    • packages_template.htmlという新しいHTMLテンプレートが追加されており、パッケージリストの表示に使用されます。これにより、godocの出力がより構造化され、視覚的に分かりやすくなりました。

これらの変更は、godocが単なるファイルビューアから、Goエコシステムにおける強力なドキュメンテーションおよびコード探索ツールへと進化する上で不可欠なものでした。

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

このコミットにおけるコアとなるコードの変更箇所は以下のファイルに集中しています。

  1. usr/gri/pretty/astprinter.go:

    • ast.IdentLitフィールドからValueフィールドへの参照変更。
    • utf8.DecodeRuneからutf8.DecodeRuneInStringへの変更。
    • リテラル(IntLit, FloatLit, CharLit, StringLit)のLitフィールドからValueフィールドへの参照変更。
    • HtmlPackageNameにおけるd.Path[0].Litからd.Path[0].Valueへの変更。
  2. usr/gri/pretty/docprinter.go:

    • ast.IdentLitフィールドからValueフィールドへの参照変更。
    • utf8.DecodeRuneからutf8.DecodeRuneInStringへの変更。
    • baseTypeName関数におけるt.Litからt.Valueへの変更。
    • addTypeaddFuncAddProgram関数におけるLitフィールドからValueフィールドへの参照変更。
    • funcDoctypeDocprintメソッドにおけるLitフィールドからValueフィールドへの参照変更。
  3. usr/gri/pretty/gds.go:

    • ファイル全体が削除されました。
  4. usr/gri/pretty/godoc.go:

    • ファイル全体が新規作成されました。これが新しいgodocサーバーの実装です。
    • 特に、makePackageMap()addDirectory()addFile()serveGoPackage()servePackageList()servePackage()といったパッケージ関連の新しい関数が追加されています。
    • regexpパッケージのインポートと利用。
  5. usr/gri/pretty/Makefile:

    • gdsターゲットがgodocターゲットに名称変更され、関連するビルドコマンドも変更されました。
  6. usr/gri/pretty/packages_template.html:

    • パッケージリスト表示用の新しいHTMLテンプレートファイルが追加されました。

コアとなるコードの解説

AST変更への対応例 (astprinter.goより)

// Before:
func isExported(name *ast.Ident) bool {
	ch, len := utf8.DecodeRune(name.Lit);
	return unicode.IsUpper(ch);
}

// After:
func isExported(name *ast.Ident) bool {
	ch, len := utf8.DecodeRuneInString(name.Value, 0);
	return unicode.IsUpper(ch);
}

この変更は、ast.Ident構造体の内部表現がLit[]byte型と推測される)からValuestring型と推測される)に変わったことを示しています。utf8.DecodeRune[]byteスライスからルーンをデコードしますが、utf8.DecodeRuneInStringstringからデコードします。この変更により、ASTの識別子フィールドがよりGoの文字列型に即した形になったことがわかります。

gdsからgodocへの移行とパッケージ機能の追加 (godoc.goより)

godoc.goは、以前のgds.goの機能を継承しつつ、大幅に拡張されています。

// 新規追加されたパッケージ関連のデータ構造
type pakDesc struct {
	dirname string;  // local to *root
	pakname string;  // local to directory
	filenames map[string] bool;  // set of file (names) belonging to this package
}

var (
	pakMap map[string]*pakDesc;  // dirname/pakname -> package descriptor
	pakList pakArray;  // sorted list of packages; in sync with pakMap
)

// パッケージマップを構築する主要な関数
func makePackageMap() {
	// TODO shold do this under a lock, eventually
	// populate package map
	pakMap = make(map[string]*pakDesc);
	addDirectory(""); // ルートディレクトリから再帰的にパッケージを収集
	
	// build sorted package list
	pakList = make([]*pakDesc, len(pakMap));
	i := 0;
	for tmp, pakdesc := range pakMap {
		pakList[i] = pakdesc;
		i++;
	}
	sort.Sort(pakList); // パッケージリストをソート
}

// ディレクトリを走査し、GoファイルをaddFileに渡す
func addDirectory(dirname string) {
	// ... (ディレクトリのオープン、読み込み、再帰処理) ...
	for i, entry := range list {
		switch {
		case entry.IsDirectory():
			if entry.Name != "." && entry.Name != ".." {
				addDirectory(dirname + "/" + entry.Name);
			}
		case isGoFile(&entry):	
			addFile(dirname, entry.Name); // Goファイルを見つけたらaddFileに渡す
		}
	}
}

// Goファイルを解析し、パッケージ情報をpakMapに追加する
func addFile(dirname string, filename string) {
	// determine package name
	prog := getAST(dirname, filename, parser.PackageClauseOnly); // ASTを解析してパッケージ名を取得
	if prog == nil {
		return;
	}
	if prog.Name.Value == "main" {
		// ignore main packages for now
		return;
	}
	pakname := dirname + "/" + prog.Name.Value; // パッケージのユニークな識別子を生成

	// find package descriptor
	pakdesc, found := pakMap[pakname];
	if !found {
		// add a new descriptor
		pakdesc = &pakDesc{dirname, prog.Name.Value, make(map[string]bool)};
		pakMap[pakname] = pakdesc;
	}
	
	// add file to package desc
	if tmp, found := pakdesc.filenames[filename]; found {
		panic("internal error: same file added more then once: " + filename);
	}
	pakdesc.filenames[filename] = true; // ファイルをパッケージ記述子に追加
}

// HTTPリクエストを処理するメインのハンドラ
func serve(c *http.Conn, req *http.Request) {
	// ... (ログ出力、パスのサニタイズ) ...

	if len(req.Url.Query) > 0 {  // クエリパラメータがある場合
		servePackage(c, path); // パッケージ関連の処理
	} else {
		serveFile(c, path); // ファイルまたはディレクトリの処理
	}
}

// パッケージ検索と表示を制御する関数
func servePackage(c *http.Conn, path string) {
	// make regexp for package matching
	rex, err := regexp.Compile(path); // パスを正規表現としてコンパイル
	// ... (エラーハンドリング) ...

	// build list of matching packages
	list := vector.New(0);
	for i, p := range pakList {
		if rex.Match(p.dirname + "/" + p.pakname) { // 正規表現にマッチするパッケージを収集
			list.Push(p);
		}
	}

	if list.Len() == 1 {
		serveGoPackage(c, list.At(0).(*pakDesc)); // マッチが1つならそのパッケージのドキュメントを表示
	} else {
		servePackageList(c, list); // 複数マッチならパッケージリストを表示
	}
}

これらのコードは、godocがGoのソースツリーを走査し、パッケージ構造を理解し、それに基づいてドキュメントを生成・提供する仕組みの核心を示しています。特に、makePackageMapによる初期のパッケージ情報の収集と、servePackageによる柔軟なパッケージ検索機能は、godocの利便性を大きく向上させました。

関連リンク

参考にした情報源リンク

  • Go言語の初期開発に関する情報(Goの歴史など)
  • Go言語のASTの進化に関する議論やドキュメント
  • godocツールの設計思想に関する記事やプレゼンテーション
  • Go言語のhttptemplateosutf8unicodeパッケージの公式ドキュメント

(注:具体的な情報源URLは、当時の情報がウェブ上に残っているか、またその情報が公開されているかによって異なります。この解説は、一般的なGo言語の知識と、コミット内容から推測される背景に基づいて記述されています。)