aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRob Pike <r@golang.org>2011-07-05 16:02:34 +1000
committerRob Pike <r@golang.org>2011-07-05 16:02:34 +1000
commit5b1658232e7a379cc7c354de625fbf497147bc6f (patch)
tree53d0858e5c3f74f23a80cc8ddc272f632059613c
parentbedee318d5e9fcde9d55bff24bc8090cef1e57dc (diff)
downloadgo-5b1658232e7a379cc7c354de625fbf497147bc6f.tar.gz
go-5b1658232e7a379cc7c354de625fbf497147bc6f.zip
exp/template: statically check that functions names have been defined.
R=golang-dev, adg CC=golang-dev https://golang.org/cl/4675046
-rw-r--r--src/pkg/exp/template/parse.go28
-rw-r--r--src/pkg/exp/template/parse_test.go21
-rw-r--r--src/pkg/exp/template/set.go2
3 files changed, 34 insertions, 17 deletions
diff --git a/src/pkg/exp/template/parse.go b/src/pkg/exp/template/parse.go
index aaed411d49..8514399b82 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 5c780cd292..70c9f5a64c 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 3aaabaad5a..bda4600192 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