Added support for listable proxy objects

These are things like slices that can be iterated over. At the moment, they're only recognised by foreach.
This commit is contained in:
Leon Mika 2024-04-23 00:41:36 +00:00
parent 690112fee1
commit 4c532e5005
3 changed files with 80 additions and 4 deletions

View file

@ -252,8 +252,10 @@ func foreachBuiltin(ctx context.Context, args macroArgs) (object, error) {
var last object
switch t := items.(type) {
case listObject:
for _, v := range t {
case listable:
l := t.Len()
for i := 0; i < l; i++ {
v := t.Index(i)
last, err = args.evalBlock(ctx, 1, []object{v}, true) // TO INCLUDE: the index
if err != nil {
return nil, err

View file

@ -4,6 +4,7 @@ import (
"context"
"errors"
"fmt"
"reflect"
"strconv"
)
@ -12,6 +13,11 @@ type object interface {
Truthy() bool
}
type listable interface {
Len() int
Index(i int) object
}
type listObject []object
func (lo *listObject) Append(o object) {
@ -26,6 +32,14 @@ func (s listObject) Truthy() bool {
return len(s) > 0
}
func (s listObject) Len() int {
return len(s)
}
func (s listObject) Index(i int) object {
return s[i]
}
type hashObject map[string]object
func (s hashObject) String() string {
@ -87,6 +101,8 @@ func toGoValue(obj object) (interface{}, bool) {
return xs, true
case proxyObject:
return v.p, true
case listableProxyObject:
return v.v.Interface(), true
}
return nil, false
@ -98,9 +114,14 @@ func fromGoValue(v any) (object, error) {
return nil, nil
case string:
return strObject(t), nil
default:
return proxyObject{t}, nil
}
resVal := reflect.ValueOf(v)
if resVal.Type().Kind() == reflect.Slice {
return listableProxyObject{resVal}, nil
}
return proxyObject{v}, nil
}
type macroArgs struct {
@ -309,3 +330,27 @@ func (p proxyObject) Truthy() bool {
//TODO implement me
panic("implement me")
}
type listableProxyObject struct {
v reflect.Value
}
func (p listableProxyObject) String() string {
return fmt.Sprintf("listableProxyObject{%v}", p.v.Type())
}
func (p listableProxyObject) Truthy() bool {
panic("implement me")
}
func (p listableProxyObject) Len() int {
return p.v.Len()
}
func (p listableProxyObject) Index(i int) object {
e, err := fromGoValue(p.v.Index(i).Interface())
if err != nil {
return nil
}
return e
}

View file

@ -1,6 +1,7 @@
package cmdlang_test
import (
"bytes"
"context"
"strings"
"testing"
@ -132,6 +133,34 @@ func TestInst_SetBuiltin(t *testing.T) {
})
}
})
t.Run("slices returned by commands treated as lists", func(t *testing.T) {
tests := []struct {
descr string
expr string
want any
wantOut string
}{
{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"},
}
for _, tt := range tests {
t.Run(tt.descr, func(t *testing.T) {
outW := bytes.NewBuffer(nil)
inst := cmdlang.New(cmdlang.WithOut(outW))
inst.SetBuiltin("countTo3", func(ctx context.Context, args cmdlang.CallArgs) (any, error) {
return []string{"1", "2", "3"}, nil
})
res, err := inst.Eval(context.Background(), tt.expr)
assert.NoError(t, err)
assert.Equal(t, tt.want, res)
assert.Equal(t, tt.wantOut, outW.String())
})
}
})
}
func TestCallArgs_Bind(t *testing.T) {