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 }