blob: 2a3dbf9870e888162da85bc3e81d96bc753af02e [file] [log] [blame]
// Copyright 2010 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package try contains the executable part of the gotry command.
// It is not intended for general use.
package try
import (
"fmt"
"io"
"os"
"reflect"
"unicode"
)
var output io.Writer = os.Stdout // redirected when testing
// Main is called directly from the gotry-generated Go source file to perform
// the evaluations.
func Main(pkg, firstArg string, functions map[string]interface{}, args []interface{}) {
switch len(args) {
case 0:
// Nothing to do.
case 1:
// Compiler has already evaluated the expression; just print the result.
printSlice(firstArg, args)
default:
// See if methods satisfy the expressions.
tryMethods(pkg, firstArg, args)
// See if functions satisfy the expressions.
for name, fn := range functions {
tryFunction(pkg, name, fn, args)
}
}
}
// printSlice prints the zeroth element of the args slice, which should (by construction)
// itself be a slice of interface{}.
func printSlice(firstArg string, args []interface{}) {
// Args should be length 1 and a slice.
if len(args) != 1 {
return
}
arg, ok := args[0].([]interface{})
if !ok {
return
}
fmt.Fprintf(output, "%s = ", firstArg)
if len(arg) > 1 {
fmt.Fprint(output, "(")
}
for i, a := range arg {
if i > 0 {
fmt.Fprint(output, ", ")
}
fmt.Fprintf(output, "%#v", a)
}
if len(arg) > 1 {
fmt.Fprint(output, ")")
}
fmt.Fprint(output, "\n")
}
// tryMethods sees if the zeroth arg has methods, and if so treats them as potential
// functions to satisfy the remaining arguments.
func tryMethods(pkg, firstArg string, args []interface{}) {
defer func() { recover() }()
// Is the first argument something with methods?
v := reflect.ValueOf(args[0])
typ := v.Type()
if typ.NumMethod() == 0 {
return
}
for i := 0; i < typ.NumMethod(); i++ {
if unicode.IsUpper(int(typ.Method(i).Name[0])) {
tryMethod(pkg, firstArg, typ.Method(i), args)
}
}
}
// tryMethod converts a method to a function for tryOneFunction.
func tryMethod(pkg, firstArg string, method reflect.Method, args []interface{}) {
rfn := method.Func
typ := method.Type
name := method.Name
tryOneFunction(pkg, firstArg, name, typ, rfn, args)
}
// tryFunction sees if fn satisfies the arguments.
func tryFunction(pkg, name string, fn interface{}, args []interface{}) {
defer func() { recover() }()
rfn := reflect.ValueOf(fn)
typ := rfn.Type()
tryOneFunction(pkg, "", name, typ, rfn, args)
}
// tryOneFunction is the common code for tryMethod and tryFunction.
func tryOneFunction(pkg, firstArg, name string, typ reflect.Type, rfn reflect.Value, args []interface{}) {
// Any results?
if typ.NumOut() == 0 {
return // Nothing to do.
}
// Right number of arguments + results?
if typ.NumIn()+typ.NumOut() != len(args) {
return
}
// Right argument and result types?
for i, a := range args {
if i < typ.NumIn() {
if !compatible(a, typ.In(i)) {
return
}
} else {
if !compatible(a, typ.Out(i-typ.NumIn())) {
return
}
}
}
// Build the call args.
argsVal := make([]reflect.Value, typ.NumIn()+typ.NumOut())
for i, a := range args {
argsVal[i] = reflect.ValueOf(a)
}
// Call the function and see if the results are as expected.
resultVal := rfn.Call(argsVal[:typ.NumIn()])
for i, v := range resultVal {
if !reflect.DeepEqual(v.Interface(), args[i+typ.NumIn()]) {
return
}
}
// Present the result including a godoc command to get more information.
firstIndex := 0
if firstArg != "" {
fmt.Fprintf(output, "%s.%s(", firstArg, name)
firstIndex = 1
} else {
fmt.Fprintf(output, "%s.%s(", pkg, name)
}
for i := firstIndex; i < typ.NumIn(); i++ {
if i > firstIndex {
fmt.Fprint(output, ", ")
}
fmt.Fprintf(output, "%#v", args[i])
}
fmt.Fprint(output, ") = ")
if typ.NumOut() > 1 {
fmt.Fprint(output, "(")
}
for i := 0; i < typ.NumOut(); i++ {
if i > 0 {
fmt.Fprint(output, ", ")
}
fmt.Fprintf(output, "%#v", resultVal[i].Interface())
}
if typ.NumOut() > 1 {
fmt.Fprint(output, ")")
}
fmt.Fprintf(output, " // godoc %s %s\n", pkg, name)
}
// compatible reports whether the argument is compatible with the type.
func compatible(arg interface{}, typ reflect.Type) bool {
if reflect.TypeOf(arg) == typ {
return true
}
if arg == nil {
// nil is OK if the type is an interface.
if typ.Kind() == reflect.Interface {
return true
}
}
return false
}