diff options
| author | Rob Pike <r@golang.org> | 2012-09-24 13:23:15 +1000 |
|---|---|---|
| committer | Rob Pike <r@golang.org> | 2012-09-24 13:23:15 +1000 |
| commit | 9050550c12e2d09cf8f0c22a270cfa90120cdf6d (patch) | |
| tree | 014cb848c742805de43d7d3f5293e830af2baef6 /src/pkg/text/template/exec.go | |
| parent | edce6349639e321c3b1a34036a8fbc08ad363cd3 (diff) | |
| download | go-9050550c12e2d09cf8f0c22a270cfa90120cdf6d.tar.xz | |
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
Diffstat (limited to 'src/pkg/text/template/exec.go')
| -rw-r--r-- | src/pkg/text/template/exec.go | 19 |
1 files changed, 17 insertions, 2 deletions
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) { |
