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