Added CanBind() call
This commit is contained in:
parent
79947e1813
commit
fb2da4928c
|
@ -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,
|
||||||
|
|
|
@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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()
|
||||||
|
|
Loading…
Reference in a new issue