aboutsummaryrefslogtreecommitdiff
path: root/src/text/template/exec.go
diff options
context:
space:
mode:
authorTim Cooper <tim.cooper@layeh.com>2017-09-26 21:14:03 -0300
committerRob Pike <r@golang.org>2017-10-17 02:06:15 +0000
commit3be5d551801a97a76e236a2a53489b1c9c22e665 (patch)
tree99281e70b044f736eef5def74773045673392747 /src/text/template/exec.go
parent0b2cb89196967030ae005076f0166ad7d6024083 (diff)
downloadgo-3be5d551801a97a76e236a2a53489b1c9c22e665.tar.xz
text/template: add break, continue actions in ranges
Adds the two range control actions "break" and "continue". They act the same as the Go keywords break and continue, but are simplified in that only the innermost range statement can be broken out of or continued. Fixes #20531 Change-Id: I4412b3bbfd4dadb0ab74ae718e308c1ac7a0a1e9 Reviewed-on: https://go-review.googlesource.com/66410 Reviewed-by: Rob Pike <r@golang.org>
Diffstat (limited to 'src/text/template/exec.go')
-rw-r--r--src/text/template/exec.go84
1 files changed, 60 insertions, 24 deletions
diff --git a/src/text/template/exec.go b/src/text/template/exec.go
index 83c38cdf13..87cf1e9b1c 100644
--- a/src/text/template/exec.go
+++ b/src/text/template/exec.go
@@ -25,11 +25,12 @@ const maxExecDepth = 100000
// template so that multiple executions of the same template
// can execute in parallel.
type state struct {
- tmpl *Template
- wr io.Writer
- node parse.Node // current node, for errors
- vars []variable // push-down stack of variable values.
- depth int // the height of the stack of executing templates.
+ tmpl *Template
+ wr io.Writer
+ node parse.Node // current node, for errors.
+ vars []variable // push-down stack of variable values.
+ depth int // the height of the stack of executing templates.
+ rangeDepth int // nesting level of range loops.
}
// variable holds the dynamic value of a variable such as $, $x etc.
@@ -220,9 +221,17 @@ func (t *Template) DefinedTemplates() string {
return s
}
+type rangeControl int8
+
+const (
+ rangeNone rangeControl = iota // no action.
+ rangeBreak // break out of range.
+ rangeContinue // continues next range iteration.
+)
+
// Walk functions step through the major pieces of the template structure,
// generating output as they go.
-func (s *state) walk(dot reflect.Value, node parse.Node) {
+func (s *state) walk(dot reflect.Value, node parse.Node) rangeControl {
s.at(node)
switch node := node.(type) {
case *parse.ActionNode:
@@ -233,13 +242,15 @@ func (s *state) walk(dot reflect.Value, node parse.Node) {
s.printValue(node, val)
}
case *parse.IfNode:
- s.walkIfOrWith(parse.NodeIf, dot, node.Pipe, node.List, node.ElseList)
+ return s.walkIfOrWith(parse.NodeIf, dot, node.Pipe, node.List, node.ElseList)
case *parse.ListNode:
for _, node := range node.Nodes {
- s.walk(dot, node)
+ if c := s.walk(dot, node); c != rangeNone {
+ return c
+ }
}
case *parse.RangeNode:
- s.walkRange(dot, node)
+ return s.walkRange(dot, node)
case *parse.TemplateNode:
s.walkTemplate(dot, node)
case *parse.TextNode:
@@ -247,15 +258,26 @@ func (s *state) walk(dot reflect.Value, node parse.Node) {
s.writeError(err)
}
case *parse.WithNode:
- s.walkIfOrWith(parse.NodeWith, dot, node.Pipe, node.List, node.ElseList)
+ return s.walkIfOrWith(parse.NodeWith, dot, node.Pipe, node.List, node.ElseList)
+ case *parse.BreakNode:
+ if s.rangeDepth == 0 {
+ s.errorf("invalid break outside of range")
+ }
+ return rangeBreak
+ case *parse.ContinueNode:
+ if s.rangeDepth == 0 {
+ s.errorf("invalid continue outside of range")
+ }
+ return rangeContinue
default:
s.errorf("unknown node: %s", node)
}
+ return rangeNone
}
// walkIfOrWith walks an 'if' or 'with' node. The two control structures
// are identical in behavior except that 'with' sets dot.
-func (s *state) walkIfOrWith(typ parse.NodeType, dot reflect.Value, pipe *parse.PipeNode, list, elseList *parse.ListNode) {
+func (s *state) walkIfOrWith(typ parse.NodeType, dot reflect.Value, pipe *parse.PipeNode, list, elseList *parse.ListNode) rangeControl {
defer s.pop(s.mark())
val := s.evalPipeline(dot, pipe)
truth, ok := isTrue(val)
@@ -264,13 +286,14 @@ func (s *state) walkIfOrWith(typ parse.NodeType, dot reflect.Value, pipe *parse.
}
if truth {
if typ == parse.NodeWith {
- s.walk(val, list)
+ return s.walk(val, list)
} else {
- s.walk(dot, list)
+ return s.walk(dot, list)
}
} else if elseList != nil {
- s.walk(dot, elseList)
+ return s.walk(dot, elseList)
}
+ return rangeNone
}
// IsTrue reports whether the value is 'true', in the sense of not the zero of its type,
@@ -308,13 +331,14 @@ func isTrue(val reflect.Value) (truth, ok bool) {
return truth, true
}
-func (s *state) walkRange(dot reflect.Value, r *parse.RangeNode) {
+func (s *state) walkRange(dot reflect.Value, r *parse.RangeNode) rangeControl {
s.at(r)
defer s.pop(s.mark())
val, _ := indirect(s.evalPipeline(dot, r.Pipe))
// mark top of stack before any variables in the body are pushed.
mark := s.mark()
- oneIteration := func(index, elem reflect.Value) {
+ s.rangeDepth++
+ oneIteration := func(index, elem reflect.Value) rangeControl {
// Set top var (lexically the second if there are two) to the element.
if len(r.Pipe.Decl) > 0 {
s.setVar(1, elem)
@@ -323,8 +347,9 @@ func (s *state) walkRange(dot reflect.Value, r *parse.RangeNode) {
if len(r.Pipe.Decl) > 1 {
s.setVar(2, index)
}
- s.walk(elem, r.List)
+ ctrl := s.walk(elem, r.List)
s.pop(mark)
+ return ctrl
}
switch val.Kind() {
case reflect.Array, reflect.Slice:
@@ -332,17 +357,23 @@ func (s *state) walkRange(dot reflect.Value, r *parse.RangeNode) {
break
}
for i := 0; i < val.Len(); i++ {
- oneIteration(reflect.ValueOf(i), val.Index(i))
+ if ctrl := oneIteration(reflect.ValueOf(i), val.Index(i)); ctrl == rangeBreak {
+ break
+ }
}
- return
+ s.rangeDepth--
+ return rangeNone
case reflect.Map:
if val.Len() == 0 {
break
}
for _, key := range sortKeys(val.MapKeys()) {
- oneIteration(key, val.MapIndex(key))
+ if ctrl := oneIteration(key, val.MapIndex(key)); ctrl == rangeBreak {
+ break
+ }
}
- return
+ s.rangeDepth--
+ return rangeNone
case reflect.Chan:
if val.IsNil() {
break
@@ -353,20 +384,25 @@ func (s *state) walkRange(dot reflect.Value, r *parse.RangeNode) {
if !ok {
break
}
- oneIteration(reflect.ValueOf(i), elem)
+ if ctrl := oneIteration(reflect.ValueOf(i), elem); ctrl == rangeBreak {
+ break
+ }
}
if i == 0 {
break
}
- return
+ s.rangeDepth--
+ return rangeNone
case reflect.Invalid:
break // An invalid value is likely a nil map, etc. and acts like an empty map.
default:
s.errorf("range can't iterate over %v", val)
}
+ s.rangeDepth--
if r.ElseList != nil {
- s.walk(dot, r.ElseList)
+ return s.walk(dot, r.ElseList)
}
+ return rangeNone
}
func (s *state) walkTemplate(dot reflect.Value, t *parse.TemplateNode) {