diff options
| author | Tim Cooper <tim.cooper@layeh.com> | 2017-09-26 21:14:03 -0300 |
|---|---|---|
| committer | Rob Pike <r@golang.org> | 2017-10-17 02:06:15 +0000 |
| commit | 3be5d551801a97a76e236a2a53489b1c9c22e665 (patch) | |
| tree | 99281e70b044f736eef5def74773045673392747 /src/text/template/parse | |
| parent | 0b2cb89196967030ae005076f0166ad7d6024083 (diff) | |
| download | go-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/parse')
| -rw-r--r-- | src/text/template/parse/lex.go | 4 | ||||
| -rw-r--r-- | src/text/template/parse/lex_test.go | 6 | ||||
| -rw-r--r-- | src/text/template/parse/node.go | 64 | ||||
| -rw-r--r-- | src/text/template/parse/parse.go | 44 | ||||
| -rw-r--r-- | src/text/template/parse/parse_test.go | 12 |
5 files changed, 123 insertions, 7 deletions
diff --git a/src/text/template/parse/lex.go b/src/text/template/parse/lex.go index 2cde4a2ca1..baf48c66c4 100644 --- a/src/text/template/parse/lex.go +++ b/src/text/template/parse/lex.go @@ -60,6 +60,8 @@ const ( // Keywords appear after all the rest. itemKeyword // used only to delimit the keywords itemBlock // block keyword + itemBreak // break keyword + itemContinue // continue keyword itemDot // the cursor, spelled '.' itemDefine // define keyword itemElse // else keyword @@ -74,6 +76,8 @@ const ( var key = map[string]itemType{ ".": itemDot, "block": itemBlock, + "break": itemBreak, + "continue": itemContinue, "define": itemDefine, "else": itemElse, "end": itemEnd, diff --git a/src/text/template/parse/lex_test.go b/src/text/template/parse/lex_test.go index cb01cd98b6..ca7c3f64bc 100644 --- a/src/text/template/parse/lex_test.go +++ b/src/text/template/parse/lex_test.go @@ -192,7 +192,7 @@ var lexTests = []lexTest{ tRight, tEOF, }}, - {"keywords", "{{range if else end with}}", []item{ + {"keywords", "{{range if else end with break continue}}", []item{ tLeft, mkItem(itemRange, "range"), tSpace, @@ -203,6 +203,10 @@ var lexTests = []lexTest{ mkItem(itemEnd, "end"), tSpace, mkItem(itemWith, "with"), + tSpace, + mkItem(itemBreak, "break"), + tSpace, + mkItem(itemContinue, "continue"), tRight, tEOF, }}, diff --git a/src/text/template/parse/node.go b/src/text/template/parse/node.go index 55ff46c17a..7e16349b31 100644 --- a/src/text/template/parse/node.go +++ b/src/text/template/parse/node.go @@ -69,6 +69,8 @@ const ( NodeTemplate // A template invocation action. NodeVariable // A $ variable. NodeWith // A with action. + NodeBreak // A break action. + NodeContinue // A continue action. ) // Nodes. @@ -796,6 +798,68 @@ func (r *RangeNode) Copy() Node { return r.tr.newRange(r.Pos, r.Line, r.Pipe.CopyPipe(), r.List.CopyList(), r.ElseList.CopyList()) } +// BreakNode represents a {{break}} action. +type BreakNode struct { + NodeType + Pos + tr *Tree +} + +func (t *Tree) newBreak(pos Pos) *BreakNode { + return &BreakNode{NodeType: NodeBreak, Pos: pos, tr: t} +} + +func (b *BreakNode) Type() NodeType { + return b.NodeType +} + +func (b *BreakNode) String() string { + return "{{break}}" +} + +func (b *BreakNode) Copy() Node { + return b.tr.newBreak(b.Pos) +} + +func (b *BreakNode) Position() Pos { + return b.Pos +} + +func (b *BreakNode) tree() *Tree { + return b.tr +} + +// ContinueNode represents a {{continue}} action. +type ContinueNode struct { + NodeType + Pos + tr *Tree +} + +func (t *Tree) newContinue(pos Pos) *ContinueNode { + return &ContinueNode{NodeType: NodeContinue, Pos: pos, tr: t} +} + +func (c *ContinueNode) Type() NodeType { + return c.NodeType +} + +func (c *ContinueNode) String() string { + return "{{continue}}" +} + +func (c *ContinueNode) Copy() Node { + return c.tr.newContinue(c.Pos) +} + +func (c *ContinueNode) Position() Pos { + return c.Pos +} + +func (c *ContinueNode) tree() *Tree { + return c.tr +} + // WithNode represents a {{with}} action and its commands. type WithNode struct { BranchNode diff --git a/src/text/template/parse/parse.go b/src/text/template/parse/parse.go index a91a544ce0..ad9c051978 100644 --- a/src/text/template/parse/parse.go +++ b/src/text/template/parse/parse.go @@ -23,12 +23,13 @@ type Tree struct { Root *ListNode // top-level root of the tree. text string // text parsed to create the template (or its parent) // Parsing only; cleared after parse. - funcs []map[string]interface{} - lex *lexer - token [3]item // three-token lookahead for parser. - peekCount int - vars []string // variables defined at the moment. - treeSet map[string]*Tree + funcs []map[string]interface{} + lex *lexer + token [3]item // three-token lookahead for parser. + peekCount int + vars []string // variables defined at the moment. + treeSet map[string]*Tree + rangeDepth int // nesting level of range loops. } // Copy returns a copy of the Tree. Any parsing state is discarded. @@ -219,6 +220,7 @@ func (t *Tree) stopParse() { t.vars = nil t.funcs = nil t.treeSet = nil + t.rangeDepth = 0 } // Parse parses the template definition string to construct a representation of @@ -373,6 +375,10 @@ func (t *Tree) action() (n Node) { return t.templateControl() case itemWith: return t.withControl() + case itemBreak: + return t.breakControl() + case itemContinue: + return t.continueControl() } t.backup() token := t.peek() @@ -453,7 +459,13 @@ func (t *Tree) parseControl(allowElseIf bool, context string) (pos Pos, line int defer t.popVars(len(t.vars)) pipe = t.pipeline(context) var next Node + if context == "range" { + t.rangeDepth++ + } list, next = t.itemList() + if context == "range" { + t.rangeDepth-- + } switch next.Type() { case nodeEnd: //done case nodeElse: @@ -498,6 +510,26 @@ func (t *Tree) rangeControl() Node { return t.newRange(t.parseControl(false, "range")) } +// Break: +// {{break}} +// Break keyword is past. +func (t *Tree) breakControl() Node { + if t.rangeDepth == 0 { + t.errorf("unexpected break outside of range") + } + return t.newBreak(t.expect(itemRightDelim, "break").pos) +} + +// Continue: +// {{continue}} +// Continue keyword is past. +func (t *Tree) continueControl() Node { + if t.rangeDepth == 0 { + t.errorf("unexpected continue outside of range") + } + return t.newContinue(t.expect(itemRightDelim, "continue").pos) +} + // With: // {{with pipeline}} itemList {{end}} // {{with pipeline}} itemList {{else}} itemList {{end}} diff --git a/src/text/template/parse/parse_test.go b/src/text/template/parse/parse_test.go index 81f14aca98..aade33ea48 100644 --- a/src/text/template/parse/parse_test.go +++ b/src/text/template/parse/parse_test.go @@ -218,6 +218,12 @@ var parseTests = []parseTest{ `{{range $x := .SI}}{{.}}{{end}}`}, {"range 2 vars", "{{range $x, $y := .SI}}{{.}}{{end}}", noError, `{{range $x, $y := .SI}}{{.}}{{end}}`}, + {"range []int with break", "{{range .SI}}{{break}}{{.}}{{end}}", noError, + `{{range .SI}}{{break}}{{.}}{{end}}`}, + {"range []int with break in else", "{{range .SI}}{{range .SI}}{{.}}{{else}}{{break}}{{end}}{{end}}", noError, + `{{range .SI}}{{range .SI}}{{.}}{{else}}{{break}}{{end}}{{end}}`}, + {"range []int with continue", "{{range .SI}}{{continue}}{{.}}{{end}}", noError, + `{{range .SI}}{{continue}}{{.}}{{end}}`}, {"constants", "{{range .SI 1 -3.2i true false 'a' nil}}{{end}}", noError, `{{range .SI 1 -3.2i true false 'a' nil}}{{end}}`}, {"template", "{{template `x`}}", noError, @@ -288,6 +294,12 @@ var parseTests = []parseTest{ {"empty pipeline", `{{printf "%d" ( ) }}`, hasError, ""}, // Missing pipeline in block {"block definition", `{{block "foo"}}hello{{end}}`, hasError, ""}, + // Invalid range control + {"break outside of range", `{{break}}`, hasError, ""}, + {"break in range else, outside of range", `{{range .}}{{.}}{{else}}{{break}}{{end}}`, hasError, ""}, + {"continue outside of range", `{{continue}}`, hasError, ""}, + {"continue in range else, outside of range", `{{range .}}{{.}}{{else}}{{continue}}{{end}}`, hasError, ""}, + {"additional break data", `{{range .}}{{break label}}{{end}}`, hasError, ""}, } var builtins = map[string]interface{}{ |
