Added 'add', 'filter' and 'reduce' builtins
This commit is contained in:
parent
ad746c05aa
commit
789add45bb
129
ucl/builtins.go
129
ucl/builtins.go
|
@ -4,6 +4,7 @@ import (
|
|||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
|
@ -28,6 +29,30 @@ func echoBuiltin(ctx context.Context, args invocationArgs) (object, error) {
|
|||
return nil, nil
|
||||
}
|
||||
|
||||
func addBuiltin(ctx context.Context, args invocationArgs) (object, error) {
|
||||
if len(args.args) == 0 {
|
||||
return intObject(0), nil
|
||||
}
|
||||
|
||||
n := 0
|
||||
for i, a := range args.args {
|
||||
switch t := a.(type) {
|
||||
case intObject:
|
||||
n += int(t)
|
||||
case strObject:
|
||||
v, err := strconv.Atoi(string(t))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("arg %v of 'add' not convertable to an int", i)
|
||||
}
|
||||
n += v
|
||||
default:
|
||||
return nil, fmt.Errorf("arg %v of 'add' not convertable to an int", i)
|
||||
}
|
||||
}
|
||||
|
||||
return intObject(n), nil
|
||||
}
|
||||
|
||||
func setBuiltin(ctx context.Context, args invocationArgs) (object, error) {
|
||||
if err := args.expectArgn(2); err != nil {
|
||||
return nil, err
|
||||
|
@ -181,8 +206,6 @@ func keysBuiltin(ctx context.Context, args invocationArgs) (object, error) {
|
|||
return nil, err
|
||||
}
|
||||
return keys, nil
|
||||
default:
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
|
@ -215,6 +238,108 @@ func mapBuiltin(ctx context.Context, args invocationArgs) (object, error) {
|
|||
return nil, errors.New("expected listable")
|
||||
}
|
||||
|
||||
func filterBuiltin(ctx context.Context, args invocationArgs) (object, error) {
|
||||
if err := args.expectArgn(2); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
inv, err := args.invokableArg(1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
switch t := args.args[0].(type) {
|
||||
case listable:
|
||||
l := t.Len()
|
||||
newList := listObject{}
|
||||
for i := 0; i < l; i++ {
|
||||
v := t.Index(i)
|
||||
m, err := inv.invoke(ctx, args.fork([]object{v}))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if m.Truthy() {
|
||||
newList = append(newList, v)
|
||||
}
|
||||
}
|
||||
return newList, nil
|
||||
case hashable:
|
||||
newHash := hashObject{}
|
||||
if err := t.Each(func(k string, v object) error {
|
||||
if m, err := inv.invoke(ctx, args.fork([]object{strObject(k), v})); err != nil {
|
||||
return err
|
||||
} else if m.Truthy() {
|
||||
newHash[k] = v
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return newHash, nil
|
||||
}
|
||||
return nil, errors.New("expected listable")
|
||||
}
|
||||
|
||||
func reduceBuiltin(ctx context.Context, args invocationArgs) (object, error) {
|
||||
var err error
|
||||
if err = args.expectArgn(2); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var (
|
||||
accum object
|
||||
setFirst bool
|
||||
block invokable
|
||||
)
|
||||
if len(args.args) == 3 {
|
||||
accum = args.args[1]
|
||||
block, err = args.invokableArg(2)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
setFirst = true
|
||||
block, err = args.invokableArg(1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
switch t := args.args[0].(type) {
|
||||
case listable:
|
||||
l := t.Len()
|
||||
for i := 0; i < l; i++ {
|
||||
v := t.Index(i)
|
||||
if setFirst {
|
||||
accum = v
|
||||
setFirst = false
|
||||
continue
|
||||
}
|
||||
|
||||
newAccum, err := block.invoke(ctx, args.fork([]object{v, accum}))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
accum = newAccum
|
||||
}
|
||||
return accum, nil
|
||||
case hashable:
|
||||
// TODO: should raise error?
|
||||
if err := t.Each(func(k string, v object) error {
|
||||
newAccum, err := block.invoke(ctx, args.fork([]object{strObject(k), v, accum}))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
accum = newAccum
|
||||
return nil
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return accum, nil
|
||||
}
|
||||
return nil, errors.New("expected listable")
|
||||
}
|
||||
|
||||
func firstBuiltin(ctx context.Context, args invocationArgs) (object, error) {
|
||||
if err := args.expectArgn(1); err != nil {
|
||||
return nil, err
|
||||
|
|
|
@ -55,9 +55,13 @@ func New(opts ...InstOption) *Inst {
|
|||
rootEC.addCmd("call", invokableFunc(callBuiltin))
|
||||
|
||||
rootEC.addCmd("map", invokableFunc(mapBuiltin))
|
||||
rootEC.addCmd("filter", invokableFunc(filterBuiltin))
|
||||
rootEC.addCmd("head", invokableFunc(firstBuiltin))
|
||||
rootEC.addCmd("reduce", invokableFunc(reduceBuiltin))
|
||||
|
||||
rootEC.addCmd("eq", invokableFunc(eqBuiltin))
|
||||
rootEC.addCmd("add", invokableFunc(addBuiltin))
|
||||
|
||||
rootEC.addCmd("cat", invokableFunc(concatBuiltin))
|
||||
rootEC.addCmd("break", invokableFunc(breakBuiltin))
|
||||
rootEC.addCmd("continue", invokableFunc(continueBuiltin))
|
||||
|
|
|
@ -697,3 +697,60 @@ func TestBuiltins_Keys(t *testing.T) {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuiltins_Filter(t *testing.T) {
|
||||
tests := []struct {
|
||||
desc string
|
||||
expr string
|
||||
want any
|
||||
}{
|
||||
{desc: "filter list 1", expr: `filter [1 2 3] { |x| eq $x 2 }`, want: []any{2}},
|
||||
{desc: "filter list 2", expr: `filter ["flim" "flam" "fla"] { |x| eq $x "flam" }`, want: []any{"flam"}},
|
||||
{desc: "filter list 3", expr: `filter ["flim" "flam" "fla"] { |x| eq $x "bogie" }`, want: []any{}},
|
||||
|
||||
{desc: "filter map 1", expr: `filter [alpha:"hello" bravo:"world"] { |k v| eq $k "alpha" }`, want: map[string]any{
|
||||
"alpha": "hello",
|
||||
}},
|
||||
{desc: "filter map 2", expr: `filter [alpha:"hello" bravo:"world"] { |k v| eq $v "world" }`, want: map[string]any{
|
||||
"bravo": "world",
|
||||
}},
|
||||
{desc: "filter map 3", expr: `filter [alpha:"hello" bravo:"world"] { |k v| eq $v "alpha" }`, want: map[string]any{}},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.desc, func(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
outW := bytes.NewBuffer(nil)
|
||||
|
||||
inst := New(WithOut(outW), WithTestBuiltin())
|
||||
|
||||
res, err := inst.Eval(ctx, tt.expr)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tt.want, res)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuiltins_Reduce(t *testing.T) {
|
||||
tests := []struct {
|
||||
desc string
|
||||
expr string
|
||||
want any
|
||||
}{
|
||||
{desc: "reduce list 1", expr: `reduce [1 1 1] { |x a| add $x $a }`, want: 3},
|
||||
{desc: "reduce list 2", expr: `reduce [1 1 1] 20 { |x a| add $x $a }`, want: 23},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.desc, func(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
outW := bytes.NewBuffer(nil)
|
||||
|
||||
inst := New(WithOut(outW), WithTestBuiltin())
|
||||
|
||||
res, err := inst.Eval(ctx, tt.expr)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tt.want, res)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue