diff options
Diffstat (limited to 'libgo/go/text')
-rw-r--r-- | libgo/go/text/tabwriter/tabwriter.go | 1 | ||||
-rw-r--r-- | libgo/go/text/template/exec.go | 47 | ||||
-rw-r--r-- | libgo/go/text/template/exec_test.go | 106 | ||||
-rw-r--r-- | libgo/go/text/template/funcs.go | 80 | ||||
-rw-r--r-- | libgo/go/text/template/multi_test.go | 36 | ||||
-rw-r--r-- | libgo/go/text/template/parse/lex.go | 77 | ||||
-rw-r--r-- | libgo/go/text/template/parse/lex_test.go | 259 | ||||
-rw-r--r-- | libgo/go/text/template/parse/parse.go | 25 | ||||
-rw-r--r-- | libgo/go/text/template/parse/parse_test.go | 34 | ||||
-rw-r--r-- | libgo/go/text/template/template.go | 15 |
10 files changed, 456 insertions, 224 deletions
diff --git a/libgo/go/text/tabwriter/tabwriter.go b/libgo/go/text/tabwriter/tabwriter.go index 796e1e8..752c9b8 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 c7c6d50..89d3e37 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 3ef065e..5892b27 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 cd0b82b..3047b27 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 c8723cb..8142f00 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 079c0ea..6fbf36d 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 e35ebf1..d655d78 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 86705e5..6060c6d 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 9d856bc..81f14ac 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 7a7f42a..b6fceb1 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 |