diff --git a/src/pkg/exp/template/doc.go b/src/pkg/exp/template/doc.go index 9dcb6b12be7..41e60027869 100644 --- a/src/pkg/exp/template/doc.go +++ b/src/pkg/exp/template/doc.go @@ -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. diff --git a/src/pkg/exp/template/exec.go b/src/pkg/exp/template/exec.go index d60b107687e..169c8c69183 100644 --- a/src/pkg/exp/template/exec.go +++ b/src/pkg/exp/template/exec.go @@ -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 diff --git a/src/pkg/exp/template/exec_test.go b/src/pkg/exp/template/exec_test.go index 7d73f89701a..efac443668e 100644 --- a/src/pkg/exp/template/exec_test.go +++ b/src/pkg/exp/template/exec_test.go @@ -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}, diff --git a/src/pkg/exp/template/parse.go b/src/pkg/exp/template/parse.go index 96099357556..9208d0d04d6 100644 --- a/src/pkg/exp/template/parse.go +++ b/src/pkg/exp/template/parse.go @@ -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) } diff --git a/src/pkg/exp/template/parse_test.go b/src/pkg/exp/template/parse_test.go index 7a1468c3839..6b4ca1989f7 100644 --- a/src/pkg/exp/template/parse_test.go +++ b/src/pkg/exp/template/parse_test.go @@ -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) { diff --git a/src/pkg/exp/template/set_test.go b/src/pkg/exp/template/set_test.go index 2f347c37dc9..b28a352a34a 100644 --- a/src/pkg/exp/template/set_test.go +++ b/src/pkg/exp/template/set_test.go @@ -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},