Added support for "opaque" return types
These are return types that are left alone by the UCL interpretor and cannot be operated on or get fields of.
This commit is contained in:
parent
505fd29032
commit
88417aba95
20
ucl/objs.go
20
ucl/objs.go
|
@ -110,6 +110,8 @@ func (b boolObject) Truthy() bool {
|
||||||
|
|
||||||
func toGoValue(obj object) (interface{}, bool) {
|
func toGoValue(obj object) (interface{}, bool) {
|
||||||
switch v := obj.(type) {
|
switch v := obj.(type) {
|
||||||
|
case OpaqueObject:
|
||||||
|
return v.v, true
|
||||||
case nil:
|
case nil:
|
||||||
return nil, true
|
return nil, true
|
||||||
case strObject:
|
case strObject:
|
||||||
|
@ -149,6 +151,8 @@ func toGoValue(obj object) (interface{}, bool) {
|
||||||
|
|
||||||
func fromGoValue(v any) (object, error) {
|
func fromGoValue(v any) (object, error) {
|
||||||
switch t := v.(type) {
|
switch t := v.(type) {
|
||||||
|
case OpaqueObject:
|
||||||
|
return t, nil
|
||||||
case nil:
|
case nil:
|
||||||
return nil, nil
|
return nil, nil
|
||||||
case string:
|
case string:
|
||||||
|
@ -476,6 +480,22 @@ func (s structProxyObject) Each(fn func(k string, v object) error) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type OpaqueObject struct {
|
||||||
|
v any
|
||||||
|
}
|
||||||
|
|
||||||
|
func Opaque(v any) OpaqueObject {
|
||||||
|
return OpaqueObject{v: v}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p OpaqueObject) String() string {
|
||||||
|
return fmt.Sprintf("opaque{%T}", p.v)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p OpaqueObject) Truthy() bool {
|
||||||
|
return p.v != nil
|
||||||
|
}
|
||||||
|
|
||||||
type errBreak struct {
|
type errBreak struct {
|
||||||
isCont bool
|
isCont bool
|
||||||
ret object
|
ret object
|
||||||
|
|
|
@ -121,6 +121,23 @@ func (ca CallArgs) bindArg(v interface{}, arg object) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
switch t := arg.(type) {
|
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:
|
case proxyObject:
|
||||||
return bindProxyObject(v, reflect.ValueOf(t.p))
|
return bindProxyObject(v, reflect.ValueOf(t.p))
|
||||||
case listableProxyObject:
|
case listableProxyObject:
|
||||||
|
@ -142,6 +159,18 @@ func canBindArg(v interface{}, arg object) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
switch t := arg.(type) {
|
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:
|
case proxyObject:
|
||||||
return canBindProxyObject(v, reflect.ValueOf(t.p))
|
return canBindProxyObject(v, reflect.ValueOf(t.p))
|
||||||
case listableProxyObject:
|
case listableProxyObject:
|
||||||
|
|
|
@ -183,6 +183,105 @@ func TestInst_SetBuiltin(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
t.Run("opaques returned as is", func(t *testing.T) {
|
||||||
|
type opaqueThingType struct {
|
||||||
|
x string
|
||||||
|
y string
|
||||||
|
z string
|
||||||
|
}
|
||||||
|
opaqueThing := &opaqueThingType{x: "do", y: "not", z: "touch"}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
descr string
|
||||||
|
expr string
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{descr: "return as is", expr: `getOpaque`, wantErr: false},
|
||||||
|
{descr: "carry around ok", expr: `set x (getOpaque) ; $x`, wantErr: false},
|
||||||
|
{descr: "iterate over", expr: `foreach (countTo3) { |x| echo $x }`, wantErr: true},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.descr, func(t *testing.T) {
|
||||||
|
|
||||||
|
outW := bytes.NewBuffer(nil)
|
||||||
|
inst := ucl.New(ucl.WithOut(outW))
|
||||||
|
|
||||||
|
inst.SetBuiltin("getOpaque", func(ctx context.Context, args ucl.CallArgs) (any, error) {
|
||||||
|
return ucl.Opaque(opaqueThing), nil
|
||||||
|
})
|
||||||
|
|
||||||
|
res, err := inst.Eval(context.Background(), tt.expr)
|
||||||
|
if tt.wantErr {
|
||||||
|
assert.Error(t, err)
|
||||||
|
} else {
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Same(t, opaqueThing, res)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("operate on opaques", func(t *testing.T) {
|
||||||
|
type opaqueThingType struct {
|
||||||
|
x string
|
||||||
|
y string
|
||||||
|
z string
|
||||||
|
}
|
||||||
|
opaqueThing := &opaqueThingType{x: "do", y: "not", z: "touch"}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
descr string
|
||||||
|
expr string
|
||||||
|
want opaqueThingType
|
||||||
|
}{
|
||||||
|
{descr: "return as is", expr: `getOpaque`, want: *opaqueThing},
|
||||||
|
{descr: "update pointer 1", expr: `set x (getOpaque) ; setProp $x -x "do" -y "touch" -z "this"`, want: opaqueThingType{x: "do", y: "touch", z: "this"}},
|
||||||
|
{descr: "update pointer 2", expr: `set x (getOpaque) ; setProp $x -x "yes" ; setProp $x -y "this" -z "too"`, want: opaqueThingType{x: "yes", y: "this", z: "too"}},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.descr, func(t *testing.T) {
|
||||||
|
|
||||||
|
outW := bytes.NewBuffer(nil)
|
||||||
|
inst := ucl.New(ucl.WithOut(outW))
|
||||||
|
|
||||||
|
inst.SetBuiltin("getOpaque", func(ctx context.Context, args ucl.CallArgs) (any, error) {
|
||||||
|
return ucl.Opaque(opaqueThing), nil
|
||||||
|
})
|
||||||
|
inst.SetBuiltin("setProp", func(ctx context.Context, args ucl.CallArgs) (any, error) {
|
||||||
|
var o *opaqueThingType
|
||||||
|
|
||||||
|
if err := args.Bind(&o); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if args.HasSwitch("x") {
|
||||||
|
var s string
|
||||||
|
_ = args.BindSwitch("x", &s)
|
||||||
|
o.x = s
|
||||||
|
}
|
||||||
|
if args.HasSwitch("y") {
|
||||||
|
var s string
|
||||||
|
_ = args.BindSwitch("y", &s)
|
||||||
|
o.y = s
|
||||||
|
}
|
||||||
|
if args.HasSwitch("z") {
|
||||||
|
var s string
|
||||||
|
_ = args.BindSwitch("z", &s)
|
||||||
|
o.z = s
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, nil
|
||||||
|
})
|
||||||
|
|
||||||
|
_, err := inst.Eval(context.Background(), tt.expr)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, tt.want, *opaqueThing)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCallArgs_Bind(t *testing.T) {
|
func TestCallArgs_Bind(t *testing.T) {
|
||||||
|
|
Loading…
Reference in New Issue