eval: Remove ScanArgs and friends.

This commit is contained in:
Qi Xiao 2018-02-15 09:16:41 +00:00
parent f3501bbeba
commit 37e491d31c
4 changed files with 169 additions and 362 deletions

View File

@ -1,149 +0,0 @@
package eval
// This file implements facilities for "scanning" arguments and options.
import (
"errors"
"reflect"
"github.com/elves/elvish/eval/vals"
"github.com/elves/elvish/parse"
"github.com/elves/elvish/util"
)
// ScanArgs scans arguments into pointers to supported argument types. If the
// arguments cannot be scanned, an error is thrown.
func ScanArgs(src []interface{}, dstPtrs ...interface{}) {
if len(src) != len(dstPtrs) {
throwf("arity mistmatch: want %d arguments, got %d", len(dstPtrs), len(src))
}
for i, value := range src {
mustScanToGo(value, dstPtrs[i])
}
}
// ScanArgsVariadic is like ScanArgs, but the last element of args should be a
// pointer to a slice, and the rest of arguments will be scanned into it.
func ScanArgsVariadic(src []interface{}, dstPtrs ...interface{}) {
if len(src) < len(dstPtrs)-1 {
throwf("arity mistmatch: want at least %d arguments, got %d", len(dstPtrs)-1, len(src))
}
ScanArgs(src[:len(dstPtrs)-1], dstPtrs[:len(dstPtrs)-1]...)
// Scan the rest of arguments into a slice.
rest := src[len(dstPtrs)-1:]
restDst := reflect.ValueOf(dstPtrs[len(dstPtrs)-1])
if restDst.Kind() != reflect.Ptr || restDst.Elem().Kind() != reflect.Slice {
throwf("internal bug: %T to ScanArgsVariadic, need pointer to slice", dstPtrs[len(dstPtrs)-1])
}
scanned := reflect.MakeSlice(restDst.Elem().Type(), len(rest), len(rest))
for i, value := range rest {
mustScanToGo(value, scanned.Index(i).Addr().Interface())
}
reflect.Indirect(restDst).Set(scanned)
}
// ScanArgsOptionalInput is like ScanArgs, but the argument can contain an
// optional iterable value at the end containing inputs to the function. The
// return value is a function that iterates the iterable value if it exists, or
// the input otherwise.
func ScanArgsOptionalInput(ec *Frame, src []interface{}, dstArgs ...interface{}) func(func(interface{})) {
switch len(src) {
case len(dstArgs):
ScanArgs(src, dstArgs...)
return ec.IterateInputs
case len(dstArgs) + 1:
ScanArgs(src[:len(dstArgs)], dstArgs...)
value := src[len(dstArgs)]
return func(f func(interface{})) {
err := vals.Iterate(value, func(v interface{}) bool {
f(v)
return true
})
maybeThrow(err)
}
default:
throwf("arity mistmatch: want %d or %d arguments, got %d", len(dstArgs), len(dstArgs)+1, len(src))
return nil
}
}
// OptToScan is a data structure for an option that is intended to be used in
// ScanOpts.
type OptToScan struct {
Name string
Ptr interface{}
Default interface{}
}
// ScanOpts scans options from a map.
func ScanOpts(m map[string]interface{}, opts ...OptToScan) {
scanned := make(map[string]bool)
for _, opt := range opts {
a := opt.Ptr
value, ok := m[opt.Name]
if !ok {
value = opt.Default
}
mustScanToGo(value, a)
scanned[opt.Name] = true
}
for key := range m {
if !scanned[key] {
throwf("unknown option %s", parse.Quote(key))
}
}
}
// ScanOptsToStruct scan options from a map like ScanOpts except the destination
// is a struct whose fields correspond to the options to be parsed. A field
// named FieldName corresponds to the option named field-name, unless the field
// has a explicit "name" tag.
func ScanOptsToStruct(m map[string]interface{}, structPtr interface{}) {
ptrValue := reflect.ValueOf(structPtr)
if ptrValue.Kind() != reflect.Ptr || ptrValue.Elem().Kind() != reflect.Struct {
throwf("internal bug: need struct ptr for ScanOptsToStruct, got %T", structPtr)
}
struc := ptrValue.Elem()
// fieldIdxForOpt maps option name to the index of field in struc.
fieldIdxForOpt := make(map[string]int)
for i := 0; i < struc.Type().NumField(); i++ {
// ignore unexported fields
if !struc.Field(i).CanSet() {
continue
}
f := struc.Type().Field(i)
optName := f.Tag.Get("name")
if optName == "" {
optName = util.CamelToDashed(f.Name)
}
fieldIdxForOpt[optName] = i
}
for k, v := range m {
fieldIdx, ok := fieldIdxForOpt[k]
if !ok {
throwf("unknown option %s", parse.Quote(k))
}
mustScanToGo(v, struc.Field(fieldIdx).Addr().Interface())
}
}
var (
ErrNoArgAccepted = errors.New("no argument accepted")
ErrNoOptAccepted = errors.New("no option accepted")
)
func TakeNoArg(args []interface{}) {
if len(args) > 0 {
throw(ErrNoArgAccepted)
}
}
func TakeNoOpt(opts map[string]interface{}) {
if len(opts) > 0 {
throw(ErrNoOptAccepted)
}
}

82
eval/scan_opts.go Normal file
View File

@ -0,0 +1,82 @@
package eval
// This file implements facilities for "scanning" arguments and options.
import (
"errors"
"reflect"
"github.com/elves/elvish/parse"
"github.com/elves/elvish/util"
)
// OptToScan is a data structure for an option that is intended to be used in
// ScanOpts.
type OptToScan struct {
Name string
Ptr interface{}
Default interface{}
}
// ScanOpts scans options from a map.
func ScanOpts(m map[string]interface{}, opts ...OptToScan) {
scanned := make(map[string]bool)
for _, opt := range opts {
a := opt.Ptr
value, ok := m[opt.Name]
if !ok {
value = opt.Default
}
mustScanToGo(value, a)
scanned[opt.Name] = true
}
for key := range m {
if !scanned[key] {
throwf("unknown option %s", parse.Quote(key))
}
}
}
// ScanOptsToStruct scan options from a map like ScanOpts except the destination
// is a struct whose fields correspond to the options to be parsed. A field
// named FieldName corresponds to the option named field-name, unless the field
// has a explicit "name" tag.
func ScanOptsToStruct(m map[string]interface{}, structPtr interface{}) {
ptrValue := reflect.ValueOf(structPtr)
if ptrValue.Kind() != reflect.Ptr || ptrValue.Elem().Kind() != reflect.Struct {
throwf("internal bug: need struct ptr for ScanOptsToStruct, got %T", structPtr)
}
struc := ptrValue.Elem()
// fieldIdxForOpt maps option name to the index of field in struc.
fieldIdxForOpt := make(map[string]int)
for i := 0; i < struc.Type().NumField(); i++ {
// ignore unexported fields
if !struc.Field(i).CanSet() {
continue
}
f := struc.Type().Field(i)
optName := f.Tag.Get("name")
if optName == "" {
optName = util.CamelToDashed(f.Name)
}
fieldIdxForOpt[optName] = i
}
for k, v := range m {
fieldIdx, ok := fieldIdxForOpt[k]
if !ok {
throwf("unknown option %s", parse.Quote(k))
}
mustScanToGo(v, struc.Field(fieldIdx).Addr().Interface())
}
}
var ErrNoOptAccepted = errors.New("no option accepted")
func TakeNoOpt(opts map[string]interface{}) {
if len(opts) > 0 {
throw(ErrNoOptAccepted)
}
}

87
eval/scan_opts_test.go Normal file
View File

@ -0,0 +1,87 @@
package eval
import (
"reflect"
"testing"
"github.com/elves/elvish/util"
)
// These are used as arguments to scanArg, since Go does not allow taking
// address of literals.
func intPtr() *int { var x int; return &x }
func intsPtr() *[]int { var x []int; return &x }
func float64Ptr() *float64 { var x float64; return &x }
func stringPtr() *string { var x string; return &x }
var scanOptsTestCases = []struct {
bad bool
src map[string]interface{}
opts []OptToScan
want []interface{}
}{
{
src: map[string]interface{}{
"foo": "bar",
},
opts: []OptToScan{
{"foo", stringPtr(), "haha"},
},
want: []interface{}{
"bar",
},
},
// Default values.
{
src: map[string]interface{}{
"foo": "bar",
},
opts: []OptToScan{
{"foo", stringPtr(), "haha"},
{"lorem", stringPtr(), "ipsum"},
},
want: []interface{}{
"bar",
"ipsum",
},
},
// Unknown option
{
bad: true,
src: map[string]interface{}{
"foo": "bar",
},
opts: []OptToScan{
{"lorem", stringPtr(), "ipsum"},
},
},
}
func TestScanOpts(t *testing.T) {
for _, tc := range scanOptsTestCases {
if tc.bad {
if !util.ThrowsAny(func() { ScanOpts(tc.src, tc.opts...) }) {
t.Errorf("ScanOpts(%v, %v...) should throw an error",
tc.src, tc.opts)
}
continue
}
ScanOpts(tc.src, tc.opts...)
dsts := make([]interface{}, len(tc.opts))
for i, opt := range tc.opts {
dsts[i] = indirect(opt.Ptr)
}
err := compareSlice(tc.want, dsts)
if err != nil {
t.Errorf("ScanOpts(%v, %v) got %v, want %v", tc.src, tc.opts,
dsts, tc.want)
}
}
}
func indirect(i interface{}) interface{} {
return reflect.Indirect(reflect.ValueOf(i)).Interface()
}

View File

@ -1,213 +0,0 @@
package eval
import (
"reflect"
"testing"
"github.com/elves/elvish/eval/vals"
"github.com/elves/elvish/util"
)
// These are used as arguments to scanArg, since Go does not allow taking
// address of literals.
func intPtr() *int { var x int; return &x }
func intsPtr() *[]int { var x []int; return &x }
func float64Ptr() *float64 { var x float64; return &x }
func stringPtr() *string { var x string; return &x }
var scanArgsTestCases = []struct {
variadic bool
src []interface{}
dstPtrs []interface{}
want []interface{}
bad bool
}{
// Scanning an int and a String
{
src: []interface{}{"20", "20"},
dstPtrs: []interface{}{intPtr(), stringPtr()},
want: []interface{}{20, "20"},
},
// Scanning a String and any number of ints (here 2)
{
variadic: true,
src: []interface{}{"a", "1", "2"},
dstPtrs: []interface{}{stringPtr(), intsPtr()},
want: []interface{}{"a", []int{1, 2}},
},
// Scanning a String and any number of ints (here 0)
{
variadic: true,
src: []interface{}{"a"},
dstPtrs: []interface{}{stringPtr(), intsPtr()},
want: []interface{}{"a", []int{}},
},
// Arity mismatch: too few arguments (non-variadic)
{
bad: true,
src: []interface{}{},
dstPtrs: []interface{}{stringPtr()},
},
// Arity mismatch: too few arguments (non-variadic)
{
bad: true,
src: []interface{}{""},
dstPtrs: []interface{}{stringPtr(), intPtr()},
},
// Arity mismatch: too many arguments (non-variadic)
{
bad: true,
src: []interface{}{"1", "2"},
dstPtrs: []interface{}{stringPtr()},
},
// Type mismatch (nonvariadic)
{
bad: true,
src: []interface{}{"x"},
dstPtrs: []interface{}{intPtr()},
},
// Arity mismatch: too few arguments (variadic)
{
bad: true,
src: []interface{}{},
dstPtrs: []interface{}{stringPtr(), intsPtr()},
variadic: true,
},
// Type mismatch within rest arg
{
bad: true,
src: []interface{}{"a", "1", "lorem"},
dstPtrs: []interface{}{stringPtr(), intsPtr()},
variadic: true,
},
}
func TestScanArgs(t *testing.T) {
for _, tc := range scanArgsTestCases {
funcToTest := ScanArgs
if tc.variadic {
funcToTest = ScanArgsVariadic
}
if tc.bad {
if !util.ThrowsAny(func() { funcToTest(tc.src, tc.dstPtrs...) }) {
t.Errorf("ScanArgs(%v, %v) should throw an error", tc.src, tc.dstPtrs)
}
continue
}
funcToTest(tc.src, tc.dstPtrs...)
dsts := make([]interface{}, len(tc.dstPtrs))
for i, ptr := range tc.dstPtrs {
dsts[i] = indirect(ptr)
}
err := compareSlice(tc.want, dsts)
if err != nil {
t.Errorf("ScanArgs(%v) got %v, want %v", tc.src, dsts, tc.want)
}
}
}
var scanArgsBadTestCases = []struct {
variadic bool
src []interface{}
dstPtrs []interface{}
}{}
var scanOptsTestCases = []struct {
bad bool
src map[string]interface{}
opts []OptToScan
want []interface{}
}{
{
src: map[string]interface{}{
"foo": "bar",
},
opts: []OptToScan{
{"foo", stringPtr(), "haha"},
},
want: []interface{}{
"bar",
},
},
// Default values.
{
src: map[string]interface{}{
"foo": "bar",
},
opts: []OptToScan{
{"foo", stringPtr(), "haha"},
{"lorem", stringPtr(), "ipsum"},
},
want: []interface{}{
"bar",
"ipsum",
},
},
// Unknown option
{
bad: true,
src: map[string]interface{}{
"foo": "bar",
},
opts: []OptToScan{
{"lorem", stringPtr(), "ipsum"},
},
},
}
func TestScanOpts(t *testing.T) {
for _, tc := range scanOptsTestCases {
if tc.bad {
if !util.ThrowsAny(func() { ScanOpts(tc.src, tc.opts...) }) {
t.Errorf("ScanOpts(%v, %v...) should throw an error",
tc.src, tc.opts)
}
continue
}
ScanOpts(tc.src, tc.opts...)
dsts := make([]interface{}, len(tc.opts))
for i, opt := range tc.opts {
dsts[i] = indirect(opt.Ptr)
}
err := compareSlice(tc.want, dsts)
if err != nil {
t.Errorf("ScanOpts(%v, %v) got %v, want %v", tc.src, tc.opts,
dsts, tc.want)
}
}
}
var scanArgTestCases = []struct {
source interface{}
destPtr interface{}
want interface{}
}{
{"20", intPtr(), 20},
{"0x20", intPtr(), 0x20},
{"20", float64Ptr(), 20.0},
{"23.33", float64Ptr(), 23.33},
{"", stringPtr(), ""},
{"1", stringPtr(), "1"},
{"2.33", stringPtr(), "2.33"},
}
func TestScanArg(t *testing.T) {
for _, tc := range scanArgTestCases {
mustScanToGo(tc.source, tc.destPtr)
if !vals.Equal(indirect(tc.destPtr), tc.want) {
t.Errorf("scanArg(%s) got %q, want %v", tc.source,
indirect(tc.destPtr), tc.want)
}
}
}
func indirect(i interface{}) interface{} {
return reflect.Indirect(reflect.ValueOf(i)).Interface()
}