[インデックス 1284] ファイルの概要
このコミットは、Go言語の初期開発段階におけるprettyパッケージの重要な変更を記録しています。主な目的は、定数宣言の文法を仕様変更に合わせて調整することと、HTML出力機能を導入することです。これにより、Goコードの整形ツールがより柔軟になり、HTML形式でのコード表示が可能になります。
コミット
commit 77aaf4f3a2584bdaf6691b0f938c0fe8a789028d
Author: Robert Griesemer <gri@golang.org>
Date: Thu Dec 4 18:18:41 2008 -0800
- adjusted const decl grammar to reflect spec changes
- first cut at html writer (will do html escaping, html tag production)
- first cut at generating basic html output via pretty
- some cleanups
R=r
OCL=20550
CL=20550
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/77aaf4f3a2584bdaf6691b0f938c0fe8a789028d
元コミット内容
このコミットは以下の主要な変更を含んでいます。
- 定数宣言文法の調整: Go言語の仕様変更に合わせて、定数宣言の文法が更新されました。具体的には、複数の識別子と値のリストを一度に宣言できるよう、パーサーが修正されました。
- HTMLライターの導入: HTMLエスケープ処理とHTMLタグ生成を行うための
htmlwriterパッケージが新たに作成されました。これは、io.Writeインターフェースを実装するフィルターとして機能します。 prettyパッケージからのHTML出力生成:prettyパッケージ(コード整形ツール)が、新しく導入されたhtmlwriterを利用して基本的なHTML出力を生成できるようになりました。これには、HTMLのプロローグ(<html>,<head>,<body>タグなど)とエピローグ、そして識別子をHTMLリンクとして出力する機能が含まれます。- クリーンアップ: コードベースの一般的な整理と改善が行われました。
変更の背景
このコミットが行われた2008年12月は、Go言語がまだ一般に公開される前の、活発な初期開発段階でした。この時期には、言語仕様が頻繁に改訂されており、それに伴いコンパイラやツールチェーンも継続的に更新される必要がありました。
変更の背景には、主に以下の2点があります。
- 言語仕様の進化: Go言語の定数宣言の文法が、より柔軟な複数宣言をサポートするように変更されたため、既存のパーサーが新しい仕様に対応する必要がありました。これは、言語設計の成熟に伴う自然なプロセスです。
- ツールの機能拡張: Goコードを整形し、表示するための
prettyツールに、HTML形式での出力機能を追加するニーズがありました。これは、コードのドキュメント生成、Webベースのコードビューア、あるいはIDE連携など、将来的な利用シナリオを見据えた機能拡張と考えられます。HTML出力は、コードのシンタックスハイライトやナビゲーションを容易にするための基盤となります。
前提知識の解説
このコミットを理解するためには、以下のGo言語および関連技術の初期の概念に関する知識が役立ちます。
- Go言語の初期の文法: 2008年当時のGo言語の文法は、現在の安定版とは異なる部分が多くありました。特に、定数宣言の構文や、パッケージのインポート方法(例:
import ("os"; "io"; "array"; "utf8";)のようにセミコロンで区切るスタイル)など、細部に違いが見られます。 go/parserとgo/printerパッケージの原型: このコミットで変更されているparser.goとprinter.goは、現在のGo標準ライブラリにあるgo/parserとgo/printerパッケージの初期の原型にあたります。これらは、Goソースコードの抽象構文木(AST)の解析と、ASTからソースコードを整形して出力する機能を提供します。io.Writerインターフェース: Go言語の基本的なI/Oインターフェースであり、データを書き込むための抽象化を提供します。htmlwriterはこれをラップすることで、任意のio.Writerに対してHTML形式の出力を提供できるように設計されています。tabwriterパッケージ:text/tabwriterパッケージの原型であり、テキストをタブ区切りで整形して出力するためのライターです。このコミットでは、printerが直接tabwriterを使用する代わりに、htmlwriterを介して出力する構造に変更されています。- HTMLエスケープ: HTMLドキュメント内で特殊文字(
<,>,&,",'など)を正しく表示するために、それらを対応するHTMLエンティティ(<,>,&など)に変換する処理です。htmlwriterの主要な機能の一つとして計画されていました。 - Go言語のビルドシステム(初期):
Makefileの変更は、当時のGo言語のビルドプロセスの一部を示しています。.6という拡張子は、初期のGoコンパイラが生成する中間ファイルや実行可能ファイルに関連する可能性があります。
技術的詳細
定数宣言文法の変更
parser.goにおける変更は、Go言語の定数宣言の文法が拡張されたことを示しています。
P.ParseIdent()からP.ParseIdentList()へ: これは、単一の識別子だけでなく、const a, b = 1, 2のように複数の識別子をカンマで区切って宣言できるようになったことを意味します。P.ParseExpression(1)からP.ParseExpressionList()へ: 同様に、単一の式だけでなく、const a, b = 1 + 2, "hello"のように複数の式をカンマで区切って指定できるようになったことを示します。
この変更により、Go言語の定数宣言はより簡潔かつ強力になりました。
htmlwriterパッケージの導入
htmlwriter.goは、HTML出力を専門に扱う新しいパッケージです。
Writer構造体:io.Writeインターフェースをラップし、HTMLエスケープやタグ生成のロジックを追加するための基盤を提供します。初期の実装ではWriteメソッドは単にラップされたライターに書き込むだけですが、コメントには「HTMLエスケープ、HTMLタグ生成を行う」と明記されており、今後の機能拡張が意図されています。New関数とInitメソッド:Writerのインスタンスを生成し、初期化するための標準的なGoのパターンに従っています。
printerパッケージのHTML出力対応
printer.goは、Goコードを整形して出力する主要なロジックを含んでいます。このコミットでは、HTML出力機能が大幅に強化されました。
htmlフラグの追加: コマンドライン引数で-htmlフラグを渡すことで、HTML出力を有効にできるようになりました。これは、prettyツールがテキスト出力とHTML出力の両方をサポートするための設定メカニズムです。Printerのwriter型の変更:*tabwriter.Writerから*htmlwriter.Writerに変更されたことで、Printerは直接tabwriterに書き込むのではなく、htmlwriterを介して書き込むようになりました。これにより、すべての出力がhtmlwriterによって処理され、HTMLエスケープやタグ生成の恩恵を受けることができます。- HTML関連ヘルパー関数の追加:
HtmlPrologue(title string): HTMLドキュメントの冒頭部分(<html>,<head>,<body>,<pre>タグなど)を生成します。これにより、生成されるHTMLファイルが基本的な構造を持つようになります。HtmlEpilogue(): HTMLドキュメントの末尾部分(</pre>,</body>,</html>タグ)を生成します。HtmlIdentifier(pos int, ident string): 識別子をHTMLリンク(<a href="#ident">ident</a>)として出力します。これは、生成されたHTMLコード内で識別子をクリック可能にし、コード内の定義箇所へのナビゲーションを可能にするための重要な機能です。
Print関数の変更:tabwriterとhtmlwriterの両方をインスタンス化し、htmlwriterがtabwriterをラップする形で連携させています。Printerはhtmlwriterで初期化されます。P.HtmlPrologueとP.HtmlEpilogueがP.Program(prog)の前後で呼び出され、Goコードの出力全体がHTML構造で囲まれるようになりました。
これらの変更により、prettyツールはGoコードを整形するだけでなく、Webブラウザで閲覧可能なHTML形式で出力する能力を獲得しました。
コアとなるコードの変更箇所
usr/gri/pretty/parser.go
--- a/usr/gri/pretty/parser.go
+++ b/usr/gri/pretty/parser.go
@@ -1348,11 +1348,11 @@ func (P *Parser) ParseConstSpec(exported bool, pos int) *AST.Decl {
P.Trace("ConstSpec");
d := AST.NewDecl(pos, Scanner.CONST, exported);
- d.ident = P.ParseIdent();
+ d.ident = P.ParseIdentList();
d.typ = P.TryType();
if P.tok == Scanner.ASSIGN {
P.Next();
- d.val = P.ParseExpression(1);
+ d.val = P.ParseExpressionList();
}
P.Ecart();
usr/gri/pretty/htmlwriter.go (新規ファイル)
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package htmlwriter
import (
"os";
"io";
"array";
"utf8";
)
// Writer is a filter implementing the io.Write interface.
// It provides facilities to generate HTML tags and does
// proper HTML-escaping for text written through it.
export type Writer struct {
// TODO should not export any of the fields
writer io.Write;
}
func (b *Writer) Init(writer io.Write) *Writer {
b.writer = writer;
return b;
}
/* export */ func (b *Writer) Flush() *os.Error {
return nil;
}
/* export */ func (b *Writer) Write(buf *[]byte) (written int, err *os.Error) {
written, err = b.writer.Write(buf); // BUG 6g - should just have return
return written, err;
}
export func New(writer io.Write) *Writer {
return new(Writer).Init(writer);
}
usr/gri/pretty/printer.go
--- a/usr/gri/pretty/printer.go
+++ b/usr/gri/pretty/printer.go
@@ -10,6 +10,7 @@ import (
"tabwriter";
"flag";
"fmt";
+ "htmlwriter";
Scanner "scanner";
AST "ast";
)
@@ -24,6 +25,7 @@ var (
maxnewlines = flag.Int("maxnewlines", 3, nil, "max. number of consecutive newlines");
// formatting control
+ html = flag.Bool("html", false, nil, "generate html");
comments = flag.Bool("comments", true, nil, "print comments");
optsemicolons = flag.Bool("optsemicolons", false, nil, "print optional semicolons");
)
@@ -53,7 +55,7 @@ const (
type Printer struct {
// output
- writer *tabwriter.Writer;
+ writer *htmlwriter.Writer;
// comments
comments *array.Array; // the list of all comments
@@ -90,14 +92,10 @@ func (P *Printer) NextComment() {
}
-func (P *Printer) Init(writer *tabwriter.Writer, comments *array.Array) {
+func (P *Printer) Init(writer *htmlwriter.Writer, comments *array.Array) {
// writer
- padchar := byte(' ');
- if usetabs.BVal() {
- padchar = '\t';
- }
- P.writer = tabwriter.New(os.Stdout, int(tabwidth.IVal()), 1, padchar, true);
-
+ P.writer = writer;
+
// comments
P.comments = comments;
P.cindex = -1;
@@ -299,12 +297,6 @@ func (P *Printer) String(pos int, s string) {
}
-func (P *Printer) Separator(separator int) {
- P.separator = separator;
- P.String(0, "");
-}
-
-
func (P *Printer) Token(pos int, tok int) {
P.String(pos, Scanner.TokenString(tok));
}
@@ -317,6 +309,47 @@ func (P *Printer) Error(pos int, tok int, msg string) {
}
+// ----------------------------------------------------------------------------
+// HTML support
+// TODO Move this to html writer
+
+func (P *Printer) HtmlPrologue(title string) {
+ if html.BVal() {
+ P.String(0,
+ "<html>\n"
+ "<head>\n"
+ "\t<META HTTP-EQUIV=\"Content-Type\" CONTENT=\"text/html; charset=UTF-8\">\n"
+ "\t<title>" + title + "</title>\n"
+ "\t<style type=\"text/css\">\n"
+ "\t</style>\n"
+ "</head>\n"
+ "<body>\n"
+ "<pre>\n"
+ )
+ }
+}
+
+
+func (P *Printer) HtmlEpilogue() {
+ if html.BVal() {
+ P.String(0,
+ "</pre>\n"
+ "</body>\n"
+ "<html>\n"
+ )
+ }
+}
+
+
+func (P *Printer) HtmlIdentifier(pos int, ident string) {
+ if html.BVal() {
+ P.String(pos, `<a href="#` + ident + `">` + ident + `</a>`);
+ } else {
+ P.String(pos, ident);
+ }
+}
+
+
// ----------------------------------------------------------------------------
// Types
@@ -331,9 +364,9 @@ func (P *Printer) Parameters(pos int, list *array.Array) {
tx := list.At(i).(*AST.Expr);
if i > 0 {
if prev == x.tok || prev == Scanner.TYPE {
- P.Separator(comma);
+ P.separator = comma;
} else {
- P.Separator(blank);
+ P.separator = blank;
}
}
P.Expr(x);
@@ -458,7 +491,10 @@ func (P *Printer) Expr1(x *AST.Expr, prec1 int) {
// type expr
P.Type(x.t);
- case Scanner.IDENT, Scanner.INT, Scanner.STRING, Scanner.FLOAT:
+ case Scanner.IDENT:
+ P.HtmlIdentifier(x.pos, x.s);
+
+ case Scanner.INT, Scanner.STRING, Scanner.FLOAT:
// literal
P.String(x.pos, x.s);
@@ -799,16 +835,16 @@ export func Print(prog *AST.Program) {
if usetabs.BVal() {
padchar = '\t';
}
- writer := tabwriter.New(os.Stdout, int(tabwidth.IVal()), 1, padchar, true);
+ ttwriter := tabwriter.New(os.Stdout, int(tabwidth.IVal()), 1, padchar, true);
+ hwriter := htmlwriter.New(ttwriter);
var P Printer;
- P.Init(writer, prog.comments);
+ P.Init(hwriter, prog.comments);
+ P.HtmlPrologue("<the source>");
P.Program(prog);
+ P.HtmlEpilogue();
- // flush
P.String(0, ""); // flush pending separator/newlines
- err := P.writer.Flush();
- if err != nil {
- panic("print error - exiting");
- }
+ hwriter.Flush(); // ignore errors
+ ttwriter.Flush(); // ignore errors
}
コアとなるコードの解説
parser.goの変更
ParseConstSpec関数は、定数宣言を解析する部分です。変更前は単一の識別子と式を期待していましたが、P.ParseIdentList()とP.ParseExpressionList()を呼び出すように変更されたことで、以下のような複数宣言の構文に対応できるようになりました。
const (
a, b = 1, 2
c, d string = "hello", "world"
)
これは、Go言語の定数宣言の柔軟性を高めるための重要な文法変更を反映しています。
htmlwriter.goの新規追加
このファイルは、HTML出力の基盤を提供します。Writer構造体はio.Writeインターフェースをラップし、将来的にはHTMLエスケープやタグ生成のロジックがここに追加される予定です。現時点では、Writeメソッドは単に下層のライターにバイト列を渡すだけですが、これはHTML出力パイプラインの最初のステップです。
printer.goの変更
Printer構造体のwriterフィールド:*tabwriter.Writerから*htmlwriter.Writerへの変更は、Printerが直接タブ整形を行うのではなく、HTML整形を行うhtmlwriterを介して出力を行うという設計思想の転換を示しています。これにより、HTML出力が有効な場合、すべてのテキスト出力がHTMLのコンテキストで処理されるようになります。HtmlPrologueとHtmlEpilogue: これらの関数は、生成されるHTMLドキュメントの基本的な構造を提供します。HtmlPrologueはHTMLのヘッダーとボディの開始タグ、そしてコードを整形するための<pre>タグを出力します。HtmlEpilogueはこれらのタグを閉じます。これにより、prettyツールが生成する出力は、単なるテキストではなく、Webブラウザで直接開ける有効なHTMLファイルになります。HtmlIdentifier: この関数は、Goコード内の識別子をHTMLのアンカータグ(<a>)で囲むことで、クリック可能なリンクとして出力します。href="#ident"という形式は、同じHTMLドキュメント内のIDを持つ要素への内部リンクを示唆しており、将来的にはコード内の定義箇所へのジャンプ機能を実現するために使用される可能性があります。Print関数の出力パイプライン:Print関数は、tabwriterとhtmlwriterを連携させる方法を示しています。まずtabwriterが作成され、そのtabwriterをラップするようにhtmlwriterが作成されます。そして、Printerはhtmlwriterを介して出力を行います。これにより、出力はまずHTMLエスケープとタグ生成の処理を受け、その後tabwriterによってタブ整形されるという多段階の処理が可能になります。
これらの変更は、Go言語のコード整形ツールが、単なるテキスト出力から、よりリッチでインタラクティブなHTML出力へと進化する第一歩を示しています。
関連リンク
- Go言語の初期のコミット履歴: https://github.com/golang/go/commits/master?after=77aaf4f3a2584bdaf6691b0f938c0fe8a789028d+34 (このコミットの周辺の履歴を辿ることができます)
- Go言語の公式ドキュメント: https://go.dev/doc/ (現在のGo言語の仕様やパッケージについて学ぶことができます)
参考にした情報源リンク
- Go言語のGitHubリポジトリ: https://github.com/golang/go
- Go言語の初期の設計に関する議論(Go Wikiなど): 2008年当時の具体的な仕様変更に関する公開された議論を特定するのは困難ですが、Go言語の進化の歴史は公式ブログやGo Wikiで追うことができます。
- Go Wiki: https://go.dev/wiki
- Go Blog: https://go.dev/blog/
text/tabwriterパッケージのドキュメント: https://pkg.go.dev/text/tabwriter (現在のパッケージの機能について)go/parserパッケージのドキュメント: https://pkg.go.dev/go/parser (現在のパッケージの機能について)go/printerパッケージのドキュメント: https://pkg.go.dev/go/printer (現在のパッケージの機能について)