1
0
mirror of https://github.com/golang/go synced 2024-11-24 20:20:03 -07:00

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

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

View File

@ -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{
{"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},

View File

@ -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) {
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) {
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 {
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)
} }

View File

@ -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{
{"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) {

View File

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