From 9050550c12e2d09cf8f0c22a270cfa90120cdf6d Mon Sep 17 00:00:00 2001 From: Rob Pike Date: Mon, 24 Sep 2012 13:23:15 +1000 Subject: text/template: allow .Field access to parenthesized expressions Change the grammar so that field access is a proper operator. This introduces a new node, ChainNode, into the public (but actually internal) API of text/template/parse. For compatibility, we only use the new node type for the specific construct, which was not parseable before. Therefore this should be backward-compatible. Before, .X.Y was a token in the lexer; this CL breaks it out into .Y applied to .X. But for compatibility we mush them back together before delivering. One day we might remove that hack; it's the simple TODO in parse.go/operand. This change also provides grammatical distinction between f and (f) which might permit function values later, but not now. Fixes #3999. R=golang-dev, dsymonds, gri, rsc, mikesamuel CC=golang-dev https://golang.org/cl/6494119 --- src/pkg/text/template/exec.go | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) (limited to 'src/pkg/text/template/exec.go') diff --git a/src/pkg/text/template/exec.go b/src/pkg/text/template/exec.go index 1739a86179..5e127d7db4 100644 --- a/src/pkg/text/template/exec.go +++ b/src/pkg/text/template/exec.go @@ -315,9 +315,15 @@ func (s *state) evalCommand(dot reflect.Value, cmd *parse.CommandNode, final ref switch n := firstWord.(type) { case *parse.FieldNode: return s.evalFieldNode(dot, n, cmd.Args, final) + case *parse.ChainNode: + return s.evalChainNode(dot, n, cmd.Args, final) case *parse.IdentifierNode: // Must be a function. return s.evalFunction(dot, n.Ident, cmd.Args, final) + case *parse.PipeNode: + // Parenthesized pipeline. The arguments are all inside the pipeline; final is ignored. + // TODO: is this right? + return s.evalPipeline(dot, n) case *parse.VariableNode: return s.evalVariableNode(dot, n, cmd.Args, final) } @@ -367,6 +373,15 @@ func (s *state) evalFieldNode(dot reflect.Value, field *parse.FieldNode, args [] return s.evalFieldChain(dot, dot, field.Ident, args, final) } +func (s *state) evalChainNode(dot reflect.Value, chain *parse.ChainNode, args []parse.Node, final reflect.Value) reflect.Value { + // (pipe).Field1.Field2 has pipe as .Node, fields as .Field. Eval the pipeline, then the fields. + pipe := s.evalArg(dot, nil, chain.Node) + if len(chain.Field) == 0 { + s.errorf("internal error: no fields in evalChainNode") + } + return s.evalFieldChain(dot, pipe, chain.Field, args, final) +} + func (s *state) evalVariableNode(dot reflect.Value, v *parse.VariableNode, args []parse.Node, final reflect.Value) reflect.Value { // $x.Field has $x as the first ident, Field as the second. Eval the var, then the fields. value := s.varValue(v.Ident[0]) @@ -521,13 +536,13 @@ func canBeNil(typ reflect.Type) bool { // validateType guarantees that the value is valid and assignable to the type. func (s *state) validateType(value reflect.Value, typ reflect.Type) reflect.Value { if !value.IsValid() { - if canBeNil(typ) { + if typ == nil || canBeNil(typ) { // An untyped nil interface{}. Accept as a proper nil value. return reflect.Zero(typ) } s.errorf("invalid value; expected %s", typ) } - if !value.Type().AssignableTo(typ) { + if typ != nil && !value.Type().AssignableTo(typ) { if value.Kind() == reflect.Interface && !value.IsNil() { value = value.Elem() if value.Type().AssignableTo(typ) { -- cgit v1.3-5-g9baa