template: add method Delims to allow alternate action delimiters.

R=golang-dev, rsc, dsymonds
CC=golang-dev
https://golang.org/cl/5209045
diff --git a/src/pkg/template/exec_test.go b/src/pkg/template/exec_test.go
index 8e1894e..57c6325 100644
--- a/src/pkg/template/exec_test.go
+++ b/src/pkg/template/exec_test.go
@@ -493,6 +493,43 @@
 	testExecute(execTests, nil, t)
 }
 
+var delimPairs = []string{
+	"", "", // default
+	"{{", "}}", // same as default
+	"<<", ">>", // distinct
+	"|", "|", // same
+	"(日)", "(本)", // peculiar
+}
+
+func TestDelims(t *testing.T) {
+	const hello = "Hello, world"
+	var value = struct{ Str string }{hello}
+	for i := 0; i < len(delimPairs); i += 2 {
+		text := ".Str"
+		left := delimPairs[i+0]
+		right := delimPairs[i+1]
+		if left == "" { // default case
+			text = "{{" + text
+		}
+		if right == "" { // default case
+			text = text + "}}"
+		}
+		text = left + text + right
+		tmpl, err := New("delims").Delims(left, right).Parse(text)
+		if err != nil {
+			t.Fatalf("delim %q text %q parse err %s", left, text, err)
+		}
+		var b = new(bytes.Buffer)
+		err = tmpl.Execute(b, value)
+		if err != nil {
+			t.Fatalf("delim %q exec err %s", left, err)
+		}
+		if b.String() != hello {
+			t.Error("expected %q got %q", hello, b.String())
+		}
+	}
+}
+
 // Check that an error from a method flows back to the top.
 func TestExecuteError(t *testing.T) {
 	b := new(bytes.Buffer)
@@ -538,18 +575,19 @@
 	Left, Right *Tree
 }
 
+// Use different delimiters to test Set.Delims.
 const treeTemplate = `
-	{{define "tree"}}
+	(define "tree")
 	[
-		{{.Val}}
-		{{with .Left}}
-			{{template "tree" .}}
-		{{end}}
-		{{with .Right}}
-			{{template "tree" .}}
-		{{end}}
+		(.Val)
+		(with .Left)
+			(template "tree" .)
+		(end)
+		(with .Right)
+			(template "tree" .)
+		(end)
 	]
-	{{end}}
+	(end)
 `
 
 func TestTree(t *testing.T) {
@@ -590,7 +628,7 @@
 		},
 	}
 	set := new(Set)
-	_, err := set.Parse(treeTemplate)
+	_, err := set.Delims("(", ")").Parse(treeTemplate)
 	if err != nil {
 		t.Fatal("parse error:", err)
 	}
diff --git a/src/pkg/template/parse.go b/src/pkg/template/parse.go
index b089c59..3068a77 100644
--- a/src/pkg/template/parse.go
+++ b/src/pkg/template/parse.go
@@ -14,6 +14,8 @@
 type Template struct {
 	name string
 	*parse.Tree
+	leftDelim  string
+	rightDelim string
 	// We use two maps, one for parsing and one for execution.
 	// This separation makes the API cleaner since it doesn't
 	// expose reflection to the client.
@@ -38,6 +40,16 @@
 	}
 }
 
+// Delims sets the action delimiters, to be used in a subsequent
+// parse, to the specified strings.
+// An empty delimiter stands for the corresponding default: {{ or }}.
+// The return value is the template, so calls can be chained.
+func (t *Template) Delims(left, right string) *Template {
+	t.leftDelim = left
+	t.rightDelim = right
+	return t
+}
+
 // Funcs adds the elements of the argument map to the template's function
 // map.  It panics if a value in the map is not a function with appropriate
 // return type.
@@ -51,7 +63,7 @@
 // Parse parses the template definition string to construct an internal
 // representation of the template for execution.
 func (t *Template) Parse(s string) (tmpl *Template, err os.Error) {
-	t.Tree, err = parse.New(t.name).Parse(s, t.parseFuncs, builtins)
+	t.Tree, err = parse.New(t.name).Parse(s, t.leftDelim, t.rightDelim, t.parseFuncs, builtins)
 	if err != nil {
 		return nil, err
 	}
@@ -67,7 +79,7 @@
 	if set != nil {
 		setFuncs = set.parseFuncs
 	}
-	t.Tree, err = parse.New(t.name).Parse(s, t.parseFuncs, setFuncs, builtins)
+	t.Tree, err = parse.New(t.name).Parse(s, t.leftDelim, t.rightDelim, t.parseFuncs, setFuncs, builtins)
 	if err != nil {
 		return nil, err
 	}
diff --git a/src/pkg/template/parse/lex.go b/src/pkg/template/parse/lex.go
index 83ad6c6..07740d7 100644
--- a/src/pkg/template/parse/lex.go
+++ b/src/pkg/template/parse/lex.go
@@ -119,13 +119,15 @@
 
 // lexer holds the state of the scanner.
 type lexer struct {
-	name  string    // the name of the input; used only for error reports.
-	input string    // the string being scanned.
-	state stateFn   // the next lexing function to enter
-	pos   int       // current position in the input.
-	start int       // start position of this item.
-	width int       // width of last rune read from input.
-	items chan item // channel of scanned items.
+	name       string    // the name of the input; used only for error reports.
+	input      string    // the string being scanned.
+	leftDelim  string    // start of action.
+	rightDelim string    // end of action.
+	state      stateFn   // the next lexing function to enter.
+	pos        int       // current position in the input.
+	start      int       // start position of this item.
+	width      int       // width of last rune read from input.
+	items      chan item // channel of scanned items.
 }
 
 // next returns the next rune in the input.
@@ -205,12 +207,20 @@
 }
 
 // lex creates a new scanner for the input string.
-func lex(name, input string) *lexer {
+func lex(name, input, left, right string) *lexer {
+	if left == "" {
+		left = leftDelim
+	}
+	if right == "" {
+		right = rightDelim
+	}
 	l := &lexer{
-		name:  name,
-		input: input,
-		state: lexText,
-		items: make(chan item, 2), // Two items of buffering is sufficient for all state functions
+		name:       name,
+		input:      input,
+		leftDelim:  left,
+		rightDelim: right,
+		state:      lexText,
+		items:      make(chan item, 2), // Two items of buffering is sufficient for all state functions
 	}
 	return l
 }
@@ -227,7 +237,7 @@
 // lexText scans until an opening action delimiter, "{{".
 func lexText(l *lexer) stateFn {
 	for {
-		if strings.HasPrefix(l.input[l.pos:], leftDelim) {
+		if strings.HasPrefix(l.input[l.pos:], l.leftDelim) {
 			if l.pos > l.start {
 				l.emit(itemText)
 			}
@@ -250,7 +260,7 @@
 	if strings.HasPrefix(l.input[l.pos:], leftComment) {
 		return lexComment
 	}
-	l.pos += len(leftDelim)
+	l.pos += len(l.leftDelim)
 	l.emit(itemLeftDelim)
 	return lexInsideAction
 }
@@ -268,7 +278,7 @@
 
 // lexRightDelim scans the right delimiter, which is known to be present.
 func lexRightDelim(l *lexer) stateFn {
-	l.pos += len(rightDelim)
+	l.pos += len(l.rightDelim)
 	l.emit(itemRightDelim)
 	return lexText
 }
@@ -278,7 +288,7 @@
 	// Either number, quoted string, or identifier.
 	// Spaces separate and are ignored.
 	// Pipe symbols separate and are emitted.
-	if strings.HasPrefix(l.input[l.pos:], rightDelim) {
+	if strings.HasPrefix(l.input[l.pos:], l.rightDelim) {
 		return lexRightDelim
 	}
 	switch r := l.next(); {
diff --git a/src/pkg/template/parse/lex_test.go b/src/pkg/template/parse/lex_test.go
index d71c8e6..f2569b1 100644
--- a/src/pkg/template/parse/lex_test.go
+++ b/src/pkg/template/parse/lex_test.go
@@ -201,8 +201,8 @@
 }
 
 // collect gathers the emitted items into a slice.
-func collect(t *lexTest) (items []item) {
-	l := lex(t.name, t.input)
+func collect(t *lexTest, left, right string) (items []item) {
+	l := lex(t.name, t.input, left, right)
 	for {
 		item := l.nextItem()
 		items = append(items, item)
@@ -215,7 +215,41 @@
 
 func TestLex(t *testing.T) {
 	for _, test := range lexTests {
-		items := collect(&test)
+		items := collect(&test, "", "")
+		if !reflect.DeepEqual(items, test.items) {
+			t.Errorf("%s: got\n\t%v\nexpected\n\t%v", test.name, items, test.items)
+		}
+	}
+}
+
+// Some easy cases from above, but with delimiters are $$ and @@
+var lexDelimTests = []lexTest{
+	{"punctuation", "$$,@%{{}}@@", []item{
+		tLeftDelim,
+		{itemChar, ","},
+		{itemChar, "@"},
+		{itemChar, "%"},
+		{itemChar, "{"},
+		{itemChar, "{"},
+		{itemChar, "}"},
+		{itemChar, "}"},
+		tRightDelim,
+		tEOF,
+	}},
+	{"empty action", `$$@@`, []item{tLeftDelim, tRightDelim, tEOF}},
+	{"for", `$$for @@`, []item{tLeftDelim, tFor, tRightDelim, tEOF}},
+	{"quote", `$$"abc \n\t\" "@@`, []item{tLeftDelim, tQuote, tRightDelim, tEOF}},
+	{"raw quote", "$$" + raw + "@@", []item{tLeftDelim, tRawQuote, tRightDelim, tEOF}},
+}
+
+var (
+	tLeftDelim  = item{itemLeftDelim, "$$"}
+	tRightDelim = item{itemRightDelim, "@@"}
+)
+
+func TestDelims(t *testing.T) {
+	for _, test := range lexDelimTests {
+		items := collect(&test, "$$", "@@")
 		if !reflect.DeepEqual(items, test.items) {
 			t.Errorf("%s: got\n\t%v\nexpected\n\t%v", test.name, items, test.items)
 		}
diff --git a/src/pkg/template/parse/parse.go b/src/pkg/template/parse/parse.go
index 6918074..9934d82 100644
--- a/src/pkg/template/parse/parse.go
+++ b/src/pkg/template/parse/parse.go
@@ -145,10 +145,11 @@
 }
 
 // Parse parses the template definition string to construct an internal
-// representation of the template for execution.
-func (t *Tree) Parse(s string, funcs ...map[string]interface{}) (tree *Tree, err os.Error) {
+// representation of the template for execution. If either action delimiter
+// string is empty, the default ("{{" or "}}") is used.
+func (t *Tree) Parse(s, leftDelim, rightDelim string, funcs ...map[string]interface{}) (tree *Tree, err os.Error) {
 	defer t.recover(&err)
-	t.startParse(funcs, lex(t.Name, s))
+	t.startParse(funcs, lex(t.Name, s, leftDelim, rightDelim))
 	t.parse(true)
 	t.stopParse()
 	return t, nil
diff --git a/src/pkg/template/parse/parse_test.go b/src/pkg/template/parse/parse_test.go
index 1928c31..f05f6e3 100644
--- a/src/pkg/template/parse/parse_test.go
+++ b/src/pkg/template/parse/parse_test.go
@@ -236,7 +236,7 @@
 
 func TestParse(t *testing.T) {
 	for _, test := range parseTests {
-		tmpl, err := New(test.name).Parse(test.input, builtins)
+		tmpl, err := New(test.name).Parse(test.input, "", "", builtins)
 		switch {
 		case err == nil && !test.ok:
 			t.Errorf("%q: expected error; got none", test.name)
diff --git a/src/pkg/template/parse/set.go b/src/pkg/template/parse/set.go
index dca41ea..b909f71c 100644
--- a/src/pkg/template/parse/set.go
+++ b/src/pkg/template/parse/set.go
@@ -13,10 +13,10 @@
 // Set returns a slice of Trees created by parsing the template set
 // definition in the argument string. If an error is encountered,
 // parsing stops and an empty slice is returned with the error.
-func Set(text string, funcs ...map[string]interface{}) (tree map[string]*Tree, err os.Error) {
+func Set(text, leftDelim, rightDelim string, funcs ...map[string]interface{}) (tree map[string]*Tree, err os.Error) {
 	tree = make(map[string]*Tree)
 	defer (*Tree)(nil).recover(&err)
-	lex := lex("set", text)
+	lex := lex("set", text, leftDelim, rightDelim)
 	const context = "define clause"
 	for {
 		t := New("set") // name will be updated once we know it.
diff --git a/src/pkg/template/set.go b/src/pkg/template/set.go
index f778fd1..712961b 100644
--- a/src/pkg/template/set.go
+++ b/src/pkg/template/set.go
@@ -17,6 +17,8 @@
 // A template may be a member of multiple sets.
 type Set struct {
 	tmpl       map[string]*Template
+	leftDelim  string
+	rightDelim string
 	parseFuncs FuncMap
 	execFuncs  map[string]reflect.Value
 }
@@ -29,6 +31,16 @@
 	}
 }
 
+// Delims sets the action delimiters, to be used in a subsequent
+// parse, to the specified strings.
+// An empty delimiter stands for the corresponding default: {{ or }}.
+// The return value is the set, so calls can be chained.
+func (s *Set) Delims(left, right string) *Set {
+	s.leftDelim = left
+	s.rightDelim = right
+	return s
+}
+
 // Funcs adds the elements of the argument map to the set's function map.  It
 // panics if a value in the map is not a function with appropriate return
 // type.
@@ -93,7 +105,7 @@
 // to the set.  If a template is redefined, the element in the set is
 // overwritten with the new definition.
 func (s *Set) Parse(text string) (*Set, os.Error) {
-	trees, err := parse.Set(text, s.parseFuncs, builtins)
+	trees, err := parse.Set(text, s.leftDelim, s.rightDelim, s.parseFuncs, builtins)
 	if err != nil {
 		return nil, err
 	}