summaryrefslogtreecommitdiff
path: root/src/pkg/try/try.go
blob: 2a3dbf9870e888162da85bc3e81d96bc753af02e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
// 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
}