src.elv.sh@v0.21.0-dev.0.20240515223629-06979efb9a2a/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 // 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 any 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(any)) 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 any) 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 any) 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 []any, opts map[string]any) 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(any)) { 237 // CanIterate(iterable) is true 238 _ = vals.Iterate(iterable, func(v any) 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 }