Commit 25d2987d authored by Rob Pike's avatar Rob Pike

text/template: refactor set parsing

Parse {{define}} blocks during template parsing rather than separately as a set-specific thing.
This cleans up set parse significantly, and enables the next step, if we want, to unify the
API for templates and sets.
Other than an argument change to parse.Parse, which is in effect an internal function and
unused by client code, there is no API change and no spec change yet.

R=golang-dev, rsc, r
CC=golang-dev
https://golang.org/cl/5393049
parent bbf952c3
...@@ -487,7 +487,11 @@ func testExecute(execTests []execTest, set *Set, t *testing.T) { ...@@ -487,7 +487,11 @@ func testExecute(execTests []execTest, set *Set, t *testing.T) {
} }
for _, test := range execTests { for _, test := range execTests {
tmpl := New(test.name).Funcs(funcs) tmpl := New(test.name).Funcs(funcs)
_, err := tmpl.ParseInSet(test.input, set) theSet := set
if theSet == nil {
theSet = new(Set)
}
_, err := tmpl.ParseInSet(test.input, theSet)
if err != nil { if err != nil {
t.Errorf("%s: parse error: %s", test.name, err) t.Errorf("%s: parse error: %s", test.name, err)
continue continue
......
...@@ -62,7 +62,7 @@ func (t *Template) Funcs(funcMap FuncMap) *Template { ...@@ -62,7 +62,7 @@ func (t *Template) Funcs(funcMap FuncMap) *Template {
// Parse parses the template definition string to construct an internal // Parse parses the template definition string to construct an internal
// representation of the template for execution. // representation of the template for execution.
func (t *Template) Parse(s string) (tmpl *Template, err error) { func (t *Template) Parse(s string) (tmpl *Template, err error) {
t.Tree, err = parse.New(t.name).Parse(s, t.leftDelim, t.rightDelim, t.parseFuncs, builtins) t.Tree, err = parse.New(t.name).Parse(s, t.leftDelim, t.rightDelim, nil, t.parseFuncs, builtins)
if err != nil { if err != nil {
return nil, err return nil, err
} }
...@@ -71,19 +71,13 @@ func (t *Template) Parse(s string) (tmpl *Template, err error) { ...@@ -71,19 +71,13 @@ func (t *Template) Parse(s string) (tmpl *Template, err error) {
// ParseInSet parses the template definition string to construct an internal // ParseInSet parses the template definition string to construct an internal
// representation of the template for execution. It also adds the template // representation of the template for execution. It also adds the template
// to the set. It is an error if s is already defined in the set. // to the set, which must not be nil. It is an error if s is already defined in the set.
// Function bindings are checked against those in the set. // Function bindings are checked against those in the set.
func (t *Template) ParseInSet(s string, set *Set) (tmpl *Template, err error) { func (t *Template) ParseInSet(s string, set *Set) (tmpl *Template, err error) {
var setFuncs FuncMap t.Tree, err = parse.New(t.name).Parse(s, t.leftDelim, t.rightDelim, set.trees, t.parseFuncs, set.parseFuncs, builtins)
if set != nil {
setFuncs = set.parseFuncs
}
t.Tree, err = parse.New(t.name).Parse(s, t.leftDelim, t.rightDelim, t.parseFuncs, setFuncs, builtins)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if set != nil { err = set.add(t)
err = set.add(t)
}
return t, err return t, err
} }
...@@ -146,29 +146,70 @@ func (t *Tree) atEOF() bool { ...@@ -146,29 +146,70 @@ func (t *Tree) atEOF() bool {
// Parse parses the template definition string to construct an internal // Parse parses the template definition string to construct an internal
// representation of the template for execution. If either action delimiter // representation of the template for execution. If either action delimiter
// string is empty, the default ("{{" or "}}") is used. // string is empty, the default ("{{" or "}}") is used.
func (t *Tree) Parse(s, leftDelim, rightDelim string, funcs ...map[string]interface{}) (tree *Tree, err error) { func (t *Tree) Parse(s, leftDelim, rightDelim string, treeSet map[string]*Tree, funcs ...map[string]interface{}) (tree *Tree, err error) {
defer t.recover(&err) defer t.recover(&err)
t.startParse(funcs, lex(t.Name, s, leftDelim, rightDelim)) t.startParse(funcs, lex(t.Name, s, leftDelim, rightDelim))
t.parse(true) t.parse(treeSet)
t.stopParse() t.stopParse()
return t, nil return t, nil
} }
// parse is the helper for Parse. // parse is the top-level parser for a template, essentially the same
// It triggers an error if we expect EOF but don't reach it. // as itemList except it also parses {{define}} actions.
func (t *Tree) parse(toEOF bool) (next Node) { // It runs to EOF.
t.Root, next = t.itemList(true) func (t *Tree) parse(treeSet map[string]*Tree) (next Node) {
if toEOF && next != nil { t.Root = newList()
t.errorf("unexpected %s", next) for t.peek().typ != itemEOF {
if t.peek().typ == itemLeftDelim {
delim := t.next()
if t.next().typ == itemDefine {
newT := New("new definition") // name will be updated once we know it.
newT.startParse(t.funcs, t.lex)
newT.parseDefinition(treeSet)
continue
}
t.backup2(delim)
}
n := t.textOrAction()
if n.Type() == nodeEnd {
t.errorf("unexpected %s", n)
}
t.Root.append(n)
} }
return next return nil
}
// parseDefinition parses a {{define}} ... {{end}} template definition and
// installs the definition in the treeSet map. The "define" keyword has already
// been scanned.
func (t *Tree) parseDefinition(treeSet map[string]*Tree) {
if treeSet == nil {
t.errorf("no set specified for template definition")
}
const context = "define clause"
name := t.expect(itemString, context)
var err error
t.Name, err = strconv.Unquote(name.val)
if err != nil {
t.error(err)
}
t.expect(itemRightDelim, context)
var end Node
t.Root, end = t.itemList()
if end.Type() != nodeEnd {
t.errorf("unexpected %s in %s", end, context)
}
t.stopParse()
if _, present := treeSet[t.Name]; present {
t.errorf("template: %q multiply defined", name)
}
treeSet[t.Name] = t
} }
// itemList: // itemList:
// textOrAction* // textOrAction*
// Terminates at EOF and at {{end}} or {{else}}, which is returned separately. // Terminates at {{end}} or {{else}}, returned separately.
// The toEOF flag tells whether we expect to reach EOF. func (t *Tree) itemList() (list *ListNode, next Node) {
func (t *Tree) itemList(toEOF bool) (list *ListNode, next Node) {
list = newList() list = newList()
for t.peek().typ != itemEOF { for t.peek().typ != itemEOF {
n := t.textOrAction() n := t.textOrAction()
...@@ -178,10 +219,8 @@ func (t *Tree) itemList(toEOF bool) (list *ListNode, next Node) { ...@@ -178,10 +219,8 @@ func (t *Tree) itemList(toEOF bool) (list *ListNode, next Node) {
} }
list.append(n) list.append(n)
} }
if !toEOF { t.errorf("unexpected EOF")
t.unexpected(t.next(), "input") return
}
return list, nil
} }
// textOrAction: // textOrAction:
...@@ -276,11 +315,11 @@ func (t *Tree) parseControl(context string) (lineNum int, pipe *PipeNode, list, ...@@ -276,11 +315,11 @@ func (t *Tree) parseControl(context string) (lineNum int, pipe *PipeNode, list,
defer t.popVars(len(t.vars)) defer t.popVars(len(t.vars))
pipe = t.pipeline(context) pipe = t.pipeline(context)
var next Node var next Node
list, next = t.itemList(false) list, next = t.itemList()
switch next.Type() { switch next.Type() {
case nodeEnd: //done case nodeEnd: //done
case nodeElse: case nodeElse:
elseList, next = t.itemList(false) elseList, next = t.itemList()
if next.Type() != nodeEnd { if next.Type() != nodeEnd {
t.errorf("expected end; found %s", next) t.errorf("expected end; found %s", next)
} }
......
...@@ -236,7 +236,7 @@ var builtins = map[string]interface{}{ ...@@ -236,7 +236,7 @@ var builtins = map[string]interface{}{
func TestParse(t *testing.T) { func TestParse(t *testing.T) {
for _, test := range parseTests { for _, test := range parseTests {
tmpl, err := New(test.name).Parse(test.input, "", "", builtins) tmpl, err := New(test.name).Parse(test.input, "", "", nil, builtins)
switch { switch {
case err == nil && !test.ok: case err == nil && !test.ok:
t.Errorf("%q: expected error; got none", test.name) t.Errorf("%q: expected error; got none", test.name)
......
...@@ -4,46 +4,12 @@ ...@@ -4,46 +4,12 @@
package parse package parse
import (
"fmt"
"strconv"
)
// Set returns a slice of Trees created by parsing the template set // Set returns a slice of Trees created by parsing the template set
// definition in the argument string. If an error is encountered, // definition in the argument string. If an error is encountered,
// parsing stops and an empty slice is returned with the error. // parsing stops and an empty slice is returned with the error.
func Set(text, leftDelim, rightDelim string, funcs ...map[string]interface{}) (tree map[string]*Tree, err error) { func Set(text, leftDelim, rightDelim string, funcs ...map[string]interface{}) (tree map[string]*Tree, err error) {
tree = make(map[string]*Tree) tree = make(map[string]*Tree)
defer (*Tree)(nil).recover(&err) // Top-level template name is needed but unused. TODO: clean this up.
lex := lex("set", text, leftDelim, rightDelim) _, err = New("ROOT").Parse(text, leftDelim, rightDelim, tree, funcs...)
const context = "define clause"
for {
t := New("set") // name will be updated once we know it.
t.startParse(funcs, lex)
// Expect EOF or "{{ define name }}".
if t.atEOF() {
break
}
t.expect(itemLeftDelim, context)
t.expect(itemDefine, context)
name := t.expect(itemString, context)
t.Name, err = strconv.Unquote(name.val)
if err != nil {
t.error(err)
}
t.expect(itemRightDelim, context)
end := t.parse(false)
if end == nil {
t.errorf("unexpected EOF in %s", context)
}
if end.Type() != nodeEnd {
t.errorf("unexpected %s in %s", end, context)
}
t.stopParse()
if _, present := tree[t.Name]; present {
return nil, fmt.Errorf("template: %q multiply defined", name)
}
tree[t.Name] = t
}
return return
} }
...@@ -16,6 +16,7 @@ import ( ...@@ -16,6 +16,7 @@ import (
// A template may be a member of multiple sets. // A template may be a member of multiple sets.
type Set struct { type Set struct {
tmpl map[string]*Template tmpl map[string]*Template
trees map[string]*parse.Tree // maintained by parse package
leftDelim string leftDelim string
rightDelim string rightDelim string
parseFuncs FuncMap parseFuncs FuncMap
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment