any type) or two return values, the second of which is an os.Error.
If it has two and the returned error is non-nil, execution terminates
and an error is returned to the caller as the value of Execute.
- Method invocations may be chained, but only the last element of
- the chain may be a method; others must be struct fields:
- .Field1.Field2.Method
+ Method invocations may be chained and combined with fields
+ to any depth:
+ .Field1.Method1.Field2.Method2
Methods can also be evaluated on variables, including chaining:
- $x.Field1.Method
+ $x.Method1.Field
- The name of a niladic function, such as
fun
The result is the value of invoking the function, fun(). The return
Argument
The result is the value of evaluating the argument.
.Method [Argument...]
- The result is the value of calling the method with the arguments:
+ The method can be alone or the last element of a chain but,
+ unlike methods in the middle of a chain, it can take arguments.
+ The result is the value of calling the method with the
+ arguments:
dot.Method(Argument1, etc.)
functionName [Argument...]
The result is the value of calling the function associated
return s.evalFieldChain(dot, value, v.ident[1:], args, final)
}
+// evalFieldChain evaluates .X.Y.Z possibly followed by arguments.
+// dot is the environment in which to evaluate arguments, while
+// receiver is the value being walked along the chain.
func (s *state) evalFieldChain(dot, receiver reflect.Value, ident []string, args []node, final reflect.Value) reflect.Value {
- // Up to the last entry, it must be a field.
n := len(ident)
for i := 0; i < n-1; i++ {
- dot = s.evalField(dot, ident[i], nil, zero, zero)
+ receiver = s.evalField(dot, ident[i], args[:1], zero, receiver)
}
- // Now it can be a field or method and if a method, gets arguments.
+ // Now if it's a method, it gets the arguments.
return s.evalField(dot, ident[n-1], args, final, receiver)
}
// evalField evaluates an expression like (.Field) or (.Field arg1 arg2).
// The 'final' argument represents the return value from the preceding
// value of the pipeline, if any.
-// 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).
-func (s *state) evalField(dot reflect.Value, fieldName string, args []node, final reflect.Value,
-receiver reflect.Value) reflect.Value {
- typ := dot.Type()
- if receiver.IsValid() {
- receiver, _ = indirect(receiver)
- // Need to get to a value of type *T to guarantee we see all
- // methods of T and *T.
- ptr := receiver
- if ptr.CanAddr() {
- ptr = ptr.Addr()
- }
- if method, ok := methodByName(ptr.Type(), fieldName); ok {
- return s.evalCall(dot, ptr, method.Func, fieldName, args, final)
- }
+func (s *state) evalField(dot reflect.Value, fieldName string, args []node, final, receiver reflect.Value) reflect.Value {
+ if !receiver.IsValid() {
+ return zero
+ }
+ typ := receiver.Type()
+ receiver, _ = indirect(receiver)
+ // Need to get to a value of type *T to guarantee we see all
+ // methods of T and *T.
+ ptr := receiver
+ if ptr.CanAddr() {
+ ptr = ptr.Addr()
+ }
+ if method, ok := methodByName(ptr.Type(), fieldName); ok {
+ return s.evalCall(dot, ptr, method.Func, fieldName, args, final)
}
// It's not a method; is it a field of a struct?
- dot, isNil := indirect(dot)
- if dot.Kind() == reflect.Struct {
- field := dot.FieldByName(fieldName)
+ receiver, isNil := indirect(receiver)
+ if receiver.Kind() == reflect.Struct {
+ field := receiver.FieldByName(fieldName)
if field.IsValid() {
if len(args) > 1 || final.IsValid() {
s.errorf("%s is not a method but has arguments", fieldName)
if isNil {
s.errorf("nil pointer evaluating %s.%s", typ, fieldName)
}
- s.errorf("can't handle evaluation of field %s in type %s", fieldName, typ)
+ s.errorf("can't evaluate field %s in type %s", fieldName, typ)
panic("not reached")
}
// T has lots of interesting pieces to use to test execution.
type T struct {
// Basics
+ True bool
I int
U16 uint16
X string
}
var tVal = &T{
+ True: true,
I: 17,
U16: 16,
X: "x",
return false, nil
}
+// A few methods to test chaining.
+func (t *T) GetU() *U {
+ return t.U
+}
+
+func (u *U) TrueFalse(b bool) string {
+ if b {
+ return "true"
+ }
+ return ""
+}
+
func typeOf(arg interface{}) string {
return fmt.Sprintf("%T", arg)
}
{".Method2(.U16, `str`)", "-{{.Method2 .U16 `str`}}-", "-Method2: 16 str-", tVal, true},
{".Method2(.U16, $x)", "{{if $x := .X}}-{{.Method2 .U16 $x}}{{end}}-", "-Method2: 16 x-", tVal, true},
{"method on var", "{{if $x := .}}-{{$x.Method2 .U16 $x.X}}{{end}}-", "-Method2: 16 x-", tVal, true},
+ {"method on chained var",
+ "{{range .MSIone}}{{if $.U.TrueFalse $.True}}{{$.U.TrueFalse $.True}}{{else}}WRONG{{end}}{{end}}",
+ "true", tVal, true},
+ {"chained method",
+ "{{range .MSIone}}{{if $.GetU.TrueFalse $.True}}{{$.U.TrueFalse $.True}}{{else}}WRONG{{end}}{{end}}",
+ "true", tVal, true},
+ {"chained method on variable",
+ "{{with $x := .}}{{with .SI}}{{$.GetU.TrueFalse $.True}}{{end}}{{end}}",
+ "true", tVal, true},
// Pipelines.
{"pipeline", "-{{.Method0 | .Method2 .U16}}-", "-Method2: 16 M0-", tVal, true},
// Fixed bugs.
// Must separate dot and receiver; otherwise args are evaluated with dot set to variable.
- {"problem", "{{range .MSIone}}-{{if $.Method1 .}}X{{end}}{{end}}-", "-X-", tVal, true},
+ {"bug0", "{{range .MSIone}}{{if $.Method1 .}}X{{end}}{{end}}", "X", tVal, true},
}
func zeroArgs() string {