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) {
|
||||
switch v := obj.(type) {
|
||||
case OpaqueObject:
|
||||
return v.v, true
|
||||
case nil:
|
||||
return nil, true
|
||||
case strObject:
|
||||
|
@ -149,6 +151,8 @@ func toGoValue(obj object) (interface{}, bool) {
|
|||
|
||||
func fromGoValue(v any) (object, error) {
|
||||
switch t := v.(type) {
|
||||
case OpaqueObject:
|
||||
return t, nil
|
||||
case nil:
|
||||
return nil, nil
|
||||
case string:
|
||||
|
@ -476,6 +480,22 @@ func (s structProxyObject) Each(fn func(k string, v object) error) error {
|
|||
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 {
|
||||
isCont bool
|
||||
ret object
|
||||
|
|
|
@ -121,6 +121,23 @@ func (ca CallArgs) bindArg(v interface{}, arg object) error {
|
|||
}
|
||||
|
||||
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:
|
||||
|
@ -142,6 +159,18 @@ func canBindArg(v interface{}, arg object) bool {
|
|||
}
|
||||
|
||||
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:
|
||||
|
|
|
@ -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) {
|
||||
|
|
Loading…
Reference in a new issue