aboutsummaryrefslogtreecommitdiff
path: root/libgo/go/text
diff options
context:
space:
mode:
authorIan Lance Taylor <iant@golang.org>2018-09-24 21:46:21 +0000
committerIan Lance Taylor <ian@gcc.gnu.org>2018-09-24 21:46:21 +0000
commitdd931d9b48647e898dc80927c532ae93cc09e192 (patch)
tree71be2295cd79b8a182f6130611658db8628772d5 /libgo/go/text
parent779d8a5ad09b01428726ea5a0e6c87bd9ac3c0e4 (diff)
downloadgcc-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.go106
-rw-r--r--libgo/go/text/scanner/scanner.go2
-rw-r--r--libgo/go/text/scanner/scanner_test.go16
-rw-r--r--libgo/go/text/tabwriter/tabwriter.go62
-rw-r--r--libgo/go/text/tabwriter/tabwriter_test.go79
-rw-r--r--libgo/go/text/template/doc.go7
-rw-r--r--libgo/go/text/template/exec.go76
-rw-r--r--libgo/go/text/template/exec_test.go18
-rw-r--r--libgo/go/text/template/parse/lex.go7
-rw-r--r--libgo/go/text/template/parse/lex_test.go8
-rw-r--r--libgo/go/text/template/parse/node.go22
-rw-r--r--libgo/go/text/template/parse/parse.go24
-rw-r--r--libgo/go/text/template/parse/parse_test.go4
-rw-r--r--libgo/go/text/template/template.go10
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}}`,
"&lt;script&gt;alert(&#34;XSS&#34;);&lt;/script&gt;", nil, true},
{"html", `{{html .PS}}`, "a string", tVal, true},
+ {"html typed nil", `{{html .NIL}}`, "&lt;nil&gt;", tVal, true},
+ {"html untyped nil", `{{html .Empty0}}`, "&lt;no value&gt;", 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
}