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

[インデックス 10457] ファイルの概要

このコミットは、Go言語の標準ライブラリである text/template パッケージ内のテンプレート解析ロジックに対する変更を記録しています。具体的には、内部関数 Set の名称を Parse に変更し、それに伴う関連ファイルの修正が行われています。この変更は、テンプレートAPIの簡素化に向けた前段階として位置づけられています。

コミット

commit 10e012c85fa95ec24d039dcfa710e8d3cd75839d
Author: Rob Pike <r@golang.org>
Date:   Fri Nov 18 13:10:15 2011 -0800

    template/parse: rename Set to Parse
    Preamble to the simplification of the template API.
    Although the signature of Parse (nee Set) changes,
    it's really an internal function, used only by
    text/template.
    
    R=golang-dev, rsc, gri, r
    CC=golang-dev
    https://golang.org/cl/5415052

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/10e012c85fa95ec24d039dcfa710e8d3cd75839d

元コミット内容

template/parse パッケージ内の Set 関数を Parse にリネームしました。これはテンプレートAPIの簡素化に向けた前段階です。Parse (旧 Set) のシグネチャは変更されますが、これは text/template の内部でのみ使用される関数です。

変更の背景

このコミットの主な背景は、Go言語の text/template パッケージのAPIを簡素化することにあります。コミットメッセージにある「Preamble to the simplification of the template API.」という記述がその意図を明確に示しています。

Goのテンプレートエンジンは、HTMLやテキストの動的な生成に広く利用されています。初期の設計段階では、APIの名称や構造が試行錯誤されることがよくあります。この変更は、parse サブパッケージ内の Set という関数名が、その実際の役割(テンプレート定義文字列を解析して構文木を構築すること)を正確に反映していないと判断されたためと考えられます。Parse という名称は、解析処理を行う関数にとってより直感的で、APIの意図を明確にする効果があります。

また、コミットメッセージには「Although the signature of Parse (nee Set) changes, it's really an internal function, used only by text/template.」とあり、この関数が text/template パッケージの内部でのみ使用されるため、外部への影響が限定的であることも示唆されています。これにより、APIの変更が比較的容易に行える状況であったことが伺えます。

前提知識の解説

このコミットを理解するためには、Go言語の text/template パッケージと、一般的なテンプレートエンジンの動作原理に関する知識が必要です。

Go言語の text/template パッケージ

text/template パッケージは、Go言語に組み込まれているデータ駆動型テンプレートエンジンです。主にテキスト出力の生成に使用され、HTMLの生成には html/template パッケージが推奨されます(html/templatetext/template をベースにしており、XSS攻撃からの保護機能が追加されています)。

基本的な機能は以下の通りです。

  1. テンプレートの定義: プレースホルダーや制御構造(条件分岐、ループなど)を含むテキストを定義します。
  2. データの結合: 定義されたテンプレートにGoのデータ構造(構造体、マップ、スライスなど)を結合し、最終的なテキストを生成します。
  3. アクション: テンプレート内で {{...}} のように記述される部分を「アクション」と呼び、データの表示、関数の呼び出し、制御構造の記述などを行います。

テンプレートエンジンの解析(パース)プロセス

一般的なテンプレートエンジンは、以下の主要なステップで動作します。

  1. 字句解析 (Lexing/Tokenizing): テンプレートの生文字列を読み込み、意味のある最小単位(トークン)に分割します。例えば、{{.Name}} という文字列は、{{ (デリミタ開始)、. (フィールドアクセス)、Name (識別子)、}} (デリミタ終了) といったトークンに分解されます。このプロセスは「レキサー (lexer)」または「スキャナー (scanner)」によって行われます。

  2. 構文解析 (Parsing): 字句解析で生成されたトークンのストリームを受け取り、それらがテンプレート言語の文法規則に従っているかを確認し、抽象構文木 (Abstract Syntax Tree: AST) を構築します。ASTは、テンプレートの構造を階層的に表現したデータ構造であり、後続の処理(実行、最適化など)の基盤となります。このプロセスは「パーサー (parser)」によって行われます。

  3. 実行 (Execution): 構築されたASTと、テンプレートに結合するデータを受け取り、ASTを走査しながらデータを埋め込み、最終的な出力を生成します。

このコミットは、主に上記の「構文解析」フェーズに関連する内部関数の名称変更と、それに伴うコードの整理を行っています。text/template/parse パッケージは、この構文解析を担当する部分です。

Tree 構造体

src/pkg/text/template/parse/parse.go に定義されている Tree 構造体は、解析されたテンプレートの抽象構文木(AST)の表現です。

  • Name: テンプレートの名前。
  • Root: ASTの最上位ノード。通常は ListNode であり、複数のノード(テキストノード、アクションノードなど)を含むことができます。
  • funcs: テンプレート内で利用可能な関数マップ。
  • lex: 字句解析器(レキサー)へのポインタ。
  • token: 現在処理中のトークン。
  • peekCount: 先読みしたトークンの数。
  • vars: テンプレート内で定義された変数。

技術的詳細

このコミットの技術的詳細は、text/template パッケージの内部構造、特にテンプレートの解析(パース)部分に焦点を当てています。

parse パッケージの役割

src/pkg/text/template/parse パッケージは、Goのテンプレートエンジンにおいて、テンプレート文字列を解析し、実行可能な内部表現(抽象構文木 Tree)に変換する役割を担っています。このパッケージは、字句解析(lex.go)と構文解析(parse.go)のロジックを含んでいます。

Set から Parse へのリネーム

以前は src/pkg/text/template/parse/set.goSet という関数が定義されていました。この関数は、引数として与えられたテンプレート定義文字列を解析し、map[string]*Tree 型のテンプレートのセットを返していました。しかし、Set という名前は、テンプレートの「設定」や「集合」を意味するように聞こえ、実際の機能である「解析」を直接的に示していませんでした。

このコミットでは、set.go ファイルを削除し、その機能を parse.go に統合するとともに、関数名を Parse に変更しました。新しい Parse 関数は、テンプレート名、テキスト、デリミタ、および関数マップを受け取り、解析されたテンプレートのマップを返します。これにより、関数の名前がその動作をより正確に表現するようになりました。

Parse 関数のシグネチャ変更と内部関数としての位置づけ

コミットメッセージにあるように、Parse (旧 Set) のシグネチャは変更されています。

Set 関数 (src/pkg/text/template/parse/set.go):

func Set(text, leftDelim, rightDelim string, funcs ...map[string]interface{}) (tree map[string]*Tree, err error)

この関数は、テンプレートのテキスト、デリミタ、および関数マップを受け取り、map[string]*Tree を返していました。

Parse 関数 (src/pkg/text/template/parse/parse.go):

func Parse(name, text, leftDelim, rightDelim string, funcs ...map[string]interface{}) (treeSet map[string]*Tree, err error)

新しい Parse 関数は、name 引数が追加されています。これは、トップレベルのテンプレートに指定された名前を与えるために使用されます。 また、この関数は text/template パッケージの内部でのみ使用される「内部関数」であると明記されており、外部APIとしての安定性よりも、内部的な整合性や簡潔性が優先されたことが示唆されます。

Tree 構造体のコメント更新

Tree 構造体のコメントも更新されています。

  • // Tree is the representation of a parsed template. から // Tree is the representation of a single parsed template. へ変更。
  • Name string // Name is the name of the template. から Name string // name of the template represented by the tree. へ変更。
  • Root *ListNode // Root is the top-level root of the parse tree. から Root *ListNode // top-level root of the tree. へ変更。

これらの変更は、Tree が単一のテンプレートの構文木を表すことをより明確にし、フィールドのコメントも簡潔にしています。

New 関数のコメント更新

New 関数のコメントも更新されています。

  • // New allocates a new template with the given name. から // New allocates a new parse tree with the given name. へ変更。

これは、New 関数がテンプレート全体ではなく、解析ツリー(Tree 構造体)を割り当てることを明確にしています。

Parse メソッドのコメント更新

Tree 型の Parse メソッドのコメントも更新されています。

  • // Parse parses the template definition string to construct an internal // representation of the template for execution. If either action delimiter // string is empty, the default ("{{" or "}}") is used. から
  • // Parse parses the template definition string to construct a representation of // the template for execution. If either action delimiter string is empty, the // default ("{{" or "}}") is used. Embedded template definitions are added to // the treeSet map. へ変更。

最後の行 Embedded template definitions are added to the treeSet map. が追加され、埋め込みテンプレート定義が treeSet マップに追加されるという重要な動作が明記されました。これは、テンプレートが他のテンプレートを定義できるGoテンプレートの機能(例: {{define "name"}}...{{end}})を反映しています。

コアとなるコードの変更箇所

このコミットにおけるコアとなるコードの変更箇所は以下のファイルに集中しています。

  1. src/pkg/text/template/parse/Makefile:

    • set.go のビルド対象からの削除。
  2. src/pkg/text/template/parse/parse.go:

    • 新しいトップレベルの Parse 関数が追加されました。この関数は、以前 set.go にあった Set 関数の機能を置き換えるものです。
    • Tree 構造体のフィールドコメントがより簡潔に、かつ正確に更新されました。
    • New 関数のコメントが更新されました。
    • Tree 型の Parse メソッドのコメントが更新され、埋め込みテンプレートの扱いに関する記述が追加されました。
  3. src/pkg/text/template/parse/set.go:

    • ファイル全体が削除されました。
  4. src/pkg/text/template/set.go:

    • parse.Set の呼び出しが parse.Parse に変更されました。また、"ROOT" というプレースホルダー名が渡されるようになりました。

コアとなるコードの解説

src/pkg/text/template/parse/parse.go の変更点

新しい Parse 関数

// Parse returns a map from template name to parse.Tree, created by parsing the
// templates described in the argument string. The top-level template will be
// given the specified name. If an error is encountered, parsing stops and an
// empty map is returned with the error.
func Parse(name, text, leftDelim, rightDelim string, funcs ...map[string]interface{}) (treeSet map[string]*Tree, err error) {
	treeSet = make(map[string]*Tree)
	_, err = New(name).Parse(text, leftDelim, rightDelim, treeSet, funcs...)
	return
}

この関数は、テンプレートの解析を開始するためのエントリポイントとなります。

  • name: 解析するトップレベルテンプレートの名前。
  • text: 解析対象のテンプレート文字列。
  • leftDelim, rightDelim: アクションデリミタ(例: {{, }})。
  • funcs: テンプレート内で使用できる関数マップ。

内部では、まず treeSet という map[string]*Tree を作成します。これは、解析中に見つかったすべてのテンプレート(トップレベルおよび埋め込み)を格納するためのマップです。 次に、New(name) で新しい Tree インスタンスを作成し、その Parse メソッドを呼び出しています。この Tree.Parse メソッドが実際の解析処理を行い、結果を treeSet に追加します。

Tree 構造体のコメント変更

// Tree is the representation of a single parsed template.
type Tree struct {
	Name string    // name of the template represented by the tree.
	Root *ListNode // top-level root of the tree.
	// Parsing only; cleared after parse.
	funcs     []map[string]interface{}
	lex       *lexer
	token     [3]item // three-token lookahead for parsing.
	peekCount int
	vars      []string // variables defined at the moment.
}

コメントがより正確になり、Tree が「単一の解析済みテンプレート」を表すこと、Name が「ツリーによって表されるテンプレートの名前」であること、Root が「ツリーのトップレベルのルート」であることが明確化されました。

Tree.Parse メソッドのコメント変更

// Parse parses the template definition string to construct a representation of
// the template for execution. If either action delimiter string is empty, the
// default ("{{" or "}}") is used. Embedded template definitions are added to
// the treeSet map.
func (t *Tree) Parse(s, leftDelim, rightDelim string, treeSet map[string]*Tree, funcs ...map[string]interface{}) (tree *Tree, err error) {
	defer t.recover(&err)
	t.startParse(funcs, lex(t.Name, s, leftDelim, rightDelim))
	t.parse(treeSet)
	return t, nil
}

特に重要なのは、Embedded template definitions are added to the treeSet map. という行が追加されたことです。これは、{{define "name"}}...{{end}} のような構文で定義されたサブテンプレートが、この Parse メソッドの実行中に treeSet に追加されることを明示しています。これにより、Parse 関数が単一のテンプレートだけでなく、その中に含まれる複数のテンプレートを処理する能力を持つことが示されます。

src/pkg/text/template/parse/set.go の削除

このファイルは完全に削除されました。これにより、Set 関数が提供していた機能が parse.go の新しい Parse 関数に完全に移行したことを意味します。コードベースの整理と、機能の集中化が図られています。

src/pkg/text/template/set.go の変更点

func (s *Set) Parse(text string) (*Set, error) {
	// TODO: "ROOT" is just a placeholder while we rejig the API.
	trees, err := parse.Parse("ROOT", text, s.leftDelim, s.rightDelim, s.parseFuncs, builtins)
	if err != nil {
		return nil, err
	}
	// ... (後続の処理)
}

text/template パッケージの Set 型の Parse メソッドは、テンプレートセット全体を解析する役割を担っています。このメソッド内で、以前は parse.Set を呼び出していましたが、このコミットにより parse.Parse を呼び出すように変更されました。

注目すべきは、"ROOT" という文字列が parse.Parsename 引数として渡されている点です。これには // TODO: "ROOT" is just a placeholder while we rejig the API. というコメントが付いています。これは、この時点ではトップレベルのテンプレート名がまだ適切に扱われておらず、APIの再構築(rejig)が進行中であることを示しています。つまり、このコミットはAPI簡素化の「前段階 (Preamble)」であり、まだ完全な解決には至っていないことを示唆しています。

関連リンク

参考にした情報源リンク

  • 上記のGitHubコミットページ
  • Go言語の公式ドキュメント
  • Go言語のソースコード
  • 一般的なコンパイラ/インタプリタの設計に関する知識(字句解析、構文解析、ASTなど)I have provided the detailed explanation as requested.