[インデックス 1829] ファイルの概要
このコミットは、Go言語の初期開発段階におけるpretty
ツール(コード整形、解析、ドキュメント生成などを目的としたツールと推測される)に対する重要な改善とリファクタリングを含んでいます。主な変更点は、Go言語仕様の更新に合わせたパーサーの修正、ドキュメントサーバーにおけるHTMLテンプレートの導入、そしてコンパイルエラーの表示方法の改善です。
コミット
commit ec77e75e5d5430e06ed22cc0886544b568d71687
Author: Robert Griesemer <gri@golang.org>
Date: Fri Mar 13 16:59:51 2009 -0700
daily snapshot:
- various parser fixes to match updated spec (&&, &^=, label decls, const decls)
- using html template for directory and error page in doc server
- show compile errors inplace in the source
- cleanups
R=rsc
OCL=26287
CL=26287
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/ec77e75e5d5430e06ed22cc0886544b568d71687
元コミット内容
このコミットは、Go言語の「daily snapshot」として記録されており、以下の主要な変更を含んでいます。
- パーサーの修正: 更新されたGo言語仕様に合わせるため、
&&
(論理AND)、&^=
(ビットクリア代入)、ラベル宣言、定数宣言など、様々なパーサーの修正が行われました。 - HTMLテンプレートの利用: ドキュメントサーバーにおいて、ディレクトリ一覧ページとエラーページにHTMLテンプレートが使用されるようになりました。
- コンパイルエラーのインプレース表示: コンパイルエラーがソースコード内に直接表示されるようになりました。
- クリーンアップ: 全体的なコードの整理と改善が行われました。
変更の背景
このコミットが行われた2009年3月は、Go言語がまだ一般公開される前の活発な開発期間でした。言語仕様は頻繁に更新され、それに伴いコンパイラやツールチェーンも継続的に調整されていました。
- 言語仕様の進化への追従: Go言語の仕様は初期段階で流動的であり、
&&
や&^=
のような演算子の挙動、ラベルの定義方法、定数宣言の構文などが洗練されていました。このコミットは、これらの仕様変更にpretty
ツールのパーサーを適合させる必要性から生まれました。 - 開発者体験の向上:
- エラー報告の改善: 従来のコンパイルエラー表示は、エラーメッセージが羅列されるだけだった可能性があります。開発者がエラーの原因を迅速に特定し、修正できるように、エラーをソースコードの該当箇所に直接表示する機能は、デバッグ効率を大幅に向上させます。
- ドキュメントサーバーの柔軟性: ドキュメントサーバーがHTMLテンプレートを使用することで、表示のカスタマイズが容易になり、将来的なUIの変更や機能追加に対応しやすくなります。これは、Go言語の公式ドキュメントやコードブラウザの基盤を強化する一歩と考えられます。
- コードベースの整理: 開発初期のコードベースでは、機能追加と並行してリファクタリングやクリーンアップが頻繁に行われます。このコミットも、AST(抽象構文木)のノード名の統一など、コードの可読性と保守性を高めるための整理を含んでいます。
前提知識の解説
このコミットを理解するためには、以下の概念が役立ちます。
-
Go言語のAST (Abstract Syntax Tree):
- ASTは、ソースコードの構文構造を木構造で表現したものです。コンパイラやリンター、コード整形ツールなどは、まずソースコードをASTに変換し、そのASTを操作することで様々な処理を行います。
- Go言語の
go/ast
パッケージは、GoプログラムのASTを定義しています。このコミットでは、usr/gri/pretty/ast.go
がその役割を担っており、Field
、LabelDecl
(後のLabeledStat
)、ImportDecl
、ConstDecl
、TypeDecl
、VarDecl
、FuncDecl
、Program
などの構造体がASTのノードを表しています。 Ident
(Identifier) は識別子(変数名、関数名など)を表す一般的なASTノードですが、このコミットではIdent
やIdents
がName
やNames
にリネームされており、より具体的な意味合いを持つように変更されています。
-
パーサー (Parser):
- パーサーは、字句解析器(lexer/scanner)によって生成されたトークン列を受け取り、それらを文法規則に従って解析し、ASTを構築する役割を担います。
- Go言語のパーサーは、言語仕様に厳密に従って構文解析を行います。仕様の変更は、パーサーのロジックに直接影響を与えます。
&&
(論理AND) や&^=
(ビットクリア代入) は、Go言語における演算子であり、その優先順位や結合規則はパーサーによって正しく解釈される必要があります。- ラベル付きステートメント (Labeled Statement): Go言語では、
label:
の形式でステートメントにラベルを付けることができます。これは主にbreak
やcontinue
文で特定のループやswitch
/select
文を制御するために使用されます。このコミットでは、ラベルが単独の宣言ではなく、特定のステートメントに付随するものとしてASTで表現されるように変更されました。
-
HTMLテンプレート:
- Webアプリケーションにおいて、HTMLの構造と動的に生成されるデータを分離するための技術です。テンプレートエンジンは、プレースホルダーを含むテンプレートファイルとデータを受け取り、最終的なHTMLを生成します。
- このコミットでは、Go言語の標準ライブラリに含まれる
html/template
パッケージ(またはその前身となるtemplate
パッケージ)が使用されています。これにより、ドキュメントサーバーの出力がより構造化され、保守しやすくなります。
-
コンパイルエラーの報告:
- コンパイラは、ソースコードの構文的または意味的な誤りを発見した場合、エラーメッセージを報告します。
- 効果的なエラー報告は、エラーの種類、発生箇所(ファイル名、行番号、列番号)、そして具体的なメッセージを含むべきです。
- 「インプレース表示」とは、エラーが発生したソースコードの行に直接エラーメッセージやハイライトを表示する手法を指します。
技術的詳細
1. ASTの構造変更と命名規則の統一
ast.go
:Field
構造体のIdents
フィールドがNames
にリネームされました。これは、フィールドが複数の識別子(例:x, y int
)を持つ場合に、それらが「名前」であることをより明確にするためと考えられます。LabelDecl
構造体がLabeledStat
にリネームされ、Stat Stat
フィールドが追加されました。これは、Go言語のラベルが単独で存在するものではなく、必ず何らかのステートメント(ループ、switch
、select
など)に付随するという言語仕様をASTで正確に表現するための重要な変更です。これにより、ASTはより意味的に正確な構造を持つことになります。ImportDecl
、ConstDecl
、TypeDecl
、VarDecl
、FuncDecl
、Program
といった宣言関連のASTノードで、識別子を表すフィールド名がIdent
からName
へ、またはIdents
からNames
へと一貫して変更されました。これは、AST全体の命名規則を統一し、可読性と保守性を向上させるためのリファクタリングです。
2. パーサーのロジック修正
parser.go
:parseSimpleStat
関数にmode
引数が導入され、label_ok
とrange_ok
というビットフラグが定義されました。これにより、パーサーが現在のコンテキストに応じて、ラベル付きステートメントやfor-range
ステートメントの解析を適切に行えるようになりました。- 特に、
token.COLON
(ラベル)の解析ロジックが大幅に修正され、ast.LabeledStat
を生成するように変更されました。これにより、パーサーはラベルとそれに続くステートメントを正しく関連付けてASTを構築します。 parseConstSpec
とparseVarSpec
における定数・変数宣言の解析ロジックが簡素化されました。これは、Go言語の宣言構文が初期段階で洗練されていく過程での調整と考えられます。例えば、var x int = 1
とvar x = 1
、var x int
のような多様な宣言形式をより堅牢に処理するための改善です。token.SEMICOLON
やtoken.RBRACE
が空のステートメントとして扱われるようになり、パーサーがより柔軟に構文を解釈できるようになりました。package
宣言の後に余分なセミコロンがある場合の共通エラーチェックが追加されました。これは、開発者が陥りやすい構文エラーに対するユーザビリティの向上です。
3. エラー報告システムの刷新
-
compilation.go
:Error
構造体(Loc scanner.Location; Msg string;
)とErrorList
型([]Error
)が新しく定義されました。ErrorList
はsort.Interface
を実装しており、エラーを発生位置(Loc.Pos
)でソートできるようになりました。errorHandler
構造体は、エラーの数を数えるnerrors
から、vector.Vector
(動的配列)を使ってError
オブジェクトを収集するerrors
フィールドを持つように変更されました。これにより、すべてのエラーを捕捉し、後で処理できるようになりました。Compile
関数の戻り値が、エラーの数(int
)からソートされたErrorList
に変更されました。これは、コンパイル結果としてエラーのリスト全体を返すことで、より詳細なエラー処理を可能にするための重要な変更です。
-
gds.go
:printErrors
という新しい関数が追加されました。この関数は、ソースファイルの内容を読み込み、error_template.html
テンプレートを使用してエラーページを生成します。printErrors
内で、Compilation.ErrorList
をイテレートし、各エラーの発生位置にエラーメッセージを挿入し、赤字でハイライト表示するロジックが実装されました。これにより、「コンパイルエラーのインプレース表示」が実現されました。serveFile
関数は、Compilation.Compile
から返されるErrorList
をチェックし、エラーが存在する場合はprintErrors
を呼び出して、ソースコード内にエラーを埋め込んだHTMLページをクライアントに返します。
4. ドキュメントサーバーのHTMLテンプレート化
dir_template.html
(新規): ディレクトリ一覧を表示するためのHTMLテンプレート。<!--PATH-->
、<!--DIRECTORIES-->
、<!--GO FILES-->
、<!--OTHER FILES-->
といったプレースホルダーを含みます。error_template.html
(新規): コンパイルエラーを表示するためのHTMLテンプレート。<!--FILE_NAME-->
、<!--ERRORS-->
といったプレースホルダーを含みます。gds.go
:- Go言語の
template
パッケージ(またはその初期バージョン)がインポートされ、dir_template.html
とerror_template.html
がそれぞれdir_template
とerror_template
というtemplate.Template
オブジェクトとしてロードされるようになりました。 serveDir
関数は、手動でHTML文字列を生成する代わりに、dir_template.Apply
メソッドを使用してdir_template.html
にデータを適用し、動的にディレクトリ一覧ページを生成するようになりました。これにより、プレゼンテーションロジックがテンプレートに分離され、コードがクリーンになりました。
- Go言語の
template.go
:NewTemplate
とNewTemplateOrDie
というヘルパー関数が追加されました。これらは、テンプレートファイルの読み込みと初期化を簡素化するためのユーティリティ関数です。特にNewTemplateOrDie
は、テンプレートの読み込みに失敗した場合にパニックを発生させることで、開発時のエラーを早期に検出します。
5. その他のクリーンアップ
usr/gri/pretty/printer.go
では、ASTの命名変更(Ident
からName
など)に合わせて、プリンター(コード整形・出力)のロジックも更新されました。usr/gri/pretty/test.sh
では、テスト対象ファイルリストが更新され、新たにエラーを含むファイルがスキップ対象に追加されました。
コアとなるコードの変更箇所
このコミットのコアとなる変更は、主に以下のファイルに集中しています。
usr/gri/pretty/ast.go
: ASTノードの定義変更(特にLabelDecl
からLabeledStat
への変更と命名規則の統一)。usr/gri/pretty/compilation.go
: エラーハンドリングの仕組みの刷新(Error
、ErrorList
の導入、Compile
関数の戻り値変更)。usr/gri/pretty/gds.go
: ドキュメントサーバーにおけるHTMLテンプレートの利用と、エラーのインプレース表示ロジックの実装。usr/gri/pretty/parser.go
: Go言語仕様の更新に合わせたパーサーのロジック修正(特にラベル付きステートメントの解析)。usr/gri/pretty/dir_template.html
: ディレクトリ一覧表示用のHTMLテンプレート(新規追加)。usr/gri/pretty/error_template.html
: エラー表示用のHTMLテンプレート(新規追加)。
コアとなるコードの解説
usr/gri/pretty/ast.go
の変更 (抜粋)
// 変更前
type (
// ...
LabelDecl struct {
Loc scanner.Location;
Label *Ident;
};
// ...
)
// 変更後
type (
// ...
LabeledStat struct {
Loc scanner.Location; // location of ":"
Label *Ident;
Stat Stat; // ラベルが適用されるステートメント
};
// ...
)
この変更は、Go言語のラベルが単独の宣言ではなく、必ず何らかのステートメント(for
、switch
、select
など)に付随するという言語仕様をASTで正確に表現するためのものです。これにより、パーサーや後続のツールがラベルの意味をより正確に解釈できるようになります。
usr/gri/pretty/compilation.go
の変更 (抜粋)
// 変更前
type errorHandler struct {
// ...
nerrors int; // エラー数のみを保持
}
func Compile(src_file string, flags *Flags) (*AST.Program, int) {
// ...
return prog, err.nerrors; // エラー数のみを返す
}
// 変更後
type Error struct {
Loc scanner.Location;
Msg string;
}
type ErrorList []Error // エラーのリスト
func (list ErrorList) Len() int { return len(list); }
func (list ErrorList) Less(i, j int) bool { return list[i].Loc.Pos < list[j].Loc.Pos; }
func (list ErrorList) Swap(i, j int) { list[i], list[j] = list[j], list[j]; }
type errorHandler struct {
// ...
errors vector.Vector; // エラーオブジェクトのリストを保持
}
func Compile(src_file string, flags *Flags) (*ast.Program, ErrorList) {
// ...
// エラーを収集し、ソートするロジック
errors := make(ErrorList, err.errors.Len());
for i := 0; i < err.errors.Len(); i++ {
errors[i] = err.errors.At(i).(Error);
}
sort.Sort(errors);
return prog, errors; // ソートされたエラーリストを返す
}
この変更により、コンパイラは単にエラーの数を返すだけでなく、発生したすべてのエラーの詳細(位置とメッセージ)をリストとして返すようになりました。これにより、エラー報告システムが大幅に強化され、より詳細なデバッグ情報を提供できるようになります。
usr/gri/pretty/gds.go
の変更 (抜粋)
// 変更前 (serveDir関数の一部)
func serveDir(c *http.Conn, dirname string) {
// ...
fmt.Fprintf(c, "<b>%s</b>\n", path);
// ディレクトリ、Goファイル、その他のファイルを手動でHTML出力
for i, entry := range list {
if entry.IsDirectory() {
printLink(c, path, entry.Name);
}
}
// ...
}
// 変更後 (serveDir関数の一部)
var dir_template = template.NewTemplateOrDie("dir_template.html");
func serveDir(c *http.Conn, dirname string) {
// ...
// HTMLテンプレートを使用してディレクトリ一覧を生成
dir_template.Apply(c, "<!--", template.Substitution {
"PATH-->" : func() {
fmt.Fprintf(c, "%s", path);
},
"DIRECTORIES-->" : func() {
for i, entry := range list {
if entry.IsDirectory() {
printLink(c, path, entry.Name);
}
}
},
// ... 他のセクションも同様にテンプレートで処理
});
}
// 新規追加されたエラー表示関数
var error_template = template.NewTemplateOrDie("error_template.html");
func printErrors(c *http.Conn, filename string, errors Compilation.ErrorList) {
src, ok := Platform.ReadSourceFile(*root + filename);
// ...
error_template.Apply(c, "<!--", template.Substitution {
"FILE_NAME-->" : func() {
fmt.Fprintf(c, "%s", filename);
},
"ERRORS-->" : func () {
// ソースコードとエラーメッセージを結合して出力
pos := 0;
for i, e := range errors {
if 0 <= e.Loc.Pos && e.Loc.Pos <= len(src) {
c.Write(src[pos : e.Loc.Pos]);
fmt.Fprintf(c, "<b><font color=red>%s >>></font></b>", e.Msg); // エラーをインプレース表示
pos = e.Loc.Pos;
}
}
c.Write(src[pos : len(src)]);
}
});
}
// serveFile関数での利用
func serveFile(c *http.Conn, filename string) {
// ...
prog, errors := Compilation.Compile(*root + filename, &flags);
if len(errors) > 0 { // エラーがある場合
c.SetHeader("content-type", "text/html; charset=utf-8");
printErrors(c, filename, errors); // エラーをインプレース表示
return;
}
// ...
}
gds.go
の変更は、ドキュメントサーバーの表示ロジックを大幅に改善しています。serveDir
関数はHTMLテンプレートを使用することで、ディレクトリ一覧の生成をより構造化しました。さらに、printErrors
関数とserveFile
関数の連携により、コンパイルエラーがソースコードの該当箇所に直接埋め込まれて表示されるようになり、開発者にとって非常に分かりやすいエラー報告が実現されました。
関連リンク
- Go言語の初期開発に関する情報: https://go.dev/doc/history
- Go言語のASTパッケージ (
go/ast
): https://pkg.go.dev/go/ast - Go言語の
text/template
およびhtml/template
パッケージ: https://pkg.go.dev/text/template
参考にした情報源リンク
- Go言語の公式ドキュメント
- Go言語のソースコード(特に
go/ast
、go/parser
、go/scanner
パッケージの初期バージョン) - Go言語のコミット履歴
- 一般的なコンパイラ設計とASTに関する資料
- HTMLテンプレートエンジンの概念に関する資料