]> Cypherpunks repositories - gostls13.git/commitdiff
exp/templates: variable scope persists until "end".
authorRob Pike <r@golang.org>
Sun, 17 Jul 2011 03:31:59 +0000 (13:31 +1000)
committerRob Pike <r@golang.org>
Sun, 17 Jul 2011 03:31:59 +0000 (13:31 +1000)
The previous CL doicumented and diagnosed the old situation.
This one changes it to something more traditional: any action
may declare a variable, and the block structure of scopes
applies only to control seequences.

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

src/pkg/exp/template/doc.go
src/pkg/exp/template/exec.go
src/pkg/exp/template/exec_test.go
src/pkg/exp/template/parse.go
src/pkg/exp/template/parse_test.go
src/pkg/exp/template/set_test.go

index 9dcb6b12be7f5ac3068c8a70248bc19c72025848..41e6002786974cda822f4e66fb5211f1a68dbe3c 100644 (file)
@@ -153,8 +153,8 @@ Execute.
 
 Variables
 
-A pipeline inside an "if" or "with" action may initialize a variable to capture
-the result.  The initialization has syntax
+A pipeline inside an action may initialize a variable to capture the result.
+The initialization has syntax
 
        $variable := pipeline
 
@@ -171,8 +171,10 @@ array/slice index or map key and element, respectively.  Note that if there is
 only one variable, it is assigned the element; this is opposite to the
 convention in Go range clauses.
 
-A variable's scope extends to the "end" action of the control structure
-declaring it.
+A variable's scope extends to the "end" action of the control structure ("if",
+"with", or "range") in which it is declared, or to the end of the template if
+there is no such control structure.  A template invocation does not inherit
+variables from the point of its invocation.
 
 When execution begins, $ is set to the data argument passed to Execute, that is,
 to the starting value of dot.
index d60b107687e1fd1bb1026342bf28e2ad85fe7fcb..169c8c6918397784b8a031c64e1a8d80bbcfc061 100644 (file)
@@ -98,9 +98,6 @@ func (t *Template) ExecuteInSet(wr io.Writer, data interface{}, set *Set) (err o
                state.errorf("must be parsed before execution")
        }
        state.walk(value, t.root)
-       if state.mark() != 1 {
-               t.errorf("internal error: variable stack at %d", state.mark())
-       }
        return
 }
 
@@ -110,7 +107,7 @@ func (s *state) walk(dot reflect.Value, n node) {
        switch n := n.(type) {
        case *actionNode:
                s.line = n.line
-               defer s.pop(s.mark())
+               // Do not pop variables so they persist until next end.
                s.printValue(n, s.evalPipeline(dot, n.pipe))
        case *ifNode:
                s.line = n.line
@@ -235,7 +232,7 @@ func (s *state) walkTemplate(dot reflect.Value, t *templateNode) {
        if tmpl == nil {
                s.errorf("template %q not in set", t.name)
        }
-       defer s.pop(s.mark())
+       // Variables declared by the pipeline persist.
        dot = s.evalPipeline(dot, t.pipe)
        newState := *s
        newState.tmpl = tmpl
index 7d73f89701ac1aa00f9c75e2089580d82c7cdf02..efac443668e978bbd4c35285c97afd3d7e83a6fa 100644 (file)
@@ -197,6 +197,7 @@ var execTests = []execTest{
        {"$ int", "{{$}}", "123", 123, true},
        {"$.I", "{{$.I}}", "17", tVal, true},
        {"$.U.V", "{{$.U.V}}", "v", tVal, true},
+       {"declare in action", "{{$x := $.U.V}},{{$x}}", "v,v", tVal, true},
 
        // Pointers.
        {"*int", "{{.PI}}", "23", tVal, true},
@@ -309,7 +310,8 @@ var execTests = []execTest{
        {"with map", "{{with .MSIone}}{{.}}{{else}}EMPTY{{end}}", "map[one:1]", tVal, true},
        {"with empty interface, struct field", "{{with .Empty4}}{{.V}}{{end}}", "v", tVal, true},
        {"with $x int", "{{with $x := .I}}{{$x}}{{end}}", "17", tVal, true},
-       {"with $x struct.U.V", "{{with $x := $}}{{$.U.V}}{{end}}", "v", tVal, true},
+       {"with $x struct.U.V", "{{with $x := $}}{{$x.U.V}}{{end}}", "v", tVal, true},
+       {"with variable and action", "{{with $x := $}}{{$y := $.U.V}},{{$y}}{{end}}", "v,v", tVal, true},
 
        // Range.
        {"range []int", "{{range .SI}}-{{.}}-{{end}}", "-3--4--5-", tVal, true},
index 96099357556529398ff5a4422b2a132218160188..9208d0d04d66f3ee1b4c21b7e01457465d300373 100644 (file)
@@ -695,14 +695,14 @@ 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", false))
+       // Do not pop variables; they persist until "end".
+       return newAction(t.lex.lineNumber(), t.pipeline("command"))
 }
 
 // Pipeline:
 //     field or command
 //     pipeline "|" pipeline
-func (t *Template) pipeline(context string, allowDecls bool) (pipe *pipeNode) {
+func (t *Template) pipeline(context string) (pipe *pipeNode) {
        var decl []*variableNode
        // Are there declarations?
        for {
@@ -714,9 +714,6 @@ func (t *Template) pipeline(context string, allowDecls bool) (pipe *pipeNode) {
                                if len(variable.ident) != 1 {
                                        t.errorf("illegal variable in declaration: %s", v.val)
                                }
-                               if !allowDecls {
-                                       t.errorf("variable %q declared but cannot be referenced", v.val)
-                               }
                                decl = append(decl, variable)
                                t.vars = append(t.vars, v.val)
                                if next.typ == itemChar && next.val == "," {
@@ -753,7 +750,7 @@ func (t *Template) pipeline(context string, allowDecls bool) (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, true)
+       pipe = t.pipeline(context)
        var next node
        list, next = t.itemList(false)
        switch next.typ() {
@@ -827,8 +824,8 @@ 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", false)
+               // Do not pop variables; they persist until "end".
+               pipe = t.pipeline("template")
        }
        return newTemplate(t.lex.lineNumber(), name, pipe)
 }
index 7a1468c3839f18388ac407d6efb1c3ac2cac02d4..6b4ca1989f7188accc30a1fb5c6795c0f49bd688 100644 (file)
@@ -181,6 +181,8 @@ var parseTests = []parseTest{
                "[(action: [(command: [I=printf S=`%d` N=23])])]"},
        {"pipeline", "{{.X|.Y}}", noError,
                `[(action: [(command: [F=[X]]) (command: [F=[Y]])])]`},
+       {"pipeline with decl", "{{$x := .X|.Y}}", noError,
+               `[(action: [V=[$x]] := [(command: [F=[X]]) (command: [F=[Y]])])]`},
        {"declaration", "{{.X|.Y}}", noError,
                `[(action: [(command: [F=[X]]) (command: [F=[Y]])])]`},
        {"simple if", "{{if .X}}hello{{end}}", noError,
@@ -224,7 +226,6 @@ var parseTests = []parseTest{
        {"invalid punctuation", "{{printf 3, 4}}", hasError, ""},
        {"multidecl outside range", "{{with $v, $u := 3}}{{end}}", hasError, ""},
        {"too many decls in range", "{{range $u, $v, $w := 3}}{{end}}", hasError, ""},
-       {"useless declaration", "{{$x := .X|.Y}}", hasError, ""},
 }
 
 func TestParse(t *testing.T) {
index 2f347c37dc97a812bd27afcf3d61aaf4df10740f..b28a352a34a2965b191e29cb695f2518357b7562 100644 (file)
@@ -81,6 +81,7 @@ var setExecTests = []execTest{
        {"invoke dot []int", `{{template "dot" .SI}}`, "[3 4 5]", tVal, true},
        {"invoke dotV", `{{template "dotV" .U}}`, "v", tVal, true},
        {"invoke nested int", `{{template "nested" .I}}`, "17", tVal, true},
+       {"variable declared by template", `{{template "nested" $x=.SI}},{{index $x 1}}`, "[3 4 5],4", tVal, true},
 
        // User-defined function: test argument evaluator.
        {"testFunc literal", `{{oneArg "joe"}}`, "oneArg=joe", tVal, true},