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
This commit is contained in:
Rob Pike 2011-07-17 13:31:59 +10:00
parent 331840f509
commit c705701c69
6 changed files with 20 additions and 20 deletions

View 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.

View 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

View 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},

View 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)
}

View 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) {

View 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},