]> Cypherpunks repositories - gostls13.git/commitdiff
exp/template: statically check that functions names have been defined.
authorRob Pike <r@golang.org>
Tue, 5 Jul 2011 06:02:34 +0000 (16:02 +1000)
committerRob Pike <r@golang.org>
Tue, 5 Jul 2011 06:02:34 +0000 (16:02 +1000)
R=golang-dev, adg
CC=golang-dev
https://golang.org/cl/4675046

src/pkg/exp/template/parse.go
src/pkg/exp/template/parse_test.go
src/pkg/exp/template/set.go

index aaed411d495e0b88cc82dd84227b3ae7bd04d704..8514399b8234c4f291a5b51db0530fae5b884cec 100644 (file)
@@ -20,7 +20,8 @@ type Template struct {
        name  string
        root  *listNode
        funcs map[string]reflect.Value
-       // Parsing.
+       // Parsing only; cleared after parse.
+       set      *Set
        lex      *lexer
        tokens   <-chan item
        token    item // token lookahead for parser
@@ -507,14 +508,15 @@ func (t *Template) recover(errp *os.Error) {
 }
 
 // startParse starts the template parsing from the lexer.
-func (t *Template) startParse(lex *lexer, tokens <-chan item) {
+func (t *Template) startParse(set *Set, lex *lexer, tokens <-chan item) {
        t.root = nil
+       t.set = set
        t.lex, t.tokens = lex, tokens
 }
 
 // stopParse terminates parsing.
 func (t *Template) stopParse() {
-       t.lex, t.tokens = nil, nil
+       t.set, t.lex, t.tokens = nil, nil, nil
 }
 
 // atEOF returns true if, possibly after spaces, we're at EOF.
@@ -541,7 +543,19 @@ func (t *Template) atEOF() bool {
 // Parse parses the template definition string to construct an internal representation
 // of the template for execution.
 func (t *Template) Parse(s string) (err os.Error) {
-       t.startParse(lex(t.name, s))
+       lexer, tokens := lex(t.name, s)
+       t.startParse(nil, lexer, tokens)
+       defer t.recover(&err)
+       t.parse(true)
+       t.stopParse()
+       return
+}
+
+// ParseInSet parses the template definition string to construct an internal representation
+// of the template for execution. Function bindings are checked against those in the set.
+func (t *Template) ParseInSet(s string, set *Set) (err os.Error) {
+       lexer, tokens := lex(t.name, s)
+       t.startParse(set, lexer, tokens)
        defer t.recover(&err)
        t.parse(true)
        t.stopParse()
@@ -701,6 +715,9 @@ func (t *Template) templateControl() node {
        var name node
        switch token := t.next(); token.typ {
        case itemIdentifier:
+               if _, ok := findFunction(token.val, t, t.set); !ok {
+                       t.errorf("function %q not defined", token.val)
+               }
                name = newIdentifier(token.val)
        case itemDot:
                name = newDot()
@@ -735,6 +752,9 @@ Loop:
                case itemError:
                        t.errorf("%s", token.val)
                case itemIdentifier:
+                       if _, ok := findFunction(token.val, t, t.set); !ok {
+                               t.errorf("function %q not defined", token.val)
+                       }
                        cmd.append(newIdentifier(token.val))
                case itemDot:
                        cmd.append(newDot())
index 5c780cd29229fa49470c95057aa4b7349fcdf572..70c9f5a64ce6de67b36a037b5fd1eef9be48e604 100644 (file)
@@ -143,16 +143,12 @@ var parseTests = []parseTest{
                `[(action: [])]`},
        {"field", "{{.X}}", noError,
                `[(action: [(command: [F=[X]])])]`},
-       {"simple command", "{{hello}}", noError,
-               `[(action: [(command: [I=hello])])]`},
-       {"multi-word command", "{{hello world}}", noError,
-               `[(action: [(command: [I=hello I=world])])]`},
-       {"multi-word command with number", "{{hello 80}}", noError,
-               `[(action: [(command: [I=hello N=80])])]`},
-       {"multi-word command with string", "{{hello `quoted text`}}", noError,
-               "[(action: [(command: [I=hello S=`quoted text`])])]"},
-       {"pipeline", "{{hello|world}}", noError,
-               `[(action: [(command: [I=hello]) (command: [I=world])])]`},
+       {"simple command", "{{printf}}", noError,
+               `[(action: [(command: [I=printf])])]`},
+       {"multi-word command", "{{printf `%d` 23}}", noError,
+               "[(action: [(command: [I=printf S=`%d` N=23])])]"},
+       {"pipeline", "{{.X|.Y}}", noError,
+               `[(action: [(command: [F=[X]]) (command: [F=[Y]])])]`},
        {"simple if", "{{if .X}}hello{{end}}", noError,
                `[({{if [(command: [F=[X]])]}} [(text: "hello")])]`},
        {"if with else", "{{if .X}}true{{else}}false{{end}}", noError,
@@ -171,8 +167,8 @@ var parseTests = []parseTest{
                `[({{range [(command: [F=[SI]])]}} [(action: [(command: [{{<.>}}])])])]`},
        {"constants", "{{range .SI 1 -3.2i true false }}{{end}}", noError,
                `[({{range [(command: [F=[SI] N=1 N=-3.2i B=true B=false])]}} [])]`},
-       {"template", "{{template foo .X}}", noError,
-               "[{{template I=foo [(command: [F=[X]])]}}]"},
+       {"template", "{{template `x` .Y}}", noError,
+               "[{{template S=`x` [(command: [F=[Y]])]}}]"},
        {"with", "{{with .X}}hello{{end}}", noError,
                `[({{with [(command: [F=[X]])]}} [(text: "hello")])]`},
        {"with with else", "{{with .X}}hello{{else}}goodbye{{end}}", noError,
@@ -181,6 +177,7 @@ var parseTests = []parseTest{
        {"unclosed action", "hello{{range", hasError, ""},
        {"missing end", "hello{{range .x}}", hasError, ""},
        {"missing end after else", "hello{{range .x}}{{else}}", hasError, ""},
+       {"undefined function", "hello{{undefined}}", hasError, ""},
 }
 
 func TestParse(t *testing.T) {
index 3aaabaad5a98096a41c05b9b122dec46d6471d5e..bda4600192d604c7a76747823fa3916167b7a111 100644 (file)
@@ -56,7 +56,7 @@ func (s *Set) Parse(text string) (err os.Error) {
        const context = "define clause"
        for {
                t := New("set") // name will be updated once we know it.
-               t.startParse(lex, tokens)
+               t.startParse(s, lex, tokens)
                // Expect EOF or "{{ define name }}".
                if t.atEOF() {
                        return