diff --git a/ucl/builtins.go b/ucl/builtins.go index f6ad77f..56c8f56 100644 --- a/ucl/builtins.go +++ b/ucl/builtins.go @@ -354,8 +354,8 @@ func objectsEqual(l, r Object) bool { } } return true - case hashable: - rv, ok := r.(hashable) + case Hashable: + rv, ok := r.(Hashable) if !ok { return false } @@ -469,7 +469,7 @@ func lenBuiltin(ctx context.Context, args invocationArgs) (Object, error) { return IntObject(len(string(v))), nil case Listable: return IntObject(v.Len()), nil - case hashable: + case Hashable: return IntObject(v.Len()), nil case Iterable: cnt := 0 @@ -497,10 +497,10 @@ func indexLookup(ctx context.Context, obj, elem Object) (Object, error) { return v.Index(int(intIdx)), nil } return nil, nil - case hashable: + case Hashable: strIdx, ok := elem.(StringObject) if !ok { - return nil, errors.New("expected string for hashable") + return nil, errors.New("expected string for Hashable") } return v.Value(string(strIdx)), nil } @@ -531,7 +531,7 @@ func keysBuiltin(ctx context.Context, args invocationArgs) (Object, error) { val := args.args[0] switch v := val.(type) { - case hashable: + case Hashable: keys := make(ListObject, 0, v.Len()) if err := v.Each(func(k string, _ Object) error { keys = append(keys, StringObject(k)) @@ -676,7 +676,7 @@ func filterBuiltin(ctx context.Context, args invocationArgs) (Object, error) { } } return &newList, nil - case hashable: + case Hashable: newHash := hashObject{} if err := t.Each(func(k string, v Object) error { if m, err := inv.invoke(ctx, args.fork([]Object{StringObject(k), v})); err != nil { @@ -741,7 +741,7 @@ func reduceBuiltin(ctx context.Context, args invocationArgs) (Object, error) { accum = newAccum } return accum, nil - case hashable: + case Hashable: // TODO: should raise error? if err := t.Each(func(k string, v Object) error { newAccum, err := block.invoke(ctx, args.fork([]Object{StringObject(k), v, accum})) @@ -942,7 +942,7 @@ func foreachBuiltin(ctx context.Context, args macroArgs) (Object, error) { } } } - case hashable: + case Hashable: err := t.Each(func(k string, v Object) error { last, err = args.evalBlock(ctx, blockIdx, []Object{StringObject(k), v}, false) return err diff --git a/ucl/objs.go b/ucl/objs.go index 5dbf158..b4b0c2a 100644 --- a/ucl/objs.go +++ b/ucl/objs.go @@ -40,7 +40,7 @@ type ModListable interface { Insert(idx int, obj Object) error } -type hashable interface { +type Hashable interface { Len() int Value(k string) Object Each(func(k string, v Object) error) error diff --git a/ucl/userbuiltin.go b/ucl/userbuiltin.go index ad39095..e38a2f8 100644 --- a/ucl/userbuiltin.go +++ b/ucl/userbuiltin.go @@ -127,6 +127,13 @@ func (ca CallArgs) bindArg(v interface{}, arg Object) error { return nil } return errors.New("exepected listable") + case *Hashable: + i, ok := arg.(Hashable) + if !ok { + return errors.New("exepected hashable") + } + *t = i + return nil case *Iterable: if i, ok := arg.(Iterable); ok { *t = i diff --git a/ucl/userbuiltin_test.go b/ucl/userbuiltin_test.go index 5a0e1c0..9c631e7 100644 --- a/ucl/userbuiltin_test.go +++ b/ucl/userbuiltin_test.go @@ -301,6 +301,49 @@ func TestCallArgs_CanBind(t *testing.T) { }) } + t.Run("can bind Hashable", func(t *testing.T) { + tests := []struct { + descr string + eval string + want any + wantErr bool + }{ + {descr: "return key 1", eval: `keyval [a:"hello" b:"world"] "a"`, want: "hello"}, + {descr: "return key 2", eval: `keyval [a:"hello" b:"world"] "b"`, want: "world"}, + {descr: "return key 3", eval: `keyval (keyval [a:"hello" b:[c:"fla"]] "b") c`, want: "fla"}, + + {descr: "err 1", eval: `keyval not-a-hashable "b"`, wantErr: true}, + } + + for _, tt := range tests { + t.Run(tt.descr, func(t *testing.T) { + ctx := context.Background() + + inst := ucl.New() + inst.SetBuiltin("keyval", func(ctx context.Context, args ucl.CallArgs) (any, error) { + var ( + h ucl.Hashable + k string + ) + if err := args.Bind(&h, &k); err != nil { + return nil, err + } + + return h.Value(k), nil + }) + + res, err := inst.Eval(ctx, tt.eval) + if tt.wantErr { + assert.Error(t, err) + assert.Nil(t, res) + } else { + assert.NoError(t, err) + assert.Equal(t, tt.want, res) + } + }) + } + }) + t.Run("can bind invokable", func(t *testing.T) { inst := ucl.New() inst.SetBuiltin("toUpper", func(ctx context.Context, args ucl.CallArgs) (any, error) {