github.com/markusbkk/elvish@v0.0.0-20231204143114-91dc52438621/pkg/eval/go_fn.go (about)

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