Fixed binding with struct
This commit is contained in:
parent
24a484ab77
commit
43098fa227
|
@ -113,6 +113,38 @@ func callBuiltin(ctx context.Context, args invocationArgs) (object, error) {
|
|||
return inv.invoke(ctx, args.shift(1))
|
||||
}
|
||||
|
||||
func indexBuiltin(ctx context.Context, args invocationArgs) (object, error) {
|
||||
if err := args.expectArgn(1); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
val := args.args[0]
|
||||
for _, idx := range args.args[1:] {
|
||||
switch v := val.(type) {
|
||||
case listable:
|
||||
intIdx, ok := idx.(intObject)
|
||||
if !ok {
|
||||
return nil, errors.New("expected int for listable")
|
||||
}
|
||||
if int(intIdx) >= 0 && int(intIdx) < v.Len() {
|
||||
val = v.Index(int(intIdx))
|
||||
} else {
|
||||
val = nil
|
||||
}
|
||||
case hashable:
|
||||
strIdx, ok := idx.(strObject)
|
||||
if !ok {
|
||||
return nil, errors.New("expected string for hashable")
|
||||
}
|
||||
val = v.Value(string(strIdx))
|
||||
default:
|
||||
return val, nil
|
||||
}
|
||||
}
|
||||
|
||||
return val, nil
|
||||
}
|
||||
|
||||
func mapBuiltin(ctx context.Context, args invocationArgs) (object, error) {
|
||||
if err := args.expectArgn(2); err != nil {
|
||||
return nil, err
|
||||
|
|
|
@ -31,6 +31,7 @@ func New(opts ...InstOption) *Inst {
|
|||
rootEC.addCmd("set", invokableFunc(setBuiltin))
|
||||
rootEC.addCmd("toUpper", invokableFunc(toUpperBuiltin))
|
||||
//rootEC.addCmd("cat", invokableFunc(catBuiltin))
|
||||
rootEC.addCmd("index", invokableFunc(indexBuiltin))
|
||||
rootEC.addCmd("call", invokableFunc(callBuiltin))
|
||||
|
||||
rootEC.addCmd("map", invokableFunc(mapBuiltin))
|
||||
|
|
|
@ -20,6 +20,7 @@ type listable interface {
|
|||
|
||||
type hashable interface {
|
||||
Len() int
|
||||
Value(k string) object
|
||||
Each(func(k string, v object) error) error
|
||||
}
|
||||
|
||||
|
@ -59,6 +60,10 @@ func (s hashObject) Len() int {
|
|||
return len(s)
|
||||
}
|
||||
|
||||
func (s hashObject) Value(k string) object {
|
||||
return s[k]
|
||||
}
|
||||
|
||||
func (s hashObject) Each(fn func(k string, v object) error) error {
|
||||
for k, v := range s {
|
||||
if err := fn(k, v); err != nil {
|
||||
|
@ -133,6 +138,8 @@ func toGoValue(obj object) (interface{}, bool) {
|
|||
return v.p, true
|
||||
case listableProxyObject:
|
||||
return v.v.Interface(), true
|
||||
case structProxyObject:
|
||||
return v.v.Interface(), true
|
||||
}
|
||||
|
||||
return nil, false
|
||||
|
@ -144,11 +151,16 @@ func fromGoValue(v any) (object, error) {
|
|||
return nil, nil
|
||||
case string:
|
||||
return strObject(t), nil
|
||||
case int:
|
||||
return intObject(t), nil
|
||||
}
|
||||
|
||||
resVal := reflect.ValueOf(v)
|
||||
if resVal.Type().Kind() == reflect.Slice {
|
||||
switch resVal.Kind() {
|
||||
case reflect.Slice:
|
||||
return listableProxyObject{resVal}, nil
|
||||
case reflect.Struct:
|
||||
return structProxyObject{resVal}, nil
|
||||
}
|
||||
|
||||
return proxyObject{v}, nil
|
||||
|
@ -378,3 +390,42 @@ func (p listableProxyObject) Index(i int) object {
|
|||
}
|
||||
return e
|
||||
}
|
||||
|
||||
type structProxyObject struct {
|
||||
v reflect.Value
|
||||
}
|
||||
|
||||
func (s structProxyObject) String() string {
|
||||
return fmt.Sprintf("structProxyObject{%v}", s.v.Type())
|
||||
}
|
||||
|
||||
func (s structProxyObject) Truthy() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (s structProxyObject) Len() int {
|
||||
return s.v.Type().NumField()
|
||||
}
|
||||
|
||||
func (s structProxyObject) Value(k string) object {
|
||||
e, err := fromGoValue(s.v.FieldByName(k).Interface())
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return e
|
||||
}
|
||||
|
||||
func (s structProxyObject) Each(fn func(k string, v object) error) error {
|
||||
for i := 0; i < s.v.Type().NumField(); i++ {
|
||||
f := s.v.Type().Field(i).Name
|
||||
v, err := fromGoValue(s.v.Field(i).Interface())
|
||||
if err != nil {
|
||||
v = nil
|
||||
}
|
||||
|
||||
if err := fn(f, v); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -188,6 +188,7 @@ func TestBuiltins_ForEach(t *testing.T) {
|
|||
// TODO: hash is not sorted, so need to find a way to sort it
|
||||
{desc: "iterate over map", expr: `
|
||||
foreach [a:"1"] { |k v| echo $k "=" $v }`, want: "a=1\n(nil)\n"},
|
||||
{desc: "iterate via pipe", expr: `["2" "4" "6"] | foreach { |x| echo $x }`, want: "2\n4\n6\n(nil)\n"},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
|
@ -329,3 +330,59 @@ func TestBuiltins_Map(t *testing.T) {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuiltins_Index(t *testing.T) {
|
||||
tests := []struct {
|
||||
desc string
|
||||
expr string
|
||||
want string
|
||||
}{
|
||||
{desc: "index from list 1", expr: `index ["alpha" "beta" "gamma"] 0`, want: "alpha\n"},
|
||||
{desc: "index from list 2", expr: `index ["alpha" "beta" "gamma"] 1`, want: "beta\n"},
|
||||
{desc: "index from list 3", expr: `index ["alpha" "beta" "gamma"] 2`, want: "gamma\n"},
|
||||
{desc: "index from list 4", expr: `index ["alpha" "beta" "gamma"] 3`, want: "(nil)\n"},
|
||||
|
||||
{desc: "index from hash 1", expr: `index ["first":"alpha" "second":"beta" "third":"gamma"] "first"`, want: "alpha\n"},
|
||||
{desc: "index from hash 2", expr: `index ["first":"alpha" "second":"beta" "third":"gamma"] "second"`, want: "beta\n"},
|
||||
{desc: "index from hash 3", expr: `index ["first":"alpha" "second":"beta" "third":"gamma"] "third"`, want: "gamma\n"},
|
||||
{desc: "index from hash 4", expr: `index ["first":"alpha" "second":"beta" "third":"gamma"] "missing"`, want: "(nil)\n"},
|
||||
|
||||
{desc: "multi-list 1", expr: `index [[1 2] [3 4]] 0 1`, want: "2\n"},
|
||||
{desc: "multi-list 2", expr: `index [[1 2] [3 4]] 1 0`, want: "3\n"},
|
||||
{desc: "list of hash 1", expr: `index [["id":"abc"] ["id":"123"]] 0 id`, want: "abc\n"},
|
||||
{desc: "list of hash 2", expr: `index [["id":"abc"] ["id":"123"]] 1 id`, want: "123\n"},
|
||||
|
||||
{desc: "go list 1", expr: `goInt | index 1`, want: "5\n"},
|
||||
{desc: "go list 2", expr: `goInt | index 2`, want: "4\n"},
|
||||
{desc: "go struct 1", expr: `goStruct | index Alpha`, want: "foo\n"},
|
||||
{desc: "go struct 2", expr: `goStruct | index Beta`, want: "bar\n"},
|
||||
{desc: "go struct 3", expr: `goStruct | index Gamma 1`, want: "33\n"},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.desc, func(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
outW := bytes.NewBuffer(nil)
|
||||
|
||||
inst := New(WithOut(outW), WithTestBuiltin())
|
||||
inst.SetBuiltin("goInt", func(ctx context.Context, args CallArgs) (any, error) {
|
||||
return []int{6, 5, 4}, nil
|
||||
})
|
||||
inst.SetBuiltin("goStruct", func(ctx context.Context, args CallArgs) (any, error) {
|
||||
return struct {
|
||||
Alpha string
|
||||
Beta string
|
||||
Gamma []int
|
||||
}{
|
||||
Alpha: "foo",
|
||||
Beta: "bar",
|
||||
Gamma: []int{22, 33},
|
||||
}, nil
|
||||
})
|
||||
err := inst.EvalAndDisplay(ctx, tt.expr)
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tt.want, outW.String())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -68,18 +68,33 @@ func bindArg(v interface{}, arg object) error {
|
|||
*t = arg.String()
|
||||
}
|
||||
|
||||
// Check for proxy object
|
||||
if po, ok := arg.(proxyObject); ok {
|
||||
poValue := reflect.ValueOf(po.p)
|
||||
argValue := reflect.ValueOf(v)
|
||||
|
||||
if argValue.Type().Kind() != reflect.Pointer {
|
||||
return nil
|
||||
} else if !poValue.Type().AssignableTo(argValue.Elem().Type()) {
|
||||
return nil
|
||||
switch t := arg.(type) {
|
||||
case proxyObject:
|
||||
return bindProxyObject(v, reflect.ValueOf(t.p))
|
||||
case listableProxyObject:
|
||||
return bindProxyObject(v, t.v)
|
||||
case structProxyObject:
|
||||
return bindProxyObject(v, t.v)
|
||||
}
|
||||
|
||||
argValue.Elem().Set(poValue)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func bindProxyObject(v interface{}, r reflect.Value) error {
|
||||
argValue := reflect.ValueOf(v)
|
||||
if argValue.Kind() != reflect.Ptr {
|
||||
return errors.New("v must be a pointer to a struct")
|
||||
}
|
||||
|
||||
for {
|
||||
if r.Type().AssignableTo(argValue.Elem().Type()) {
|
||||
argValue.Elem().Set(r)
|
||||
return nil
|
||||
}
|
||||
if r.Type().Kind() != reflect.Pointer {
|
||||
return nil
|
||||
}
|
||||
|
||||
r = r.Elem()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -143,7 +143,6 @@ func TestInst_SetBuiltin(t *testing.T) {
|
|||
}{
|
||||
{descr: "return as is", expr: `countTo3`, want: []string{"1", "2", "3"}},
|
||||
{descr: "iterate over", expr: `foreach (countTo3) { |x| echo $x }`, wantOut: "1\n2\n3\n"},
|
||||
{descr: "iterate via foreach", expr: `["2" "4" "6"] | foreach { |x| echo $x }`, wantOut: "2\n4\n6\n"},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
|
|
Loading…
Reference in a new issue