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:
parent
331840f509
commit
c705701c69
@ -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.
|
||||||
|
@ -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
|
||||||
|
@ -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},
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -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) {
|
||||||
|
@ -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},
|
||||||
|
Loading…
Reference in New Issue
Block a user