github.com/kolbycrouch/elvish@v0.14.1-0.20210614162631-215b9ac1c423/pkg/eval/go_fn.go (about)

     1  package eval
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"reflect"
     7  	"unsafe"
     8  
     9  	"src.elv.sh/pkg/eval/errs"
    10  	"src.elv.sh/pkg/eval/vals"
    11  	"src.elv.sh/pkg/persistent/hash"
    12  )
    13  
    14  var (
    15  	// ErrArgs is thrown when a Go function gets erroneous arguments.
    16  	//
    17  	// TODO(xiaq): Replace this single error type with multiple types that carry
    18  	// richer error information.
    19  	ErrArgs = errors.New("args error")
    20  	// ErrNoOptAccepted is thrown when a Go function that does not accept any
    21  	// options gets passed options.
    22  	ErrNoOptAccepted = errors.New("function does not accept any options")
    23  )
    24  
    25  type goFn struct {
    26  	name string
    27  	impl interface{}
    28  
    29  	// Type information of impl.
    30  
    31  	// If true, pass the frame as a *Frame argument.
    32  	frame bool
    33  	// If true, pass options as a RawOptions argument.
    34  	rawOptions bool
    35  	// If not nil, type of the parameter that gets options via RawOptions.Scan.
    36  	options reflect.Type
    37  	// If not nil, pass the inputs as an Input-typed last argument.
    38  	inputs bool
    39  	// Type of "normal" (non-frame, non-options, non-variadic) arguments.
    40  	normalArgs []reflect.Type
    41  	// If not nil, type of variadic arguments.
    42  	variadicArg reflect.Type
    43  }
    44  
    45  // An interface to be implemented by pointers to structs that should hold
    46  // scanned options.
    47  type optionsPtr interface {
    48  	SetDefaultOptions()
    49  }
    50  
    51  // Inputs is the type that the last parameter of a Go-native function can take.
    52  // When that is the case, it is a callback to get inputs. See the doc of GoFn
    53  // for details.
    54  type Inputs func(func(interface{}))
    55  
    56  var (
    57  	frameType      = reflect.TypeOf((*Frame)(nil))
    58  	rawOptionsType = reflect.TypeOf(RawOptions(nil))
    59  	optionsPtrType = reflect.TypeOf((*optionsPtr)(nil)).Elem()
    60  	inputsType     = reflect.TypeOf(Inputs(nil))
    61  )
    62  
    63  // NewGoFn wraps a Go function into an Elvish function using reflection.
    64  //
    65  // Parameters are passed following these rules:
    66  //
    67  // 1. If the first parameter of function has type *Frame, it gets the current
    68  // call frame.
    69  //
    70  // 2. After the potential *Frame argument, the first parameter has type
    71  // RawOptions, it gets a map of option names to their values.
    72  //
    73  // Alternatively, this parameter may be a (non-pointer) struct whose pointer
    74  // type implements a SetDefaultOptions method that takes no arguments and has no
    75  // return value. In this case, a new instance of the struct is constructed, the
    76  // SetDefaultOptions method is called, and any option passed to the Elvish
    77  // function is used to populate the fields of the struct. Field names are mapped
    78  // to option names using strutil.CamelToDashed, unless they have a field tag
    79  // "name", in which case the tag is preferred.
    80  //
    81  // If the function does not declare that it accepts options via either method
    82  // described above, it accepts no options.
    83  //
    84  // 3. If the last parameter is non-variadic and has type Inputs, it represents
    85  // an optional parameter that contains the input to this function. If the
    86  // argument is not supplied, the input channel of the Frame will be used to
    87  // supply the inputs.
    88  //
    89  // 4. Other parameters are converted using vals.ScanToGo.
    90  //
    91  // Return values go to the channel part of the stdout port, after being
    92  // converted using vals.FromGo. If the last return value has type error and is
    93  // not nil, it is turned into an exception and no outputting happens. If the
    94  // last return value is a nil error, it is ignored.
    95  func NewGoFn(name string, impl interface{}) Callable {
    96  	implType := reflect.TypeOf(impl)
    97  	b := &goFn{name: name, impl: impl}
    98  
    99  	i := 0
   100  	if i < implType.NumIn() && implType.In(i) == frameType {
   101  		b.frame = true
   102  		i++
   103  	}
   104  	if i < implType.NumIn() && implType.In(i) == rawOptionsType {
   105  		b.rawOptions = true
   106  		i++
   107  	}
   108  	if i < implType.NumIn() && reflect.PtrTo(implType.In(i)).Implements(optionsPtrType) {
   109  		if b.rawOptions {
   110  			panic("Function declares both RawOptions and Options parameters")
   111  		}
   112  		b.options = implType.In(i)
   113  		i++
   114  	}
   115  	for ; i < implType.NumIn(); i++ {
   116  		paramType := implType.In(i)
   117  		if i == implType.NumIn()-1 {
   118  			if implType.IsVariadic() {
   119  				b.variadicArg = paramType.Elem()
   120  				break
   121  			} else if paramType == inputsType {
   122  				b.inputs = true
   123  				break
   124  			}
   125  		}
   126  		b.normalArgs = append(b.normalArgs, paramType)
   127  	}
   128  	return b
   129  }
   130  
   131  // Kind returns "fn".
   132  func (*goFn) Kind() string {
   133  	return "fn"
   134  }
   135  
   136  // Equal compares identity.
   137  func (b *goFn) Equal(rhs interface{}) bool {
   138  	return b == rhs
   139  }
   140  
   141  // Hash hashes the address.
   142  func (b *goFn) Hash() uint32 {
   143  	return hash.Pointer(unsafe.Pointer(b))
   144  }
   145  
   146  // Repr returns an opaque representation "<builtin $name>".
   147  func (b *goFn) Repr(int) string {
   148  	return "<builtin " + b.name + ">"
   149  }
   150  
   151  // error(nil) is treated as nil by reflect.TypeOf, so we first get the type of
   152  // *error and use Elem to obtain type of error.
   153  var errorType = reflect.TypeOf((*error)(nil)).Elem()
   154  
   155  // Call calls the implementation using reflection.
   156  func (b *goFn) Call(f *Frame, args []interface{}, opts map[string]interface{}) error {
   157  	if b.variadicArg != nil {
   158  		if len(args) < len(b.normalArgs) {
   159  			return errs.ArityMismatch{
   160  				What:     "arguments here",
   161  				ValidLow: len(b.normalArgs), ValidHigh: -1, Actual: len(args)}
   162  		}
   163  	} else if b.inputs {
   164  		if len(args) != len(b.normalArgs) && len(args) != len(b.normalArgs)+1 {
   165  			return errs.ArityMismatch{
   166  				What:     "arguments here",
   167  				ValidLow: len(b.normalArgs), ValidHigh: len(b.normalArgs) + 1, Actual: len(args)}
   168  		}
   169  	} else if len(args) != len(b.normalArgs) {
   170  		return errs.ArityMismatch{
   171  			What:     "arguments here",
   172  			ValidLow: len(b.normalArgs), ValidHigh: len(b.normalArgs), Actual: len(args)}
   173  	}
   174  	if !b.rawOptions && b.options == nil && len(opts) > 0 {
   175  		return ErrNoOptAccepted
   176  	}
   177  
   178  	var in []reflect.Value
   179  	if b.frame {
   180  		in = append(in, reflect.ValueOf(f))
   181  	}
   182  	if b.rawOptions {
   183  		in = append(in, reflect.ValueOf(opts))
   184  	}
   185  	if b.options != nil {
   186  		ptrValue := reflect.New(b.options)
   187  		ptr := ptrValue.Interface()
   188  		ptr.(optionsPtr).SetDefaultOptions()
   189  		err := scanOptions(opts, ptr)
   190  		if err != nil {
   191  			return err
   192  		}
   193  		in = append(in, ptrValue.Elem())
   194  	}
   195  	for i, arg := range args {
   196  		var typ reflect.Type
   197  		if i < len(b.normalArgs) {
   198  			typ = b.normalArgs[i]
   199  		} else if b.variadicArg != nil {
   200  			typ = b.variadicArg
   201  		} else if b.inputs {
   202  			break // Handled after the loop
   203  		} else {
   204  			panic("impossible")
   205  		}
   206  		ptr := reflect.New(typ)
   207  		err := vals.ScanToGo(arg, ptr.Interface())
   208  		if err != nil {
   209  			return fmt.Errorf("wrong type of argument %d: %v", i, err)
   210  		}
   211  		in = append(in, ptr.Elem())
   212  	}
   213  
   214  	if b.inputs {
   215  		var inputs Inputs
   216  		if len(args) == len(b.normalArgs) {
   217  			inputs = f.IterateInputs
   218  		} else {
   219  			// Wrap an iterable argument in Inputs.
   220  			iterable := args[len(args)-1]
   221  			if !vals.CanIterate(iterable) {
   222  				return fmt.Errorf("%s cannot be iterated", vals.Kind(iterable))
   223  			}
   224  			inputs = func(f func(interface{})) {
   225  				// CanIterate(iterable) is true
   226  				_ = vals.Iterate(iterable, func(v interface{}) bool {
   227  					f(v)
   228  					return true
   229  				})
   230  			}
   231  		}
   232  		in = append(in, reflect.ValueOf(inputs))
   233  	}
   234  
   235  	outs := reflect.ValueOf(b.impl).Call(in)
   236  
   237  	if len(outs) > 0 && outs[len(outs)-1].Type() == errorType {
   238  		err := outs[len(outs)-1].Interface()
   239  		if err != nil {
   240  			return err.(error)
   241  		}
   242  		outs = outs[:len(outs)-1]
   243  	}
   244  
   245  	for _, out := range outs {
   246  		f.OutputChan() <- vals.FromGo(out.Interface())
   247  	}
   248  	return nil
   249  }