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

[インデックス 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

元コミット内容

このコミットは以下の主要な変更を含んでいます。

  1. 定数宣言文法の調整: Go言語の仕様変更に合わせて、定数宣言の文法が更新されました。具体的には、複数の識別子と値のリストを一度に宣言できるよう、パーサーが修正されました。
  2. HTMLライターの導入: HTMLエスケープ処理とHTMLタグ生成を行うためのhtmlwriterパッケージが新たに作成されました。これは、io.Writeインターフェースを実装するフィルターとして機能します。
  3. prettyパッケージからのHTML出力生成: prettyパッケージ(コード整形ツール)が、新しく導入されたhtmlwriterを利用して基本的なHTML出力を生成できるようになりました。これには、HTMLのプロローグ(<html>, <head>, <body>タグなど)とエピローグ、そして識別子をHTMLリンクとして出力する機能が含まれます。
  4. クリーンアップ: コードベースの一般的な整理と改善が行われました。

変更の背景

このコミットが行われた2008年12月は、Go言語がまだ一般に公開される前の、活発な初期開発段階でした。この時期には、言語仕様が頻繁に改訂されており、それに伴いコンパイラやツールチェーンも継続的に更新される必要がありました。

変更の背景には、主に以下の2点があります。

  1. 言語仕様の進化: Go言語の定数宣言の文法が、より柔軟な複数宣言をサポートするように変更されたため、既存のパーサーが新しい仕様に対応する必要がありました。これは、言語設計の成熟に伴う自然なプロセスです。
  2. ツールの機能拡張: Goコードを整形し、表示するためのprettyツールに、HTML形式での出力機能を追加するニーズがありました。これは、コードのドキュメント生成、Webベースのコードビューア、あるいはIDE連携など、将来的な利用シナリオを見据えた機能拡張と考えられます。HTML出力は、コードのシンタックスハイライトやナビゲーションを容易にするための基盤となります。

前提知識の解説

このコミットを理解するためには、以下のGo言語および関連技術の初期の概念に関する知識が役立ちます。

  • Go言語の初期の文法: 2008年当時のGo言語の文法は、現在の安定版とは異なる部分が多くありました。特に、定数宣言の構文や、パッケージのインポート方法(例: import ("os"; "io"; "array"; "utf8";) のようにセミコロンで区切るスタイル)など、細部に違いが見られます。
  • go/parsergo/printerパッケージの原型: このコミットで変更されているparser.goprinter.goは、現在のGo標準ライブラリにあるgo/parsergo/printerパッケージの初期の原型にあたります。これらは、Goソースコードの抽象構文木(AST)の解析と、ASTからソースコードを整形して出力する機能を提供します。
  • io.Writerインターフェース: Go言語の基本的なI/Oインターフェースであり、データを書き込むための抽象化を提供します。htmlwriterはこれをラップすることで、任意のio.Writerに対してHTML形式の出力を提供できるように設計されています。
  • tabwriterパッケージ: text/tabwriterパッケージの原型であり、テキストをタブ区切りで整形して出力するためのライターです。このコミットでは、printerが直接tabwriterを使用する代わりに、htmlwriterを介して出力する構造に変更されています。
  • HTMLエスケープ: HTMLドキュメント内で特殊文字(<, >, &, ", 'など)を正しく表示するために、それらを対応するHTMLエンティティ(&lt;, &gt;, &amp;など)に変換する処理です。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出力の両方をサポートするための設定メカニズムです。
  • Printerwriter型の変更: *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関数の変更:
    • tabwriterhtmlwriterの両方をインスタンス化し、htmlwritertabwriterをラップする形で連携させています。
    • Printerhtmlwriterで初期化されます。
    • P.HtmlPrologueP.HtmlEpilogueP.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の変更

  1. Printer構造体のwriterフィールド: *tabwriter.Writerから*htmlwriter.Writerへの変更は、Printerが直接タブ整形を行うのではなく、HTML整形を行うhtmlwriterを介して出力を行うという設計思想の転換を示しています。これにより、HTML出力が有効な場合、すべてのテキスト出力がHTMLのコンテキストで処理されるようになります。
  2. HtmlPrologueHtmlEpilogue: これらの関数は、生成されるHTMLドキュメントの基本的な構造を提供します。HtmlPrologueはHTMLのヘッダーとボディの開始タグ、そしてコードを整形するための<pre>タグを出力します。HtmlEpilogueはこれらのタグを閉じます。これにより、prettyツールが生成する出力は、単なるテキストではなく、Webブラウザで直接開ける有効なHTMLファイルになります。
  3. HtmlIdentifier: この関数は、Goコード内の識別子をHTMLのアンカータグ(<a>)で囲むことで、クリック可能なリンクとして出力します。href="#ident"という形式は、同じHTMLドキュメント内のIDを持つ要素への内部リンクを示唆しており、将来的にはコード内の定義箇所へのジャンプ機能を実現するために使用される可能性があります。
  4. Print関数の出力パイプライン: Print関数は、tabwriterhtmlwriterを連携させる方法を示しています。まずtabwriterが作成され、そのtabwriterをラップするようにhtmlwriterが作成されます。そして、Printerhtmlwriterを介して出力を行います。これにより、出力はまずHTMLエスケープとタグ生成の処理を受け、その後tabwriterによってタブ整形されるという多段階の処理が可能になります。

これらの変更は、Go言語のコード整形ツールが、単なるテキスト出力から、よりリッチでインタラクティブなHTML出力へと進化する第一歩を示しています。

関連リンク

参考にした情報源リンク