summaryrefslogtreecommitdiff
path: root/libgo/go/text
diff options
context:
space:
mode:
authorIan Lance Taylor <iant@golang.org>2018-01-09 01:23:08 +0000
committerIan Lance Taylor <ian@gcc.gnu.org>2018-01-09 01:23:08 +0000
commit1a2f01efa63036a5104f203a4789e682c0e0915d (patch)
tree373e15778dc8295354584e1f86915ae493b604ff /libgo/go/text
parent8799df67f2dab88f9fda11739c501780a85575e2 (diff)
downloadgcc-1a2f01efa63036a5104f203a4789e682c0e0915d.tar.gz
libgo: update to Go1.10beta1
Update the Go library to the 1.10beta1 release. Requires a few changes to the compiler for modifications to the map runtime code, and to handle some nowritebarrier cases in the runtime. Reviewed-on: https://go-review.googlesource.com/86455 gotools/: * Makefile.am (go_cmd_vet_files): New variable. (go_cmd_buildid_files, go_cmd_test2json_files): New variables. (s-zdefaultcc): Change from constants to functions. (noinst_PROGRAMS): Add vet, buildid, and test2json. (cgo$(EXEEXT)): Link against $(LIBGOTOOL). (vet$(EXEEXT)): New target. (buildid$(EXEEXT)): New target. (test2json$(EXEEXT)): New target. (install-exec-local): Install all $(noinst_PROGRAMS). (uninstall-local): Uninstasll all $(noinst_PROGRAMS). (check-go-tool): Depend on $(noinst_PROGRAMS). Copy down objabi.go. (check-runtime): Depend on $(noinst_PROGRAMS). (check-cgo-test, check-carchive-test): Likewise. (check-vet): New target. (check): Depend on check-vet. Look at cmd_vet-testlog. (.PHONY): Add check-vet. * Makefile.in: Rebuild. From-SVN: r256365
Diffstat (limited to 'libgo/go/text')
-rw-r--r--libgo/go/text/tabwriter/tabwriter.go84
-rw-r--r--libgo/go/text/template/doc.go6
-rw-r--r--libgo/go/text/template/exec.go128
-rw-r--r--libgo/go/text/template/exec_test.go21
-rw-r--r--libgo/go/text/template/funcs.go20
-rw-r--r--libgo/go/text/template/multi_test.go3
-rw-r--r--libgo/go/text/template/parse/lex.go22
-rw-r--r--libgo/go/text/template/parse/lex_test.go32
-rw-r--r--libgo/go/text/template/parse/node.go64
-rw-r--r--libgo/go/text/template/parse/parse.go44
-rw-r--r--libgo/go/text/template/parse/parse_test.go12
11 files changed, 316 insertions, 120 deletions
diff --git a/libgo/go/text/tabwriter/tabwriter.go b/libgo/go/text/tabwriter/tabwriter.go
index 752c9b8e9fb..ae6c7a29493 100644
--- a/libgo/go/text/tabwriter/tabwriter.go
+++ b/libgo/go/text/tabwriter/tabwriter.go
@@ -333,52 +333,52 @@ func (b *Writer) format(pos0 int, line0, line1 int) (pos int) {
for this := line0; this < line1; this++ {
line := b.lines[this]
- if column < len(line)-1 {
- // cell exists in this column => this line
- // has more cells than the previous line
- // (the last cell per line is ignored because cells are
- // tab-terminated; the last cell per line describes the
- // text before the newline/formfeed and does not belong
- // to a column)
-
- // print unprinted lines until beginning of block
- pos = b.writeLines(pos, line0, this)
- line0 = this
-
- // column block begin
- width := b.minwidth // minimal column width
- discardable := true // true if all cells in this column are empty and "soft"
- for ; this < line1; this++ {
- line = b.lines[this]
- if column < len(line)-1 {
- // cell exists in this column
- c := line[column]
- // update width
- if w := c.width + b.padding; w > width {
- width = w
- }
- // update discardable
- if c.width > 0 || c.htab {
- discardable = false
- }
- } else {
- break
- }
+ if column >= len(line)-1 {
+ continue
+ }
+ // cell exists in this column => this line
+ // has more cells than the previous line
+ // (the last cell per line is ignored because cells are
+ // tab-terminated; the last cell per line describes the
+ // text before the newline/formfeed and does not belong
+ // to a column)
+
+ // print unprinted lines until beginning of block
+ pos = b.writeLines(pos, line0, this)
+ line0 = this
+
+ // column block begin
+ width := b.minwidth // minimal column width
+ discardable := true // true if all cells in this column are empty and "soft"
+ for ; this < line1; this++ {
+ line = b.lines[this]
+ if column >= len(line)-1 {
+ break
}
- // column block end
-
- // discard empty columns if necessary
- if discardable && b.flags&DiscardEmptyColumns != 0 {
- width = 0
+ // cell exists in this column
+ c := line[column]
+ // update width
+ if w := c.width + b.padding; w > width {
+ width = w
+ }
+ // update discardable
+ if c.width > 0 || c.htab {
+ discardable = false
}
+ }
+ // column block end
- // format and print all columns to the right of this column
- // (we know the widths of this column and all columns to the left)
- b.widths = append(b.widths, width) // push width
- pos = b.format(pos, line0, this)
- b.widths = b.widths[0 : len(b.widths)-1] // pop width
- line0 = this
+ // discard empty columns if necessary
+ if discardable && b.flags&DiscardEmptyColumns != 0 {
+ width = 0
}
+
+ // format and print all columns to the right of this column
+ // (we know the widths of this column and all columns to the left)
+ b.widths = append(b.widths, width) // push width
+ pos = b.format(pos, line0, this)
+ b.widths = b.widths[0 : len(b.widths)-1] // pop width
+ line0 = this
}
// print unprinted lines until end
diff --git a/libgo/go/text/template/doc.go b/libgo/go/text/template/doc.go
index d174ebd9cfe..f7609293cea 100644
--- a/libgo/go/text/template/doc.go
+++ b/libgo/go/text/template/doc.go
@@ -110,6 +110,12 @@ data, defined in detail in the corresponding sections that follow.
T0 is executed; otherwise, dot is set to the successive elements
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"}}
The template with the specified name is executed with nil data.
diff --git a/libgo/go/text/template/exec.go b/libgo/go/text/template/exec.go
index 29eb68fba75..90810090e29 100644
--- a/libgo/go/text/template/exec.go
+++ b/libgo/go/text/template/exec.go
@@ -27,11 +27,12 @@ const maxExecDepth = 1000
// 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.
@@ -81,10 +82,7 @@ func (s *state) at(node parse.Node) {
// doublePercent returns the string with %'s replaced by %%, if necessary,
// so it can be used safely inside a Printf format string.
func doublePercent(str string) string {
- if strings.Contains(str, "%") {
- str = strings.Replace(str, "%", "%%", -1)
- }
- return str
+ return strings.Replace(str, "%", "%%", -1)
}
// TODO: It would be nice if ExecError was more broken down, but
@@ -225,9 +223,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:
@@ -238,13 +244,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:
@@ -252,15 +260,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)
@@ -269,13 +288,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,
@@ -313,13 +333,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)
@@ -328,8 +349,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:
@@ -337,17 +359,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
@@ -358,20 +386,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) {
@@ -932,29 +965,6 @@ func printableValue(v reflect.Value) (interface{}, bool) {
return v.Interface(), true
}
-// Types to help sort the keys in a map for reproducible output.
-
-type rvs []reflect.Value
-
-func (x rvs) Len() int { return len(x) }
-func (x rvs) Swap(i, j int) { x[i], x[j] = x[j], x[i] }
-
-type rvInts struct{ rvs }
-
-func (x rvInts) Less(i, j int) bool { return x.rvs[i].Int() < x.rvs[j].Int() }
-
-type rvUints struct{ rvs }
-
-func (x rvUints) Less(i, j int) bool { return x.rvs[i].Uint() < x.rvs[j].Uint() }
-
-type rvFloats struct{ rvs }
-
-func (x rvFloats) Less(i, j int) bool { return x.rvs[i].Float() < x.rvs[j].Float() }
-
-type rvStrings struct{ rvs }
-
-func (x rvStrings) Less(i, j int) bool { return x.rvs[i].String() < x.rvs[j].String() }
-
// sortKeys sorts (if it can) the slice of reflect.Values, which is a slice of map keys.
func sortKeys(v []reflect.Value) []reflect.Value {
if len(v) <= 1 {
@@ -962,13 +972,21 @@ func sortKeys(v []reflect.Value) []reflect.Value {
}
switch v[0].Kind() {
case reflect.Float32, reflect.Float64:
- sort.Sort(rvFloats{v})
+ sort.Slice(v, func(i, j int) bool {
+ return v[i].Float() < v[j].Float()
+ })
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
- sort.Sort(rvInts{v})
+ sort.Slice(v, func(i, j int) bool {
+ return v[i].Int() < v[j].Int()
+ })
case reflect.String:
- sort.Sort(rvStrings{v})
+ sort.Slice(v, func(i, j int) bool {
+ return v[i].String() < v[j].String()
+ })
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
- sort.Sort(rvUints{v})
+ sort.Slice(v, func(i, j int) bool {
+ return v[i].Uint() < v[j].Uint()
+ })
}
return v
}
diff --git a/libgo/go/text/template/exec_test.go b/libgo/go/text/template/exec_test.go
index 9f7e637c190..79b504f8a4d 100644
--- a/libgo/go/text/template/exec_test.go
+++ b/libgo/go/text/template/exec_test.go
@@ -44,6 +44,12 @@ type T struct {
MSIEmpty map[string]int
MXI map[interface{}]int
MII map[int]int
+ MI32S map[int32]string
+ MI64S map[int64]string
+ MUI32S map[uint32]string
+ MUI64S map[uint64]string
+ MI8S map[int8]string
+ MUI8S map[uint8]string
SMSI []map[string]int
// Empty interfaces; used to see if we can dig inside one.
Empty0 interface{} // nil
@@ -124,6 +130,12 @@ var tVal = &T{
MSIone: map[string]int{"one": 1},
MXI: map[interface{}]int{"one": 1},
MII: map[int]int{1: 1},
+ MI32S: map[int32]string{1: "one", 2: "two"},
+ MI64S: map[int64]string{2: "i642", 3: "i643"},
+ MUI32S: map[uint32]string{2: "u322", 3: "u323"},
+ MUI64S: map[uint64]string{2: "ui642", 3: "ui643"},
+ MI8S: map[int8]string{2: "i82", 3: "i83"},
+ MUI8S: map[uint8]string{2: "u82", 3: "u83"},
SMSI: []map[string]int{
{"one": 1, "two": 2},
{"eleven": 11, "twelve": 12},
@@ -448,6 +460,11 @@ var execTests = []execTest{
{"map[WRONG]", "{{index .MSI 10}}", "", tVal, false},
{"double index", "{{index .SMSI 1 `eleven`}}", "11", tVal, true},
{"nil[1]", "{{index nil 1}}", "", tVal, false},
+ {"map MI64S", "{{index .MI64S 2}}", "i642", tVal, true},
+ {"map MI32S", "{{index .MI32S 2}}", "two", tVal, true},
+ {"map MUI64S", "{{index .MUI64S 3}}", "ui643", tVal, true},
+ {"map MI8S", "{{index .MI8S 3}}", "i83", tVal, true},
+ {"map MUI8S", "{{index .MUI8S 2}}", "u82", tVal, true},
// Len.
{"slice", "{{len .SI}}", "3", tVal, true},
@@ -496,6 +513,10 @@ var execTests = []execTest{
{"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 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.
{"or as if true", `{{or .SI "slice is empty"}}`, "[3 4 5]", tVal, true},
diff --git a/libgo/go/text/template/funcs.go b/libgo/go/text/template/funcs.go
index 91074310374..abddfa1141b 100644
--- a/libgo/go/text/template/funcs.go
+++ b/libgo/go/text/template/funcs.go
@@ -139,10 +139,24 @@ func prepareArg(value reflect.Value, argType reflect.Type) (reflect.Value, error
}
value = reflect.Zero(argType)
}
- if !value.Type().AssignableTo(argType) {
- return reflect.Value{}, fmt.Errorf("value has type %s; should be %s", value.Type(), argType)
+ if value.Type().AssignableTo(argType) {
+ return value, nil
}
- return value, nil
+ if intLike(value.Kind()) && intLike(argType.Kind()) && value.Type().ConvertibleTo(argType) {
+ value = value.Convert(argType)
+ return value, nil
+ }
+ return reflect.Value{}, fmt.Errorf("value has type %s; should be %s", value.Type(), argType)
+}
+
+func intLike(typ reflect.Kind) bool {
+ switch typ {
+ case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
+ return true
+ case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
+ return true
+ }
+ return false
}
// Indexing.
diff --git a/libgo/go/text/template/multi_test.go b/libgo/go/text/template/multi_test.go
index 5d8c08f06f1..5769470ff90 100644
--- a/libgo/go/text/template/multi_test.go
+++ b/libgo/go/text/template/multi_test.go
@@ -247,6 +247,9 @@ func TestAddParseTree(t *testing.T) {
t.Fatal(err)
}
added, err := root.AddParseTree("c", tree["c"])
+ if err != nil {
+ t.Fatal(err)
+ }
// Execute.
var b bytes.Buffer
err = added.ExecuteTemplate(&b, "a", 0)
diff --git a/libgo/go/text/template/parse/lex.go b/libgo/go/text/template/parse/lex.go
index 6fbf36d7a4a..da766cc7c32 100644
--- a/libgo/go/text/template/parse/lex.go
+++ b/libgo/go/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,
@@ -110,11 +114,9 @@ type lexer struct {
input string // the string being scanned
leftDelim string // start of action
rightDelim string // end of action
- state stateFn // the next lexing function to enter
pos Pos // current position in the input
start Pos // start position of this item
width Pos // width of last rune read from input
- lastPos Pos // position of most recent item returned by nextItem
items chan item // channel of scanned items
parenDepth int // nesting depth of ( ) exprs
line int // 1+number of newlines seen
@@ -164,6 +166,7 @@ func (l *lexer) emit(t itemType) {
// ignore skips over the pending input before this point.
func (l *lexer) ignore() {
+ l.line += strings.Count(l.input[l.start:l.pos], "\n")
l.start = l.pos
}
@@ -193,9 +196,7 @@ func (l *lexer) errorf(format string, args ...interface{}) stateFn {
// nextItem returns the next item from the input.
// Called by the parser, not in the lexing goroutine.
func (l *lexer) nextItem() item {
- item := <-l.items
- l.lastPos = item.pos
- return item
+ return <-l.items
}
// drain drains the output so the lexing goroutine will exit.
@@ -227,8 +228,8 @@ func lex(name, input, left, right string) *lexer {
// run runs the state machine for the lexer.
func (l *lexer) run() {
- for l.state = lexText; l.state != nil; {
- l.state = l.state(l)
+ for state := lexText; state != nil; {
+ state = state(l)
}
close(l.items)
}
@@ -281,10 +282,9 @@ func (l *lexer) atRightDelim() (delim, trimSpaces bool) {
return true, false
}
// The right delim might have the marker before.
- if strings.HasPrefix(l.input[l.pos:], rightTrimMarker) {
- if strings.HasPrefix(l.input[l.pos+trimMarkerLen:], l.rightDelim) {
- return true, true
- }
+ if strings.HasPrefix(l.input[l.pos:], rightTrimMarker) &&
+ strings.HasPrefix(l.input[l.pos+trimMarkerLen:], l.rightDelim) {
+ return true, true
}
return false, false
}
diff --git a/libgo/go/text/template/parse/lex_test.go b/libgo/go/text/template/parse/lex_test.go
index 2c73bb623ae..ca7c3f64bc7 100644
--- a/libgo/go/text/template/parse/lex_test.go
+++ b/libgo/go/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,
}},
@@ -404,6 +408,9 @@ func equal(i1, i2 []item, checkPos bool) bool {
if checkPos && i1[k].pos != i2[k].pos {
return false
}
+ if checkPos && i1[k].line != i2[k].line {
+ return false
+ }
}
return true
}
@@ -452,7 +459,7 @@ func TestDelims(t *testing.T) {
}
var lexPosTests = []lexTest{
- {"empty", "", []item{tEOF}},
+ {"empty", "", []item{{itemEOF, 0, "", 1}}},
{"punctuation", "{{,@%#}}", []item{
{itemLeftDelim, 0, "{{", 1},
{itemChar, 2, ",", 1},
@@ -470,6 +477,24 @@ var lexPosTests = []lexTest{
{itemText, 13, "xyz", 1},
{itemEOF, 16, "", 1},
}},
+ {"trimafter", "{{x -}}\n{{y}}", []item{
+ {itemLeftDelim, 0, "{{", 1},
+ {itemIdentifier, 2, "x", 1},
+ {itemRightDelim, 5, "}}", 1},
+ {itemLeftDelim, 8, "{{", 2},
+ {itemIdentifier, 10, "y", 2},
+ {itemRightDelim, 11, "}}", 2},
+ {itemEOF, 13, "", 2},
+ }},
+ {"trimbefore", "{{x}}\n{{- y}}", []item{
+ {itemLeftDelim, 0, "{{", 1},
+ {itemIdentifier, 2, "x", 1},
+ {itemRightDelim, 3, "}}", 1},
+ {itemLeftDelim, 6, "{{", 2},
+ {itemIdentifier, 10, "y", 2},
+ {itemRightDelim, 11, "}}", 2},
+ {itemEOF, 13, "", 2},
+ }},
}
// The other tests don't check position, to make the test cases easier to construct.
@@ -485,7 +510,8 @@ func TestPos(t *testing.T) {
if !equal(items[i:i+1], test.items[i:i+1], true) {
i1 := items[i]
i2 := test.items[i]
- t.Errorf("\t#%d: got {%v %d %q} expected {%v %d %q}", i, i1.typ, i1.pos, i1.val, i2.typ, i2.pos, i2.val)
+ t.Errorf("\t#%d: got {%v %d %q %d} expected {%v %d %q %d}",
+ i, i1.typ, i1.pos, i1.val, i1.line, i2.typ, i2.pos, i2.val, i2.line)
}
}
}
diff --git a/libgo/go/text/template/parse/node.go b/libgo/go/text/template/parse/node.go
index 55ff46c17ab..7e16349b31e 100644
--- a/libgo/go/text/template/parse/node.go
+++ b/libgo/go/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/libgo/go/text/template/parse/parse.go b/libgo/go/text/template/parse/parse.go
index a91a544ce01..ad9c0519789 100644
--- a/libgo/go/text/template/parse/parse.go
+++ b/libgo/go/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/libgo/go/text/template/parse/parse_test.go b/libgo/go/text/template/parse/parse_test.go
index 81f14aca986..aade33ea48e 100644
--- a/libgo/go/text/template/parse/parse_test.go
+++ b/libgo/go/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{}{