ucl/ucl/userbuiltin.go

286 lines
5.4 KiB
Go

package ucl
import (
"context"
"errors"
"reflect"
"github.com/lmika/gopkgs/fp/slices"
)
type BuiltinHandler func(ctx context.Context, args CallArgs) (any, error)
type MissingBuiltinHandler func(ctx context.Context, name string, args CallArgs) (any, error)
type CallArgs struct {
args invocationArgs
}
func (ca *CallArgs) NArgs() int {
return len(ca.args.args)
}
func (ca *CallArgs) Bind(vars ...interface{}) error {
if len(ca.args.args) < len(vars) {
return errors.New("wrong number of arguments")
}
for i, v := range vars {
if err := ca.bindArg(v, ca.args.args[i]); err != nil {
return err
}
}
ca.args = ca.args.shift(len(vars))
return nil
}
func (ca *CallArgs) CanBind(vars ...interface{}) bool {
if len(ca.args.args) < len(vars) {
return false
}
for i, v := range vars {
if !canBindArg(v, ca.args.args[i]) {
return false
}
}
return true
}
func (ca *CallArgs) Shift(n int) {
ca.args = ca.args.shift(n)
}
func (ca CallArgs) IsTopLevel() bool {
return ca.args.ec.parent == nil || ca.args.ec == ca.args.ec.root
}
func (ca CallArgs) HasSwitch(name string) bool {
if ca.args.kwargs == nil {
return false
}
_, ok := ca.args.kwargs[name]
return ok
}
func (ca CallArgs) BindSwitch(name string, val interface{}) error {
if ca.args.kwargs == nil {
return nil
}
vars, ok := ca.args.kwargs[name]
if !ok || len(*vars) != 1 {
return nil
}
return ca.bindArg(val, (*vars)[0])
}
func (inst *Inst) SetBuiltin(name string, fn BuiltinHandler) {
inst.rootEC.addCmd(name, userBuiltin{fn: fn})
}
type userBuiltin struct {
fn func(ctx context.Context, args CallArgs) (any, error)
}
func (u userBuiltin) invoke(ctx context.Context, args invocationArgs) (object, error) {
v, err := u.fn(ctx, CallArgs{args: args})
if err != nil {
return nil, err
}
return fromGoValue(v)
}
func (ca CallArgs) bindArg(v interface{}, arg object) error {
switch t := v.(type) {
case *interface{}:
*t, _ = toGoValue(arg)
case *Invokable:
i, ok := arg.(invokable)
if !ok {
return errors.New("exepected invokable")
}
*t = Invokable{
inv: i,
eval: ca.args.eval,
inst: ca.args.inst,
ec: ca.args.ec,
}
return nil
case *string:
if arg != nil {
*t = arg.String()
} else {
*t = ""
}
case *int:
if iArg, ok := arg.(intObject); ok {
*t = int(iArg)
} else {
return errors.New("invalid arg")
}
}
switch t := arg.(type) {
case OpaqueObject:
if v == nil {
return errors.New("opaque object not bindable to nil")
}
vr := reflect.ValueOf(v)
tr := reflect.ValueOf(t.v)
if vr.Kind() != reflect.Pointer {
return errors.New("expected pointer for an opaque object bind")
}
if !tr.Type().AssignableTo(vr.Elem().Type()) {
return errors.New("opaque object not assignable to passed in value")
}
vr.Elem().Set(tr)
return nil
case proxyObject:
return bindProxyObject(v, reflect.ValueOf(t.p))
case listableProxyObject:
return bindProxyObject(v, t.v)
case structProxyObject:
return bindProxyObject(v, t.v)
}
return nil
}
func canBindArg(v interface{}, arg object) bool {
switch v.(type) {
case *string:
return true
case *int:
_, ok := arg.(intObject)
return ok
}
switch t := arg.(type) {
case OpaqueObject:
vr := reflect.ValueOf(v)
tr := reflect.ValueOf(t.v)
if vr.Kind() != reflect.Pointer {
return false
}
if !tr.Type().AssignableTo(vr.Elem().Type()) {
return false
}
return true
case proxyObject:
return canBindProxyObject(v, reflect.ValueOf(t.p))
case listableProxyObject:
return canBindProxyObject(v, t.v)
case structProxyObject:
return canBindProxyObject(v, t.v)
}
return true
}
func bindProxyObject(v interface{}, r reflect.Value) error {
argValue := reflect.ValueOf(v)
if argValue.Kind() != reflect.Ptr {
return errors.New("v must be a pointer to a struct")
}
for {
if r.Type().AssignableTo(argValue.Elem().Type()) {
argValue.Elem().Set(r)
return nil
}
if r.Type().Kind() != reflect.Pointer {
return nil
}
r = r.Elem()
}
}
func canBindProxyObject(v interface{}, r reflect.Value) bool {
argValue := reflect.ValueOf(v)
if argValue.Kind() != reflect.Ptr {
return false
}
for {
if r.Type().AssignableTo(argValue.Elem().Type()) {
argValue.Elem().Set(r)
return true
}
if r.Type().Kind() != reflect.Pointer {
return true
}
r = r.Elem()
}
}
func (inst *Inst) missingHandlerInvokable(name string) missingHandlerInvokable {
return missingHandlerInvokable{name: name, handler: inst.missingBuiltinHandler}
}
type missingHandlerInvokable struct {
name string
handler MissingBuiltinHandler
}
func (m missingHandlerInvokable) invoke(ctx context.Context, args invocationArgs) (object, error) {
v, err := m.handler(ctx, m.name, CallArgs{args: args})
if err != nil {
return nil, err
}
return fromGoValue(v)
}
type Invokable struct {
inv invokable
eval evaluator
inst *Inst
ec *evalCtx
}
func (i Invokable) IsNil() bool {
return i.inv == nil
}
func (i Invokable) Invoke(ctx context.Context, args ...any) (any, error) {
if i.inv == nil {
return nil, nil
}
var err error
invArgs := invocationArgs{
eval: i.eval,
ec: i.ec,
inst: i.inst,
}
invArgs.args, err = slices.MapWithError(args, func(a any) (object, error) {
return fromGoValue(a)
})
if err != nil {
return nil, err
}
res, err := i.inv.invoke(ctx, invArgs)
if err != nil {
return nil, err
}
goRes, ok := toGoValue(res)
if !ok {
return nil, errors.New("cannot convert result to Go Value")
}
return goRes, err
}