Commit c705701c authored by Rob Pike's avatar Rob Pike

exp/templates: variable scope persists until "end".

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
parent 331840f5
......@@ -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.
......
......@@ -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
......
......@@ -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},
......
......@@ -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)
}
......
......@@ -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) {
......
......@@ -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},
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment