diff options
author | Ian Lance Taylor <iant@golang.org> | 2018-09-24 21:46:21 +0000 |
---|---|---|
committer | Ian Lance Taylor <ian@gcc.gnu.org> | 2018-09-24 21:46:21 +0000 |
commit | dd931d9b48647e898dc80927c532ae93cc09e192 (patch) | |
tree | 71be2295cd79b8a182f6130611658db8628772d5 /libgo/go/text | |
parent | 779d8a5ad09b01428726ea5a0e6c87bd9ac3c0e4 (diff) | |
download | gcc-dd931d9b48647e898dc80927c532ae93cc09e192.zip gcc-dd931d9b48647e898dc80927c532ae93cc09e192.tar.gz gcc-dd931d9b48647e898dc80927c532ae93cc09e192.tar.bz2 |
libgo: update to Go 1.11
Reviewed-on: https://go-review.googlesource.com/136435
gotools/:
* Makefile.am (mostlyclean-local): Run chmod on check-go-dir to
make sure it is writable.
(check-go-tools): Likewise.
(check-vet): Copy internal/objabi to check-vet-dir.
* Makefile.in: Rebuild.
From-SVN: r264546
Diffstat (limited to 'libgo/go/text')
-rw-r--r-- | libgo/go/text/scanner/example_test.go | 106 | ||||
-rw-r--r-- | libgo/go/text/scanner/scanner.go | 2 | ||||
-rw-r--r-- | libgo/go/text/scanner/scanner_test.go | 16 | ||||
-rw-r--r-- | libgo/go/text/tabwriter/tabwriter.go | 62 | ||||
-rw-r--r-- | libgo/go/text/tabwriter/tabwriter_test.go | 79 | ||||
-rw-r--r-- | libgo/go/text/template/doc.go | 7 | ||||
-rw-r--r-- | libgo/go/text/template/exec.go | 76 | ||||
-rw-r--r-- | libgo/go/text/template/exec_test.go | 18 | ||||
-rw-r--r-- | libgo/go/text/template/parse/lex.go | 7 | ||||
-rw-r--r-- | libgo/go/text/template/parse/lex_test.go | 8 | ||||
-rw-r--r-- | libgo/go/text/template/parse/node.go | 22 | ||||
-rw-r--r-- | libgo/go/text/template/parse/parse.go | 24 | ||||
-rw-r--r-- | libgo/go/text/template/parse/parse_test.go | 4 | ||||
-rw-r--r-- | libgo/go/text/template/template.go | 10 |
14 files changed, 357 insertions, 84 deletions
diff --git a/libgo/go/text/scanner/example_test.go b/libgo/go/text/scanner/example_test.go index 9e2d5b7..88b992b 100644 --- a/libgo/go/text/scanner/example_test.go +++ b/libgo/go/text/scanner/example_test.go @@ -1,4 +1,4 @@ -// Copyright 2015 The Go Authors. All rights reserved. +// Copyright 2018 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. @@ -10,6 +10,7 @@ import ( "fmt" "strings" "text/scanner" + "unicode" ) func Example() { @@ -18,6 +19,7 @@ func Example() { if a > 10 { someParsable = text }` + var s scanner.Scanner s.Init(strings.NewReader(src)) s.Filename = "example" @@ -36,3 +38,105 @@ if a > 10 { // example:4:17: text // example:5:1: } } + +func Example_isIdentRune() { + const src = "%var1 var2%" + + var s scanner.Scanner + s.Init(strings.NewReader(src)) + s.Filename = "default" + + for tok := s.Scan(); tok != scanner.EOF; tok = s.Scan() { + fmt.Printf("%s: %s\n", s.Position, s.TokenText()) + } + + fmt.Println() + s.Init(strings.NewReader(src)) + s.Filename = "percent" + + // treat leading '%' as part of an identifier + s.IsIdentRune = func(ch rune, i int) bool { + return ch == '%' && i == 0 || unicode.IsLetter(ch) || unicode.IsDigit(ch) && i > 0 + } + + for tok := s.Scan(); tok != scanner.EOF; tok = s.Scan() { + fmt.Printf("%s: %s\n", s.Position, s.TokenText()) + } + + // Output: + // default:1:1: % + // default:1:2: var1 + // default:1:7: var2 + // default:1:11: % + // + // percent:1:1: %var1 + // percent:1:7: var2 + // percent:1:11: % +} + +func Example_mode() { + const src = ` + // Comment begins at column 5. + +This line should not be included in the output. + +/* +This multiline comment +should be extracted in +its entirety. +*/ +` + + var s scanner.Scanner + s.Init(strings.NewReader(src)) + s.Filename = "comments" + s.Mode ^= scanner.SkipComments // don't skip comments + + for tok := s.Scan(); tok != scanner.EOF; tok = s.Scan() { + txt := s.TokenText() + if strings.HasPrefix(txt, "//") || strings.HasPrefix(txt, "/*") { + fmt.Printf("%s: %s\n", s.Position, txt) + } + } + + // Output: + // comments:2:5: // Comment begins at column 5. + // comments:6:1: /* + // This multiline comment + // should be extracted in + // its entirety. + // */ +} + +func Example_whitespace() { + // tab-separated values + const src = `aa ab ac ad +ba bb bc bd +ca cb cc cd +da db dc dd` + + var ( + col, row int + s scanner.Scanner + tsv [4][4]string // large enough for example above + ) + s.Init(strings.NewReader(src)) + s.Whitespace ^= 1<<'\t' | 1<<'\n' // don't skip tabs and new lines + + for tok := s.Scan(); tok != scanner.EOF; tok = s.Scan() { + switch tok { + case '\n': + row++ + col = 0 + case '\t': + col++ + default: + tsv[row][col] = s.TokenText() + } + } + + fmt.Print(tsv) + + // Output: + // [[aa ab ac ad] [ba bb bc bd] [ca cb cc cd] [da db dc dd]] +} diff --git a/libgo/go/text/scanner/scanner.go b/libgo/go/text/scanner/scanner.go index 6fb0422..4e76664 100644 --- a/libgo/go/text/scanner/scanner.go +++ b/libgo/go/text/scanner/scanner.go @@ -621,7 +621,7 @@ redo: case '`': if s.Mode&ScanRawStrings != 0 { s.scanRawString() - tok = String + tok = RawString } ch = s.next() default: diff --git a/libgo/go/text/scanner/scanner_test.go b/libgo/go/text/scanner/scanner_test.go index 3e92d65..9a6b72e 100644 --- a/libgo/go/text/scanner/scanner_test.go +++ b/libgo/go/text/scanner/scanner_test.go @@ -209,10 +209,10 @@ var tokenList = []token{ {String, `"` + f100 + `"`}, {Comment, "// raw strings"}, - {String, "``"}, - {String, "`\\`"}, - {String, "`" + "\n\n/* foobar */\n\n" + "`"}, - {String, "`" + f100 + "`"}, + {RawString, "``"}, + {RawString, "`\\`"}, + {RawString, "`" + "\n\n/* foobar */\n\n" + "`"}, + {RawString, "`" + f100 + "`"}, {Comment, "// individual characters"}, // NUL character is not allowed @@ -463,9 +463,9 @@ func TestError(t *testing.T) { testError(t, `"ab`+"\x80", "<input>:1:4", "illegal UTF-8 encoding", String) testError(t, `"abc`+"\xff", "<input>:1:5", "illegal UTF-8 encoding", String) - testError(t, "`a"+"\x00", "<input>:1:3", "illegal character NUL", String) - testError(t, "`ab"+"\x80", "<input>:1:4", "illegal UTF-8 encoding", String) - testError(t, "`abc"+"\xff", "<input>:1:5", "illegal UTF-8 encoding", String) + testError(t, "`a"+"\x00", "<input>:1:3", "illegal character NUL", RawString) + testError(t, "`ab"+"\x80", "<input>:1:4", "illegal UTF-8 encoding", RawString) + testError(t, "`abc"+"\xff", "<input>:1:5", "illegal UTF-8 encoding", RawString) testError(t, `'\"'`, "<input>:1:3", "illegal char escape", Char) testError(t, `"\'"`, "<input>:1:3", "illegal char escape", String) @@ -480,7 +480,7 @@ func TestError(t *testing.T) { testError(t, `'`+"\n", "<input>:1:2", "literal not terminated", Char) testError(t, `"abc`, "<input>:1:5", "literal not terminated", String) testError(t, `"abc`+"\n", "<input>:1:5", "literal not terminated", String) - testError(t, "`abc\n", "<input>:2:1", "literal not terminated", String) + testError(t, "`abc\n", "<input>:2:1", "literal not terminated", RawString) testError(t, `/*/`, "<input>:1:4", "comment not terminated", EOF) } diff --git a/libgo/go/text/tabwriter/tabwriter.go b/libgo/go/text/tabwriter/tabwriter.go index ae6c7a2..36d999b 100644 --- a/libgo/go/text/tabwriter/tabwriter.go +++ b/libgo/go/text/tabwriter/tabwriter.go @@ -12,7 +12,6 @@ package tabwriter import ( - "bytes" "io" "unicode/utf8" ) @@ -99,25 +98,50 @@ type Writer struct { flags uint // current state - buf bytes.Buffer // collected text excluding tabs or line breaks - pos int // buffer position up to which cell.width of incomplete cell has been computed - cell cell // current incomplete cell; cell.width is up to buf[pos] excluding ignored sections - endChar byte // terminating char of escaped sequence (Escape for escapes, '>', ';' for HTML tags/entities, or 0) - lines [][]cell // list of lines; each line is a list of cells - widths []int // list of column widths in runes - re-used during formatting + buf []byte // collected text excluding tabs or line breaks + pos int // buffer position up to which cell.width of incomplete cell has been computed + cell cell // current incomplete cell; cell.width is up to buf[pos] excluding ignored sections + endChar byte // terminating char of escaped sequence (Escape for escapes, '>', ';' for HTML tags/entities, or 0) + lines [][]cell // list of lines; each line is a list of cells + widths []int // list of column widths in runes - re-used during formatting } -func (b *Writer) addLine() { b.lines = append(b.lines, []cell{}) } +// addLine adds a new line. +// flushed is a hint indicating whether the underlying writer was just flushed. +// If so, the previous line is not likely to be a good indicator of the new line's cells. +func (b *Writer) addLine(flushed bool) { + // Grow slice instead of appending, + // as that gives us an opportunity + // to re-use an existing []cell. + if n := len(b.lines) + 1; n <= cap(b.lines) { + b.lines = b.lines[:n] + b.lines[n-1] = b.lines[n-1][:0] + } else { + b.lines = append(b.lines, nil) + } + + if !flushed { + // The previous line is probably a good indicator + // of how many cells the current line will have. + // If the current line's capacity is smaller than that, + // abandon it and make a new one. + if n := len(b.lines); n >= 2 { + if prev := len(b.lines[n-2]); prev > cap(b.lines[n-1]) { + b.lines[n-1] = make([]cell, 0, prev) + } + } + } +} // Reset the current state. func (b *Writer) reset() { - b.buf.Reset() + b.buf = b.buf[:0] b.pos = 0 b.cell = cell{} b.endChar = 0 b.lines = b.lines[0:0] b.widths = b.widths[0:0] - b.addLine() + b.addLine(true) } // Internal representation (current state): @@ -212,7 +236,7 @@ func (b *Writer) dump() { for i, line := range b.lines { print("(", i, ") ") for _, c := range line { - print("[", string(b.buf.Bytes()[pos:pos+c.size]), "]") + print("[", string(b.buf[pos:pos+c.size]), "]") pos += c.size } print("\n") @@ -294,7 +318,7 @@ func (b *Writer) writeLines(pos0 int, line0, line1 int) (pos int) { // non-empty cell useTabs = false if b.flags&AlignRight == 0 { // align left - b.write0(b.buf.Bytes()[pos : pos+c.size]) + b.write0(b.buf[pos : pos+c.size]) pos += c.size if j < len(b.widths) { b.writePadding(c.width, b.widths[j], false) @@ -303,7 +327,7 @@ func (b *Writer) writeLines(pos0 int, line0, line1 int) (pos int) { if j < len(b.widths) { b.writePadding(c.width, b.widths[j], false) } - b.write0(b.buf.Bytes()[pos : pos+c.size]) + b.write0(b.buf[pos : pos+c.size]) pos += c.size } } @@ -312,7 +336,7 @@ func (b *Writer) writeLines(pos0 int, line0, line1 int) (pos int) { if i+1 == len(b.lines) { // last buffered line - we don't have a newline, so just write // any outstanding buffered data - b.write0(b.buf.Bytes()[pos : pos+b.cell.size]) + b.write0(b.buf[pos : pos+b.cell.size]) pos += b.cell.size } else { // not the last line - write newline @@ -387,14 +411,14 @@ func (b *Writer) format(pos0 int, line0, line1 int) (pos int) { // Append text to current cell. func (b *Writer) append(text []byte) { - b.buf.Write(text) + b.buf = append(b.buf, text...) b.cell.size += len(text) } // Update the cell width. func (b *Writer) updateWidth() { - b.cell.width += utf8.RuneCount(b.buf.Bytes()[b.pos:b.buf.Len()]) - b.pos = b.buf.Len() + b.cell.width += utf8.RuneCount(b.buf[b.pos:]) + b.pos = len(b.buf) } // To escape a text segment, bracket it with Escape characters. @@ -434,7 +458,7 @@ func (b *Writer) endEscape() { case ';': b.cell.width++ // entity, count as one rune } - b.pos = b.buf.Len() + b.pos = len(b.buf) b.endChar = 0 } @@ -508,7 +532,7 @@ func (b *Writer) Write(buf []byte) (n int, err error) { ncells := b.terminateCell(ch == '\t') if ch == '\n' || ch == '\f' { // terminate line - b.addLine() + b.addLine(ch == '\f') if ch == '\f' || ncells == 1 { // A '\f' always forces a flush. Otherwise, if the previous // line has only one cell which does not have an impact on diff --git a/libgo/go/text/tabwriter/tabwriter_test.go b/libgo/go/text/tabwriter/tabwriter_test.go index 9d3111e..07bae0c 100644 --- a/libgo/go/text/tabwriter/tabwriter_test.go +++ b/libgo/go/text/tabwriter/tabwriter_test.go @@ -5,7 +5,10 @@ package tabwriter_test import ( + "bytes" + "fmt" "io" + "io/ioutil" "testing" . "text/tabwriter" ) @@ -650,3 +653,79 @@ func TestPanicDuringWrite(t *testing.T) { io.WriteString(w, "a\n\n") // the second \n triggers a call to w.Write and thus a panic t.Errorf("failed to panic during Write") } + +func BenchmarkTable(b *testing.B) { + for _, w := range [...]int{1, 10, 100} { + // Build a line with w cells. + line := bytes.Repeat([]byte("a\t"), w) + line = append(line, '\n') + for _, h := range [...]int{10, 1000, 100000} { + b.Run(fmt.Sprintf("%dx%d", w, h), func(b *testing.B) { + b.Run("new", func(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + w := NewWriter(ioutil.Discard, 4, 4, 1, ' ', 0) // no particular reason for these settings + // Write the line h times. + for j := 0; j < h; j++ { + w.Write(line) + } + w.Flush() + } + }) + + b.Run("reuse", func(b *testing.B) { + b.ReportAllocs() + w := NewWriter(ioutil.Discard, 4, 4, 1, ' ', 0) // no particular reason for these settings + for i := 0; i < b.N; i++ { + // Write the line h times. + for j := 0; j < h; j++ { + w.Write(line) + } + w.Flush() + } + }) + }) + } + } +} + +func BenchmarkPyramid(b *testing.B) { + for _, x := range [...]int{10, 100, 1000} { + // Build a line with x cells. + line := bytes.Repeat([]byte("a\t"), x) + b.Run(fmt.Sprintf("%d", x), func(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + w := NewWriter(ioutil.Discard, 4, 4, 1, ' ', 0) // no particular reason for these settings + // Write increasing prefixes of that line. + for j := 0; j < x; j++ { + w.Write(line[:j*2]) + w.Write([]byte{'\n'}) + } + w.Flush() + } + }) + } +} + +func BenchmarkRagged(b *testing.B) { + var lines [8][]byte + for i, w := range [8]int{6, 2, 9, 5, 5, 7, 3, 8} { + // Build a line with w cells. + lines[i] = bytes.Repeat([]byte("a\t"), w) + } + for _, h := range [...]int{10, 100, 1000} { + b.Run(fmt.Sprintf("%d", h), func(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + w := NewWriter(ioutil.Discard, 4, 4, 1, ' ', 0) // no particular reason for these settings + // Write the lines in turn h times. + for j := 0; j < h; j++ { + w.Write(lines[j%len(lines)]) + w.Write([]byte{'\n'}) + } + w.Flush() + } + }) + } +} diff --git a/libgo/go/text/template/doc.go b/libgo/go/text/template/doc.go index d174ebd..4b24306 100644 --- a/libgo/go/text/template/doc.go +++ b/libgo/go/text/template/doc.go @@ -69,6 +69,7 @@ data, defined in detail in the corresponding sections that follow. */ // {{/* a comment */}} +// {{- /* a comment with white space trimmed from preceding and following text */ -}} // A comment; discarded. May contain newlines. // Comments do not nest and must start and end at the // delimiters, as shown here. @@ -121,7 +122,7 @@ data, defined in detail in the corresponding sections that follow. A block is shorthand for defining a template {{define "name"}} T1 {{end}} and then executing it in place - {{template "name" .}} + {{template "name" pipeline}} The typical use is to define a set of root templates that are then customized by redefining the block templates within. @@ -241,6 +242,10 @@ The initialization has syntax where $variable is the name of the variable. An action that declares a variable produces no output. +Variables previously declared can also be assigned, using the syntax + + $variable = pipeline + If a "range" action initializes a variable, the variable is set to the successive elements of the iteration. Also, a "range" may declare two variables, separated by a comma: diff --git a/libgo/go/text/template/exec.go b/libgo/go/text/template/exec.go index 2923dd9..7ee60bd 100644 --- a/libgo/go/text/template/exec.go +++ b/libgo/go/text/template/exec.go @@ -19,9 +19,16 @@ import ( // templates. This limit is only practically reached by accidentally // recursive template invocations. This limit allows us to return // an error instead of triggering a stack overflow. -// For gccgo we make this 1000 rather than 100000 to avoid stack overflow -// on non-split-stack systems. -const maxExecDepth = 1000 +var maxExecDepth = initMaxExecDepth() + +func initMaxExecDepth() int { + // For gccgo we make this 1000 rather than 100000 to avoid + // stack overflow on non-split-stack systems. + if runtime.GOARCH == "wasm" || runtime.Compiler == "gccgo" { + return 1000 + } + return 100000 +} // state represents the state of an execution. It's not part of the // template so that multiple executions of the same template @@ -55,8 +62,20 @@ func (s *state) pop(mark int) { s.vars = s.vars[0:mark] } -// setVar overwrites the top-nth variable on the stack. Used by range iterations. -func (s *state) setVar(n int, value reflect.Value) { +// setVar overwrites the last declared variable with the given name. +// Used by variable assignments. +func (s *state) setVar(name string, value reflect.Value) { + for i := s.mark() - 1; i >= 0; i-- { + if s.vars[i].name == name { + s.vars[i].value = value + return + } + } + s.errorf("undefined variable: %s", name) +} + +// setTopVar overwrites the top-nth variable on the stack. Used by range iterations. +func (s *state) setTopVar(n int, value reflect.Value) { s.vars[len(s.vars)-n].value = value } @@ -73,6 +92,10 @@ func (s *state) varValue(name string) reflect.Value { var zero reflect.Value +type missingValType struct{} + +var missingVal = reflect.ValueOf(missingValType{}) + // at marks the state to be on node n, for error reporting. func (s *state) at(node parse.Node) { s.node = node @@ -319,11 +342,11 @@ func (s *state) walkRange(dot reflect.Value, r *parse.RangeNode) { oneIteration := func(index, elem reflect.Value) { // Set top var (lexically the second if there are two) to the element. if len(r.Pipe.Decl) > 0 { - s.setVar(1, elem) + s.setTopVar(1, elem) } // Set next var (lexically the first if there are two) to the index. if len(r.Pipe.Decl) > 1 { - s.setVar(2, index) + s.setTopVar(2, index) } s.walk(elem, r.List) s.pop(mark) @@ -403,6 +426,7 @@ func (s *state) evalPipeline(dot reflect.Value, pipe *parse.PipeNode) (value ref return } s.at(pipe) + value = missingVal for _, cmd := range pipe.Cmds { value = s.evalCommand(dot, cmd, value) // previous value is this one's final arg. // If the object has type interface{}, dig down one level to the thing inside. @@ -411,13 +435,17 @@ func (s *state) evalPipeline(dot reflect.Value, pipe *parse.PipeNode) (value ref } } for _, variable := range pipe.Decl { - s.push(variable.Ident[0], value) + if pipe.IsAssign { + s.setVar(variable.Ident[0], value) + } else { + s.push(variable.Ident[0], value) + } } return value } func (s *state) notAFunction(args []parse.Node, final reflect.Value) { - if len(args) > 1 || final.IsValid() { + if len(args) > 1 || final != missingVal { s.errorf("can't give argument to non-function %s", args[0]) } } @@ -521,7 +549,7 @@ func (s *state) evalVariableNode(dot reflect.Value, variable *parse.VariableNode func (s *state) evalFieldChain(dot, receiver reflect.Value, node parse.Node, ident []string, args []parse.Node, final reflect.Value) reflect.Value { n := len(ident) for i := 0; i < n-1; i++ { - receiver = s.evalField(dot, ident[i], node, nil, zero, receiver) + receiver = s.evalField(dot, ident[i], node, nil, missingVal, receiver) } // Now if it's a method, it gets the arguments. return s.evalField(dot, ident[n-1], node, args, final, receiver) @@ -558,7 +586,7 @@ func (s *state) evalField(dot reflect.Value, fieldName string, node parse.Node, if method := ptr.MethodByName(fieldName); method.IsValid() { return s.evalCall(dot, method, node, fieldName, args, final) } - hasArgs := len(args) > 1 || final.IsValid() + hasArgs := len(args) > 1 || final != missingVal // It's not a method; must be a field of a struct or an element of a map. switch receiver.Kind() { case reflect.Struct: @@ -620,7 +648,7 @@ func (s *state) evalCall(dot, fun reflect.Value, node parse.Node, name string, a } typ := fun.Type() numIn := len(args) - if final.IsValid() { + if final != missingVal { numIn++ } numFixed := len(args) @@ -630,7 +658,7 @@ func (s *state) evalCall(dot, fun reflect.Value, node parse.Node, name string, a s.errorf("wrong number of args for %s: want at least %d got %d", name, typ.NumIn()-1, len(args)) } } else if numIn != typ.NumIn() { - s.errorf("wrong number of args for %s: want %d got %d", name, typ.NumIn(), len(args)) + s.errorf("wrong number of args for %s: want %d got %d", name, typ.NumIn(), numIn) } if !goodFunc(typ) { // TODO: This could still be a confusing error; maybe goodFunc should provide info. @@ -651,7 +679,7 @@ func (s *state) evalCall(dot, fun reflect.Value, node parse.Node, name string, a } } // Add final value if necessary. - if final.IsValid() { + if final != missingVal { t := typ.In(typ.NumIn() - 1) if typ.IsVariadic() { if numIn-1 < numFixed { @@ -693,8 +721,12 @@ func canBeNil(typ reflect.Type) bool { // validateType guarantees that the value is valid and assignable to the type. func (s *state) validateType(value reflect.Value, typ reflect.Type) reflect.Value { if !value.IsValid() { - if typ == nil || canBeNil(typ) { + if typ == nil { // An untyped nil interface{}. Accept as a proper nil value. + return reflect.ValueOf(nil) + } + if canBeNil(typ) { + // Like above, but use the zero value of the non-nil type. return reflect.Zero(typ) } s.errorf("invalid value; expected %s", typ) @@ -740,15 +772,15 @@ func (s *state) evalArg(dot reflect.Value, typ reflect.Type, n parse.Node) refle } s.errorf("cannot assign nil to %s", typ) case *parse.FieldNode: - return s.validateType(s.evalFieldNode(dot, arg, []parse.Node{n}, zero), typ) + return s.validateType(s.evalFieldNode(dot, arg, []parse.Node{n}, missingVal), typ) case *parse.VariableNode: - return s.validateType(s.evalVariableNode(dot, arg, nil, zero), typ) + return s.validateType(s.evalVariableNode(dot, arg, nil, missingVal), typ) case *parse.PipeNode: return s.validateType(s.evalPipeline(dot, arg), typ) case *parse.IdentifierNode: - return s.validateType(s.evalFunction(dot, arg, arg, nil, zero), typ) + return s.validateType(s.evalFunction(dot, arg, arg, nil, missingVal), typ) case *parse.ChainNode: - return s.validateType(s.evalChainNode(dot, arg, nil, zero), typ) + return s.validateType(s.evalChainNode(dot, arg, nil, missingVal), typ) } switch typ.Kind() { case reflect.Bool: @@ -849,9 +881,9 @@ func (s *state) evalEmptyInterface(dot reflect.Value, n parse.Node) reflect.Valu case *parse.DotNode: return dot case *parse.FieldNode: - return s.evalFieldNode(dot, n, nil, zero) + return s.evalFieldNode(dot, n, nil, missingVal) case *parse.IdentifierNode: - return s.evalFunction(dot, n, n, nil, zero) + return s.evalFunction(dot, n, n, nil, missingVal) case *parse.NilNode: // NilNode is handled in evalArg, the only place that calls here. s.errorf("evalEmptyInterface: nil (can't happen)") @@ -860,7 +892,7 @@ func (s *state) evalEmptyInterface(dot reflect.Value, n parse.Node) reflect.Valu case *parse.StringNode: return reflect.ValueOf(n.Text) case *parse.VariableNode: - return s.evalVariableNode(dot, n, nil, zero) + return s.evalVariableNode(dot, n, nil, missingVal) case *parse.PipeNode: return s.evalPipeline(dot, n) } diff --git a/libgo/go/text/template/exec_test.go b/libgo/go/text/template/exec_test.go index d0cda6b..6f40d80 100644 --- a/libgo/go/text/template/exec_test.go +++ b/libgo/go/text/template/exec_test.go @@ -304,6 +304,13 @@ var execTests = []execTest{ {"$.I", "{{$.I}}", "17", tVal, true}, {"$.U.V", "{{$.U.V}}", "v", tVal, true}, {"declare in action", "{{$x := $.U.V}}{{$x}}", "v", tVal, true}, + {"simple assignment", "{{$x := 2}}{{$x = 3}}{{$x}}", "3", tVal, true}, + {"nested assignment", + "{{$x := 2}}{{if true}}{{$x = 3}}{{end}}{{$x}}", + "3", tVal, true}, + {"nested assignment changes the last declaration", + "{{$x := 1}}{{if true}}{{$x := 2}}{{if true}}{{$x = 3}}{{end}}{{end}}{{$x}}", + "1", tVal, true}, // Type with String method. {"V{6666}.String()", "-{{.V0}}-", "-<6666>-", tVal, true}, @@ -330,6 +337,10 @@ var execTests = []execTest{ {"empty with struct", "{{.Empty4}}", "{UinEmpty}", tVal, true}, {"empty with struct, field", "{{.Empty4.V}}", "UinEmpty", tVal, true}, + // Edge cases with <no value> with an interface value + {"field on interface", "{{.foo}}", "<no value>", nil, true}, + {"field on parenthesized interface", "{{(.).foo}}", "<no value>", nil, true}, + // Method calls. {".Method0", "-{{.Method0}}-", "-M0-", tVal, true}, {".Method1(1234)", "-{{.Method1 1234}}-", "-1234-", tVal, true}, @@ -378,6 +389,11 @@ var execTests = []execTest{ {"pipeline", "-{{.Method0 | .Method2 .U16}}-", "-Method2: 16 M0-", tVal, true}, {"pipeline func", "-{{call .VariadicFunc `llo` | call .VariadicFunc `he` }}-", "-<he+<llo>>-", tVal, true}, + // Nil values aren't missing arguments. + {"nil pipeline", "{{ .Empty0 | call .NilOKFunc }}", "true", tVal, true}, + {"nil call arg", "{{ call .NilOKFunc .Empty0 }}", "true", tVal, true}, + {"bad nil pipeline", "{{ .Empty0 | .VariadicFunc }}", "", tVal, false}, + // Parenthesized expressions {"parens in pipeline", "{{printf `%d %d %d` (1) (2 | add 3) (add 4 (add 5 6))}}", "1 5 15", tVal, true}, @@ -432,6 +448,8 @@ var execTests = []execTest{ {"html pipeline", `{{printf "<script>alert(\"XSS\");</script>" | html}}`, "<script>alert("XSS");</script>", nil, true}, {"html", `{{html .PS}}`, "a string", tVal, true}, + {"html typed nil", `{{html .NIL}}`, "<nil>", tVal, true}, + {"html untyped nil", `{{html .Empty0}}`, "<no value>", tVal, true}, // JavaScript. {"js", `{{js .}}`, `It\'d be nice.`, `It'd be nice.`, true}, diff --git a/libgo/go/text/template/parse/lex.go b/libgo/go/text/template/parse/lex.go index e112cb7..fc259f3 100644 --- a/libgo/go/text/template/parse/lex.go +++ b/libgo/go/text/template/parse/lex.go @@ -42,7 +42,8 @@ const ( itemChar // printable ASCII character; grab bag for comma etc. itemCharConstant // character constant itemComplex // complex constant (1+2i); imaginary is just a number - itemColonEquals // colon-equals (':=') introducing a declaration + itemAssign // equals ('=') introducing an assignment + itemDeclare // colon-equals (':=') introducing a declaration itemEOF itemField // alphanumeric identifier starting with '.' itemIdentifier // alphanumeric identifier not starting with '.' @@ -366,11 +367,13 @@ func lexInsideAction(l *lexer) stateFn { return l.errorf("unclosed action") case isSpace(r): return lexSpace + case r == '=': + l.emit(itemAssign) case r == ':': if l.next() != '=' { return l.errorf("expected :=") } - l.emit(itemColonEquals) + l.emit(itemDeclare) case r == '|': l.emit(itemPipe) case r == '"': diff --git a/libgo/go/text/template/parse/lex_test.go b/libgo/go/text/template/parse/lex_test.go index cb01cd9..6e7ece9 100644 --- a/libgo/go/text/template/parse/lex_test.go +++ b/libgo/go/text/template/parse/lex_test.go @@ -16,7 +16,7 @@ var itemName = map[itemType]string{ itemChar: "char", itemCharConstant: "charconst", itemComplex: "complex", - itemColonEquals: ":=", + itemDeclare: ":=", itemEOF: "EOF", itemField: "field", itemIdentifier: "identifier", @@ -210,7 +210,7 @@ var lexTests = []lexTest{ tLeft, mkItem(itemVariable, "$c"), tSpace, - mkItem(itemColonEquals, ":="), + mkItem(itemDeclare, ":="), tSpace, mkItem(itemIdentifier, "printf"), tSpace, @@ -262,7 +262,7 @@ var lexTests = []lexTest{ tLeft, mkItem(itemVariable, "$v"), tSpace, - mkItem(itemColonEquals, ":="), + mkItem(itemDeclare, ":="), tSpace, mkItem(itemNumber, "3"), tRight, @@ -276,7 +276,7 @@ var lexTests = []lexTest{ tSpace, mkItem(itemVariable, "$w"), tSpace, - mkItem(itemColonEquals, ":="), + mkItem(itemDeclare, ":="), tSpace, mkItem(itemNumber, "3"), tRight, diff --git a/libgo/go/text/template/parse/node.go b/libgo/go/text/template/parse/node.go index 55ff46c..dca83da 100644 --- a/libgo/go/text/template/parse/node.go +++ b/libgo/go/text/template/parse/node.go @@ -144,14 +144,15 @@ func (t *TextNode) Copy() Node { type PipeNode struct { NodeType Pos - tr *Tree - Line int // The line number in the input. Deprecated: Kept for compatibility. - Decl []*VariableNode // Variable declarations in lexical order. - Cmds []*CommandNode // The commands in lexical order. + tr *Tree + Line int // The line number in the input. Deprecated: Kept for compatibility. + IsAssign bool // The variables are being assigned, not declared. + Decl []*VariableNode // Variables in lexical order. + Cmds []*CommandNode // The commands in lexical order. } -func (t *Tree) newPipeline(pos Pos, line int, decl []*VariableNode) *PipeNode { - return &PipeNode{tr: t, NodeType: NodePipe, Pos: pos, Line: line, Decl: decl} +func (t *Tree) newPipeline(pos Pos, line int, vars []*VariableNode) *PipeNode { + return &PipeNode{tr: t, NodeType: NodePipe, Pos: pos, Line: line, Decl: vars} } func (p *PipeNode) append(command *CommandNode) { @@ -186,11 +187,12 @@ func (p *PipeNode) CopyPipe() *PipeNode { if p == nil { return p } - var decl []*VariableNode + var vars []*VariableNode for _, d := range p.Decl { - decl = append(decl, d.Copy().(*VariableNode)) + vars = append(vars, d.Copy().(*VariableNode)) } - n := p.tr.newPipeline(p.Pos, p.Line, decl) + n := p.tr.newPipeline(p.Pos, p.Line, vars) + n.IsAssign = p.IsAssign for _, c := range p.Cmds { n.append(c.Copy().(*CommandNode)) } @@ -317,7 +319,7 @@ func (i *IdentifierNode) Copy() Node { return NewIdentifier(i.Ident).SetTree(i.tr).SetPos(i.Pos) } -// VariableNode holds a list of variable names, possibly with chained field +// AssignNode holds a list of variable names, possibly with chained field // accesses. The dollar sign is part of the (first) name. type VariableNode struct { NodeType diff --git a/libgo/go/text/template/parse/parse.go b/libgo/go/text/template/parse/parse.go index a91a544..cb9b44e 100644 --- a/libgo/go/text/template/parse/parse.go +++ b/libgo/go/text/template/parse/parse.go @@ -383,10 +383,11 @@ func (t *Tree) action() (n Node) { // Pipeline: // declarations? command ('|' command)* func (t *Tree) pipeline(context string) (pipe *PipeNode) { - var decl []*VariableNode + decl := false + var vars []*VariableNode token := t.peekNonSpace() pos := token.pos - // Are there declarations? + // Are there declarations or assignments? for { if v := t.peekNonSpace(); v.typ == itemVariable { t.next() @@ -395,26 +396,33 @@ func (t *Tree) pipeline(context string) (pipe *PipeNode) { // argument variable rather than a declaration. So remember the token // adjacent to the variable so we can push it back if necessary. tokenAfterVariable := t.peek() - if next := t.peekNonSpace(); next.typ == itemColonEquals || (next.typ == itemChar && next.val == ",") { + next := t.peekNonSpace() + switch { + case next.typ == itemAssign, next.typ == itemDeclare, + next.typ == itemChar && next.val == ",": t.nextNonSpace() variable := t.newVariable(v.pos, v.val) - decl = append(decl, variable) + vars = append(vars, variable) t.vars = append(t.vars, v.val) + if next.typ == itemDeclare { + decl = true + } if next.typ == itemChar && next.val == "," { - if context == "range" && len(decl) < 2 { + if context == "range" && len(vars) < 2 { continue } t.errorf("too many declarations in %s", context) } - } else if tokenAfterVariable.typ == itemSpace { + case tokenAfterVariable.typ == itemSpace: t.backup3(v, tokenAfterVariable) - } else { + default: t.backup2(v) } } break } - pipe = t.newPipeline(pos, token.line, decl) + pipe = t.newPipeline(pos, token.line, vars) + pipe.IsAssign = !decl for { switch token := t.nextNonSpace(); token.typ { case itemRightDelim, itemRightParen: diff --git a/libgo/go/text/template/parse/parse_test.go b/libgo/go/text/template/parse/parse_test.go index 81f14ac..c1f80c1 100644 --- a/libgo/go/text/template/parse/parse_test.go +++ b/libgo/go/text/template/parse/parse_test.go @@ -259,9 +259,9 @@ var parseTests = []parseTest{ {"adjacent args", "{{printf 3`x`}}", hasError, ""}, {"adjacent args with .", "{{printf `x`.}}", hasError, ""}, {"extra end after if", "{{if .X}}a{{else if .Y}}b{{end}}{{end}}", hasError, ""}, - // Equals (and other chars) do not assignments make (yet). + // Other kinds of assignments and operators aren't available yet. {"bug0a", "{{$x := 0}}{{$x}}", noError, "{{$x := 0}}{{$x}}"}, - {"bug0b", "{{$x = 1}}{{$x}}", hasError, ""}, + {"bug0b", "{{$x += 1}}{{$x}}", hasError, ""}, {"bug0c", "{{$x ! 2}}{{$x}}", hasError, ""}, {"bug0d", "{{$x % 3}}{{$x}}", hasError, ""}, // Check the parse fails for := rather than comma. diff --git a/libgo/go/text/template/template.go b/libgo/go/text/template/template.go index 2246f67..41cdd56 100644 --- a/libgo/go/text/template/template.go +++ b/libgo/go/text/template/template.go @@ -125,9 +125,7 @@ func (t *Template) AddParseTree(name string, tree *parse.Tree) (*Template, error nt = t.New(name) } // Even if nt == t, we need to install it in the common.tmpl map. - if replace, err := t.associate(nt, tree); err != nil { - return nil, err - } else if replace || nt.Tree == nil { + if t.associate(nt, tree) || nt.Tree == nil { nt.Tree = tree } return nt, nil @@ -212,15 +210,15 @@ func (t *Template) Parse(text string) (*Template, error) { // associate installs the new template into the group of templates associated // with t. The two are already known to share the common structure. // The boolean return value reports whether to store this tree as t.Tree. -func (t *Template) associate(new *Template, tree *parse.Tree) (bool, error) { +func (t *Template) associate(new *Template, tree *parse.Tree) bool { if new.common != t.common { panic("internal error: associate not common") } if old := t.tmpl[new.name]; old != nil && parse.IsEmptyTree(tree.Root) && old.Tree != nil { // If a template by that name exists, // don't replace it with an empty template. - return false, nil + return false } t.tmpl[new.name] = new - return true, nil + return true } |