]> Cypherpunks repositories - gostls13.git/commitdiff
exp/template: static check for defined variables.
authorRob Pike <r@golang.org>
Mon, 11 Jul 2011 00:01:15 +0000 (10:01 +1000)
committerRob Pike <r@golang.org>
Mon, 11 Jul 2011 00:01:15 +0000 (10:01 +1000)
Worth catching at parse time rather than execution. Plus it's really easy.

R=golang-dev, dsymonds
CC=golang-dev
https://golang.org/cl/4641100

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

index 734399820538b0745718d39e74db58283a608e47..55cb681363c2ccd2a1cb2d87b99cded022eefcec 100644 (file)
@@ -163,7 +163,6 @@ var execTests = []execTest{
        {"with $x int", "{{with $x := .I}}{{$x}}{{end}}", "17", tVal, true},
        {"range $x SI", "{{range $x := .SI}}<{{$x}}>{{end}}", "<3><4><5>", tVal, true},
        {"range $x PSI", "{{range $x := .PSI}}<{{$x}}>{{end}}", "<21><22><23>", tVal, true},
-       {"after range $x", "{{range $x := .SI}}{{end}}{{$x}}", "", tVal, false},
        {"if $x with $y int", "{{if $x := true}}{{with $y := .I}}{{$x}},{{$y}}{{end}}{{end}}", "true,17", tVal, true},
        {"if $x with $x int", "{{if $x := true}}{{with $x := .I}}{{$x}},{{end}}{{$x}}{{end}}", "17,true", tVal, true},
 
index effd8245045b107e16dbb9e2dc4c056ea30f011b..b7fea497e2429d640055643a2011ec435bbc7f77 100644 (file)
@@ -25,6 +25,7 @@ type Template struct {
        lex       *lexer
        token     [2]item // two-token lookahead for parser
        peekCount int
+       vars      []string // variables defined at the moment
 }
 
 // Name returns the name of the template.
@@ -32,6 +33,11 @@ func (t *Template) Name() string {
        return t.name
 }
 
+// popVars trims the variable list to the specified length
+func (t *Template) popVars(n int) {
+       t.vars = t.vars[:n]
+}
+
 // next returns the next token.
 func (t *Template) next() item {
        if t.peekCount > 0 {
@@ -560,11 +566,13 @@ func (t *Template) startParse(set *Set, lex *lexer) {
        t.root = nil
        t.set = set
        t.lex = lex
+       t.vars = []string{"$"}
 }
 
 // stopParse terminates parsing.
 func (t *Template) stopParse() {
        t.set, t.lex = nil, nil
+       t.vars = nil
 }
 
 // atEOF returns true if, possibly after spaces, we're at EOF.
@@ -605,6 +613,9 @@ func (t *Template) ParseInSet(s string, set *Set) (err os.Error) {
        t.startParse(set, lex(t.name, s))
        defer t.recover(&err)
        t.parse(true)
+       if len(t.vars) != 1 { // $ should still be defined
+               t.errorf("internal error: vars not popped")
+       }
        t.stopParse()
        return
 }
@@ -674,6 +685,7 @@ func (t *Template) action() (n node) {
                return t.withControl()
        }
        t.backup()
+       defer t.popVars(len(t.vars))
        return newAction(t.lex.lineNumber(), t.pipeline("command"))
 }
 
@@ -688,6 +700,7 @@ func (t *Template) pipeline(context string) (pipe *pipeNode) {
                if ce := t.peek(); ce.typ == itemColonEquals {
                        t.next()
                        decl = newVariable(v.val)
+                       t.vars = append(t.vars, v.val)
                } else {
                        t.backup2(v)
                }
@@ -712,6 +725,7 @@ func (t *Template) pipeline(context string) (pipe *pipeNode) {
 
 func (t *Template) parseControl(context string) (lineNum int, pipe *pipeNode, list, elseList *listNode) {
        lineNum = t.lex.lineNumber()
+       defer t.popVars(len(t.vars))
        pipe = t.pipeline(context)
        var next node
        list, next = t.itemList(false)
@@ -795,6 +809,7 @@ func (t *Template) templateControl() node {
        var pipe *pipeNode
        if t.next().typ != itemRightDelim {
                t.backup()
+               defer t.popVars(len(t.vars))
                pipe = t.pipeline("template")
        }
        return newTemplate(t.lex.lineNumber(), name, pipe)
@@ -823,6 +838,16 @@ Loop:
                case itemDot:
                        cmd.append(newDot())
                case itemVariable:
+                       found := false
+                       for _, varName := range t.vars {
+                               if varName == token.val {
+                                       found = true
+                                       break
+                               }
+                       }
+                       if !found {
+                               t.errorf("undefined variable %q", token.val)
+                       }
                        cmd.append(newVariable(token.val))
                case itemField:
                        cmd.append(newField(token.val))
index 1d358209abcbdf53fa6a7b2e6fcd34f08bb0cdd7..7524ac8b251583fcbbdedb5f321f4c539cf56bce 100644 (file)
@@ -146,8 +146,10 @@ var parseTests = []parseTest{
                `[(action: [(command: [F=[X]])])]`},
        {"simple command", "{{printf}}", noError,
                `[(action: [(command: [I=printf])])]`},
-       {"variable invocation", "{{$x 23}}", noError,
-               "[(action: [(command: [V=$x N=23])])]"},
+       {"$ invocation", "{{$}}", noError,
+               "[(action: [(command: [V=$])])]"},
+       {"variable invocation", "{{with $x := 3}}{{$x 23}}{{end}}", noError,
+               "[({{with $x := [(command: [N=3])]}} [(action: [(command: [V=$x N=23])])])]"},
        {"multi-word command", "{{printf `%d` 23}}", noError,
                "[(action: [(command: [I=printf S=`%d` N=23])])]"},
        {"pipeline", "{{.X|.Y}}", noError,
@@ -184,9 +186,12 @@ var parseTests = []parseTest{
                `[({{with [(command: [F=[X]])]}} [(text: "hello")] {{else}} [(text: "goodbye")])]`},
        // Errors.
        {"unclosed action", "hello{{range", hasError, ""},
+       {"unmatched end", "{{end}}", hasError, ""},
        {"missing end", "hello{{range .x}}", hasError, ""},
        {"missing end after else", "hello{{range .x}}{{else}}", hasError, ""},
        {"undefined function", "hello{{undefined}}", hasError, ""},
+       {"undefined variable", "{{$x}}", hasError, ""},
+       {"variable undefined after end", "{{with $x := 4}}{{end}}{{$x}}", hasError, ""},
 }
 
 func TestParse(t *testing.T) {