diff options
Diffstat (limited to 'libgo/go/try')
-rw-r--r-- | libgo/go/try/try.go | 174 | ||||
-rw-r--r-- | libgo/go/try/try_test.go | 60 |
2 files changed, 234 insertions, 0 deletions
diff --git a/libgo/go/try/try.go b/libgo/go/try/try.go new file mode 100644 index 000000000..af31d0d2c --- /dev/null +++ b/libgo/go/try/try.go @@ -0,0 +1,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.NewValue(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.NewValue(fn).(*reflect.FuncValue) + typ := rfn.Type().(*reflect.FuncType) + tryOneFunction(pkg, "", name, typ, rfn, args) +} + +// tryOneFunction is the common code for tryMethod and tryFunction. +func tryOneFunction(pkg, firstArg, name string, typ *reflect.FuncType, rfn *reflect.FuncValue, 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.NewValue(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 _, ok := typ.(*reflect.InterfaceType); ok { + return true + } + } + return false +} diff --git a/libgo/go/try/try_test.go b/libgo/go/try/try_test.go new file mode 100644 index 000000000..617b2c7c3 --- /dev/null +++ b/libgo/go/try/try_test.go @@ -0,0 +1,60 @@ +// 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 + +import ( + "bytes" + "regexp" // Used as the package to try. + "testing" +) + +// The global functions in package regexp at time of writing. +// Doesn't need to be updated unless the entries in this list become invalid. +var functions = map[string]interface{}{ + "Compile": regexp.Compile, + "Match": regexp.Match, + "MatchString": regexp.MatchString, + "MustCompile": regexp.MustCompile, + "QuoteMeta": regexp.QuoteMeta, +} + +// A wraps arguments to make the test cases nicer to read. +func A(args ...interface{}) []interface{} { + return args +} + +type Test struct { + firstArg string // only needed if there is exactly one argument + result string // minus final newline; might be just the godoc string + args []interface{} +} + +var testRE = regexp.MustCompile("a(.)(.)d") + +var tests = []Test{ + // A simple expression. The final value is a slice in case the expression is multivalue. + {"3+4", "3+4 = 7", A([]interface{}{7})}, + // A search for a function. + {"", "regexp QuoteMeta", A("([])", `\(\[\]\)`)}, + // A search for a function with multiple return values. + {"", "regexp MatchString", A("abc", "xabcd", true, nil)}, + // Searches for methods. + {"", "regexp MatchString", A(testRE, "xabcde", true)}, + {"", "regexp NumSubexp", A(testRE, 2)}, +} + +func TestAll(t *testing.T) { + re := regexp.MustCompile(".*// godoc ") + for _, test := range tests { + b := new(bytes.Buffer) + output = b + Main("regexp", test.firstArg, functions, test.args) + expect := test.result + "\n" + got := re.ReplaceAllString(b.String(), "") + if got != expect { + t.Errorf("expected %q; got %q", expect, got) + } + } +} |