From d0dd26a88c019d54f22463daae81e785f5867565 Mon Sep 17 00:00:00 2001 From: Russ Cox Date: Thu, 20 May 2021 12:46:33 -0400 Subject: html/template, text/template: implement break and continue for range loops Break and continue for range loops was accepted as a proposal in June 2017. It was implemented in CL 66410 (Oct 2017) but then rolled back in CL 92155 (Feb 2018) because html/template changes had not been implemented. This CL reimplements break and continue in text/template and then adds support for them in html/template as well. Fixes #20531. Change-Id: I05330482a976f1c078b4b49c2287bd9031bb7616 Reviewed-on: https://go-review.googlesource.com/c/go/+/321491 Trust: Russ Cox Run-TryBot: Russ Cox TryBot-Result: Go Bot Reviewed-by: Rob Pike --- src/text/template/exec.go | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) (limited to 'src/text/template/exec.go') diff --git a/src/text/template/exec.go b/src/text/template/exec.go index 6e005b57d7..e03920964e 100644 --- a/src/text/template/exec.go +++ b/src/text/template/exec.go @@ -5,6 +5,7 @@ package template import ( + "errors" "fmt" "internal/fmtsort" "io" @@ -243,6 +244,12 @@ func (t *Template) DefinedTemplates() string { return b.String() } +// Sentinel errors for use with panic to signal early exits from range loops. +var ( + walkBreak = errors.New("break") + walkContinue = errors.New("continue") +) + // 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) { @@ -255,7 +262,11 @@ func (s *state) walk(dot reflect.Value, node parse.Node) { if len(node.Pipe.Decl) == 0 { s.printValue(node, val) } + case *parse.BreakNode: + panic(walkBreak) case *parse.CommentNode: + case *parse.ContinueNode: + panic(walkContinue) case *parse.IfNode: s.walkIfOrWith(parse.NodeIf, dot, node.Pipe, node.List, node.ElseList) case *parse.ListNode: @@ -334,6 +345,11 @@ func isTrue(val reflect.Value) (truth, ok bool) { func (s *state) walkRange(dot reflect.Value, r *parse.RangeNode) { s.at(r) + defer func() { + if r := recover(); r != nil && r != walkBreak { + panic(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. @@ -347,8 +363,14 @@ func (s *state) walkRange(dot reflect.Value, r *parse.RangeNode) { if len(r.Pipe.Decl) > 1 { s.setTopVar(2, index) } + defer s.pop(mark) + defer func() { + // Consume panic(walkContinue) + if r := recover(); r != nil && r != walkContinue { + panic(r) + } + }() s.walk(elem, r.List) - s.pop(mark) } switch val.Kind() { case reflect.Array, reflect.Slice: -- cgit v1.3-6-g1900