summaryrefslogtreecommitdiff
path: root/libgo/go/text
diff options
context:
space:
mode:
authorIan Lance Taylor <iant@golang.org>2017-01-14 00:05:42 +0000
committerIan Lance Taylor <ian@gcc.gnu.org>2017-01-14 00:05:42 +0000
commitc2047754c300b68c05d65faa8dc2925fe67b71b4 (patch)
treee183ae81a1f48a02945cb6de463a70c5be1b06f6 /libgo/go/text
parent829afb8f05602bb31c9c597b24df7377fed4f059 (diff)
downloadgcc-c2047754c300b68c05d65faa8dc2925fe67b71b4.tar.gz
libgo: update to Go 1.8 release candidate 1
Compiler changes: * Change map assignment to use mapassign and assign value directly. * Change string iteration to use decoderune, faster for ASCII strings. * Change makeslice to take int, and use makeslice64 for larger values. * Add new noverflow field to hmap struct used for maps. Unresolved problems, to be fixed later: * Commented out test in go/types/sizes_test.go that doesn't compile. * Commented out reflect.TestStructOf test for padding after zero-sized field. Reviewed-on: https://go-review.googlesource.com/35231 gotools/: Updates for Go 1.8rc1. * Makefile.am (go_cmd_go_files): Add bug.go. (s-zdefaultcc): Write defaultPkgConfig. * Makefile.in: Rebuild. From-SVN: r244456
Diffstat (limited to 'libgo/go/text')
-rw-r--r--libgo/go/text/tabwriter/tabwriter.go1
-rw-r--r--libgo/go/text/template/exec.go47
-rw-r--r--libgo/go/text/template/exec_test.go106
-rw-r--r--libgo/go/text/template/funcs.go80
-rw-r--r--libgo/go/text/template/multi_test.go36
-rw-r--r--libgo/go/text/template/parse/lex.go77
-rw-r--r--libgo/go/text/template/parse/lex_test.go259
-rw-r--r--libgo/go/text/template/parse/parse.go25
-rw-r--r--libgo/go/text/template/parse/parse_test.go34
-rw-r--r--libgo/go/text/template/template.go15
10 files changed, 456 insertions, 224 deletions
diff --git a/libgo/go/text/tabwriter/tabwriter.go b/libgo/go/text/tabwriter/tabwriter.go
index 796e1e86995..752c9b8e9fb 100644
--- a/libgo/go/text/tabwriter/tabwriter.go
+++ b/libgo/go/text/tabwriter/tabwriter.go
@@ -8,6 +8,7 @@
// The package is using the Elastic Tabstops algorithm described at
// http://nickgravgaard.com/elastictabstops/index.html.
//
+// The text/tabwriter package is frozen and is not accepting new features.
package tabwriter
import (
diff --git a/libgo/go/text/template/exec.go b/libgo/go/text/template/exec.go
index c7c6d504900..89d3e379b4b 100644
--- a/libgo/go/text/template/exec.go
+++ b/libgo/go/text/template/exec.go
@@ -173,20 +173,26 @@ func (t *Template) ExecuteTemplate(wr io.Writer, name string, data interface{})
// execution stops, but partial results may already have been written to
// the output writer.
// A template may be executed safely in parallel.
+//
+// If data is a reflect.Value, the template applies to the concrete
+// value that the reflect.Value holds, as in fmt.Print.
func (t *Template) Execute(wr io.Writer, data interface{}) error {
return t.execute(wr, data)
}
func (t *Template) execute(wr io.Writer, data interface{}) (err error) {
defer errRecover(&err)
- value := reflect.ValueOf(data)
+ value, ok := data.(reflect.Value)
+ if !ok {
+ value = reflect.ValueOf(data)
+ }
state := &state{
tmpl: t,
wr: wr,
vars: []variable{{"$", value}},
}
if t.Tree == nil || t.Root == nil {
- state.errorf("%q is an incomplete or empty template%s", t.Name(), t.DefinedTemplates())
+ state.errorf("%q is an incomplete or empty template", t.Name())
}
state.walk(value, t.Root)
return
@@ -537,6 +543,9 @@ func (s *state) evalFunction(dot reflect.Value, node *parse.IdentifierNode, cmd
// value of the pipeline, if any.
func (s *state) evalField(dot reflect.Value, fieldName string, node parse.Node, args []parse.Node, final, receiver reflect.Value) reflect.Value {
if !receiver.IsValid() {
+ if s.tmpl.option.missingKey == mapError { // Treat invalid value as missing map key.
+ s.errorf("nil data; no entry for key %q", fieldName)
+ }
return zero
}
typ := receiver.Type()
@@ -598,8 +607,9 @@ func (s *state) evalField(dot reflect.Value, fieldName string, node parse.Node,
}
var (
- errorType = reflect.TypeOf((*error)(nil)).Elem()
- fmtStringerType = reflect.TypeOf((*fmt.Stringer)(nil)).Elem()
+ errorType = reflect.TypeOf((*error)(nil)).Elem()
+ fmtStringerType = reflect.TypeOf((*fmt.Stringer)(nil)).Elem()
+ reflectValueType = reflect.TypeOf((*reflect.Value)(nil)).Elem()
)
// evalCall executes a function or method call. If it's a method, fun already has the receiver bound, so
@@ -663,7 +673,11 @@ func (s *state) evalCall(dot, fun reflect.Value, node parse.Node, name string, a
s.at(node)
s.errorf("error calling %s: %s", name, result[1].Interface().(error))
}
- return result[0]
+ v := result[0]
+ if v.Type() == reflectValueType {
+ v = v.Interface().(reflect.Value)
+ }
+ return v
}
// canBeNil reports whether an untyped nil can be assigned to the type. See reflect.Zero.
@@ -671,6 +685,8 @@ func canBeNil(typ reflect.Type) bool {
switch typ.Kind() {
case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice:
return true
+ case reflect.Struct:
+ return typ == reflectValueType
}
return false
}
@@ -684,6 +700,9 @@ func (s *state) validateType(value reflect.Value, typ reflect.Type) reflect.Valu
}
s.errorf("invalid value; expected %s", typ)
}
+ if typ == reflectValueType && value.Type() != typ {
+ return reflect.ValueOf(value)
+ }
if typ != nil && !value.Type().AssignableTo(typ) {
if value.Kind() == reflect.Interface && !value.IsNil() {
value = value.Elem()
@@ -745,6 +764,10 @@ func (s *state) evalArg(dot reflect.Value, typ reflect.Type, n parse.Node) refle
if typ.NumMethod() == 0 {
return s.evalEmptyInterface(dot, n)
}
+ case reflect.Struct:
+ if typ == reflectValueType {
+ return reflect.ValueOf(s.evalEmptyInterface(dot, n))
+ }
case reflect.String:
return s.evalString(typ, n)
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
@@ -856,6 +879,20 @@ func indirect(v reflect.Value) (rv reflect.Value, isNil bool) {
return v, false
}
+// indirectInterface returns the concrete value in an interface value,
+// or else the zero reflect.Value.
+// That is, if v represents the interface value x, the result is the same as reflect.ValueOf(x):
+// the fact that x was an interface value is forgotten.
+func indirectInterface(v reflect.Value) reflect.Value {
+ if v.Kind() != reflect.Interface {
+ return v
+ }
+ if v.IsNil() {
+ return reflect.Value{}
+ }
+ return v.Elem()
+}
+
// printValue writes the textual representation of the value to the output of
// the template.
func (s *state) printValue(n parse.Node, v reflect.Value) {
diff --git a/libgo/go/text/template/exec_test.go b/libgo/go/text/template/exec_test.go
index 3ef065edcfd..5892b27391b 100644
--- a/libgo/go/text/template/exec_test.go
+++ b/libgo/go/text/template/exec_test.go
@@ -932,7 +932,7 @@ func TestMessageForExecuteEmpty(t *testing.T) {
t.Fatal("expected second error")
}
got = err.Error()
- want = `template: empty: "empty" is an incomplete or empty template; defined templates are: "secondary"`
+ want = `template: empty: "empty" is an incomplete or empty template`
if got != want {
t.Errorf("expected error %s got %s", want, got)
}
@@ -1142,6 +1142,12 @@ func TestMissingMapKey(t *testing.T) {
if err == nil {
t.Errorf("expected error; got none")
}
+ // same Option, but now a nil interface: ask for an error
+ err = tmpl.Execute(&b, nil)
+ t.Log(err)
+ if err == nil {
+ t.Errorf("expected error for nil-interface; got none")
+ }
}
// Test that the error message for multiline unterminated string
@@ -1152,7 +1158,7 @@ func TestUnterminatedStringError(t *testing.T) {
t.Fatal("expected error")
}
str := err.Error()
- if !strings.Contains(str, "X:3: unexpected unterminated raw quoted strin") {
+ if !strings.Contains(str, "X:3: unexpected unterminated raw quoted string") {
t.Fatalf("unexpected error: %s", str)
}
}
@@ -1310,3 +1316,99 @@ func TestMaxExecDepth(t *testing.T) {
t.Errorf("got error %q; want %q", got, want)
}
}
+
+func TestAddrOfIndex(t *testing.T) {
+ // golang.org/issue/14916.
+ // Before index worked on reflect.Values, the .String could not be
+ // found on the (incorrectly unaddressable) V value,
+ // in contrast to range, which worked fine.
+ // Also testing that passing a reflect.Value to tmpl.Execute works.
+ texts := []string{
+ `{{range .}}{{.String}}{{end}}`,
+ `{{with index . 0}}{{.String}}{{end}}`,
+ }
+ for _, text := range texts {
+ tmpl := Must(New("tmpl").Parse(text))
+ var buf bytes.Buffer
+ err := tmpl.Execute(&buf, reflect.ValueOf([]V{{1}}))
+ if err != nil {
+ t.Fatalf("%s: Execute: %v", text, err)
+ }
+ if buf.String() != "<1>" {
+ t.Fatalf("%s: template output = %q, want %q", text, &buf, "<1>")
+ }
+ }
+}
+
+func TestInterfaceValues(t *testing.T) {
+ // golang.org/issue/17714.
+ // Before index worked on reflect.Values, interface values
+ // were always implicitly promoted to the underlying value,
+ // except that nil interfaces were promoted to the zero reflect.Value.
+ // Eliminating a round trip to interface{} and back to reflect.Value
+ // eliminated this promotion, breaking these cases.
+ tests := []struct {
+ text string
+ out string
+ }{
+ {`{{index .Nil 1}}`, "ERROR: index of untyped nil"},
+ {`{{index .Slice 2}}`, "2"},
+ {`{{index .Slice .Two}}`, "2"},
+ {`{{call .Nil 1}}`, "ERROR: call of nil"},
+ {`{{call .PlusOne 1}}`, "2"},
+ {`{{call .PlusOne .One}}`, "2"},
+ {`{{and (index .Slice 0) true}}`, "0"},
+ {`{{and .Zero true}}`, "0"},
+ {`{{and (index .Slice 1) false}}`, "false"},
+ {`{{and .One false}}`, "false"},
+ {`{{or (index .Slice 0) false}}`, "false"},
+ {`{{or .Zero false}}`, "false"},
+ {`{{or (index .Slice 1) true}}`, "1"},
+ {`{{or .One true}}`, "1"},
+ {`{{not (index .Slice 0)}}`, "true"},
+ {`{{not .Zero}}`, "true"},
+ {`{{not (index .Slice 1)}}`, "false"},
+ {`{{not .One}}`, "false"},
+ {`{{eq (index .Slice 0) .Zero}}`, "true"},
+ {`{{eq (index .Slice 1) .One}}`, "true"},
+ {`{{ne (index .Slice 0) .Zero}}`, "false"},
+ {`{{ne (index .Slice 1) .One}}`, "false"},
+ {`{{ge (index .Slice 0) .One}}`, "false"},
+ {`{{ge (index .Slice 1) .Zero}}`, "true"},
+ {`{{gt (index .Slice 0) .One}}`, "false"},
+ {`{{gt (index .Slice 1) .Zero}}`, "true"},
+ {`{{le (index .Slice 0) .One}}`, "true"},
+ {`{{le (index .Slice 1) .Zero}}`, "false"},
+ {`{{lt (index .Slice 0) .One}}`, "true"},
+ {`{{lt (index .Slice 1) .Zero}}`, "false"},
+ }
+
+ for _, tt := range tests {
+ tmpl := Must(New("tmpl").Parse(tt.text))
+ var buf bytes.Buffer
+ err := tmpl.Execute(&buf, map[string]interface{}{
+ "PlusOne": func(n int) int {
+ return n + 1
+ },
+ "Slice": []int{0, 1, 2, 3},
+ "One": 1,
+ "Two": 2,
+ "Nil": nil,
+ "Zero": 0,
+ })
+ if strings.HasPrefix(tt.out, "ERROR:") {
+ e := strings.TrimSpace(strings.TrimPrefix(tt.out, "ERROR:"))
+ if err == nil || !strings.Contains(err.Error(), e) {
+ t.Errorf("%s: Execute: %v, want error %q", tt.text, err, e)
+ }
+ continue
+ }
+ if err != nil {
+ t.Errorf("%s: Execute: %v", tt.text, err)
+ continue
+ }
+ if buf.String() != tt.out {
+ t.Errorf("%s: template output = %q, want %q", tt.text, &buf, tt.out)
+ }
+ }
+}
diff --git a/libgo/go/text/template/funcs.go b/libgo/go/text/template/funcs.go
index cd0b82b243d..3047b272e57 100644
--- a/libgo/go/text/template/funcs.go
+++ b/libgo/go/text/template/funcs.go
@@ -21,6 +21,12 @@ import (
// which the second has type error. In that case, if the second (error)
// return value evaluates to non-nil during execution, execution terminates and
// Execute returns that error.
+//
+// When template execution invokes a function with an argument list, that list
+// must be assignable to the function's parameter types. Functions meant to
+// apply to arguments of arbitrary type can use parameters of type interface{} or
+// of type reflect.Value. Similarly, functions meant to return a result of arbitrary
+// type can return interface{} or reflect.Value.
type FuncMap map[string]interface{}
var builtins = FuncMap{
@@ -144,16 +150,16 @@ func prepareArg(value reflect.Value, argType reflect.Type) (reflect.Value, error
// index returns the result of indexing its first argument by the following
// arguments. Thus "index x 1 2 3" is, in Go syntax, x[1][2][3]. Each
// indexed item must be a map, slice, or array.
-func index(item interface{}, indices ...interface{}) (interface{}, error) {
- v := reflect.ValueOf(item)
+func index(item reflect.Value, indices ...reflect.Value) (reflect.Value, error) {
+ v := indirectInterface(item)
if !v.IsValid() {
- return nil, fmt.Errorf("index of untyped nil")
+ return reflect.Value{}, fmt.Errorf("index of untyped nil")
}
for _, i := range indices {
- index := reflect.ValueOf(i)
+ index := indirectInterface(i)
var isNil bool
if v, isNil = indirect(v); isNil {
- return nil, fmt.Errorf("index of nil pointer")
+ return reflect.Value{}, fmt.Errorf("index of nil pointer")
}
switch v.Kind() {
case reflect.Array, reflect.Slice, reflect.String:
@@ -164,18 +170,18 @@ func index(item interface{}, indices ...interface{}) (interface{}, error) {
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
x = int64(index.Uint())
case reflect.Invalid:
- return nil, fmt.Errorf("cannot index slice/array with nil")
+ return reflect.Value{}, fmt.Errorf("cannot index slice/array with nil")
default:
- return nil, fmt.Errorf("cannot index slice/array with type %s", index.Type())
+ return reflect.Value{}, fmt.Errorf("cannot index slice/array with type %s", index.Type())
}
if x < 0 || x >= int64(v.Len()) {
- return nil, fmt.Errorf("index out of range: %d", x)
+ return reflect.Value{}, fmt.Errorf("index out of range: %d", x)
}
v = v.Index(int(x))
case reflect.Map:
index, err := prepareArg(index, v.Type().Key())
if err != nil {
- return nil, err
+ return reflect.Value{}, err
}
if x := v.MapIndex(index); x.IsValid() {
v = x
@@ -186,10 +192,10 @@ func index(item interface{}, indices ...interface{}) (interface{}, error) {
// the loop holds invariant: v.IsValid()
panic("unreachable")
default:
- return nil, fmt.Errorf("can't index item of type %s", v.Type())
+ return reflect.Value{}, fmt.Errorf("can't index item of type %s", v.Type())
}
}
- return v.Interface(), nil
+ return v, nil
}
// Length
@@ -215,33 +221,33 @@ func length(item interface{}) (int, error) {
// call returns the result of evaluating the first argument as a function.
// The function must return 1 result, or 2 results, the second of which is an error.
-func call(fn interface{}, args ...interface{}) (interface{}, error) {
- v := reflect.ValueOf(fn)
+func call(fn reflect.Value, args ...reflect.Value) (reflect.Value, error) {
+ v := indirectInterface(fn)
if !v.IsValid() {
- return nil, fmt.Errorf("call of nil")
+ return reflect.Value{}, fmt.Errorf("call of nil")
}
typ := v.Type()
if typ.Kind() != reflect.Func {
- return nil, fmt.Errorf("non-function of type %s", typ)
+ return reflect.Value{}, fmt.Errorf("non-function of type %s", typ)
}
if !goodFunc(typ) {
- return nil, fmt.Errorf("function called with %d args; should be 1 or 2", typ.NumOut())
+ return reflect.Value{}, fmt.Errorf("function called with %d args; should be 1 or 2", typ.NumOut())
}
numIn := typ.NumIn()
var dddType reflect.Type
if typ.IsVariadic() {
if len(args) < numIn-1 {
- return nil, fmt.Errorf("wrong number of args: got %d want at least %d", len(args), numIn-1)
+ return reflect.Value{}, fmt.Errorf("wrong number of args: got %d want at least %d", len(args), numIn-1)
}
dddType = typ.In(numIn - 1).Elem()
} else {
if len(args) != numIn {
- return nil, fmt.Errorf("wrong number of args: got %d want %d", len(args), numIn)
+ return reflect.Value{}, fmt.Errorf("wrong number of args: got %d want %d", len(args), numIn)
}
}
argv := make([]reflect.Value, len(args))
for i, arg := range args {
- value := reflect.ValueOf(arg)
+ value := indirectInterface(arg)
// Compute the expected type. Clumsy because of variadics.
var argType reflect.Type
if !typ.IsVariadic() || i < numIn-1 {
@@ -252,26 +258,26 @@ func call(fn interface{}, args ...interface{}) (interface{}, error) {
var err error
if argv[i], err = prepareArg(value, argType); err != nil {
- return nil, fmt.Errorf("arg %d: %s", i, err)
+ return reflect.Value{}, fmt.Errorf("arg %d: %s", i, err)
}
}
result := v.Call(argv)
if len(result) == 2 && !result[1].IsNil() {
- return result[0].Interface(), result[1].Interface().(error)
+ return result[0], result[1].Interface().(error)
}
- return result[0].Interface(), nil
+ return result[0], nil
}
// Boolean logic.
-func truth(a interface{}) bool {
- t, _ := IsTrue(a)
+func truth(arg reflect.Value) bool {
+ t, _ := isTrue(indirectInterface(arg))
return t
}
// and computes the Boolean AND of its arguments, returning
// the first false argument it encounters, or the last argument.
-func and(arg0 interface{}, args ...interface{}) interface{} {
+func and(arg0 reflect.Value, args ...reflect.Value) reflect.Value {
if !truth(arg0) {
return arg0
}
@@ -286,7 +292,7 @@ func and(arg0 interface{}, args ...interface{}) interface{} {
// or computes the Boolean OR of its arguments, returning
// the first true argument it encounters, or the last argument.
-func or(arg0 interface{}, args ...interface{}) interface{} {
+func or(arg0 reflect.Value, args ...reflect.Value) reflect.Value {
if truth(arg0) {
return arg0
}
@@ -300,7 +306,7 @@ func or(arg0 interface{}, args ...interface{}) interface{} {
}
// not returns the Boolean negation of its argument.
-func not(arg interface{}) bool {
+func not(arg reflect.Value) bool {
return !truth(arg)
}
@@ -345,8 +351,8 @@ func basicKind(v reflect.Value) (kind, error) {
}
// eq evaluates the comparison a == b || a == c || ...
-func eq(arg1 interface{}, arg2 ...interface{}) (bool, error) {
- v1 := reflect.ValueOf(arg1)
+func eq(arg1 reflect.Value, arg2 ...reflect.Value) (bool, error) {
+ v1 := indirectInterface(arg1)
k1, err := basicKind(v1)
if err != nil {
return false, err
@@ -355,7 +361,7 @@ func eq(arg1 interface{}, arg2 ...interface{}) (bool, error) {
return false, errNoComparison
}
for _, arg := range arg2 {
- v2 := reflect.ValueOf(arg)
+ v2 := indirectInterface(arg)
k2, err := basicKind(v2)
if err != nil {
return false, err
@@ -397,20 +403,20 @@ func eq(arg1 interface{}, arg2 ...interface{}) (bool, error) {
}
// ne evaluates the comparison a != b.
-func ne(arg1, arg2 interface{}) (bool, error) {
+func ne(arg1, arg2 reflect.Value) (bool, error) {
// != is the inverse of ==.
equal, err := eq(arg1, arg2)
return !equal, err
}
// lt evaluates the comparison a < b.
-func lt(arg1, arg2 interface{}) (bool, error) {
- v1 := reflect.ValueOf(arg1)
+func lt(arg1, arg2 reflect.Value) (bool, error) {
+ v1 := indirectInterface(arg1)
k1, err := basicKind(v1)
if err != nil {
return false, err
}
- v2 := reflect.ValueOf(arg2)
+ v2 := indirectInterface(arg2)
k2, err := basicKind(v2)
if err != nil {
return false, err
@@ -446,7 +452,7 @@ func lt(arg1, arg2 interface{}) (bool, error) {
}
// le evaluates the comparison <= b.
-func le(arg1, arg2 interface{}) (bool, error) {
+func le(arg1, arg2 reflect.Value) (bool, error) {
// <= is < or ==.
lessThan, err := lt(arg1, arg2)
if lessThan || err != nil {
@@ -456,7 +462,7 @@ func le(arg1, arg2 interface{}) (bool, error) {
}
// gt evaluates the comparison a > b.
-func gt(arg1, arg2 interface{}) (bool, error) {
+func gt(arg1, arg2 reflect.Value) (bool, error) {
// > is the inverse of <=.
lessOrEqual, err := le(arg1, arg2)
if err != nil {
@@ -466,7 +472,7 @@ func gt(arg1, arg2 interface{}) (bool, error) {
}
// ge evaluates the comparison a >= b.
-func ge(arg1, arg2 interface{}) (bool, error) {
+func ge(arg1, arg2 reflect.Value) (bool, error) {
// >= is the inverse of <.
lessThan, err := lt(arg1, arg2)
if err != nil {
diff --git a/libgo/go/text/template/multi_test.go b/libgo/go/text/template/multi_test.go
index c8723cb7a8b..8142f008fdf 100644
--- a/libgo/go/text/template/multi_test.go
+++ b/libgo/go/text/template/multi_test.go
@@ -349,3 +349,39 @@ func TestParse(t *testing.T) {
t.Fatalf("parsing test: %s", err)
}
}
+
+func TestEmptyTemplate(t *testing.T) {
+ cases := []struct {
+ defn []string
+ in string
+ want string
+ }{
+ {[]string{""}, "once", ""},
+ {[]string{"", ""}, "twice", ""},
+ {[]string{"{{.}}", "{{.}}"}, "twice", "twice"},
+ {[]string{"{{/* a comment */}}", "{{/* a comment */}}"}, "comment", ""},
+ {[]string{"{{.}}", ""}, "twice", ""},
+ }
+
+ for _, c := range cases {
+ root := New("root")
+
+ var (
+ m *Template
+ err error
+ )
+ for _, d := range c.defn {
+ m, err = root.New(c.in).Parse(d)
+ if err != nil {
+ t.Fatal(err)
+ }
+ }
+ buf := &bytes.Buffer{}
+ if err := m.Execute(buf, c.in); err != nil {
+ t.Fatal(err)
+ }
+ if buf.String() != c.want {
+ t.Errorf("expected string %q: got %q", c.want, buf.String())
+ }
+ }
+}
diff --git a/libgo/go/text/template/parse/lex.go b/libgo/go/text/template/parse/lex.go
index 079c0ea6f71..6fbf36d7a4a 100644
--- a/libgo/go/text/template/parse/lex.go
+++ b/libgo/go/text/template/parse/lex.go
@@ -13,9 +13,10 @@ import (
// item represents a token or text string returned from the scanner.
type item struct {
- typ itemType // The type of this item.
- pos Pos // The starting position, in bytes, of this item in the input string.
- val string // The value of this item.
+ typ itemType // The type of this item.
+ pos Pos // The starting position, in bytes, of this item in the input string.
+ val string // The value of this item.
+ line int // The line number at the start of this item.
}
func (i item) String() string {
@@ -116,6 +117,7 @@ type lexer struct {
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
}
// next returns the next rune in the input.
@@ -127,6 +129,9 @@ func (l *lexer) next() rune {
r, w := utf8.DecodeRuneInString(l.input[l.pos:])
l.width = Pos(w)
l.pos += l.width
+ if r == '\n' {
+ l.line++
+ }
return r
}
@@ -140,11 +145,20 @@ func (l *lexer) peek() rune {
// backup steps back one rune. Can only be called once per call of next.
func (l *lexer) backup() {
l.pos -= l.width
+ // Correct newline count.
+ if l.width == 1 && l.input[l.pos] == '\n' {
+ l.line--
+ }
}
// emit passes an item back to the client.
func (l *lexer) emit(t itemType) {
- l.items <- item{t, l.start, l.input[l.start:l.pos]}
+ l.items <- item{t, l.start, l.input[l.start:l.pos], l.line}
+ // Some items contain text internally. If so, count their newlines.
+ switch t {
+ case itemText, itemRawString, itemLeftDelim, itemRightDelim:
+ l.line += strings.Count(l.input[l.start:l.pos], "\n")
+ }
l.start = l.pos
}
@@ -169,17 +183,10 @@ func (l *lexer) acceptRun(valid string) {
l.backup()
}
-// lineNumber reports which line we're on, based on the position of
-// the previous item returned by nextItem. Doing it this way
-// means we don't have to worry about peek double counting.
-func (l *lexer) lineNumber() int {
- return 1 + strings.Count(l.input[:l.lastPos], "\n")
-}
-
// errorf returns an error token and terminates the scan by passing
// back a nil pointer that will be the next state, terminating l.nextItem.
func (l *lexer) errorf(format string, args ...interface{}) stateFn {
- l.items <- item{itemError, l.start, fmt.Sprintf(format, args...)}
+ l.items <- item{itemError, l.start, fmt.Sprintf(format, args...), l.line}
return nil
}
@@ -212,6 +219,7 @@ func lex(name, input, left, right string) *lexer {
leftDelim: left,
rightDelim: right,
items: make(chan item),
+ line: 1,
}
go l.run()
return l
@@ -236,24 +244,23 @@ const (
// lexText scans until an opening action delimiter, "{{".
func lexText(l *lexer) stateFn {
- for {
- delim, trimSpace := l.atLeftDelim()
- if delim {
- trimLength := Pos(0)
- if trimSpace {
- trimLength = rightTrimLength(l.input[l.start:l.pos])
- }
- l.pos -= trimLength
- if l.pos > l.start {
- l.emit(itemText)
- }
- l.pos += trimLength
- l.ignore()
- return lexLeftDelim
+ l.width = 0
+ if x := strings.Index(l.input[l.pos:], l.leftDelim); x >= 0 {
+ ldn := Pos(len(l.leftDelim))
+ l.pos += Pos(x)
+ trimLength := Pos(0)
+ if strings.HasPrefix(l.input[l.pos+ldn:], leftTrimMarker) {
+ trimLength = rightTrimLength(l.input[l.start:l.pos])
}
- if l.next() == eof {
- break
+ l.pos -= trimLength
+ if l.pos > l.start {
+ l.emit(itemText)
}
+ l.pos += trimLength
+ l.ignore()
+ return lexLeftDelim
+ } else {
+ l.pos = Pos(len(l.input))
}
// Correctly reached EOF.
if l.pos > l.start {
@@ -263,16 +270,6 @@ func lexText(l *lexer) stateFn {
return nil
}
-// atLeftDelim reports whether the lexer is at a left delimiter, possibly followed by a trim marker.
-func (l *lexer) atLeftDelim() (delim, trimSpaces bool) {
- if !strings.HasPrefix(l.input[l.pos:], l.leftDelim) {
- return false, false
- }
- // The left delim might have the marker afterwards.
- trimSpaces = strings.HasPrefix(l.input[l.pos+Pos(len(l.leftDelim)):], leftTrimMarker)
- return true, trimSpaces
-}
-
// rightTrimLength returns the length of the spaces at the end of the string.
func rightTrimLength(s string) Pos {
return Pos(len(s) - len(strings.TrimRight(s, spaceChars)))
@@ -613,10 +610,14 @@ Loop:
// lexRawQuote scans a raw quoted string.
func lexRawQuote(l *lexer) stateFn {
+ startLine := l.line
Loop:
for {
switch l.next() {
case eof:
+ // Restore line number to location of opening quote.
+ // We will error out so it's ok just to overwrite the field.
+ l.line = startLine
return l.errorf("unterminated raw quoted string")
case '`':
break Loop
diff --git a/libgo/go/text/template/parse/lex_test.go b/libgo/go/text/template/parse/lex_test.go
index e35ebf1a854..d655d788b3b 100644
--- a/libgo/go/text/template/parse/lex_test.go
+++ b/libgo/go/text/template/parse/lex_test.go
@@ -58,39 +58,46 @@ type lexTest struct {
items []item
}
+func mkItem(typ itemType, text string) item {
+ return item{
+ typ: typ,
+ val: text,
+ }
+}
+
var (
- tDot = item{itemDot, 0, "."}
- tBlock = item{itemBlock, 0, "block"}
- tEOF = item{itemEOF, 0, ""}
- tFor = item{itemIdentifier, 0, "for"}
- tLeft = item{itemLeftDelim, 0, "{{"}
- tLpar = item{itemLeftParen, 0, "("}
- tPipe = item{itemPipe, 0, "|"}
- tQuote = item{itemString, 0, `"abc \n\t\" "`}
- tRange = item{itemRange, 0, "range"}
- tRight = item{itemRightDelim, 0, "}}"}
- tRpar = item{itemRightParen, 0, ")"}
- tSpace = item{itemSpace, 0, " "}
+ tDot = mkItem(itemDot, ".")
+ tBlock = mkItem(itemBlock, "block")
+ tEOF = mkItem(itemEOF, "")
+ tFor = mkItem(itemIdentifier, "for")
+ tLeft = mkItem(itemLeftDelim, "{{")
+ tLpar = mkItem(itemLeftParen, "(")
+ tPipe = mkItem(itemPipe, "|")
+ tQuote = mkItem(itemString, `"abc \n\t\" "`)
+ tRange = mkItem(itemRange, "range")
+ tRight = mkItem(itemRightDelim, "}}")
+ tRpar = mkItem(itemRightParen, ")")
+ tSpace = mkItem(itemSpace, " ")
raw = "`" + `abc\n\t\" ` + "`"
rawNL = "`now is{{\n}}the time`" // Contains newline inside raw quote.
- tRawQuote = item{itemRawString, 0, raw}
- tRawQuoteNL = item{itemRawString, 0, rawNL}
+ tRawQuote = mkItem(itemRawString, raw)
+ tRawQuoteNL = mkItem(itemRawString, rawNL)
)
var lexTests = []lexTest{
{"empty", "", []item{tEOF}},
- {"spaces", " \t\n", []item{{itemText, 0, " \t\n"}, tEOF}},
- {"text", `now is the time`, []item{{itemText, 0, "now is the time"}, tEOF}},
+ {"spaces", " \t\n", []item{mkItem(itemText, " \t\n"), tEOF}},
+ {"text", `now is the time`, []item{mkItem(itemText, "now is the time"), tEOF}},
{"text with comment", "hello-{{/* this is a comment */}}-world", []item{
- {itemText, 0, "hello-"},
- {itemText, 0, "-world"},
+ mkItem(itemText, "hello-"),
+ mkItem(itemText, "-world"),
tEOF,
}},
{"punctuation", "{{,@% }}", []item{
tLeft,
- {itemChar, 0, ","},
- {itemChar, 0, "@"},
- {itemChar, 0, "%"},
+ mkItem(itemChar, ","),
+ mkItem(itemChar, "@"),
+ mkItem(itemChar, "%"),
tSpace,
tRight,
tEOF,
@@ -99,7 +106,7 @@ var lexTests = []lexTest{
tLeft,
tLpar,
tLpar,
- {itemNumber, 0, "3"},
+ mkItem(itemNumber, "3"),
tRpar,
tRpar,
tRight,
@@ -108,54 +115,54 @@ var lexTests = []lexTest{
{"empty action", `{{}}`, []item{tLeft, tRight, tEOF}},
{"for", `{{for}}`, []item{tLeft, tFor, tRight, tEOF}},
{"block", `{{block "foo" .}}`, []item{
- tLeft, tBlock, tSpace, {itemString, 0, `"foo"`}, tSpace, tDot, tRight, tEOF,
+ tLeft, tBlock, tSpace, mkItem(itemString, `"foo"`), tSpace, tDot, tRight, tEOF,
}},
{"quote", `{{"abc \n\t\" "}}`, []item{tLeft, tQuote, tRight, tEOF}},
{"raw quote", "{{" + raw + "}}", []item{tLeft, tRawQuote, tRight, tEOF}},
{"raw quote with newline", "{{" + rawNL + "}}", []item{tLeft, tRawQuoteNL, tRight, tEOF}},
{"numbers", "{{1 02 0x14 -7.2i 1e3 +1.2e-4 4.2i 1+2i}}", []item{
tLeft,
- {itemNumber, 0, "1"},
+ mkItem(itemNumber, "1"),
tSpace,
- {itemNumber, 0, "02"},
+ mkItem(itemNumber, "02"),
tSpace,
- {itemNumber, 0, "0x14"},
+ mkItem(itemNumber, "0x14"),
tSpace,
- {itemNumber, 0, "-7.2i"},
+ mkItem(itemNumber, "-7.2i"),
tSpace,
- {itemNumber, 0, "1e3"},
+ mkItem(itemNumber, "1e3"),
tSpace,
- {itemNumber, 0, "+1.2e-4"},
+ mkItem(itemNumber, "+1.2e-4"),
tSpace,
- {itemNumber, 0, "4.2i"},
+ mkItem(itemNumber, "4.2i"),
tSpace,
- {itemComplex, 0, "1+2i"},
+ mkItem(itemComplex, "1+2i"),
tRight,
tEOF,
}},
{"characters", `{{'a' '\n' '\'' '\\' '\u00FF' '\xFF' '本'}}`, []item{
tLeft,
- {itemCharConstant, 0, `'a'`},
+ mkItem(itemCharConstant, `'a'`),
tSpace,
- {itemCharConstant, 0, `'\n'`},
+ mkItem(itemCharConstant, `'\n'`),
tSpace,
- {itemCharConstant, 0, `'\''`},
+ mkItem(itemCharConstant, `'\''`),
tSpace,
- {itemCharConstant, 0, `'\\'`},
+ mkItem(itemCharConstant, `'\\'`),
tSpace,
- {itemCharConstant, 0, `'\u00FF'`},
+ mkItem(itemCharConstant, `'\u00FF'`),
tSpace,
- {itemCharConstant, 0, `'\xFF'`},
+ mkItem(itemCharConstant, `'\xFF'`),
tSpace,
- {itemCharConstant, 0, `'本'`},
+ mkItem(itemCharConstant, `'本'`),
tRight,
tEOF,
}},
{"bools", "{{true false}}", []item{
tLeft,
- {itemBool, 0, "true"},
+ mkItem(itemBool, "true"),
tSpace,
- {itemBool, 0, "false"},
+ mkItem(itemBool, "false"),
tRight,
tEOF,
}},
@@ -167,178 +174,178 @@ var lexTests = []lexTest{
}},
{"nil", "{{nil}}", []item{
tLeft,
- {itemNil, 0, "nil"},
+ mkItem(itemNil, "nil"),
tRight,
tEOF,
}},
{"dots", "{{.x . .2 .x.y.z}}", []item{
tLeft,
- {itemField, 0, ".x"},
+ mkItem(itemField, ".x"),
tSpace,
tDot,
tSpace,
- {itemNumber, 0, ".2"},
+ mkItem(itemNumber, ".2"),
tSpace,
- {itemField, 0, ".x"},
- {itemField, 0, ".y"},
- {itemField, 0, ".z"},
+ mkItem(itemField, ".x"),
+ mkItem(itemField, ".y"),
+ mkItem(itemField, ".z"),
tRight,
tEOF,
}},
{"keywords", "{{range if else end with}}", []item{
tLeft,
- {itemRange, 0, "range"},
+ mkItem(itemRange, "range"),
tSpace,
- {itemIf, 0, "if"},
+ mkItem(itemIf, "if"),
tSpace,
- {itemElse, 0, "else"},
+ mkItem(itemElse, "else"),
tSpace,
- {itemEnd, 0, "end"},
+ mkItem(itemEnd, "end"),
tSpace,
- {itemWith, 0, "with"},
+ mkItem(itemWith, "with"),
tRight,
tEOF,
}},
{"variables", "{{$c := printf $ $hello $23 $ $var.Field .Method}}", []item{
tLeft,
- {itemVariable, 0, "$c"},
+ mkItem(itemVariable, "$c"),
tSpace,
- {itemColonEquals, 0, ":="},
+ mkItem(itemColonEquals, ":="),
tSpace,
- {itemIdentifier, 0, "printf"},
+ mkItem(itemIdentifier, "printf"),
tSpace,
- {itemVariable, 0, "$"},
+ mkItem(itemVariable, "$"),
tSpace,
- {itemVariable, 0, "$hello"},
+ mkItem(itemVariable, "$hello"),
tSpace,
- {itemVariable, 0, "$23"},
+ mkItem(itemVariable, "$23"),
tSpace,
- {itemVariable, 0, "$"},
+ mkItem(itemVariable, "$"),
tSpace,
- {itemVariable, 0, "$var"},
- {itemField, 0, ".Field"},
+ mkItem(itemVariable, "$var"),
+ mkItem(itemField, ".Field"),
tSpace,
- {itemField, 0, ".Method"},
+ mkItem(itemField, ".Method"),
tRight,
tEOF,
}},
{"variable invocation", "{{$x 23}}", []item{
tLeft,
- {itemVariable, 0, "$x"},
+ mkItem(itemVariable, "$x"),
tSpace,
- {itemNumber, 0, "23"},
+ mkItem(itemNumber, "23"),
tRight,
tEOF,
}},
{"pipeline", `intro {{echo hi 1.2 |noargs|args 1 "hi"}} outro`, []item{
- {itemText, 0, "intro "},
+ mkItem(itemText, "intro "),
tLeft,
- {itemIdentifier, 0, "echo"},
+ mkItem(itemIdentifier, "echo"),
tSpace,
- {itemIdentifier, 0, "hi"},
+ mkItem(itemIdentifier, "hi"),
tSpace,
- {itemNumber, 0, "1.2"},
+ mkItem(itemNumber, "1.2"),
tSpace,
tPipe,
- {itemIdentifier, 0, "noargs"},
+ mkItem(itemIdentifier, "noargs"),
tPipe,
- {itemIdentifier, 0, "args"},
+ mkItem(itemIdentifier, "args"),
tSpace,
- {itemNumber, 0, "1"},
+ mkItem(itemNumber, "1"),
tSpace,
- {itemString, 0, `"hi"`},
+ mkItem(itemString, `"hi"`),
tRight,
- {itemText, 0, " outro"},
+ mkItem(itemText, " outro"),
tEOF,
}},
{"declaration", "{{$v := 3}}", []item{
tLeft,
- {itemVariable, 0, "$v"},
+ mkItem(itemVariable, "$v"),
tSpace,
- {itemColonEquals, 0, ":="},
+ mkItem(itemColonEquals, ":="),
tSpace,
- {itemNumber, 0, "3"},
+ mkItem(itemNumber, "3"),
tRight,
tEOF,
}},
{"2 declarations", "{{$v , $w := 3}}", []item{
tLeft,
- {itemVariable, 0, "$v"},
+ mkItem(itemVariable, "$v"),
tSpace,
- {itemChar, 0, ","},
+ mkItem(itemChar, ","),
tSpace,
- {itemVariable, 0, "$w"},
+ mkItem(itemVariable, "$w"),
tSpace,
- {itemColonEquals, 0, ":="},
+ mkItem(itemColonEquals, ":="),
tSpace,
- {itemNumber, 0, "3"},
+ mkItem(itemNumber, "3"),
tRight,
tEOF,
}},
{"field of parenthesized expression", "{{(.X).Y}}", []item{
tLeft,
tLpar,
- {itemField, 0, ".X"},
+ mkItem(itemField, ".X"),
tRpar,
- {itemField, 0, ".Y"},
+ mkItem(itemField, ".Y"),
tRight,
tEOF,
}},
{"trimming spaces before and after", "hello- {{- 3 -}} -world", []item{
- {itemText, 0, "hello-"},
+ mkItem(itemText, "hello-"),
tLeft,
- {itemNumber, 0, "3"},
+ mkItem(itemNumber, "3"),
tRight,
- {itemText, 0, "-world"},
+ mkItem(itemText, "-world"),
tEOF,
}},
{"trimming spaces before and after comment", "hello- {{- /* hello */ -}} -world", []item{
- {itemText, 0, "hello-"},
- {itemText, 0, "-world"},
+ mkItem(itemText, "hello-"),
+ mkItem(itemText, "-world"),
tEOF,
}},
// errors
{"badchar", "#{{\x01}}", []item{
- {itemText, 0, "#"},
+ mkItem(itemText, "#"),
tLeft,
- {itemError, 0, "unrecognized character in action: U+0001"},
+ mkItem(itemError, "unrecognized character in action: U+0001"),
}},
{"unclosed action", "{{\n}}", []item{
tLeft,
- {itemError, 0, "unclosed action"},
+ mkItem(itemError, "unclosed action"),
}},
{"EOF in action", "{{range", []item{
tLeft,
tRange,
- {itemError, 0, "unclosed action"},
+ mkItem(itemError, "unclosed action"),
}},
{"unclosed quote", "{{\"\n\"}}", []item{
tLeft,
- {itemError, 0, "unterminated quoted string"},
+ mkItem(itemError, "unterminated quoted string"),
}},
{"unclosed raw quote", "{{`xx}}", []item{
tLeft,
- {itemError, 0, "unterminated raw quoted string"},
+ mkItem(itemError, "unterminated raw quoted string"),
}},
{"unclosed char constant", "{{'\n}}", []item{
tLeft,
- {itemError, 0, "unterminated character constant"},
+ mkItem(itemError, "unterminated character constant"),
}},
{"bad number", "{{3k}}", []item{
tLeft,
- {itemError, 0, `bad number syntax: "3k"`},
+ mkItem(itemError, `bad number syntax: "3k"`),
}},
{"unclosed paren", "{{(3}}", []item{
tLeft,
tLpar,
- {itemNumber, 0, "3"},
- {itemError, 0, `unclosed left paren`},
+ mkItem(itemNumber, "3"),
+ mkItem(itemError, `unclosed left paren`),
}},
{"extra right paren", "{{3)}}", []item{
tLeft,
- {itemNumber, 0, "3"},
+ mkItem(itemNumber, "3"),
tRpar,
- {itemError, 0, `unexpected right paren U+0029 ')'`},
+ mkItem(itemError, `unexpected right paren U+0029 ')'`),
}},
// Fixed bugs
@@ -355,17 +362,17 @@ var lexTests = []lexTest{
tEOF,
}},
{"text with bad comment", "hello-{{/*/}}-world", []item{
- {itemText, 0, "hello-"},
- {itemError, 0, `unclosed comment`},
+ mkItem(itemText, "hello-"),
+ mkItem(itemError, `unclosed comment`),
}},
{"text with comment close separated from delim", "hello-{{/* */ }}-world", []item{
- {itemText, 0, "hello-"},
- {itemError, 0, `comment ends before closing delimiter`},
+ mkItem(itemText, "hello-"),
+ mkItem(itemError, `comment ends before closing delimiter`),
}},
// This one is an error that we can't catch because it breaks templates with
// minimized JavaScript. Should have fixed it before Go 1.1.
{"unmatched right delimiter", "hello-{.}}-world", []item{
- {itemText, 0, "hello-{.}}-world"},
+ mkItem(itemText, "hello-{.}}-world"),
tEOF,
}},
}
@@ -414,13 +421,13 @@ func TestLex(t *testing.T) {
var lexDelimTests = []lexTest{
{"punctuation", "$$,@%{{}}@@", []item{
tLeftDelim,
- {itemChar, 0, ","},
- {itemChar, 0, "@"},
- {itemChar, 0, "%"},
- {itemChar, 0, "{"},
- {itemChar, 0, "{"},
- {itemChar, 0, "}"},
- {itemChar, 0, "}"},
+ mkItem(itemChar, ","),
+ mkItem(itemChar, "@"),
+ mkItem(itemChar, "%"),
+ mkItem(itemChar, "{"),
+ mkItem(itemChar, "{"),
+ mkItem(itemChar, "}"),
+ mkItem(itemChar, "}"),
tRightDelim,
tEOF,
}},
@@ -431,8 +438,8 @@ var lexDelimTests = []lexTest{
}
var (
- tLeftDelim = item{itemLeftDelim, 0, "$$"}
- tRightDelim = item{itemRightDelim, 0, "@@"}
+ tLeftDelim = mkItem(itemLeftDelim, "$$")
+ tRightDelim = mkItem(itemRightDelim, "@@")
)
func TestDelims(t *testing.T) {
@@ -447,21 +454,21 @@ func TestDelims(t *testing.T) {
var lexPosTests = []lexTest{
{"empty", "", []item{tEOF}},
{"punctuation", "{{,@%#}}", []item{
- {itemLeftDelim, 0, "{{"},
- {itemChar, 2, ","},
- {itemChar, 3, "@"},
- {itemChar, 4, "%"},
- {itemChar, 5, "#"},
- {itemRightDelim, 6, "}}"},
- {itemEOF, 8, ""},
+ {itemLeftDelim, 0, "{{", 1},
+ {itemChar, 2, ",", 1},
+ {itemChar, 3, "@", 1},
+ {itemChar, 4, "%", 1},
+ {itemChar, 5, "#", 1},
+ {itemRightDelim, 6, "}}", 1},
+ {itemEOF, 8, "", 1},
}},
{"sample", "0123{{hello}}xyz", []item{
- {itemText, 0, "0123"},
- {itemLeftDelim, 4, "{{"},
- {itemIdentifier, 6, "hello"},
- {itemRightDelim, 11, "}}"},
- {itemText, 13, "xyz"},
- {itemEOF, 16, ""},
+ {itemText, 0, "0123", 1},
+ {itemLeftDelim, 4, "{{", 1},
+ {itemIdentifier, 6, "hello", 1},
+ {itemRightDelim, 11, "}}", 1},
+ {itemText, 13, "xyz", 1},
+ {itemEOF, 16, "", 1},
}},
}
diff --git a/libgo/go/text/template/parse/parse.go b/libgo/go/text/template/parse/parse.go
index 86705e5a379..6060c6d74b7 100644
--- a/libgo/go/text/template/parse/parse.go
+++ b/libgo/go/text/template/parse/parse.go
@@ -157,7 +157,7 @@ func (t *Tree) ErrorContext(n Node) (location, context string) {
// errorf formats the error and terminates processing.
func (t *Tree) errorf(format string, args ...interface{}) {
t.Root = nil
- format = fmt.Sprintf("template: %s:%d: %s", t.ParseName, t.lex.lineNumber(), format)
+ format = fmt.Sprintf("template: %s:%d: %s", t.ParseName, t.token[0].line, format)
panic(fmt.Errorf(format, args...))
}
@@ -277,7 +277,7 @@ func IsEmptyTree(n Node) bool {
// parse is the top-level parser for a template, essentially the same
// as itemList except it also parses {{define}} actions.
// It runs to EOF.
-func (t *Tree) parse() (next Node) {
+func (t *Tree) parse() {
t.Root = t.newList(t.peek().pos)
for t.peek().typ != itemEOF {
if t.peek().typ == itemLeftDelim {
@@ -299,7 +299,6 @@ func (t *Tree) parse() (next Node) {
t.Root.append(n)
}
}
- return nil
}
// parseDefinition parses a {{define}} ... {{end}} template definition and
@@ -377,15 +376,17 @@ func (t *Tree) action() (n Node) {
return t.withControl()
}
t.backup()
+ token := t.peek()
// Do not pop variables; they persist until "end".
- return t.newAction(t.peek().pos, t.lex.lineNumber(), t.pipeline("command"))
+ return t.newAction(token.pos, token.line, t.pipeline("command"))
}
// Pipeline:
// declarations? command ('|' command)*
func (t *Tree) pipeline(context string) (pipe *PipeNode) {
var decl []*VariableNode
- pos := t.peekNonSpace().pos
+ token := t.peekNonSpace()
+ pos := token.pos
// Are there declarations?
for {
if v := t.peekNonSpace(); v.typ == itemVariable {
@@ -414,7 +415,7 @@ func (t *Tree) pipeline(context string) (pipe *PipeNode) {
}
break
}
- pipe = t.newPipeline(pos, t.lex.lineNumber(), decl)
+ pipe = t.newPipeline(pos, token.line, decl)
for {
switch token := t.nextNonSpace(); token.typ {
case itemRightDelim, itemRightParen:
@@ -451,7 +452,6 @@ func (t *Tree) checkPipeline(pipe *PipeNode, context string) {
func (t *Tree) parseControl(allowElseIf bool, context string) (pos Pos, line int, pipe *PipeNode, list, elseList *ListNode) {
defer t.popVars(len(t.vars))
- line = t.lex.lineNumber()
pipe = t.pipeline(context)
var next Node
list, next = t.itemList()
@@ -480,7 +480,7 @@ func (t *Tree) parseControl(allowElseIf bool, context string) (pos Pos, line int
t.errorf("expected end; found %s", next)
}
}
- return pipe.Position(), line, pipe, list, elseList
+ return pipe.Position(), pipe.Line, pipe, list, elseList
}
// If:
@@ -522,9 +522,10 @@ func (t *Tree) elseControl() Node {
peek := t.peekNonSpace()
if peek.typ == itemIf {
// We see "{{else if ... " but in effect rewrite it to {{else}}{{if ... ".
- return t.newElse(peek.pos, t.lex.lineNumber())
+ return t.newElse(peek.pos, peek.line)
}
- return t.newElse(t.expect(itemRightDelim, "else").pos, t.lex.lineNumber())
+ token := t.expect(itemRightDelim, "else")
+ return t.newElse(token.pos, token.line)
}
// Block:
@@ -551,7 +552,7 @@ func (t *Tree) blockControl() Node {
block.add()
block.stopParse()
- return t.newTemplate(token.pos, t.lex.lineNumber(), name, pipe)
+ return t.newTemplate(token.pos, token.line, name, pipe)
}
// Template:
@@ -568,7 +569,7 @@ func (t *Tree) templateControl() Node {
// Do not pop variables; they persist until "end".
pipe = t.pipeline(context)
}
- return t.newTemplate(token.pos, t.lex.lineNumber(), name, pipe)
+ return t.newTemplate(token.pos, token.line, name, pipe)
}
func (t *Tree) parseTemplateName(token item, context string) (name string) {
diff --git a/libgo/go/text/template/parse/parse_test.go b/libgo/go/text/template/parse/parse_test.go
index 9d856bcb3df..81f14aca986 100644
--- a/libgo/go/text/template/parse/parse_test.go
+++ b/libgo/go/text/template/parse/parse_test.go
@@ -484,3 +484,37 @@ func TestBlock(t *testing.T) {
t.Errorf("inner template = %q, want %q", g, w)
}
}
+
+func TestLineNum(t *testing.T) {
+ const count = 100
+ text := strings.Repeat("{{printf 1234}}\n", count)
+ tree, err := New("bench").Parse(text, "", "", make(map[string]*Tree), builtins)
+ if err != nil {
+ t.Fatal(err)
+ }
+ // Check the line numbers. Each line is an action containing a template, followed by text.
+ // That's two nodes per line.
+ nodes := tree.Root.Nodes
+ for i := 0; i < len(nodes); i += 2 {
+ line := 1 + i/2
+ // Action first.
+ action := nodes[i].(*ActionNode)
+ if action.Line != line {
+ t.Fatalf("line %d: action is line %d", line, action.Line)
+ }
+ pipe := action.Pipe
+ if pipe.Line != line {
+ t.Fatalf("line %d: pipe is line %d", line, pipe.Line)
+ }
+ }
+}
+
+func BenchmarkParseLarge(b *testing.B) {
+ text := strings.Repeat("{{1234}}\n", 10000)
+ for i := 0; i < b.N; i++ {
+ _, err := New("bench").Parse(text, "", "", make(map[string]*Tree), builtins)
+ if err != nil {
+ b.Fatal(err)
+ }
+ }
+}
diff --git a/libgo/go/text/template/template.go b/libgo/go/text/template/template.go
index 7a7f42a7153..b6fceb1795c 100644
--- a/libgo/go/text/template/template.go
+++ b/libgo/go/text/template/template.go
@@ -181,9 +181,16 @@ func (t *Template) Lookup(name string) *Template {
return t.tmpl[name]
}
-// Parse defines the template by parsing the text. Nested template definitions will be
-// associated with the top-level template t. Parse may be called multiple times
-// to parse definitions of templates to associate with t.
+// Parse parses text as a template body for t.
+// Named template definitions ({{define ...}} or {{block ...}} statements) in text
+// define additional templates associated with t and are removed from the
+// definition of t itself.
+//
+// Templates can be redefined in successive calls to Parse.
+// A template definition with a body containing only white space and comments
+// is considered empty and will not replace an existing template's body.
+// This allows using Parse to add new named template definitions without
+// overwriting the main template body.
func (t *Template) Parse(text string) (*Template, error) {
t.init()
t.muFuncs.RLock()
@@ -208,7 +215,7 @@ func (t *Template) associate(new *Template, tree *parse.Tree) (bool, error) {
if new.common != t.common {
panic("internal error: associate not common")
}
- if t.tmpl[new.name] != nil && parse.IsEmptyTree(tree.Root) {
+ if t.tmpl[new.name] != nil && parse.IsEmptyTree(tree.Root) && t.Tree != nil {
// If a template by that name exists,
// don't replace it with an empty template.
return false, nil