elvish/eval/builtin_fn.go

230 lines
6.2 KiB
Go
Raw Normal View History

2018-02-04 11:38:23 +08:00
package eval
import (
"errors"
2018-02-04 11:38:23 +08:00
"fmt"
"reflect"
"unsafe"
2018-02-15 17:14:05 +08:00
"github.com/elves/elvish/eval/vals"
2018-02-04 11:38:23 +08:00
"github.com/xiaq/persistent/hash"
)
2018-03-03 12:54:20 +08:00
var ErrArgs = errors.New("args error")
2018-02-06 16:02:27 +08:00
// BuiltinFn uses reflection to wrap arbitrary Go functions into Elvish
2018-02-04 11:38:23 +08:00
// functions.
//
// Parameters are passed following these rules:
2018-02-04 11:38:23 +08:00
//
2018-03-03 12:49:45 +08:00
// 1. If the first parameter of function has type *Frame, it gets the current
// call frame.
//
// 2. If (possibly after a *Frame parameter) the first parameter has type
2018-03-03 12:49:45 +08:00
// RawOptions, it gets a map of options. If the function has not declared an
// RawOptions parameter but is passed options, an error is thrown.
//
// Alternatively, a (non-pointer) struct argument whose type implements the
// Options interface can also be declared, in which case options will be scanned
// into it using RawOptions.Scan. If the pointer type of the struct implements a
// SetDefault method, it will be called before scanning.
//
// 3. If the last parameter is non-variadic and has type Inputs, it represents
2018-03-03 12:49:45 +08:00
// an optional parameter that contains the input to this function. If the
// argument is not supplied, the input channel of the Frame will be used to
// supply the inputs.
//
// 4. Other parameters are converted using elvToGo.
//
// Return values go to the channel part of the stdout port, after being
// converted using goToElv. If the last return value has type error and is not
// nil, it is turned into an exception and no ouputting happens. If the last
// return value is a nil error, it is ignored.
2018-02-06 16:02:27 +08:00
type BuiltinFn struct {
2018-02-04 11:38:23 +08:00
name string
impl interface{}
// Type information of impl.
// If true, pass the frame as a *Frame argument.
frame bool
// If true, pass options as a RawOptions argument.
rawOptions bool
// If not nil, type of the parameter that gets options via RawOptions.Scan.
options reflect.Type
// If not nil, pass the inputs as an Input-typed last argument.
inputs bool
// Type of "normal" (non-frame, non-options, non-variadic) arguments.
2018-02-04 11:38:23 +08:00
normalArgs []reflect.Type
// If not nil, type of variadic arguments.
2018-02-04 11:38:23 +08:00
variadicArg reflect.Type
}
2018-02-06 16:02:27 +08:00
var _ Callable = &BuiltinFn{}
2018-02-04 11:38:23 +08:00
// An interface to be implemented by pointers to structs that should hold
// scanned options.
type optionsPtr interface {
SetDefaultOptions()
}
type Inputs func(func(interface{}))
2018-02-04 11:38:23 +08:00
var (
frameType = reflect.TypeOf((*Frame)(nil))
rawOptionsType = reflect.TypeOf(RawOptions(nil))
optionsPtrType = reflect.TypeOf((*optionsPtr)(nil)).Elem()
inputsType = reflect.TypeOf(Inputs(nil))
2018-02-04 11:38:23 +08:00
)
2018-02-06 16:02:27 +08:00
// NewBuiltinFn creates a new ReflectBuiltinFn instance.
func NewBuiltinFn(name string, impl interface{}) *BuiltinFn {
2018-02-04 11:38:23 +08:00
implType := reflect.TypeOf(impl)
2018-02-06 16:02:27 +08:00
b := &BuiltinFn{name: name, impl: impl}
2018-02-04 11:38:23 +08:00
i := 0
if i < implType.NumIn() && implType.In(i) == frameType {
b.frame = true
i++
}
if i < implType.NumIn() && implType.In(i) == rawOptionsType {
b.rawOptions = true
i++
}
if i < implType.NumIn() && reflect.PtrTo(implType.In(i)).Implements(optionsPtrType) {
if b.rawOptions {
panic("Function declares both RawOptions and Options parameters")
}
b.options = implType.In(i)
2018-02-04 11:38:23 +08:00
i++
}
for ; i < implType.NumIn(); i++ {
paramType := implType.In(i)
if i == implType.NumIn()-1 {
if implType.IsVariadic() {
b.variadicArg = paramType.Elem()
break
} else if paramType == inputsType {
b.inputs = true
break
}
2018-02-04 11:38:23 +08:00
}
b.normalArgs = append(b.normalArgs, paramType)
2018-02-04 11:38:23 +08:00
}
return b
}
// Kind returns "fn".
2018-02-06 16:02:27 +08:00
func (*BuiltinFn) Kind() string {
2018-02-04 11:38:23 +08:00
return "fn"
}
// Equal compares identity.
2018-02-06 16:02:27 +08:00
func (b *BuiltinFn) Equal(rhs interface{}) bool {
2018-02-04 11:38:23 +08:00
return b == rhs
}
// Hash hashes the address.
2018-02-06 16:02:27 +08:00
func (b *BuiltinFn) Hash() uint32 {
2018-02-04 11:38:23 +08:00
return hash.Pointer(unsafe.Pointer(b))
}
// Repr returns an opaque representation "<builtin $name>".
2018-02-06 16:02:27 +08:00
func (b *BuiltinFn) Repr(int) string {
2018-02-04 11:38:23 +08:00
return "<builtin " + b.name + ">"
}
// error(nil) is treated as nil by reflect.TypeOf, so we first get the type of
// *error and use Elem to obtain type of error.
var errorType = reflect.TypeOf((*error)(nil)).Elem()
var errNoOptions = errors.New("function does not accept any options")
2018-02-04 11:38:23 +08:00
// Call calls the implementation using reflection.
2018-02-06 16:02:27 +08:00
func (b *BuiltinFn) Call(f *Frame, args []interface{}, opts map[string]interface{}) error {
2018-02-04 11:38:23 +08:00
if b.variadicArg != nil {
if len(args) < len(b.normalArgs) {
return fmt.Errorf("want %d or more arguments, got %d",
len(b.normalArgs), len(args))
}
} else if b.inputs {
if len(args) != len(b.normalArgs) && len(args) != len(b.normalArgs)+1 {
return fmt.Errorf("want %d or %d arguments, got %d",
len(b.normalArgs), len(b.normalArgs)+1, len(args))
}
2018-02-04 11:38:23 +08:00
} else if len(args) != len(b.normalArgs) {
return fmt.Errorf("want %d arguments, got %d", len(b.normalArgs), len(args))
}
if !b.rawOptions && b.options == nil && len(opts) > 0 {
return ErrNoOptAccepted
}
2018-02-04 11:38:23 +08:00
var in []reflect.Value
if b.frame {
in = append(in, reflect.ValueOf(f))
}
if b.rawOptions {
2018-02-04 11:38:23 +08:00
in = append(in, reflect.ValueOf(opts))
}
if b.options != nil {
ptrValue := reflect.New(b.options)
ptr := ptrValue.Interface()
ptr.(optionsPtr).SetDefaultOptions()
RawOptions(opts).Scan(ptr)
in = append(in, ptrValue.Elem())
}
2018-02-04 11:38:23 +08:00
for i, arg := range args {
var typ reflect.Type
if i < len(b.normalArgs) {
typ = b.normalArgs[i]
} else if b.variadicArg != nil {
2018-02-04 11:38:23 +08:00
typ = b.variadicArg
} else if b.inputs {
break // Handled after the loop
} else {
panic("impossible")
2018-02-04 11:38:23 +08:00
}
ptr := reflect.New(typ)
err := vals.ScanToGo(arg, ptr.Interface())
2018-02-04 11:38:23 +08:00
if err != nil {
return fmt.Errorf("wrong type of %d'th argument: %v", i+1, err)
}
in = append(in, ptr.Elem())
2018-02-04 11:38:23 +08:00
}
if b.inputs {
var inputs Inputs
if len(args) == len(b.normalArgs) {
inputs = Inputs(f.IterateInputs)
} else {
// Wrap an iterable argument in Inputs.
iterable := args[len(args)-1]
inputs = Inputs(func(f func(interface{})) {
2018-02-15 17:14:05 +08:00
err := vals.Iterate(iterable, func(v interface{}) bool {
f(v)
return true
})
2018-09-27 08:48:44 +08:00
if err != nil {
throw(err)
}
})
}
in = append(in, reflect.ValueOf(inputs))
}
2018-02-04 11:38:23 +08:00
outs := reflect.ValueOf(b.impl).Call(in)
if len(outs) > 0 && outs[len(outs)-1].Type() == errorType {
err := outs[len(outs)-1].Interface()
if err != nil {
return err.(error)
2018-02-04 11:38:23 +08:00
}
outs = outs[:len(outs)-1]
}
for _, out := range outs {
2018-02-15 17:14:05 +08:00
f.OutputChan() <- vals.FromGo(out.Interface())
2018-02-04 11:38:23 +08:00
}
return nil
}