From 5b1658232e7a379cc7c354de625fbf497147bc6f Mon Sep 17 00:00:00 2001 From: Rob Pike Date: Tue, 5 Jul 2011 16:02:34 +1000 Subject: [PATCH] exp/template: statically check that functions names have been defined. R=golang-dev, adg CC=golang-dev https://golang.org/cl/4675046 --- src/pkg/exp/template/parse.go | 28 ++++++++++++++++++++++++---- src/pkg/exp/template/parse_test.go | 21 +++++++++------------ src/pkg/exp/template/set.go | 2 +- 3 files changed, 34 insertions(+), 17 deletions(-) diff --git a/src/pkg/exp/template/parse.go b/src/pkg/exp/template/parse.go index aaed411d495..8514399b823 100644 --- a/src/pkg/exp/template/parse.go +++ b/src/pkg/exp/template/parse.go @@ -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()) diff --git a/src/pkg/exp/template/parse_test.go b/src/pkg/exp/template/parse_test.go index 5c780cd2922..70c9f5a64ce 100644 --- a/src/pkg/exp/template/parse_test.go +++ b/src/pkg/exp/template/parse_test.go @@ -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) { diff --git a/src/pkg/exp/template/set.go b/src/pkg/exp/template/set.go index 3aaabaad5a9..bda4600192d 100644 --- a/src/pkg/exp/template/set.go +++ b/src/pkg/exp/template/set.go @@ -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