Added the seq builtin

This commit is contained in:
Leon Mika 2024-09-04 22:18:15 +10:00
parent 7bb26465f2
commit 20ea8bac06
4 changed files with 144 additions and 0 deletions

View File

@ -361,6 +361,73 @@ func firstBuiltin(ctx context.Context, args invocationArgs) (object, error) {
return nil, errors.New("expected listable") return nil, errors.New("expected listable")
} }
type seqObject struct {
from int
to int
inclusive bool
}
func (s seqObject) String() string {
return fmt.Sprintf("%d:%d", s.from, s.to)
}
func (s seqObject) Truthy() bool {
return s.from != s.to || s.inclusive
}
func (s seqObject) Len() int {
var l int
if s.from > s.to {
l = s.from - s.to
} else {
l = s.to - s.from
}
if s.inclusive {
l += 1
}
return l
}
func (s seqObject) Index(i int) object {
l := s.Len()
if i < 0 || i > l {
return nil
}
if s.from > s.to {
return intObject(s.from - i)
}
return intObject(s.from + i)
}
func seqBuiltin(ctx context.Context, args invocationArgs) (object, error) {
inclusive := false
if inc, ok := args.kwargs["inc"]; ok {
inclusive = (inc.Len() == 0) || inc.Truthy()
}
switch len(args.args) {
case 1:
n, err := args.intArg(0)
if err != nil {
return nil, err
}
return seqObject{from: 0, to: n, inclusive: inclusive}, nil
case 2:
f, err := args.intArg(0)
if err != nil {
return nil, err
}
t, err := args.intArg(1)
if err != nil {
return nil, err
}
return seqObject{from: f, to: t, inclusive: inclusive}, nil
default:
return nil, errors.New("expected either 1 or 2 arguments")
}
}
func ifBuiltin(ctx context.Context, args macroArgs) (object, error) { func ifBuiltin(ctx context.Context, args macroArgs) (object, error) {
if args.nargs() < 2 { if args.nargs() < 2 {
return nil, errors.New("need at least 2 arguments") return nil, errors.New("need at least 2 arguments")

View File

@ -53,6 +53,7 @@ func New(opts ...InstOption) *Inst {
rootEC.addCmd("keys", invokableFunc(keysBuiltin)) rootEC.addCmd("keys", invokableFunc(keysBuiltin))
rootEC.addCmd("index", invokableFunc(indexBuiltin)) rootEC.addCmd("index", invokableFunc(indexBuiltin))
rootEC.addCmd("call", invokableFunc(callBuiltin)) rootEC.addCmd("call", invokableFunc(callBuiltin))
rootEC.addCmd("seq", invokableFunc(seqBuiltin))
rootEC.addCmd("map", invokableFunc(mapBuiltin)) rootEC.addCmd("map", invokableFunc(mapBuiltin))
rootEC.addCmd("filter", invokableFunc(filterBuiltin)) rootEC.addCmd("filter", invokableFunc(filterBuiltin))

View File

@ -298,6 +298,19 @@ func (ia invocationArgs) stringArg(i int) (string, error) {
return s.String(), nil return s.String(), nil
} }
func (ia invocationArgs) intArg(i int) (int, error) {
if len(ia.args) < i {
return 0, errors.New("expected at least " + strconv.Itoa(i) + " args")
}
switch v := ia.args[i].(type) {
case intObject:
return int(v), nil
default:
return 0, errors.New("expected an int arg")
}
}
func (ia invocationArgs) invokableArg(i int) (invokable, error) { func (ia invocationArgs) invokableArg(i int) (invokable, error) {
if len(ia.args) < i { if len(ia.args) < i {
return nil, errors.New("expected at least " + strconv.Itoa(i) + " args") return nil, errors.New("expected at least " + strconv.Itoa(i) + " args")

View File

@ -461,6 +461,69 @@ func TestBuiltins_Return(t *testing.T) {
} }
} }
func TestBuiltins_Seq(t *testing.T) {
tests := []struct {
desc string
expr string
want string
}{
{desc: "empty seq", expr: `seq 0 0`, want: ``},
{desc: "simple seq 1", expr: `seq 5`, want: "0\n1\n2\n3\n4\n"},
{desc: "simple seq 2", expr: `seq 3`, want: "0\n1\n2\n"},
{desc: "simple seq 3", expr: `seq -5`, want: "0\n-1\n-2\n-3\n-4\n"},
{desc: "asc seq 1", expr: `seq 3 5`, want: "3\n4\n"},
{desc: "asc seq 2", expr: `seq 3 8`, want: "3\n4\n5\n6\n7\n"},
{desc: "desc seq 1", expr: `seq 8 0`, want: "8\n7\n6\n5\n4\n3\n2\n1\n"},
{desc: "desc seq 2", expr: `seq 5 2`, want: "5\n4\n3\n"},
{desc: "desc seq 3", expr: `seq 3 -3`, want: "3\n2\n1\n0\n-1\n-2\n"},
{desc: "inclusive seq 1", expr: `seq 5 -inc`, want: "0\n1\n2\n3\n4\n5\n"},
{desc: "inclusive seq 2", expr: `seq -3 -inc`, want: "0\n-1\n-2\n-3\n"},
{desc: "inclusive seq 3", expr: `seq 5 8 -inc`, want: "5\n6\n7\n8\n"},
{desc: "inclusive seq 4", expr: `seq 4 0 -inc`, want: "4\n3\n2\n1\n0\n"},
{desc: "len of empty seq", expr: `seq 0 0 | len`, want: "0\n"},
{desc: "len of simple seq 1", expr: `seq 5 | len`, want: "5\n"},
{desc: "len of simple seq 2", expr: `seq 3 | len`, want: "3\n"},
{desc: "len of simple seq 3", expr: `seq -5 | len`, want: "5\n"},
{desc: "len of asc seq 1", expr: `seq 3 5 | len`, want: "2\n"},
{desc: "len of asc seq 2", expr: `seq 3 8 | len`, want: "5\n"},
{desc: "len of desc seq 1", expr: `seq 8 0 | len`, want: "8\n"},
{desc: "len of desc seq 2", expr: `seq 5 2 | len`, want: "3\n"},
{desc: "len of desc seq 3", expr: `seq 3 -3 | len`, want: "6\n"},
{desc: "len of inclusive seq 1", expr: `seq 5 -inc | len`, want: "6\n"},
{desc: "len of inclusive seq 2", expr: `seq -3 -inc | len`, want: "4\n"},
{desc: "len of inclusive seq 3", expr: `seq 5 8 -inc | len`, want: "4\n"},
{desc: "len of inclusive seq 4", expr: `seq 4 0 -inc | len`, want: "5\n"},
{desc: "truthy of empty seq 1", expr: `if (seq 0 0) { echo "t" }`, want: "(nil)\n"},
{desc: "truthy of empty seq 2", expr: `if (seq 3 3) { echo "t" }`, want: "(nil)\n"},
{desc: "truthy of empty seq 3", expr: `if (seq -5 -5) { echo "t" }`, want: "(nil)\n"},
{desc: "truthy of empty seq 4", expr: `if (seq 0) { echo "t" }`, want: "(nil)\n"},
{desc: "truthy simple seq", expr: `if (seq 5) { echo "t" }`, want: "t\n(nil)\n"},
{desc: "truthy asc seq", expr: `if (seq 3 5) { echo "t" }`, want: "t\n(nil)\n"},
{desc: "truthy desc seq", expr: `if (seq 3 -6) { echo "t" }`, want: "t\n(nil)\n"},
{desc: "truthy inclusive 1", expr: `if (seq 0 -inc) { echo "t" }`, want: "t\n(nil)\n"},
{desc: "truthy inclusive 2", expr: `if (seq 0 0 -inc) { echo "t" }`, want: "t\n(nil)\n"},
{desc: "truthy inclusive 3", expr: `if (seq 3 3 -inc) { echo "t" }`, want: "t\n(nil)\n"},
{desc: "map seq", expr: `seq 4 | map { |x| cat "[" $x "]" }`, want: "[0]\n[1]\n[2]\n[3]\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())
err := EvalAndDisplay(ctx, inst, tt.expr)
assert.NoError(t, err)
assert.Equal(t, tt.want, outW.String())
})
}
}
func TestBuiltins_Map(t *testing.T) { func TestBuiltins_Map(t *testing.T) {
tests := []struct { tests := []struct {
desc string desc string