mirror of
https://github.com/golang/go
synced 2024-11-21 17:44:40 -07:00
exp/template: statically check that functions names have been defined.
R=golang-dev, adg CC=golang-dev https://golang.org/cl/4675046
This commit is contained in:
parent
bedee318d5
commit
5b1658232e
@ -20,7 +20,8 @@ type Template struct {
|
|||||||
name string
|
name string
|
||||||
root *listNode
|
root *listNode
|
||||||
funcs map[string]reflect.Value
|
funcs map[string]reflect.Value
|
||||||
// Parsing.
|
// Parsing only; cleared after parse.
|
||||||
|
set *Set
|
||||||
lex *lexer
|
lex *lexer
|
||||||
tokens <-chan item
|
tokens <-chan item
|
||||||
token item // token lookahead for parser
|
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.
|
// 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.root = nil
|
||||||
|
t.set = set
|
||||||
t.lex, t.tokens = lex, tokens
|
t.lex, t.tokens = lex, tokens
|
||||||
}
|
}
|
||||||
|
|
||||||
// stopParse terminates parsing.
|
// stopParse terminates parsing.
|
||||||
func (t *Template) stopParse() {
|
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.
|
// 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
|
// Parse parses the template definition string to construct an internal representation
|
||||||
// of the template for execution.
|
// of the template for execution.
|
||||||
func (t *Template) Parse(s string) (err os.Error) {
|
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)
|
defer t.recover(&err)
|
||||||
t.parse(true)
|
t.parse(true)
|
||||||
t.stopParse()
|
t.stopParse()
|
||||||
@ -701,6 +715,9 @@ func (t *Template) templateControl() node {
|
|||||||
var name node
|
var name node
|
||||||
switch token := t.next(); token.typ {
|
switch token := t.next(); token.typ {
|
||||||
case itemIdentifier:
|
case itemIdentifier:
|
||||||
|
if _, ok := findFunction(token.val, t, t.set); !ok {
|
||||||
|
t.errorf("function %q not defined", token.val)
|
||||||
|
}
|
||||||
name = newIdentifier(token.val)
|
name = newIdentifier(token.val)
|
||||||
case itemDot:
|
case itemDot:
|
||||||
name = newDot()
|
name = newDot()
|
||||||
@ -735,6 +752,9 @@ Loop:
|
|||||||
case itemError:
|
case itemError:
|
||||||
t.errorf("%s", token.val)
|
t.errorf("%s", token.val)
|
||||||
case itemIdentifier:
|
case itemIdentifier:
|
||||||
|
if _, ok := findFunction(token.val, t, t.set); !ok {
|
||||||
|
t.errorf("function %q not defined", token.val)
|
||||||
|
}
|
||||||
cmd.append(newIdentifier(token.val))
|
cmd.append(newIdentifier(token.val))
|
||||||
case itemDot:
|
case itemDot:
|
||||||
cmd.append(newDot())
|
cmd.append(newDot())
|
||||||
|
@ -143,16 +143,12 @@ var parseTests = []parseTest{
|
|||||||
`[(action: [])]`},
|
`[(action: [])]`},
|
||||||
{"field", "{{.X}}", noError,
|
{"field", "{{.X}}", noError,
|
||||||
`[(action: [(command: [F=[X]])])]`},
|
`[(action: [(command: [F=[X]])])]`},
|
||||||
{"simple command", "{{hello}}", noError,
|
{"simple command", "{{printf}}", noError,
|
||||||
`[(action: [(command: [I=hello])])]`},
|
`[(action: [(command: [I=printf])])]`},
|
||||||
{"multi-word command", "{{hello world}}", noError,
|
{"multi-word command", "{{printf `%d` 23}}", noError,
|
||||||
`[(action: [(command: [I=hello I=world])])]`},
|
"[(action: [(command: [I=printf S=`%d` N=23])])]"},
|
||||||
{"multi-word command with number", "{{hello 80}}", noError,
|
{"pipeline", "{{.X|.Y}}", noError,
|
||||||
`[(action: [(command: [I=hello N=80])])]`},
|
`[(action: [(command: [F=[X]]) (command: [F=[Y]])])]`},
|
||||||
{"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 if", "{{if .X}}hello{{end}}", noError,
|
{"simple if", "{{if .X}}hello{{end}}", noError,
|
||||||
`[({{if [(command: [F=[X]])]}} [(text: "hello")])]`},
|
`[({{if [(command: [F=[X]])]}} [(text: "hello")])]`},
|
||||||
{"if with else", "{{if .X}}true{{else}}false{{end}}", noError,
|
{"if with else", "{{if .X}}true{{else}}false{{end}}", noError,
|
||||||
@ -171,8 +167,8 @@ var parseTests = []parseTest{
|
|||||||
`[({{range [(command: [F=[SI]])]}} [(action: [(command: [{{<.>}}])])])]`},
|
`[({{range [(command: [F=[SI]])]}} [(action: [(command: [{{<.>}}])])])]`},
|
||||||
{"constants", "{{range .SI 1 -3.2i true false }}{{end}}", noError,
|
{"constants", "{{range .SI 1 -3.2i true false }}{{end}}", noError,
|
||||||
`[({{range [(command: [F=[SI] N=1 N=-3.2i B=true B=false])]}} [])]`},
|
`[({{range [(command: [F=[SI] N=1 N=-3.2i B=true B=false])]}} [])]`},
|
||||||
{"template", "{{template foo .X}}", noError,
|
{"template", "{{template `x` .Y}}", noError,
|
||||||
"[{{template I=foo [(command: [F=[X]])]}}]"},
|
"[{{template S=`x` [(command: [F=[Y]])]}}]"},
|
||||||
{"with", "{{with .X}}hello{{end}}", noError,
|
{"with", "{{with .X}}hello{{end}}", noError,
|
||||||
`[({{with [(command: [F=[X]])]}} [(text: "hello")])]`},
|
`[({{with [(command: [F=[X]])]}} [(text: "hello")])]`},
|
||||||
{"with with else", "{{with .X}}hello{{else}}goodbye{{end}}", noError,
|
{"with with else", "{{with .X}}hello{{else}}goodbye{{end}}", noError,
|
||||||
@ -181,6 +177,7 @@ var parseTests = []parseTest{
|
|||||||
{"unclosed action", "hello{{range", hasError, ""},
|
{"unclosed action", "hello{{range", hasError, ""},
|
||||||
{"missing end", "hello{{range .x}}", hasError, ""},
|
{"missing end", "hello{{range .x}}", hasError, ""},
|
||||||
{"missing end after else", "hello{{range .x}}{{else}}", hasError, ""},
|
{"missing end after else", "hello{{range .x}}{{else}}", hasError, ""},
|
||||||
|
{"undefined function", "hello{{undefined}}", hasError, ""},
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestParse(t *testing.T) {
|
func TestParse(t *testing.T) {
|
||||||
|
@ -56,7 +56,7 @@ func (s *Set) Parse(text string) (err os.Error) {
|
|||||||
const context = "define clause"
|
const context = "define clause"
|
||||||
for {
|
for {
|
||||||
t := New("set") // name will be updated once we know it.
|
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 }}".
|
// Expect EOF or "{{ define name }}".
|
||||||
if t.atEOF() {
|
if t.atEOF() {
|
||||||
return
|
return
|
||||||
|
Loading…
Reference in New Issue
Block a user