[インデックス 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テンプレートエンジンの概念に関する資料