diff options
author | Ian Lance Taylor <iant@golang.org> | 2018-01-09 01:23:08 +0000 |
---|---|---|
committer | Ian Lance Taylor <ian@gcc.gnu.org> | 2018-01-09 01:23:08 +0000 |
commit | 1a2f01efa63036a5104f203a4789e682c0e0915d (patch) | |
tree | 373e15778dc8295354584e1f86915ae493b604ff /libgo/go/text | |
parent | 8799df67f2dab88f9fda11739c501780a85575e2 (diff) | |
download | gcc-1a2f01efa63036a5104f203a4789e682c0e0915d.zip gcc-1a2f01efa63036a5104f203a4789e682c0e0915d.tar.gz gcc-1a2f01efa63036a5104f203a4789e682c0e0915d.tar.bz2 |
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.go | 84 | ||||
-rw-r--r-- | libgo/go/text/template/doc.go | 6 | ||||
-rw-r--r-- | libgo/go/text/template/exec.go | 128 | ||||
-rw-r--r-- | libgo/go/text/template/exec_test.go | 21 | ||||
-rw-r--r-- | libgo/go/text/template/funcs.go | 20 | ||||
-rw-r--r-- | libgo/go/text/template/multi_test.go | 3 | ||||
-rw-r--r-- | libgo/go/text/template/parse/lex.go | 22 | ||||
-rw-r--r-- | libgo/go/text/template/parse/lex_test.go | 32 | ||||
-rw-r--r-- | libgo/go/text/template/parse/node.go | 64 | ||||
-rw-r--r-- | libgo/go/text/template/parse/parse.go | 44 | ||||
-rw-r--r-- | libgo/go/text/template/parse/parse_test.go | 12 |
11 files changed, 316 insertions, 120 deletions
diff --git a/libgo/go/text/tabwriter/tabwriter.go b/libgo/go/text/tabwriter/tabwriter.go index 752c9b8..ae6c7a2 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 d174ebd..f760929 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 29eb68f..9081009 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 9f7e637..79b504f 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 9107431..abddfa1 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 5d8c08f..5769470 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 6fbf36d..da766cc 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 2c73bb6..ca7c3f6 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 55ff46c..7e16349 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 a91a544..ad9c051 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 81f14ac..aade33e 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{}{ |