diff --git a/src/cmd/gotest/Makefile b/src/cmd/gotest/Makefile index 367c8ce640..74054e974c 100644 --- a/src/cmd/gotest/Makefile +++ b/src/cmd/gotest/Makefile @@ -1,16 +1,18 @@ -# Copyright 2009 The Go Authors. All rights reserved. +# 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. include ../../Make.inc -TARG=gotest +TARG=install clean: @true -install: $(TARG) - ! test -f "$(GOBIN)"/$(TARG) || chmod u+w "$(GOBIN)"/$(TARG) - sed 's`@@GOROOT@@`$(GOROOT_FINAL)`' gotest >"$(GOBIN)"/$(TARG) - chmod +x "$(GOBIN)"/$(TARG) +install: install-gotest install-gotry + +install-%: % + ! test -f "$(GOBIN)"/$* || chmod u+w "$(GOBIN)"/$* + sed 's`@@GOROOT@@`$(GOROOT_FINAL)`' $* >"$(GOBIN)"/$* + chmod +x "$(GOBIN)"/$* diff --git a/src/cmd/gotest/gotry b/src/cmd/gotest/gotry new file mode 100755 index 0000000000..52c5d2d586 --- /dev/null +++ b/src/cmd/gotest/gotry @@ -0,0 +1,167 @@ +#!/usr/bin/env bash +# 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. + +# Using all the non-test *.go files in the named directory, write +# out a file /tmp/$USER.try.go to evaluate the expressions on the +# command line, perhaps to discover a function or method that +# gives the desired results. See usage message. +# Compile the program and run it. + +# Makes egrep,grep work better in general if we put them +# in ordinary C mode instead of what the current language is. +unset LANG +export LC_ALL=C +export LC_CTYPE=C + +export GOROOT=${GOROOT:-"@@GOROOT@@"} +eval $(gomake -j1 --no-print-directory -f "$GOROOT"/src/Make.inc go-env) +if [ -z "$O" ]; then + echo 'missing $O - maybe no Make.$GOARCH?' 1>&2 + exit 2 +fi + +# Allow overrides +GC="${_GC:-$GC} -I _test" +GL="${GL:-$LD} -L _test" +AS="$AS" +CC="$CC" +LD="$LD" +export GC GL O AS CC LD + +# Macros for tab and quotes for easier readability. +T=' ' +BQ='`' +SQ="'" +DQ='"' +SD="$SQ$DQ" +DS="$DQ$SQ" + +usage="usage: gotry [packagedirectory] expression ... +Given one expression, gotry attempts to evaluate that expression. +Given multiple expressions, gotry treats them as a list of arguments +and result values and attempts to find a function in the package +that, given the first few expressions as arguments, evaluates to +the remaining expressions as results. If the first expression has +methods, it will also search for applicable methods. + +If there are multiple expressions, a package directory must be +specified. If there is a package argument, the expressions are +evaluated in an environment that includes + import . ${DQ}packagedirectory${DQ} + +Examples: + gotry 3+4 + # evaluates to 7 + gotry strings ${SD}abc${DS} ${SD}c${DS} 7-5 + # finds strings.Index etc. + gotry regexp ${SQ}MustCompile(${DQ}^[0-9]+${DQ})${SQ} ${SD}12345${DS} true + # finds Regexp.MatchString + +" + +function fail() { + echo 2>&1 "$@" + exit 2 +} + +case $# in + 0) + fail "$usage" + ;; + *) + case "$1" in + -*help|-*'?'|'?') + fail "$usage" + esac + if test -d "$GOROOT/src/pkg/$1" + then + pkg=$(basename $1) + dir=$GOROOT/src/pkg/$1 + importdir=$1 + shift + case "$pkg" in + os|syscall) + fail "gotry: cannot try packages os or syscall; they are too dangerous" + esac + fi + ;; +esac + +spaces='[ ][ ]*' + +function getFunctions() { + if [ "$pkg" = "" ] + then + return + fi + for file in $dir/*.go + do + case $file in + *_test*) + continue + esac + grep "func$spaces[A-Z]" $file | # TODO: should be Unicode upper case + sed "s/func$spaces//;s/(.*//" + done | sort -u +} + +# Generate list of public functions. +functions=$(getFunctions) + +# Write file to compile +rm -f /tmp/$USER.try.go +( +cat <<'!' +package main + +import ( + "os" + "try" +! + +if [ "$pkg" != "" ] +then + echo "$T" . '"'$importdir'"' +fi + +cat </tmp/$USER.try.go + +$GC -o /tmp/$USER.try.$O /tmp/$USER.try.go && +$GL -o /tmp/$USER.try /tmp/$USER.try.$O && +/tmp/$USER.try "_$@" +rm -f /tmp/$USER.try /tmp/$USER.try.go /tmp/$USER.try.$O diff --git a/src/pkg/Makefile b/src/pkg/Makefile index d5c8e3997c..58de326a25 100644 --- a/src/pkg/Makefile +++ b/src/pkg/Makefile @@ -123,6 +123,7 @@ DIRS=\ testing/quick\ testing/script\ time\ + try\ unicode\ utf16\ utf8\ @@ -153,6 +154,7 @@ NOTEST=\ runtime/pprof\ syscall\ testing/iotest\ + try\ ../libcgo\ ../cmd/cgo\ ../cmd/ebnflint\ diff --git a/src/pkg/try/Makefile b/src/pkg/try/Makefile new file mode 100644 index 0000000000..06981a6fc8 --- /dev/null +++ b/src/pkg/try/Makefile @@ -0,0 +1,11 @@ +# Copyright 2009 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. + +include ../../Make.inc + +TARG=try +GOFILES=\ + try.go\ + +include ../../Make.pkg diff --git a/src/pkg/try/try.go b/src/pkg/try/try.go new file mode 100644 index 0000000000..af31d0d2cf --- /dev/null +++ b/src/pkg/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/src/pkg/try/try_test.go b/src/pkg/try/try_test.go new file mode 100644 index 0000000000..7ec540feea --- /dev/null +++ b/src/pkg/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. + Test{"3+4", "3+4 = 7", A([]interface{}{7})}, + // A search for a function. + Test{"", "regexp QuoteMeta", A("([])", `\(\[\]\)`)}, + // A search for a function with multiple return values. + Test{"", "regexp MatchString", A("abc", "xabcd", true, nil)}, + // Searches for methods. + Test{"", "regexp MatchString", A(testRE, "xabcde", true)}, + Test{"", "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) + } + } +}