mirror of
https://github.com/golang/go
synced 2024-11-19 21:24:40 -07:00
text/template: revert CL 66410 "add break, continue actions in ranges"
The new break and continue actions do not work in html/template, and fixing them requires thinking about security issues that seem too tricky at this stage of the release. We will try again for 1.11. Original CL description: 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 Updates #20531 Updates #23683 Change-Id: Ia7fd3c409163e3bcb5dc42947ae90b15bdf89853 Reviewed-on: https://go-review.googlesource.com/92155 Run-TryBot: Ian Lance Taylor <iant@golang.org> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Russ Cox <rsc@golang.org>
This commit is contained in:
parent
f54f780d2f
commit
fd7331a821
@ -618,24 +618,6 @@ pkg syscall (windows-386), func CreateProcessAsUser(Token, *uint16, *uint16, *Se
|
|||||||
pkg syscall (windows-386), type SysProcAttr struct, Token Token
|
pkg syscall (windows-386), type SysProcAttr struct, Token Token
|
||||||
pkg syscall (windows-amd64), func CreateProcessAsUser(Token, *uint16, *uint16, *SecurityAttributes, *SecurityAttributes, bool, uint32, *uint16, *uint16, *StartupInfo, *ProcessInformation) error
|
pkg syscall (windows-amd64), func CreateProcessAsUser(Token, *uint16, *uint16, *SecurityAttributes, *SecurityAttributes, bool, uint32, *uint16, *uint16, *StartupInfo, *ProcessInformation) error
|
||||||
pkg syscall (windows-amd64), type SysProcAttr struct, Token Token
|
pkg syscall (windows-amd64), type SysProcAttr struct, Token Token
|
||||||
pkg text/template/parse, const NodeBreak = 20
|
|
||||||
pkg text/template/parse, const NodeBreak NodeType
|
|
||||||
pkg text/template/parse, const NodeContinue = 21
|
|
||||||
pkg text/template/parse, const NodeContinue NodeType
|
|
||||||
pkg text/template/parse, method (*BreakNode) Copy() Node
|
|
||||||
pkg text/template/parse, method (*BreakNode) Position() Pos
|
|
||||||
pkg text/template/parse, method (*BreakNode) String() string
|
|
||||||
pkg text/template/parse, method (*BreakNode) Type() NodeType
|
|
||||||
pkg text/template/parse, method (*ContinueNode) Copy() Node
|
|
||||||
pkg text/template/parse, method (*ContinueNode) Position() Pos
|
|
||||||
pkg text/template/parse, method (*ContinueNode) String() string
|
|
||||||
pkg text/template/parse, method (*ContinueNode) Type() NodeType
|
|
||||||
pkg text/template/parse, type BreakNode struct
|
|
||||||
pkg text/template/parse, type BreakNode struct, embedded NodeType
|
|
||||||
pkg text/template/parse, type BreakNode struct, embedded Pos
|
|
||||||
pkg text/template/parse, type ContinueNode struct
|
|
||||||
pkg text/template/parse, type ContinueNode struct, embedded NodeType
|
|
||||||
pkg text/template/parse, type ContinueNode struct, embedded Pos
|
|
||||||
pkg time, func LoadLocationFromTZData(string, []uint8) (*Location, error)
|
pkg time, func LoadLocationFromTZData(string, []uint8) (*Location, error)
|
||||||
pkg unicode, const Version = "10.0.0"
|
pkg unicode, const Version = "10.0.0"
|
||||||
pkg unicode, var Masaram_Gondi *RangeTable
|
pkg unicode, var Masaram_Gondi *RangeTable
|
||||||
|
@ -1069,11 +1069,6 @@ now implement those interfaces.
|
|||||||
<dl id="html/template"><dt><a href="/pkg/html/template/">html/template</a></dt>
|
<dl id="html/template"><dt><a href="/pkg/html/template/">html/template</a></dt>
|
||||||
<dd>
|
<dd>
|
||||||
<p>
|
<p>
|
||||||
The new actions <code>{{"{{break}}"}}</code> and <code>{{"{{continue}}"}}</code>
|
|
||||||
break out of the innermost <code>{{"{{range"}}</code> ...<code>}}</code> loop,
|
|
||||||
like the corresponding Go statements.
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
The new <a href="/pkg/html/template#Srcset"><code>Srcset</code></a> content
|
The new <a href="/pkg/html/template#Srcset"><code>Srcset</code></a> content
|
||||||
type allows for proper handling of values within the
|
type allows for proper handling of values within the
|
||||||
<a href="https://w3c.github.io/html/semantics-embedded-content.html#element-attrdef-img-srcset"><code>srcset</code></a>
|
<a href="https://w3c.github.io/html/semantics-embedded-content.html#element-attrdef-img-srcset"><code>srcset</code></a>
|
||||||
@ -1411,15 +1406,6 @@ is now implemented.
|
|||||||
</p>
|
</p>
|
||||||
</dl>
|
</dl>
|
||||||
|
|
||||||
<dl id="text/template"><dt><a href="/pkg/text/template/">text/template</a></dt>
|
|
||||||
<dd>
|
|
||||||
<p>
|
|
||||||
The new actions <code>{{"{{break}}"}}</code> and <code>{{"{{continue}}"}}</code>
|
|
||||||
break out of the innermost <code>{{"{{range"}}</code> ...<code>}}</code> loop,
|
|
||||||
like the corresponding Go statements.
|
|
||||||
</p>
|
|
||||||
</dl>
|
|
||||||
|
|
||||||
<dl id="time"><dt><a href="/pkg/time/">time</a></dt>
|
<dl id="time"><dt><a href="/pkg/time/">time</a></dt>
|
||||||
<dd>
|
<dd>
|
||||||
<p>
|
<p>
|
||||||
|
@ -110,12 +110,6 @@ data, defined in detail in the corresponding sections that follow.
|
|||||||
T0 is executed; otherwise, dot is set to the successive elements
|
T0 is executed; otherwise, dot is set to the successive elements
|
||||||
of the array, slice, or map and T1 is executed.
|
of the array, slice, or map and T1 is executed.
|
||||||
|
|
||||||
{{break}}
|
|
||||||
Break out of the surrounding range loop.
|
|
||||||
|
|
||||||
{{continue}}
|
|
||||||
Begin the next iteration of the surrounding range loop.
|
|
||||||
|
|
||||||
{{template "name"}}
|
{{template "name"}}
|
||||||
The template with the specified name is executed with nil data.
|
The template with the specified name is executed with nil data.
|
||||||
|
|
||||||
|
@ -25,12 +25,11 @@ const maxExecDepth = 100000
|
|||||||
// template so that multiple executions of the same template
|
// template so that multiple executions of the same template
|
||||||
// can execute in parallel.
|
// can execute in parallel.
|
||||||
type state struct {
|
type state struct {
|
||||||
tmpl *Template
|
tmpl *Template
|
||||||
wr io.Writer
|
wr io.Writer
|
||||||
node parse.Node // current node, for errors.
|
node parse.Node // current node, for errors
|
||||||
vars []variable // push-down stack of variable values.
|
vars []variable // push-down stack of variable values.
|
||||||
depth int // the height of the stack of executing templates.
|
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.
|
// variable holds the dynamic value of a variable such as $, $x etc.
|
||||||
@ -221,17 +220,9 @@ func (t *Template) DefinedTemplates() string {
|
|||||||
return s
|
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,
|
// Walk functions step through the major pieces of the template structure,
|
||||||
// generating output as they go.
|
// generating output as they go.
|
||||||
func (s *state) walk(dot reflect.Value, node parse.Node) rangeControl {
|
func (s *state) walk(dot reflect.Value, node parse.Node) {
|
||||||
s.at(node)
|
s.at(node)
|
||||||
switch node := node.(type) {
|
switch node := node.(type) {
|
||||||
case *parse.ActionNode:
|
case *parse.ActionNode:
|
||||||
@ -242,15 +233,13 @@ func (s *state) walk(dot reflect.Value, node parse.Node) rangeControl {
|
|||||||
s.printValue(node, val)
|
s.printValue(node, val)
|
||||||
}
|
}
|
||||||
case *parse.IfNode:
|
case *parse.IfNode:
|
||||||
return s.walkIfOrWith(parse.NodeIf, dot, node.Pipe, node.List, node.ElseList)
|
s.walkIfOrWith(parse.NodeIf, dot, node.Pipe, node.List, node.ElseList)
|
||||||
case *parse.ListNode:
|
case *parse.ListNode:
|
||||||
for _, node := range node.Nodes {
|
for _, node := range node.Nodes {
|
||||||
if c := s.walk(dot, node); c != rangeNone {
|
s.walk(dot, node)
|
||||||
return c
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
case *parse.RangeNode:
|
case *parse.RangeNode:
|
||||||
return s.walkRange(dot, node)
|
s.walkRange(dot, node)
|
||||||
case *parse.TemplateNode:
|
case *parse.TemplateNode:
|
||||||
s.walkTemplate(dot, node)
|
s.walkTemplate(dot, node)
|
||||||
case *parse.TextNode:
|
case *parse.TextNode:
|
||||||
@ -258,26 +247,15 @@ func (s *state) walk(dot reflect.Value, node parse.Node) rangeControl {
|
|||||||
s.writeError(err)
|
s.writeError(err)
|
||||||
}
|
}
|
||||||
case *parse.WithNode:
|
case *parse.WithNode:
|
||||||
return s.walkIfOrWith(parse.NodeWith, dot, node.Pipe, node.List, node.ElseList)
|
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:
|
default:
|
||||||
s.errorf("unknown node: %s", node)
|
s.errorf("unknown node: %s", node)
|
||||||
}
|
}
|
||||||
return rangeNone
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// walkIfOrWith walks an 'if' or 'with' node. The two control structures
|
// walkIfOrWith walks an 'if' or 'with' node. The two control structures
|
||||||
// are identical in behavior except that 'with' sets dot.
|
// 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) rangeControl {
|
func (s *state) walkIfOrWith(typ parse.NodeType, dot reflect.Value, pipe *parse.PipeNode, list, elseList *parse.ListNode) {
|
||||||
defer s.pop(s.mark())
|
defer s.pop(s.mark())
|
||||||
val := s.evalPipeline(dot, pipe)
|
val := s.evalPipeline(dot, pipe)
|
||||||
truth, ok := isTrue(val)
|
truth, ok := isTrue(val)
|
||||||
@ -286,14 +264,13 @@ func (s *state) walkIfOrWith(typ parse.NodeType, dot reflect.Value, pipe *parse.
|
|||||||
}
|
}
|
||||||
if truth {
|
if truth {
|
||||||
if typ == parse.NodeWith {
|
if typ == parse.NodeWith {
|
||||||
return s.walk(val, list)
|
s.walk(val, list)
|
||||||
} else {
|
} else {
|
||||||
return s.walk(dot, list)
|
s.walk(dot, list)
|
||||||
}
|
}
|
||||||
} else if elseList != nil {
|
} else if elseList != nil {
|
||||||
return s.walk(dot, elseList)
|
s.walk(dot, elseList)
|
||||||
}
|
}
|
||||||
return rangeNone
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsTrue reports whether the value is 'true', in the sense of not the zero of its type,
|
// IsTrue reports whether the value is 'true', in the sense of not the zero of its type,
|
||||||
@ -331,14 +308,13 @@ func isTrue(val reflect.Value) (truth, ok bool) {
|
|||||||
return truth, true
|
return truth, true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *state) walkRange(dot reflect.Value, r *parse.RangeNode) rangeControl {
|
func (s *state) walkRange(dot reflect.Value, r *parse.RangeNode) {
|
||||||
s.at(r)
|
s.at(r)
|
||||||
defer s.pop(s.mark())
|
defer s.pop(s.mark())
|
||||||
val, _ := indirect(s.evalPipeline(dot, r.Pipe))
|
val, _ := indirect(s.evalPipeline(dot, r.Pipe))
|
||||||
// mark top of stack before any variables in the body are pushed.
|
// mark top of stack before any variables in the body are pushed.
|
||||||
mark := s.mark()
|
mark := s.mark()
|
||||||
s.rangeDepth++
|
oneIteration := func(index, elem reflect.Value) {
|
||||||
oneIteration := func(index, elem reflect.Value) rangeControl {
|
|
||||||
// Set top var (lexically the second if there are two) to the element.
|
// Set top var (lexically the second if there are two) to the element.
|
||||||
if len(r.Pipe.Decl) > 0 {
|
if len(r.Pipe.Decl) > 0 {
|
||||||
s.setVar(1, elem)
|
s.setVar(1, elem)
|
||||||
@ -347,9 +323,8 @@ func (s *state) walkRange(dot reflect.Value, r *parse.RangeNode) rangeControl {
|
|||||||
if len(r.Pipe.Decl) > 1 {
|
if len(r.Pipe.Decl) > 1 {
|
||||||
s.setVar(2, index)
|
s.setVar(2, index)
|
||||||
}
|
}
|
||||||
ctrl := s.walk(elem, r.List)
|
s.walk(elem, r.List)
|
||||||
s.pop(mark)
|
s.pop(mark)
|
||||||
return ctrl
|
|
||||||
}
|
}
|
||||||
switch val.Kind() {
|
switch val.Kind() {
|
||||||
case reflect.Array, reflect.Slice:
|
case reflect.Array, reflect.Slice:
|
||||||
@ -357,23 +332,17 @@ func (s *state) walkRange(dot reflect.Value, r *parse.RangeNode) rangeControl {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
for i := 0; i < val.Len(); i++ {
|
for i := 0; i < val.Len(); i++ {
|
||||||
if ctrl := oneIteration(reflect.ValueOf(i), val.Index(i)); ctrl == rangeBreak {
|
oneIteration(reflect.ValueOf(i), val.Index(i))
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
s.rangeDepth--
|
return
|
||||||
return rangeNone
|
|
||||||
case reflect.Map:
|
case reflect.Map:
|
||||||
if val.Len() == 0 {
|
if val.Len() == 0 {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
for _, key := range sortKeys(val.MapKeys()) {
|
for _, key := range sortKeys(val.MapKeys()) {
|
||||||
if ctrl := oneIteration(key, val.MapIndex(key)); ctrl == rangeBreak {
|
oneIteration(key, val.MapIndex(key))
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
s.rangeDepth--
|
return
|
||||||
return rangeNone
|
|
||||||
case reflect.Chan:
|
case reflect.Chan:
|
||||||
if val.IsNil() {
|
if val.IsNil() {
|
||||||
break
|
break
|
||||||
@ -384,25 +353,20 @@ func (s *state) walkRange(dot reflect.Value, r *parse.RangeNode) rangeControl {
|
|||||||
if !ok {
|
if !ok {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
if ctrl := oneIteration(reflect.ValueOf(i), elem); ctrl == rangeBreak {
|
oneIteration(reflect.ValueOf(i), elem)
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if i == 0 {
|
if i == 0 {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
s.rangeDepth--
|
return
|
||||||
return rangeNone
|
|
||||||
case reflect.Invalid:
|
case reflect.Invalid:
|
||||||
break // An invalid value is likely a nil map, etc. and acts like an empty map.
|
break // An invalid value is likely a nil map, etc. and acts like an empty map.
|
||||||
default:
|
default:
|
||||||
s.errorf("range can't iterate over %v", val)
|
s.errorf("range can't iterate over %v", val)
|
||||||
}
|
}
|
||||||
s.rangeDepth--
|
|
||||||
if r.ElseList != nil {
|
if r.ElseList != nil {
|
||||||
return s.walk(dot, r.ElseList)
|
s.walk(dot, r.ElseList)
|
||||||
}
|
}
|
||||||
return rangeNone
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *state) walkTemplate(dot reflect.Value, t *parse.TemplateNode) {
|
func (s *state) walkTemplate(dot reflect.Value, t *parse.TemplateNode) {
|
||||||
|
@ -513,10 +513,6 @@ var execTests = []execTest{
|
|||||||
{"declare in range", "{{range $x := .PSI}}<{{$foo:=$x}}{{$x}}>{{end}}", "<21><22><23>", tVal, true},
|
{"declare in range", "{{range $x := .PSI}}<{{$foo:=$x}}{{$x}}>{{end}}", "<21><22><23>", tVal, true},
|
||||||
{"range count", `{{range $i, $x := count 5}}[{{$i}}]{{$x}}{{end}}`, "[0]a[1]b[2]c[3]d[4]e", tVal, true},
|
{"range count", `{{range $i, $x := count 5}}[{{$i}}]{{$x}}{{end}}`, "[0]a[1]b[2]c[3]d[4]e", tVal, true},
|
||||||
{"range nil count", `{{range $i, $x := count 0}}{{else}}empty{{end}}`, "empty", tVal, true},
|
{"range nil count", `{{range $i, $x := count 0}}{{else}}empty{{end}}`, "empty", tVal, true},
|
||||||
{"range quick break", `{{range .SI}}{{break}}{{.}}{{end}}`, "", tVal, true},
|
|
||||||
{"range break after two", `{{range $i, $x := .SI}}{{if ge $i 2}}{{break}}{{end}}{{.}}{{end}}`, "34", tVal, true},
|
|
||||||
{"range continue", `{{range .SI}}{{continue}}{{.}}{{end}}`, "", tVal, true},
|
|
||||||
{"range continue condition", `{{range .SI}}{{if eq . 3 }}{{continue}}{{end}}{{.}}{{end}}`, "45", tVal, true},
|
|
||||||
|
|
||||||
// Cute examples.
|
// Cute examples.
|
||||||
{"or as if true", `{{or .SI "slice is empty"}}`, "[3 4 5]", tVal, true},
|
{"or as if true", `{{or .SI "slice is empty"}}`, "[3 4 5]", tVal, true},
|
||||||
|
@ -60,8 +60,6 @@ const (
|
|||||||
// Keywords appear after all the rest.
|
// Keywords appear after all the rest.
|
||||||
itemKeyword // used only to delimit the keywords
|
itemKeyword // used only to delimit the keywords
|
||||||
itemBlock // block keyword
|
itemBlock // block keyword
|
||||||
itemBreak // break keyword
|
|
||||||
itemContinue // continue keyword
|
|
||||||
itemDot // the cursor, spelled '.'
|
itemDot // the cursor, spelled '.'
|
||||||
itemDefine // define keyword
|
itemDefine // define keyword
|
||||||
itemElse // else keyword
|
itemElse // else keyword
|
||||||
@ -76,8 +74,6 @@ const (
|
|||||||
var key = map[string]itemType{
|
var key = map[string]itemType{
|
||||||
".": itemDot,
|
".": itemDot,
|
||||||
"block": itemBlock,
|
"block": itemBlock,
|
||||||
"break": itemBreak,
|
|
||||||
"continue": itemContinue,
|
|
||||||
"define": itemDefine,
|
"define": itemDefine,
|
||||||
"else": itemElse,
|
"else": itemElse,
|
||||||
"end": itemEnd,
|
"end": itemEnd,
|
||||||
|
@ -192,7 +192,7 @@ var lexTests = []lexTest{
|
|||||||
tRight,
|
tRight,
|
||||||
tEOF,
|
tEOF,
|
||||||
}},
|
}},
|
||||||
{"keywords", "{{range if else end with break continue}}", []item{
|
{"keywords", "{{range if else end with}}", []item{
|
||||||
tLeft,
|
tLeft,
|
||||||
mkItem(itemRange, "range"),
|
mkItem(itemRange, "range"),
|
||||||
tSpace,
|
tSpace,
|
||||||
@ -203,10 +203,6 @@ var lexTests = []lexTest{
|
|||||||
mkItem(itemEnd, "end"),
|
mkItem(itemEnd, "end"),
|
||||||
tSpace,
|
tSpace,
|
||||||
mkItem(itemWith, "with"),
|
mkItem(itemWith, "with"),
|
||||||
tSpace,
|
|
||||||
mkItem(itemBreak, "break"),
|
|
||||||
tSpace,
|
|
||||||
mkItem(itemContinue, "continue"),
|
|
||||||
tRight,
|
tRight,
|
||||||
tEOF,
|
tEOF,
|
||||||
}},
|
}},
|
||||||
|
@ -69,8 +69,6 @@ const (
|
|||||||
NodeTemplate // A template invocation action.
|
NodeTemplate // A template invocation action.
|
||||||
NodeVariable // A $ variable.
|
NodeVariable // A $ variable.
|
||||||
NodeWith // A with action.
|
NodeWith // A with action.
|
||||||
NodeBreak // A break action.
|
|
||||||
NodeContinue // A continue action.
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Nodes.
|
// Nodes.
|
||||||
@ -798,68 +796,6 @@ func (r *RangeNode) Copy() Node {
|
|||||||
return r.tr.newRange(r.Pos, r.Line, r.Pipe.CopyPipe(), r.List.CopyList(), r.ElseList.CopyList())
|
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.
|
// WithNode represents a {{with}} action and its commands.
|
||||||
type WithNode struct {
|
type WithNode struct {
|
||||||
BranchNode
|
BranchNode
|
||||||
|
@ -23,13 +23,12 @@ type Tree struct {
|
|||||||
Root *ListNode // top-level root of the tree.
|
Root *ListNode // top-level root of the tree.
|
||||||
text string // text parsed to create the template (or its parent)
|
text string // text parsed to create the template (or its parent)
|
||||||
// Parsing only; cleared after parse.
|
// Parsing only; cleared after parse.
|
||||||
funcs []map[string]interface{}
|
funcs []map[string]interface{}
|
||||||
lex *lexer
|
lex *lexer
|
||||||
token [3]item // three-token lookahead for parser.
|
token [3]item // three-token lookahead for parser.
|
||||||
peekCount int
|
peekCount int
|
||||||
vars []string // variables defined at the moment.
|
vars []string // variables defined at the moment.
|
||||||
treeSet map[string]*Tree
|
treeSet map[string]*Tree
|
||||||
rangeDepth int // nesting level of range loops.
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Copy returns a copy of the Tree. Any parsing state is discarded.
|
// Copy returns a copy of the Tree. Any parsing state is discarded.
|
||||||
@ -220,7 +219,6 @@ func (t *Tree) stopParse() {
|
|||||||
t.vars = nil
|
t.vars = nil
|
||||||
t.funcs = nil
|
t.funcs = nil
|
||||||
t.treeSet = nil
|
t.treeSet = nil
|
||||||
t.rangeDepth = 0
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse parses the template definition string to construct a representation of
|
// Parse parses the template definition string to construct a representation of
|
||||||
@ -375,10 +373,6 @@ func (t *Tree) action() (n Node) {
|
|||||||
return t.templateControl()
|
return t.templateControl()
|
||||||
case itemWith:
|
case itemWith:
|
||||||
return t.withControl()
|
return t.withControl()
|
||||||
case itemBreak:
|
|
||||||
return t.breakControl()
|
|
||||||
case itemContinue:
|
|
||||||
return t.continueControl()
|
|
||||||
}
|
}
|
||||||
t.backup()
|
t.backup()
|
||||||
token := t.peek()
|
token := t.peek()
|
||||||
@ -459,13 +453,7 @@ func (t *Tree) parseControl(allowElseIf bool, context string) (pos Pos, line int
|
|||||||
defer t.popVars(len(t.vars))
|
defer t.popVars(len(t.vars))
|
||||||
pipe = t.pipeline(context)
|
pipe = t.pipeline(context)
|
||||||
var next Node
|
var next Node
|
||||||
if context == "range" {
|
|
||||||
t.rangeDepth++
|
|
||||||
}
|
|
||||||
list, next = t.itemList()
|
list, next = t.itemList()
|
||||||
if context == "range" {
|
|
||||||
t.rangeDepth--
|
|
||||||
}
|
|
||||||
switch next.Type() {
|
switch next.Type() {
|
||||||
case nodeEnd: //done
|
case nodeEnd: //done
|
||||||
case nodeElse:
|
case nodeElse:
|
||||||
@ -510,26 +498,6 @@ func (t *Tree) rangeControl() Node {
|
|||||||
return t.newRange(t.parseControl(false, "range"))
|
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:
|
||||||
// {{with pipeline}} itemList {{end}}
|
// {{with pipeline}} itemList {{end}}
|
||||||
// {{with pipeline}} itemList {{else}} itemList {{end}}
|
// {{with pipeline}} itemList {{else}} itemList {{end}}
|
||||||
|
@ -218,12 +218,6 @@ var parseTests = []parseTest{
|
|||||||
`{{range $x := .SI}}{{.}}{{end}}`},
|
`{{range $x := .SI}}{{.}}{{end}}`},
|
||||||
{"range 2 vars", "{{range $x, $y := .SI}}{{.}}{{end}}", noError,
|
{"range 2 vars", "{{range $x, $y := .SI}}{{.}}{{end}}", noError,
|
||||||
`{{range $x, $y := .SI}}{{.}}{{end}}`},
|
`{{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,
|
{"constants", "{{range .SI 1 -3.2i true false 'a' nil}}{{end}}", noError,
|
||||||
`{{range .SI 1 -3.2i true false 'a' nil}}{{end}}`},
|
`{{range .SI 1 -3.2i true false 'a' nil}}{{end}}`},
|
||||||
{"template", "{{template `x`}}", noError,
|
{"template", "{{template `x`}}", noError,
|
||||||
@ -294,12 +288,6 @@ var parseTests = []parseTest{
|
|||||||
{"empty pipeline", `{{printf "%d" ( ) }}`, hasError, ""},
|
{"empty pipeline", `{{printf "%d" ( ) }}`, hasError, ""},
|
||||||
// Missing pipeline in block
|
// Missing pipeline in block
|
||||||
{"block definition", `{{block "foo"}}hello{{end}}`, hasError, ""},
|
{"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{}{
|
var builtins = map[string]interface{}{
|
||||||
|
Loading…
Reference in New Issue
Block a user