1
0
mirror of https://github.com/golang/go synced 2024-11-25 16:07:56 -07:00

exp/template: delete upward evaluation.

It was an ill-advised carryover from the previous template package.
Also clean up function evaluation.
Also add a Name method to Template.

R=golang-dev, gri
CC=golang-dev
https://golang.org/cl/4657088
This commit is contained in:
Rob Pike 2011-07-09 08:59:56 +10:00
parent d3d08e1e3a
commit bbf5eb5ba2
3 changed files with 47 additions and 70 deletions

View File

@ -22,21 +22,6 @@ type state struct {
wr io.Writer wr io.Writer
set *Set set *Set
line int // line number for errors line int // line number for errors
// parent holds the state for the surrounding data object,
// typically the structure containing the field we are evaluating.
parent struct {
state *state
data reflect.Value
}
}
// down returns a new state representing a child of the current state.
// data represents the parent of the child.
func (s *state) down(data reflect.Value) *state {
var child = *s
child.parent.state = s
child.parent.data = data
return &child
} }
var zero reflect.Value var zero reflect.Value
@ -118,7 +103,7 @@ func (s *state) walkIfOrWith(typ nodeType, data reflect.Value, pipe *pipeNode, l
} }
if truth { if truth {
if typ == nodeWith { if typ == nodeWith {
s.down(data).walk(val, list) s.walk(val, list)
} else { } else {
s.walk(data, list) s.walk(data, list)
} }
@ -153,14 +138,13 @@ func isTrue(val reflect.Value) (truth, ok bool) {
func (s *state) walkRange(data reflect.Value, r *rangeNode) { func (s *state) walkRange(data reflect.Value, r *rangeNode) {
val := s.evalPipeline(data, r.pipe) val := s.evalPipeline(data, r.pipe)
down := s.down(data)
switch val.Kind() { switch val.Kind() {
case reflect.Array, reflect.Slice: case reflect.Array, reflect.Slice:
if val.Len() == 0 { if val.Len() == 0 {
break break
} }
for i := 0; i < val.Len(); i++ { for i := 0; i < val.Len(); i++ {
down.walk(val.Index(i), r.list) s.walk(val.Index(i), r.list)
} }
return return
case reflect.Map: case reflect.Map:
@ -168,7 +152,7 @@ func (s *state) walkRange(data reflect.Value, r *rangeNode) {
break break
} }
for _, key := range val.MapKeys() { for _, key := range val.MapKeys() {
down.walk(val.MapIndex(key), r.list) s.walk(val.MapIndex(key), r.list)
} }
return return
default: default:
@ -216,7 +200,8 @@ func (s *state) evalCommand(data reflect.Value, cmd *commandNode, final reflect.
case *fieldNode: case *fieldNode:
return s.evalFieldNode(data, n, cmd.args, final) return s.evalFieldNode(data, n, cmd.args, final)
case *identifierNode: case *identifierNode:
return s.evalField(data, n.ident, cmd.args, final, true, true) // Must be a function.
return s.evalFunction(data, n.ident, cmd.args, final)
} }
if len(cmd.args) > 1 || final.IsValid() { if len(cmd.args) > 1 || final.IsValid() {
s.errorf("can't give argument to non-function %s", cmd.args[0]) s.errorf("can't give argument to non-function %s", cmd.args[0])
@ -251,10 +236,18 @@ func (s *state) evalFieldNode(data reflect.Value, field *fieldNode, args []node,
// Up to the last entry, it must be a field. // Up to the last entry, it must be a field.
n := len(field.ident) n := len(field.ident)
for i := 0; i < n-1; i++ { for i := 0; i < n-1; i++ {
data = s.evalField(data, field.ident[i], nil, zero, i == 0, false) data = s.evalField(data, field.ident[i], nil, zero, false)
} }
// Now it can be a field or method and if a method, gets arguments. // Now it can be a field or method and if a method, gets arguments.
return s.evalField(data, field.ident[n-1], args, final, len(field.ident) == 1, true) return s.evalField(data, field.ident[n-1], args, final, true)
}
func (s *state) evalFunction(data reflect.Value, name string, args []node, final reflect.Value) reflect.Value {
function, ok := findFunction(name, s.tmpl, s.set)
if !ok {
s.errorf("%q is not a defined function", name)
}
return s.evalCall(data, function, name, false, args, final)
} }
// Is this an exported - upper case - name? // Is this an exported - upper case - name?
@ -268,17 +261,9 @@ func isExported(name string) bool {
// value of the pipeline, if any. // value of the pipeline, if any.
// If we're in a chain, such as (.X.Y.Z), .X and .Y cannot be methods; // If we're in a chain, such as (.X.Y.Z), .X and .Y cannot be methods;
// canBeMethod will be true only for the last element of such chains (here .Z). // canBeMethod will be true only for the last element of such chains (here .Z).
// The isFirst argument tells whether this is the first element of a chain (here .X).
// If true, evaluation is allowed to examine the parent to resolve the reference.
func (s *state) evalField(data reflect.Value, fieldName string, args []node, final reflect.Value, func (s *state) evalField(data reflect.Value, fieldName string, args []node, final reflect.Value,
isFirst, canBeMethod bool) reflect.Value { canBeMethod bool) reflect.Value {
topState, topData := s, data // Remember initial state for diagnostics. typ := data.Type()
// Is it a function?
if function, ok := findFunction(fieldName, s.tmpl, s.set); ok {
return s.evalCall(data, function, fieldName, false, args, final)
}
// Look for methods and fields at this level, and then in the parent.
for s != nil {
var isNil bool var isNil bool
data, isNil = indirect(data) data, isNil = indirect(data)
if canBeMethod { if canBeMethod {
@ -304,17 +289,10 @@ isFirst, canBeMethod bool) reflect.Value {
} }
} }
} }
if !isFirst {
// We check for nil pointers only if there's no possibility of resolution
// in the parent.
if isNil { if isNil {
s.errorf("nil pointer evaluating %s.%s", topData.Type(), fieldName) s.errorf("nil pointer evaluating %s.%s", typ, fieldName)
} }
break s.errorf("can't handle evaluation of field %s in type %s", fieldName, typ)
}
s, data = s.parent.state, s.parent.data
}
topState.errorf("can't handle evaluation of field %s in type %s", fieldName, topData.Type())
panic("not reached") panic("not reached")
} }
@ -489,7 +467,7 @@ func (s *state) evalEmptyInterface(data reflect.Value, typ reflect.Type, n node)
case *fieldNode: case *fieldNode:
return s.evalFieldNode(data, n, nil, zero) return s.evalFieldNode(data, n, nil, zero)
case *identifierNode: case *identifierNode:
return s.evalField(data, n.ident, nil, zero, false, true) return s.evalFunction(data, n.ident, nil, zero)
case *numberNode: case *numberNode:
if n.isComplex { if n.isComplex {
return reflect.ValueOf(n.complex128) return reflect.ValueOf(n.complex128)

View File

@ -249,12 +249,6 @@ var execTests = []execTest{
{"with map", "{{with .MSIone}}{{.}}{{else}}EMPTY{{end}}", "map[one:1]", tVal, true}, {"with map", "{{with .MSIone}}{{.}}{{else}}EMPTY{{end}}", "map[one:1]", tVal, true},
{"with empty interface, struct field", "{{with .Empty4}}{{.V}}{{end}}", "v", tVal, true}, {"with empty interface, struct field", "{{with .Empty4}}{{.V}}{{end}}", "v", tVal, true},
// Fields and methods in parent struct.
{"with .U, get .I", "{{with .U}}{{.I}}{{end}}", "17", tVal, true},
{"with .U, do .Method0", "{{with .U}}{{.Method0}}{{end}}", "M0", tVal, true},
{"range .SI .I", "{{range .SI}}<{{.I}}>{{end}}", "<17><17><17>", tVal, true},
{"range .SI .Method0", "{{range .SI}}{{.Method0}}{{end}}", "M0M0M0", tVal, true},
// Range. // Range.
{"range []int", "{{range .SI}}-{{.}}-{{end}}", "-3--4--5-", tVal, true}, {"range []int", "{{range .SI}}-{{.}}-{{end}}", "-3--4--5-", tVal, true},
{"range empty no else", "{{range .SIEmpty}}-{{.}}-{{end}}", "", tVal, true}, {"range empty no else", "{{range .SIEmpty}}-{{.}}-{{end}}", "", tVal, true},

View File

@ -27,6 +27,11 @@ type Template struct {
peekCount int peekCount int
} }
// Name returns the name of the template.
func (t *Template) Name() string {
return t.name
}
// next returns the next token. // next returns the next token.
func (t *Template) next() item { func (t *Template) next() item {
if t.peekCount > 0 { if t.peekCount > 0 {