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
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.
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
}
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
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
{"$ 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},
{"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},
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 {
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 == "," {
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() {
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)
}
"[(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,
{"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) {
{"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},