[インデックス 1801] ファイルの概要
このコミットは、Go言語の初期の「pretty」パッケージにおける変更を記録しています。このパッケージは、Goのソースコードを整形して表示したり、ドキュメントを生成したりするためのツールであると推測されます。具体的には、以下の4つのファイルが変更されています。
usr/gri/pretty/ast.go
: 抽象構文木(AST)の定義に関連するファイル。usr/gri/pretty/parser.go
: Goソースコードを解析し、ASTを構築するパーサーに関連するファイル。usr/gri/pretty/printer.go
: ASTを整形して出力するプリンターに関連するファイル。usr/gri/pretty/template.html
: プリンターが出力するHTMLのテンプレートファイル。
これらの変更は、特にGoパッケージの「インターフェース」(外部に公開される要素)の表示方法を改善し、エクスポートされた関数名をリストアップすることに焦点を当てています。
コミット
- コミットハッシュ:
61815b8316d59b13dcf7542977e0239f3dc2b7bc
- Author: Robert Griesemer gri@golang.org
- Date: Tue Mar 10 18:20:08 2009 -0700
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/61815b8316d59b13dcf7542977e0239f3dc2b7bc
元コミット内容
snapshot of today
(little progress with interface printing, but now shows a
list of exported function names)
R=r
OCL=26082
CL=26082
変更の背景
このコミットは、Go言語の初期開発段階における「pretty」パッケージの進化の一部です。コミットメッセージにある「interface printing」とは、Goパッケージが外部に公開するAPI(関数、型など)をどのように表現するか、という課題を指しています。以前の実装では、インターフェースの表示が不十分であったか、あるいは単にすべての関数宣言を羅列するだけだった可能性があります。
この変更の主な目的は、パッケージのインターフェースをより分かりやすく、特に「エクスポートされた関数名」のリストとして提示することです。これにより、生成されるドキュメントや整形されたコードが、ユーザーにとってより有用な情報を提供するようになります。これは、Go言語の設計思想である「シンプルさ」と「明確さ」を反映し、コードの可読性と理解を深めるための初期の取り組みの一環と考えられます。
前提知識の解説
このコミットを理解するためには、以下の概念が重要です。
-
抽象構文木 (Abstract Syntax Tree, AST): ASTは、ソースコードの構造を木構造で表現したものです。コンパイラやリンター、コード整形ツールなどは、まずソースコードをASTに変換し、そのASTを操作することで様々な処理を行います。Go言語には標準ライブラリとして
go/ast
パッケージがあり、GoコードのASTを扱うための型と関数を提供しています。このコミットで変更されているast.go
ファイルは、この「pretty」パッケージ独自のAST構造を定義しているものと推測されます。 -
パーサー (Parser): パーサーは、ソースコード(トークンのストリーム)を入力として受け取り、その文法構造を解析してASTを生成するプログラムです。
parser.go
ファイルは、Goコードを解析し、ast.go
で定義されたAST構造に変換するロジックを含んでいます。 -
プリンター (Printer): プリンターは、ASTを入力として受け取り、それを人間が読める形式(この場合は整形されたGoコードやHTML)で出力するプログラムです。
printer.go
ファイルは、ASTを走査し、指定されたフォーマットで出力するロジックを含んでいます。 -
Go言語におけるエクスポートされた識別子 (Exported Identifiers): Go言語では、識別子(変数名、関数名、型名など)の最初の文字が大文字である場合、その識別子はパッケージ外からアクセス可能(エクスポートされている)と見なされます。小文字で始まる識別子は、そのパッケージ内でのみ使用可能なプライベートな要素です。このルールは、Goのモジュール性とAPI設計において非常に重要です。このコミットでは、このルールを適用してエクスポートされた関数を識別しています。
-
utf8
およびunicode
パッケージ: Goの標準ライブラリであるutf8
パッケージはUTF-8エンコードされたテキストを操作するための関数を提供し、unicode
パッケージはUnicode文字のプロパティ(例えば、文字が大文字かどうか)をテストするための関数を提供します。エクスポートされた識別子を正確に判断するためには、これらのパッケージが不可欠です。
技術的詳細
このコミットの技術的な変更点は多岐にわたりますが、特に「インターフェースの表示」と「コードの整形」に関する改善が見られます。
-
ast.go
の変更:FuncDecl
構造体内のフィールド名がPos_
からPos
に変更されました。これは、Goの標準的なASTパッケージ(go/ast
)における命名規則に合わせたものと考えられます。Pos
は、ソースコード内での要素の開始位置を示す一般的なフィールド名です。
-
parser.go
の変更:- ヘルパー関数の削除とインライン化:
unimplemented()
,assert(pred bool)
,OptSemicolon()
といったヘルパー関数が削除されました。unimplemented()
とassert()
の削除は、コードベースの成熟に伴い、これらのデバッグ/開発用関数が不要になったか、より堅牢なエラーハンドリングメカニズムに置き換えられたことを示唆しています。OptSemicolon()
は、Goの文法でセミコロンが省略可能な箇所(例えば、ステートメントの終わりや構造体フィールドの区切り)でセミコロンを消費するための関数でした。この関数が削除され、そのロジックが呼び出し元に直接インライン化されました。具体的には、parseStructType()
,parseImportDecls()
,ParseProgram()
内で、P.OptSemicolon();
がif P.tok == token.SEMICOLON { P.next(); }
に置き換えられています。これは、関数の呼び出しオーバーヘッドを削減するか、あるいはコードの可読性を向上させるためのリファクタリングの一環と考えられます。
parseStringLit()
の変更:assert(P.tok == token.STRING);
が削除され、P.next();
がP.expect(token.STRING);
に置き換えられました。これは、P.expect()
関数が内部でトークンのアサーションと消費の両方を処理するようになったことを示唆しています。これにより、パーサーのコードがより簡潔になります。
- ヘルパー関数の削除とインライン化:
-
printer.go
の変更:- 新しいインポート:
utf8
とunicode
パッケージがインポートされました。これは、Goの識別子がエクスポートされているかどうかを判断するために必要となる、Unicode文字のプロパティを扱うためのものです。 funcDecl
の変更:P.Token(d.Pos_, token.FUNC);
がP.Token(d.Pos, token.FUNC);
に変更されました。これはast.go
の変更と同期しています。Interface(p *ast.Program)
関数の大幅な変更:- この関数は、パッケージのインターフェースを生成する役割を担っています。以前は、単にすべての関数宣言を(
P.funcDecl(fun, false)
を使って)出力していたようです。 - 新しい実装では、
switch
文を使用してASTノードの型をチェックし、特に*ast.FuncDecl
(関数宣言)を処理します。 isExported
ヘルパー関数の導入: このコミットの最も重要な変更点の一つは、isExported(name *ast.Ident) bool
関数の導入です。この関数は、Go言語のルールに従って、識別子(関数名など)がエクスポートされているかどうかを判断します。具体的には、utf8.DecodeRuneInString
を使って識別子文字列の最初のUnicodeルーン(文字)を取得し、unicode.IsUpper
を使ってそのルーンが大文字であるかをチェックします。- エクスポートされた関数のリスト表示:
Interface
関数内で、関数宣言がエクスポートされていると判断された場合(isExported(d.Ident)
がtrueの場合)、その関数名が<h2>
タグで囲まれて出力されるようになりました(P.Printf("<h2>%s</h2>\n", d.Ident.Str);
)。これにより、生成されるHTMLドキュメントに、パッケージのエクスポートされた関数の一覧が明確に表示されるようになります。 - コメントアウトされたコードブロック(
P.Printf("<code>"); P.funcDecl(d, false); P.String(0, ""); P.Printf("</code></p>");
)は、エクスポートされた関数の完全なシグネチャも表示する予定であったことを示唆しています。これは、このコミットが「インターフェースの表示」に関する進行中の作業のスナップショットであることを裏付けています。
- この関数は、パッケージのインターフェースを生成する役割を担っています。以前は、単にすべての関数宣言を(
- 新しいインポート:
-
template.html
の変更:<h1><!--PACKAGE--></h1>
が<h1>package <!--PACKAGE--></h1>
に変更され、パッケージ名の前に「package」というキーワードが追加されました。これは純粋に表示上の改善です。<!--INTERFACE-->
という新しいプレースホルダーが追加されました。これは、printer.go
のInterface
関数によって生成された内容が、この位置に挿入されることを意味します。<!--INTERFACE-->
の後に<hr />
(水平線)と、再度<h1>package <!--PACKAGE--></h1>
が追加されています。これは、エクスポートされたインターフェースのリストと、パッケージのメインコンテンツ(<!--BODY-->
で表される)との間に視覚的な区切りを設け、構造を明確にするためのものです。
これらの変更は、Goのコードベースを解析し、その構造を理解しやすくするための初期のツール開発における重要なステップを示しています。特に、Goのエクスポートルールを適用して、公開APIの概要を自動生成する機能の基礎が築かれました。
コアとなるコードの変更箇所
usr/gri/pretty/parser.go
からの変更 (セミコロン処理のインライン化の例)
--- a/usr/gri/pretty/parser.go
+++ b/usr/gri/pretty/parser.go
@@ -662,7 +641,9 @@ func (P *Parser) parseStructType() ast.Expr {
break;
}
}
- P.OptSemicolon();
+ if P.tok == token.SEMICOLON {
+ P.next();
+ }
end = P.pos;
P.expect(token.RBRACE);
@@ -1605,7 +1585,9 @@ func (P *Parser) Parser) parseImportDecls() *vector.Vector {
list := vector.New(0);
for P.tok == token.IMPORT {
list.Push(P.parseDecl(token.IMPORT));
- P.OptSemicolon();
+ if P.tok == token.SEMICOLON {
+ P.next();
+ }
}
return list;
@@ -1651,7 +1633,9 @@ func (P *Parser) ParseProgram() *ast.Program {
list := P.parseImportDecls();
for P.tok != token.EOF {
list.Push(P.parseDeclaration());
- P.OptSemicolon();
+ if P.tok == token.SEMICOLON {
+ P.next();
+ }
}
// convert list
usr/gri/pretty/printer.go
からの変更 (エクスポートされた関数の検出と表示)
--- a/usr/gri/pretty/printer.go
+++ b/usr/gri/pretty/printer.go
@@ -16,6 +16,8 @@ import (
"token";
"ast";
"template";
+ "utf8";
+ "unicode";
SymbolTable "symboltable";
)
@@ -1079,14 +1081,28 @@ func (P *Printer) Decl(d ast.Decl) {
// ----------------------------------------------------------------------------
-// Interface
+// Package interface
+
+// TODO this should be an AST method
+func isExported(name *ast.Ident) bool {
+ ch, len := utf8.DecodeRuneInString(name.Str, 0);
+ return unicode.IsUpper(ch);
+}
+
+
func (P *Printer) Interface(p *ast.Program) {
for i := 0; i < len(p.Decls); i++ {
- decl := p.Decls[i];
- // TODO use type switch
- if fun, is_fun := decl.(*ast.FuncDecl); is_fun {
- P.funcDecl(fun, false);
+ switch d := p.Decls[i].(type) {
+ case *ast.FuncDecl:
+ if isExported(d.Ident) {
+ P.Printf("<h2>%s</h2>\n", d.Ident.Str);
+ /*
+ P.Printf("<p><code>");
+ P.funcDecl(d, false);
+ P.String(0, "");
+ P.Printf("</code></p>");
+ */
+ }
}
}
}
usr/gri/pretty/template.html
からの変更 (インターフェースのプレースホルダー追加)
--- a/usr/gri/pretty/template.html
+++ b/usr/gri/pretty/template.html
@@ -1,5 +1,11 @@
-<h1><!--PACKAGE--></h1>
+<h1>package <!--PACKAGE--></h1>
+
+<!--INTERFACE-->
+
+<hr />
+
+<h1>package <!--PACKAGE--></h1>
<pre>
<!--BODY-->
コアとなるコードの解説
parser.go
の変更
parser.go
におけるセミコロン処理の変更は、P.OptSemicolon()
というヘルパー関数を削除し、そのロジックを直接呼び出し元にインライン化したものです。
- 変更前:
P.OptSemicolon();
- これは、
OptSemicolon
という関数を呼び出し、その関数内で現在のトークンがセミコロンであれば消費するという処理を行っていました。
- これは、
- 変更後:
if P.tok == token.SEMICOLON { P.next(); }
- これは、現在のパーサーのトークン(
P.tok
)がtoken.SEMICOLON
であるかを直接チェックし、もしそうであれば次のトークンに進む(P.next()
)という処理をその場で行っています。
- これは、現在のパーサーのトークン(
この変更の意図は、小さなヘルパー関数の呼び出しオーバーヘッドを削減し、コードのフローをより直接的にすることにあると考えられます。また、パーサーの内部ロジックをより明確にする効果もあります。
printer.go
の変更
printer.go
における変更は、このコミットの核心部分であり、Goパッケージのインターフェース表示を改善するためのものです。
-
isExported
関数の導入:func isExported(name *ast.Ident) bool { ch, len := utf8.DecodeRuneInString(name.Str, 0); return unicode.IsUpper(ch); }
この関数は、Go言語の識別子のエクスポートルールを実装しています。
name *ast.Ident
: ASTの識別子ノードを引数として受け取ります。name.Str
は識別子の文字列値です。utf8.DecodeRuneInString(name.Str, 0)
: 識別子文字列の先頭から最初のUnicodeルーン(文字)をデコードします。Goの識別子はUTF-8でエンコードされているため、マルチバイト文字も正しく扱えます。unicode.IsUpper(ch)
: デコードされたルーンch
が大文字であるかをチェックします。Goでは、識別子の最初の文字が大文字であればエクスポートされます。- この関数は、識別子がエクスポートされていれば
true
を、そうでなければfalse
を返します。
-
Interface
関数の変更:func (P *Printer) Interface(p *ast.Program) { for i := 0; i < len(p.Decls); i++ { switch d := p.Decls[i].(type) { case *ast.FuncDecl: if isExported(d.Ident) { P.Printf("<h2>%s</h2>\n", d.Ident.Str); /* P.Printf("<p><code>"); P.funcDecl(d, false); P.String(0, ""); P.Printf("</code></p>"); */ } } } }
この関数は、プログラムの宣言(
p.Decls
)を走査し、その中で関数宣言(*ast.FuncDecl
)を見つけます。switch d := p.Decls[i].(type)
: 型アサーションとswitch
文を組み合わせて、宣言の具体的な型を効率的に判別しています。case *ast.FuncDecl
: 宣言が関数宣言である場合にこのブロックが実行されます。if isExported(d.Ident)
: 先ほど解説したisExported
関数を使って、その関数がエクスポートされているかどうかをチェックします。P.Printf("<h2>%s</h2>\n", d.Ident.Str);
: もし関数がエクスポートされていれば、その関数名をHTMLの<h2>
タグで囲んで出力します。これにより、生成されるHTMLドキュメントに、エクスポートされた関数名が目立つ形でリストアップされます。- コメントアウトされた部分は、将来的にエクスポートされた関数の完全なシグネチャも表示する可能性を示唆しています。
template.html
の変更
template.html
の変更は、printer.go
で生成されたインターフェース情報を組み込むためのものです。
<!--INTERFACE-->
という新しいプレースホルダーが追加されました。printer.go
のInterface
関数が生成するHTMLコンテンツは、このプレースホルダーの位置に挿入されます。- これにより、生成されるHTMLドキュメントは、パッケージ名、エクスポートされたインターフェースのリスト、そしてパッケージの本体という構造を持つようになります。これは、Goパッケージのドキュメント生成における初期の試みであり、より構造化された情報提供を目指していることがわかります。
これらの変更は、Go言語のコードベースを解析し、その構造を理解しやすくするための初期のツール開発における重要なステップを示しています。特に、Goのエクスポートルールを適用して、公開APIの概要を自動生成する機能の基礎が築かれました。
関連リンク
- Go言語の公式ドキュメント: https://go.dev/doc/
go/ast
パッケージのドキュメント: https://pkg.go.dev/go/ast (現在のGoのASTパッケージ)go/parser
パッケージのドキュメント: https://pkg.go.dev/go/parser (現在のGoのパーサーパッケージ)go/printer
パッケージのドキュメント: https://pkg.go.dev/go/printer (現在のGoのプリンターパッケージ)unicode
パッケージのドキュメント: https://pkg.go.dev/unicodeutf8
パッケージのドキュメント: https://pkg.go.dev/unicode/utf8
参考にした情報源リンク
- Go言語の公式ドキュメント (上記に記載)
- Go言語のソースコード (GitHub): https://github.com/golang/go
- Go言語におけるエクスポートのルールに関する一般的な情報源 (例: Go by Example - Exported names): https://gobyexample.com/exported-names
- Unicodeの概念とUTF-8エンコーディングに関する一般的な情報源