Added CanBind() call

This commit is contained in:
Leon Mika 2024-04-28 10:07:20 +10:00
parent 79947e1813
commit fb2da4928c
3 changed files with 122 additions and 0 deletions

View File

@ -297,6 +297,9 @@ func (ia invocationArgs) fork(args []object) invocationArgs {
} }
func (ia invocationArgs) shift(i int) invocationArgs { func (ia invocationArgs) shift(i int) invocationArgs {
if len(ia.args) < i {
return ia
}
return invocationArgs{ return invocationArgs{
eval: ia.eval, eval: ia.eval,
inst: ia.inst, inst: ia.inst,

View File

@ -24,6 +24,23 @@ func (ca *CallArgs) Bind(vars ...interface{}) error {
return nil 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 { func (ca CallArgs) IsTopLevel() bool {
return ca.args.ec.parent == nil || ca.args.ec == ca.args.ec.root return ca.args.ec.parent == nil || ca.args.ec == ca.args.ec.root
} }
@ -71,6 +88,12 @@ func bindArg(v interface{}, arg object) error {
switch t := v.(type) { switch t := v.(type) {
case *string: case *string:
*t = arg.String() *t = arg.String()
case *int:
if iArg, ok := arg.(intObject); ok {
*t = int(iArg)
} else {
return errors.New("invalid arg")
}
} }
switch t := arg.(type) { switch t := arg.(type) {
@ -85,6 +108,27 @@ func bindArg(v interface{}, arg object) error {
return nil 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 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 { func bindProxyObject(v interface{}, r reflect.Value) error {
argValue := reflect.ValueOf(v) argValue := reflect.ValueOf(v)
if argValue.Kind() != reflect.Ptr { if argValue.Kind() != reflect.Ptr {
@ -103,3 +147,22 @@ func bindProxyObject(v interface{}, r reflect.Value) error {
r = r.Elem() 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()
}
}

View File

@ -214,6 +214,62 @@ func TestCallArgs_Bind(t *testing.T) {
}) })
} }
func TestCallArgs_CanBind(t *testing.T) {
t.Run("returns ture of all passed in arguments can be bound without consuming them", func(t *testing.T) {
tests := []struct {
descr string
eval string
want []string
}{
{descr: "bind nothing", eval: `test`, want: []string{}},
{descr: "bind one", eval: `test "yes"`, want: []string{"str"}},
{descr: "bind two", eval: `test "yes" 213`, want: []string{"str", "int"}},
{descr: "bind three", eval: `test "yes" 213 (proxy)`, want: []string{"all", "str", "int", "proxy"}},
}
for _, tt := range tests {
t.Run(tt.descr, func(t *testing.T) {
type proxyObj struct{}
ctx := context.Background()
res := make([]string, 0)
inst := ucl.New()
inst.SetBuiltin("proxy", func(ctx context.Context, args ucl.CallArgs) (any, error) {
return proxyObj{}, nil
})
inst.SetBuiltin("test", func(ctx context.Context, args ucl.CallArgs) (any, error) {
var (
s string
i int
p proxyObj
)
if args.CanBind(&s, &i, &p) {
res = append(res, "all")
}
if args.CanBind(&s) {
res = append(res, "str")
}
args.Shift(1)
if args.CanBind(&i) {
res = append(res, "int")
}
args.Shift(1)
if args.CanBind(&p) {
res = append(res, "proxy")
}
return nil, nil
})
_, err := inst.Eval(ctx, tt.eval)
assert.NoError(t, err)
assert.Equal(t, tt.want, res)
})
}
})
}
func TestCallArgs_IsTopLevel(t *testing.T) { func TestCallArgs_IsTopLevel(t *testing.T) {
t.Run("true if the command is running at the top-level frame", func(t *testing.T) { t.Run("true if the command is running at the top-level frame", func(t *testing.T) {
ctx := context.Background() ctx := context.Background()