1547 lines
44 KiB
Go
1547 lines
44 KiB
Go
package ucl
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"github.com/stretchr/testify/assert"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
type testIterator struct {
|
|
cnt int
|
|
max int
|
|
err error
|
|
}
|
|
|
|
func (ti *testIterator) HasNext() bool {
|
|
return ti.cnt < ti.max
|
|
}
|
|
|
|
func (ti *testIterator) Next(ctx context.Context) (Object, error) {
|
|
ti.cnt++
|
|
return IntObject(ti.cnt), nil
|
|
}
|
|
|
|
// Builtins used for test
|
|
func WithTestBuiltin() InstOption {
|
|
return func(i *Inst) {
|
|
i.rootEC.addCmd("firstarg", invokableFunc(func(ctx context.Context, args invocationArgs) (Object, error) {
|
|
return args.args[0], nil
|
|
}))
|
|
|
|
i.rootEC.addCmd("toUpper", invokableFunc(func(ctx context.Context, args invocationArgs) (Object, error) {
|
|
return StringObject(strings.ToUpper(args.args[0].String())), nil
|
|
}))
|
|
|
|
i.rootEC.addCmd("sjoin", invokableFunc(func(ctx context.Context, args invocationArgs) (Object, error) {
|
|
if len(args.args) == 0 {
|
|
return StringObject(""), nil
|
|
}
|
|
|
|
var line strings.Builder
|
|
for _, arg := range args.args {
|
|
if s, ok := arg.(fmt.Stringer); ok {
|
|
line.WriteString(s.String())
|
|
}
|
|
}
|
|
|
|
return StringObject(line.String()), nil
|
|
}))
|
|
|
|
i.rootEC.addCmd("list", invokableFunc(func(ctx context.Context, args invocationArgs) (Object, error) {
|
|
var a ListObject = make([]Object, len(args.args))
|
|
copy(a, args.args)
|
|
return &a, nil
|
|
}))
|
|
|
|
i.rootEC.addCmd("error", invokableFunc(func(ctx context.Context, args invocationArgs) (Object, error) {
|
|
if len(args.args) == 0 {
|
|
return nil, errors.New("an error occurred")
|
|
}
|
|
return nil, errors.New(args.args[0].String())
|
|
}))
|
|
|
|
i.rootEC.addCmd("joinpipe", invokableFunc(func(ctx context.Context, args invocationArgs) (Object, error) {
|
|
sb := strings.Builder{}
|
|
|
|
lst, ok := args.args[0].(Listable)
|
|
if !ok {
|
|
return StringObject(""), nil
|
|
}
|
|
|
|
l := lst.Len()
|
|
for x := 0; x < l; x++ {
|
|
if x > 0 {
|
|
sb.WriteString(",")
|
|
}
|
|
sb.WriteString(lst.Index(x).String())
|
|
}
|
|
return StringObject(sb.String()), nil
|
|
}))
|
|
|
|
i.rootEC.addCmd("itr", invokableFunc(func(ctx context.Context, args invocationArgs) (Object, error) {
|
|
return iteratorObject{Iterable: &testIterator{max: 3}}, nil
|
|
}))
|
|
|
|
i.rootEC.setOrDefineVar("a", StringObject("alpha"))
|
|
i.rootEC.setOrDefineVar("bee", StringObject("buzz"))
|
|
}
|
|
}
|
|
|
|
func TestBuiltins_Echo(t *testing.T) {
|
|
tests := []struct {
|
|
desc string
|
|
expr string
|
|
want string
|
|
}{
|
|
{desc: "no args", expr: `echo`, want: "\n"},
|
|
{desc: "single arg", expr: `echo "hello"`, want: "hello\n"},
|
|
{desc: "dual args", expr: `echo "hello " "world"`, want: "hello world\n"},
|
|
{desc: "multi-line 1", expr: `
|
|
echo "Hello"
|
|
echo "world"
|
|
`, want: "Hello\nworld\n"},
|
|
{desc: "multi-line 2", expr: `
|
|
echo "Hello"
|
|
|
|
|
|
echo "world"
|
|
`, want: "Hello\nworld\n"},
|
|
{desc: "multi-line 3", expr: `
|
|
|
|
;;;
|
|
echo "Hello"
|
|
;
|
|
|
|
echo "world"
|
|
;
|
|
`, want: "Hello\nworld\n"},
|
|
{desc: "multi-line 4", expr: `
|
|
# This is a comment
|
|
#
|
|
|
|
;;;
|
|
# This is another comment
|
|
echo "Hello"
|
|
;
|
|
|
|
echo "world" # command after this
|
|
;
|
|
`, want: "Hello\nworld\n"},
|
|
{desc: "interpolated string 1", expr: `$what = "world" ; echo "Hello, $what"`, want: "Hello, world\n"},
|
|
{desc: "interpolated string 2", expr: `$what = "world" ; echo "Hello, \$what"`, want: "Hello, $what\n"},
|
|
{desc: "interpolated string 3", expr: `echo "separate\nlines\n\tand tabs"`, want: "separate\nlines\n\tand tabs\n"},
|
|
{desc: "interpolated string 4", expr: `$what = "Hello" ; $where = "world" ; echo "$what, $where"`, want: "Hello, world\n"},
|
|
{desc: "interpolated string 5", expr: `
|
|
foreach [123 "foo" true ()] { |x|
|
|
echo "[[$x]]"
|
|
}
|
|
`, want: "[[123]]\n[[foo]]\n[[true]]\n[[]]\n"},
|
|
{desc: "single quote string 1", expr: `echo 'Hello, world'`, want: "Hello, world\n"},
|
|
{desc: "single quote string 2", expr: `echo 'No $vars here'`, want: "No $vars here\n"},
|
|
{desc: "single quote string 3", expr: `echo 'No \escape \nhere'`, want: "No \\escape \\nhere\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())
|
|
res, err := inst.Eval(ctx, tt.expr)
|
|
|
|
assert.NoError(t, err)
|
|
assert.Nil(t, res)
|
|
assert.Equal(t, tt.want, outW.String())
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestBuiltins_If(t *testing.T) {
|
|
tests := []struct {
|
|
desc string
|
|
expr string
|
|
want string
|
|
}{
|
|
{desc: "single then", expr: `
|
|
$x = "Hello"
|
|
if $x {
|
|
echo "true"
|
|
}`, want: "true\n(nil)\n"},
|
|
{desc: "single then and else", expr: `
|
|
$x = "Hello"
|
|
if $x {
|
|
echo "true"
|
|
} else {
|
|
echo "false"
|
|
}`, want: "true\n(nil)\n"},
|
|
{desc: "single then, elif and else", expr: `
|
|
$x = "Hello"
|
|
if $y {
|
|
echo "y is true"
|
|
} elif $x {
|
|
echo "x is true"
|
|
} else {
|
|
echo "nothings x"
|
|
}`, want: "x is true\n(nil)\n"},
|
|
{desc: "single then and elif, no else", expr: `
|
|
$x = "Hello"
|
|
if $y {
|
|
echo "y is true"
|
|
} elif $x {
|
|
echo "x is true"
|
|
}`, want: "x is true\n(nil)\n"},
|
|
{desc: "single then, two elif, and else", expr: `
|
|
$x = "Hello"
|
|
if $z {
|
|
echo "z is true"
|
|
} elif $y {
|
|
echo "y is true"
|
|
} elif $x {
|
|
echo "x is true"
|
|
}`, want: "x is true\n(nil)\n"},
|
|
{desc: "single then, two elif, and else, expecting else", expr: `
|
|
if $z {
|
|
echo "z is true"
|
|
} elif $y {
|
|
echo "y is true"
|
|
} elif $x {
|
|
echo "x is true"
|
|
} else {
|
|
echo "none is true"
|
|
}`, want: "none is true\n(nil)\n"},
|
|
{desc: "compressed then", expr: `$x = "Hello" ; if $x { echo "true" }`, want: "true\n(nil)\n"},
|
|
{desc: "compressed else", expr: `if $x { echo "true" } else { echo "false" }`, want: "false\n(nil)\n"},
|
|
{desc: "compressed if", expr: `if $x { echo "x" } elif $y { echo "y" } else { echo "false" }`, want: "false\n(nil)\n"},
|
|
{desc: "if of itr 1", expr: `$i = itr ; if $i { echo "more" } else { echo "none" }`, want: "more\n(nil)\n"},
|
|
{desc: "if of itr 2", expr: `$i = itr ; foreach (seq 1) { head $i } ; if $i { echo "more" } else { echo "none" }`, want: "more\n(nil)\n"},
|
|
{desc: "if of itr 3", expr: `$i = itr ; foreach (seq 3) { head $i } ; if $i { echo "more" } else { echo "none" }`, want: "none\n(nil)\n"},
|
|
{desc: "if of itr 4", expr: `$i = (itr | map { |x| add 2 $x }) ; if $i { echo "more" } else { echo "none" }`, want: "more\n(nil)\n"},
|
|
{desc: "if of itr 5", expr: `$i = (itr | filter { |x| () }) ; if $i { echo "more" } else { echo "none" }`, want: "none\n(nil)\n"},
|
|
{desc: "if of itr 6", expr: `$i = (itr | filter { |x| 1 }) ; if $i { echo "more" } else { echo "none" }`, want: "more\n(nil)\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_ForEach(t *testing.T) {
|
|
tests := []struct {
|
|
desc string
|
|
expr string
|
|
want string
|
|
}{
|
|
{desc: "iterate over list 1", expr: `
|
|
foreach ["1" "2" "3"] { |v|
|
|
echo $v
|
|
}`, want: "1\n2\n3\n(nil)\n"},
|
|
{desc: "iterate over list 2",
|
|
expr: `foreach ["1" "2" "3"] echo`,
|
|
want: "1\n2\n3\n(nil)\n"},
|
|
// TODO: hash is not sorted, so need to find a way to sort it
|
|
{desc: "iterate over map 1", expr: `
|
|
foreach [a:"1"] { |k v| echo $k "=" $v }`, want: "a=1\n(nil)\n"},
|
|
{desc: "iterate over map 2", expr: `
|
|
foreach [a:"1"] echo`, want: "a1\n(nil)\n"},
|
|
{desc: "iterate via pipe", expr: `["2" "4" "6"] | foreach { |x| echo $x }`, want: "2\n4\n6\n(nil)\n"},
|
|
{desc: "iterate from iterator 1", expr: `itr | foreach { |x| echo $x }`, want: "1\n2\n3\n(nil)\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_While(t *testing.T) {
|
|
tests := []struct {
|
|
desc string
|
|
expr string
|
|
want string
|
|
}{
|
|
{desc: "iterate while true 1", expr: `
|
|
$x = 0
|
|
while (lt $x 5) {
|
|
echo $x
|
|
$x = (add $x 1)
|
|
}
|
|
echo "done"`, want: "0\n1\n2\n3\n4\ndone\n(nil)\n"},
|
|
{desc: "iterate while true 2", expr: `
|
|
$x = 20
|
|
while (lt $x 5) {
|
|
echo $x
|
|
$x = (add $x 1)
|
|
}
|
|
echo "done"`, want: "done\n(nil)\n"},
|
|
{desc: "iterate while true with pipeline", expr: `
|
|
$x = 0
|
|
while (lt $x 5) {
|
|
echo $x
|
|
$x = (add $x 1)
|
|
if (ge $x 3) {
|
|
break "Ahh"
|
|
}
|
|
} | echo " was the break"
|
|
echo "done"`, want: "0\n1\n2\nAhh was the break\ndone\n(nil)\n"},
|
|
{desc: "iterate for ever with break 1", expr: `
|
|
$x = 0
|
|
while {
|
|
echo $x
|
|
$x = (add $x 1)
|
|
if (ge $x 5) {
|
|
break
|
|
}
|
|
}
|
|
echo "done"`, want: "0\n1\n2\n3\n4\ndone\n(nil)\n"},
|
|
{desc: "iterate for ever with break 2", expr: `
|
|
$x = 0
|
|
echo (while {
|
|
echo $x
|
|
$x = add $x 1
|
|
if (ge $x 5) {
|
|
break $x
|
|
}
|
|
})
|
|
`, want: "0\n1\n2\n3\n4\n5\n(nil)\n"},
|
|
{desc: "iterate for ever with continue", expr: `
|
|
$x = 0
|
|
while {
|
|
$x = (add $x 1)
|
|
if (or (eq $x 2) (eq $x 4)) {
|
|
echo "quack"
|
|
continue
|
|
}
|
|
echo $x
|
|
if (ge $x 5) {
|
|
break
|
|
}
|
|
}
|
|
echo "done"`, want: "1\nquack\n3\nquack\n5\ndone\n(nil)\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_Break(t *testing.T) {
|
|
tests := []struct {
|
|
desc string
|
|
expr string
|
|
want string
|
|
}{
|
|
{desc: "break unconditionally returning nothing", expr: `
|
|
foreach ["1" "2" "3"] { |v|
|
|
break
|
|
echo $v
|
|
}`, want: "(nil)\n"},
|
|
{desc: "break conditionally returning nothing", expr: `
|
|
foreach ["1" "2" "3"] { |v|
|
|
echo $v
|
|
if (eq $v "2") { break }
|
|
}`, want: "1\n2\n(nil)\n"},
|
|
{desc: "break inner loop only returning nothing", expr: `
|
|
foreach ["a" "b"] { |u|
|
|
foreach ["1" "2" "3"] { |v|
|
|
echo $u $v
|
|
if (eq $v "2") { break }
|
|
}
|
|
}`, want: "a1\na2\nb1\nb2\n(nil)\n"},
|
|
{desc: "break returning value 1", expr: `
|
|
echo (foreach ["1" "2" "3"] { |v|
|
|
echo $v
|
|
if (eq $v "2") { break "hello" }
|
|
})`, want: "1\n2\nhello\n(nil)\n"},
|
|
{desc: "break returning value 2", expr: `
|
|
echo (foreach (itr) { |v|
|
|
echo $v
|
|
if (eq $v 2) { break "hello" }
|
|
})`, want: "1\n2\nhello\n(nil)\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_Continue(t *testing.T) {
|
|
tests := []struct {
|
|
desc string
|
|
expr string
|
|
want string
|
|
}{
|
|
{desc: "continue unconditionally", expr: `
|
|
foreach ["1" "2" "3"] { |v|
|
|
echo $v "s"
|
|
continue
|
|
echo $v "e"
|
|
}`, want: "1s\n2s\n3s\n(nil)\n"},
|
|
{desc: "conditionally conditionally", expr: `
|
|
foreach ["1" "2" "3"] { |v|
|
|
echo $v "s"
|
|
if (eq $v "2") { continue }
|
|
echo $v "e"
|
|
}`, want: "1s\n1e\n2s\n3s\n3e\n(nil)\n"},
|
|
{desc: "continue inner loop only", expr: `
|
|
foreach ["a" "b"] { |u|
|
|
foreach ["1" "2" "3"] { |v|
|
|
if (eq $v "2") { continue }
|
|
echo $u $v
|
|
}
|
|
}`, want: "a1\na3\nb1\nb3\n(nil)\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_Procs(t *testing.T) {
|
|
tests := []struct {
|
|
desc string
|
|
expr string
|
|
want string
|
|
}{
|
|
{desc: "simple procs", expr: `
|
|
proc greet {
|
|
echo "Hello, world"
|
|
}
|
|
|
|
greet
|
|
greet`, want: "Hello, world\nHello, world\n(nil)\n"},
|
|
{desc: "multiple procs", expr: `
|
|
proc greet { |what|
|
|
echo "Hello, " $what
|
|
}
|
|
proc greetWorld { greet "world" }
|
|
proc greetMoon { greet "moon" }
|
|
proc greetTheThing { |what| greet (cat "the " $what) }
|
|
|
|
greetWorld
|
|
greetMoon
|
|
greetTheThing "sun"
|
|
`, want: "Hello, world\nHello, moon\nHello, the sun\n(nil)\n"},
|
|
{desc: "recursive procs", expr: `
|
|
proc four4 { |xs|
|
|
if (eq $xs "xxxx") {
|
|
$xs
|
|
} else {
|
|
four4 (cat $xs "x")
|
|
}
|
|
}
|
|
|
|
four4
|
|
`, want: "xxxx\n"},
|
|
{desc: "closures", expr: `
|
|
proc makeGreeter { |greeting|
|
|
proc { |what|
|
|
echo $greeting ", " $what
|
|
}
|
|
}
|
|
|
|
$helloGreater = makeGreeter "Hello"
|
|
$helloGreater "world"
|
|
|
|
$goodbye = makeGreeter "Goodbye cruel"
|
|
$goodbye "world"
|
|
|
|
call (makeGreeter "Quick") ["call me"]
|
|
|
|
`, want: "Hello, world\nGoodbye cruel, world\nQuick, call me\n(nil)\n"},
|
|
{desc: "modifying closed over variables", expr: `
|
|
proc makeSetter {
|
|
$bla = "X"
|
|
proc appendToBla { |x|
|
|
$bla = cat $bla $x
|
|
}
|
|
}
|
|
|
|
$er = makeSetter
|
|
echo (call $er ["xxx"])
|
|
echo (call $er ["yyy"])
|
|
`, want: "Xxxx\nXxxxyyy\n(nil)\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_Return(t *testing.T) {
|
|
tests := []struct {
|
|
desc string
|
|
expr string
|
|
want string
|
|
}{
|
|
// syntax tests
|
|
{desc: "empty proc 1", expr: `
|
|
proc greet {}
|
|
greet
|
|
`, want: "(nil)\n"},
|
|
{desc: "empty proc 2", expr: `
|
|
proc greet {
|
|
}
|
|
|
|
greet
|
|
`, want: "(nil)\n"},
|
|
{desc: "empty proc 3", expr: `
|
|
proc greet {
|
|
|
|
|
|
}
|
|
|
|
greet
|
|
`, want: "(nil)\n"},
|
|
{desc: "empty proc 4", expr: `
|
|
proc greet {
|
|
# bla
|
|
|
|
# di
|
|
# bla!
|
|
}
|
|
|
|
greet
|
|
`, want: "(nil)\n"},
|
|
|
|
{desc: "nil return", expr: `
|
|
proc greet {
|
|
echo "Hello"
|
|
return
|
|
echo "World"
|
|
}
|
|
|
|
greet
|
|
`, want: "Hello\n(nil)\n"},
|
|
|
|
{desc: "simple arg 1", expr: `
|
|
proc greet { |x|
|
|
return (cat "Hello, " $x)
|
|
}
|
|
|
|
greet "person"
|
|
`, want: "Hello, person\n"},
|
|
{desc: "simple arg 2", expr: `
|
|
proc greet {
|
|
# This will greet someone
|
|
# here are the args:
|
|
|x|
|
|
|
|
# And here is the code
|
|
return (cat "Hello, " $x)
|
|
}
|
|
|
|
greet "person"
|
|
`, want: "Hello, person\n"},
|
|
|
|
{desc: "simple return", expr: `
|
|
proc greet {
|
|
return "Hello, world"
|
|
echo "But not me"
|
|
}
|
|
|
|
greet
|
|
`, want: "Hello, world\n"},
|
|
|
|
{desc: "only return current frame", expr: `
|
|
proc greetWhat {
|
|
echo "Greet the"
|
|
return "moon"
|
|
echo "world"
|
|
}
|
|
proc greet {
|
|
$what = (greetWhat)
|
|
echo "Hello, " $what
|
|
}
|
|
|
|
greet
|
|
`, want: "Greet the\nHello, moon\n(nil)\n"},
|
|
{desc: "return in loop", expr: `
|
|
proc countdown { |nums|
|
|
foreach $nums { |n|
|
|
echo $n
|
|
if (eq $n 3) {
|
|
return "abort"
|
|
}
|
|
}
|
|
}
|
|
countdown [5 4 3 2 1]
|
|
`, want: "5\n4\n3\nabort\n"},
|
|
{desc: "recursive procs", expr: `
|
|
proc four4 { |xs|
|
|
if (eq $xs "xxxx") {
|
|
return $xs
|
|
}
|
|
four4 (cat $xs "x")
|
|
}
|
|
|
|
four4
|
|
`, want: "xxxx\n"},
|
|
{desc: "check closure 1", expr: `
|
|
proc do-thing { |p|
|
|
call $p
|
|
}
|
|
|
|
proc test-thing {
|
|
foreach [1 2 3] { |x|
|
|
do-thing {
|
|
echo $x
|
|
}
|
|
}
|
|
}
|
|
|
|
test-thing
|
|
`, want: "1\n2\n3\n(nil)\n"},
|
|
{desc: "check closure 2", expr: `
|
|
proc do-thing { |p|
|
|
call $p
|
|
}
|
|
|
|
proc test-thing {
|
|
foreach [1 2 3] { |x|
|
|
do-thing (proc {
|
|
echo $x
|
|
})
|
|
}
|
|
}
|
|
|
|
test-thing
|
|
`, want: "1\n2\n3\n(nil)\n"},
|
|
{desc: "check closure 3", expr: `
|
|
proc do-thing { |p|
|
|
call $p
|
|
}
|
|
|
|
proc test-thing {
|
|
foreach [1 2 3] { |x|
|
|
$myClosure = proc { echo $x }
|
|
do-thing $myClosure
|
|
}
|
|
}
|
|
|
|
test-thing
|
|
`, want: "1\n2\n3\n(nil)\n"},
|
|
{desc: "check closure 4", expr: `
|
|
proc do-thing { |p|
|
|
call $p
|
|
}
|
|
|
|
proc test-thing {
|
|
[1 2 3] | map { |x|
|
|
proc { echo $x }
|
|
}
|
|
}
|
|
|
|
foreach (test-thing) { |y| call $y }
|
|
`, want: "1\n2\n3\n(nil)\n"},
|
|
{desc: "check closure 5", expr: `
|
|
proc do-thing { |p|
|
|
call $p
|
|
}
|
|
|
|
proc test-thing {
|
|
[1 2 3] | map { |x|
|
|
$myProc = proc { echo $x }
|
|
proc { do-thing $myProc }
|
|
}
|
|
}
|
|
|
|
$hello = "xx"
|
|
foreach (test-thing) { |y| call $y ; echo $hello }
|
|
`, want: "1\nxx\n2\nxx\n3\nxx\n(nil)\n"},
|
|
{desc: "check closure 7", expr: `
|
|
proc do-thing { |p|
|
|
call $p
|
|
}
|
|
|
|
proc test-thing {
|
|
$f = 0
|
|
[1 2 3] | map { |x|
|
|
$myProc = proc { echo $f }
|
|
$f = (add $f 1)
|
|
proc { do-thing $myProc }
|
|
}
|
|
}
|
|
|
|
$hello = "xx"
|
|
foreach (test-thing) { |y| call $y ; echo $hello }
|
|
`, want: "3\nxx\n3\nxx\n3\nxx\n(nil)\n"},
|
|
{desc: "check closure 7", expr: `
|
|
proc do-thing { |p|
|
|
call $p
|
|
}
|
|
|
|
proc test-thing {
|
|
$f = 1
|
|
[1 2 3] | map { |x|
|
|
$g = $f
|
|
$myProc = (proc { echo $g })
|
|
$f = (add $f 1)
|
|
proc { do-thing $myProc }
|
|
}
|
|
}
|
|
|
|
$hello = "xx"
|
|
foreach (test-thing) { |y| call $y ; echo $hello }
|
|
`, want: "1\nxx\n2\nxx\n3\nxx\n(nil)\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_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) {
|
|
tests := []struct {
|
|
desc string
|
|
expr string
|
|
want string
|
|
}{
|
|
{desc: "map list", expr: `
|
|
proc makeUpper { |x| $x | toUpper }
|
|
|
|
map ["a" "b" "c"] (proc { |x| makeUpper $x })
|
|
`, want: "A\nB\nC\n"},
|
|
{desc: "map list 2", expr: `
|
|
$makeUpper = proc { |x| $x | toUpper }
|
|
|
|
map ["a" "b" "c"] $makeUpper
|
|
`, want: "A\nB\nC\n"},
|
|
{desc: "map list with pipe", expr: `
|
|
$makeUpper = proc { |x| $x | toUpper }
|
|
|
|
["a" "b" "c"] | map $makeUpper
|
|
`, want: "A\nB\nC\n"},
|
|
{desc: "map list with block", expr: `
|
|
map ["a" "b" "c"] { |x| toUpper $x }
|
|
`, want: "A\nB\nC\n"},
|
|
{desc: "map list with stream", expr: `
|
|
$makeUpper = proc { |x| toUpper $x }
|
|
|
|
$l = ["a" "b" "c"] | map $makeUpper
|
|
echo $l
|
|
`, want: "[A B C]\n(nil)\n"},
|
|
{desc: "map itr stream", expr: `
|
|
$add2 = proc { |x| add $x 2 }
|
|
|
|
$l = itr | map $add2
|
|
foreach $l { |x| echo $x }
|
|
`, want: "3\n4\n5\n(nil)\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_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 int 1", expr: `goInt | index 1`, want: "5\n"},
|
|
{desc: "go int 2", expr: `goInt | index 2`, want: "4\n"},
|
|
{desc: "go int 3", expr: `goInt | index 555`, want: "(nil)\n"},
|
|
{desc: "go int 4", expr: `goInt | index -12`, want: "(nil)\n"},
|
|
{desc: "go int 5", expr: `goInt | index NotAnIndex`, want: "(nil)\n"},
|
|
|
|
{desc: "go list 1", expr: `goList | index 0 This`, want: "thing 1\n"},
|
|
{desc: "go list 2", expr: `goList | index 1 This`, want: "thing 2\n"},
|
|
{desc: "go list 3", expr: `goList | index 2`, want: "(nil)\n"},
|
|
{desc: "go list 4", expr: `goList | index 2 This`, want: "(nil)\n"},
|
|
{desc: "go list 5", expr: `goList | index 30`, want: "(nil)\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"},
|
|
{desc: "go struct 4", expr: `goStruct | index Nested This`, want: "fla\n"},
|
|
{desc: "go struct 5", expr: `goStruct | index Nested That`, want: "132\n"},
|
|
{desc: "go struct 6", expr: `goStruct | index NestedPtr This`, want: "flaPtr\n"},
|
|
{desc: "go struct 7", expr: `goStruct | index NestedPtr That`, want: "6678\n"},
|
|
{desc: "go struct 8", expr: `goStruct | index Missing`, want: "(nil)\n"},
|
|
{desc: "go struct 9", expr: `goStruct | index Nested Missing 123 Stuff`, want: "(nil)\n"},
|
|
{desc: "go struct 10", expr: `goStruct | index NestedPtrNil`, want: "(nil)\n"},
|
|
{desc: "go struct 11", expr: `goStruct | index NestedPtrNil This`, want: "(nil)\n"},
|
|
{desc: "go struct 12", expr: `goStruct | index NestedPtrNil Missing`, want: "(nil)\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("goList", func(ctx context.Context, args CallArgs) (any, error) {
|
|
type nest struct {
|
|
This string
|
|
}
|
|
return []*nest{
|
|
{This: "thing 1"},
|
|
{This: "thing 2"},
|
|
nil,
|
|
}, nil
|
|
})
|
|
inst.SetBuiltin("goStruct", func(ctx context.Context, args CallArgs) (any, error) {
|
|
type nested struct {
|
|
This string
|
|
That int
|
|
}
|
|
return struct {
|
|
Alpha string
|
|
Beta string
|
|
Gamma []int
|
|
Nested nested
|
|
NestedPtr *nested
|
|
NestedPtrNil *nested
|
|
}{
|
|
Alpha: "foo",
|
|
Beta: "bar",
|
|
Gamma: []int{22, 33},
|
|
Nested: nested{
|
|
This: "fla",
|
|
That: 132,
|
|
},
|
|
NestedPtr: &nested{
|
|
This: "flaPtr",
|
|
That: 6678,
|
|
},
|
|
}, nil
|
|
})
|
|
err := evalAndDisplay(ctx, inst, tt.expr)
|
|
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, tt.want, outW.String())
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestBuiltins_Len(t *testing.T) {
|
|
tests := []struct {
|
|
desc string
|
|
expr string
|
|
want string
|
|
}{
|
|
{desc: "len of list 1", expr: `len ["alpha" "beta" "gamma"]`, want: "3\n"},
|
|
{desc: "len of list 2", expr: `len ["alpha"]`, want: "1\n"},
|
|
{desc: "len of list 3", expr: `len []`, want: "0\n"},
|
|
|
|
{desc: "len of hash 1", expr: `len ["first":"alpha" "second":"beta" "third":"gamma"]`, want: "3\n"},
|
|
{desc: "len of hash 2", expr: `len ["first":"alpha" "second":"beta"]`, want: "2\n"},
|
|
{desc: "len of hash 3", expr: `len ["first":"alpha"]`, want: "1\n"},
|
|
{desc: "len of hash 4", expr: `len [:]`, want: "0\n"},
|
|
|
|
{desc: "len of string 1", expr: `len "Hello, world"`, want: "12\n"},
|
|
{desc: "len of string 2", expr: `len "chair"`, want: "5\n"},
|
|
{desc: "len of string 3", expr: `len ""`, want: "0\n"},
|
|
|
|
{desc: "len of int", expr: `len 1232`, want: "0\n"},
|
|
{desc: "len of nil", expr: `len ()`, want: "0\n"},
|
|
|
|
{desc: "len of itr 1", expr: `len (itr)`, want: "3\n"},
|
|
|
|
{desc: "go list 1", expr: `goInt | len`, want: "3\n"},
|
|
{desc: "go struct 1", expr: `goStruct | len`, want: "3\n"},
|
|
{desc: "go struct 2", expr: `index (goStruct) Gamma | len`, want: "2\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
|
|
hidden string
|
|
missing string
|
|
}{
|
|
Alpha: "foo",
|
|
Beta: "bar",
|
|
Gamma: []int{22, 33},
|
|
hidden: "hidden",
|
|
missing: "missing",
|
|
}, nil
|
|
})
|
|
err := evalAndDisplay(ctx, inst, tt.expr)
|
|
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, tt.want, outW.String())
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestBuiltins_Keys(t *testing.T) {
|
|
type testNested struct {
|
|
Nested string
|
|
Type string
|
|
}
|
|
|
|
tests := []struct {
|
|
desc string
|
|
expr string
|
|
wantItems []string
|
|
}{
|
|
{desc: "keys of map", expr: `keys [alpha: "hello" bravo: "world"]`, wantItems: []string{"alpha", "bravo"}},
|
|
{desc: "keys of go struct 1", expr: `goStruct | keys`, wantItems: []string{"Alpha", "Beta", "Gamma"}},
|
|
{desc: "keys of go struct 2", expr: `index (goStruct) Gamma | keys`, wantItems: []string{"Nested", "Type"}},
|
|
}
|
|
|
|
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 testNested
|
|
hidden string
|
|
missing string
|
|
}{
|
|
Alpha: "foo",
|
|
Beta: "bar",
|
|
Gamma: testNested{
|
|
Nested: "ads",
|
|
Type: "asd",
|
|
},
|
|
hidden: "hidden",
|
|
missing: "missing",
|
|
}, nil
|
|
})
|
|
|
|
res, err := inst.Eval(ctx, tt.expr)
|
|
assert.NoError(t, err)
|
|
assert.Len(t, res, len(tt.wantItems))
|
|
for _, i := range tt.wantItems {
|
|
assert.Contains(t, res, i)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
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 list 4", expr: `filter [() () ()] { |x| $x }`, want: []any{}},
|
|
{desc: "filter list 5", expr: `filter [] { |x| $x }`, 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{}},
|
|
|
|
{desc: "filter itr 1", expr: `$s = "" ; itr | filter { |x| ne $x 2 } | foreach { |x| $s = "$s $x" }; $s`, want: " 1 3"},
|
|
}
|
|
|
|
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},
|
|
|
|
{desc: "reduce itr 1", expr: `reduce (itr) 1 { |x a| add $x $a }`, want: 7},
|
|
}
|
|
|
|
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_Head(t *testing.T) {
|
|
tests := []struct {
|
|
desc string
|
|
expr string
|
|
want any
|
|
}{
|
|
{desc: "head list 1", expr: `head [1 2 3]`, want: 1},
|
|
|
|
{desc: "head itr 1", expr: `head (itr)`, want: 1},
|
|
{desc: "head itr 2", expr: `$h = (itr) ; head $h`, want: 1},
|
|
{desc: "head itr 3", expr: `$h = (itr) ; head $h ; head $h`, want: 2},
|
|
{desc: "head itr 4", expr: `$h = (itr) ; head $h ; head $h ; head $h`, want: 3},
|
|
{desc: "head itr 5", expr: `$h = (itr) ; head $h ; head $h ; head $h ; head $h`, want: nil},
|
|
}
|
|
|
|
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_LtLeGtLe(t *testing.T) {
|
|
tests := []struct {
|
|
desc string
|
|
expr string
|
|
want bool
|
|
wantErr bool
|
|
}{
|
|
{desc: "str 1 - lt", expr: `lt "hello" "world"`, want: true},
|
|
{desc: "str 1 - le", expr: `le "hello" "world"`, want: true},
|
|
{desc: "str 1 - gt", expr: `gt "hello" "world"`, want: false},
|
|
{desc: "str 1 - ge", expr: `ge "hello" "world"`, want: false},
|
|
{desc: "str 2 - lt", expr: `lt "zzzzz" "world"`, want: false},
|
|
{desc: "str 2 - le", expr: `le "zzzzz" "world"`, want: false},
|
|
{desc: "str 2 - gt", expr: `gt "zzzzz" "world"`, want: true},
|
|
{desc: "str 2 - ge", expr: `ge "zzzzz" "world"`, want: true},
|
|
{desc: "str 3 - lt", expr: `lt "hello" "hello"`, want: false},
|
|
{desc: "str 3 - le", expr: `le "hello" "hello"`, want: true},
|
|
{desc: "str 3 - gt", expr: `gt "hello" "hello"`, want: false},
|
|
{desc: "str 3 - ge", expr: `ge "hello" "hello"`, want: true},
|
|
|
|
{desc: "int 1 - lt", expr: `lt 5 8`, want: true},
|
|
{desc: "int 1 - le", expr: `le 5 8`, want: true},
|
|
{desc: "int 1 - gt", expr: `gt 5 8`, want: false},
|
|
{desc: "int 1 - ge", expr: `ge 5 8`, want: false},
|
|
{desc: "int 2 - lt", expr: `lt 5 -8`, want: false},
|
|
{desc: "int 2 - le", expr: `le 5 -8`, want: false},
|
|
{desc: "int 2 - gt", expr: `gt 5 -8`, want: true},
|
|
{desc: "int 2 - ge", expr: `ge 5 -8`, want: true},
|
|
{desc: "int 3 - lt", expr: `lt 5 5`, want: false},
|
|
{desc: "int 3 - le", expr: `le 5 5`, want: true},
|
|
{desc: "int 3 - gt", expr: `gt 5 5`, want: false},
|
|
{desc: "int 3 - ge", expr: `ge 5 5`, want: true},
|
|
|
|
{desc: "not comparable 1", expr: `lt () ()`, wantErr: true},
|
|
{desc: "not comparable 2", expr: `lt $true $false`, wantErr: true},
|
|
{desc: "not comparable 3", expr: `lt [1 2 3] [2 3 4]`, wantErr: true},
|
|
{desc: "not comparable 4", expr: `lt ["1":2] ["2":3]`, wantErr: true},
|
|
}
|
|
|
|
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.SetVar("true", true)
|
|
inst.SetVar("false", false)
|
|
|
|
eqRes, err := inst.Eval(ctx, tt.expr)
|
|
|
|
if tt.wantErr {
|
|
assert.Error(t, err)
|
|
} else {
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, tt.want, eqRes)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestBuiltins_EqNe(t *testing.T) {
|
|
tests := []struct {
|
|
desc string
|
|
expr string
|
|
want bool
|
|
}{
|
|
{desc: "equal strs 1", expr: `eq "hello" "hello"`, want: true},
|
|
{desc: "equal strs 2", expr: `eq "bla" "bla"`, want: true},
|
|
{desc: "equal strs 3", expr: `eq "" ""`, want: true},
|
|
{desc: "equal ints 1", expr: `eq 123 123`, want: true},
|
|
{desc: "equal ints 2", expr: `eq -21 -21`, want: true},
|
|
{desc: "equal ints 3", expr: `eq 0 0`, want: true},
|
|
{desc: "equal lists 1", expr: `eq [1 2 3] [1 2 3]`, want: true},
|
|
{desc: "equal lists 2", expr: `eq ["foo" "bar"] ["foo" "bar"]`, want: true},
|
|
{desc: "equal lists 3", expr: `eq [] []`, want: true},
|
|
{desc: "equal hashes 1", expr: `eq ["this":1 "that":"thing"] ["that":"thing" "this":1]`, want: true},
|
|
{desc: "equal hashes 2", expr: `eq ["foo":"bar"] ["foo":"bar"]`, want: true},
|
|
{desc: "equal bools 1", expr: `eq $true $true`, want: true},
|
|
{desc: "equal bools 2", expr: `eq $false $false`, want: true},
|
|
{desc: "equal nil 1", expr: `eq () ()`, want: true},
|
|
{desc: "equal undef 1", expr: `eq $undef $missing`, want: true},
|
|
{desc: "equal undef 2", expr: `eq $missing $undef`, want: true},
|
|
|
|
{desc: "not equal strs 1", expr: `eq "hello" "world"`, want: false},
|
|
{desc: "not equal strs 2", expr: `eq "bla" "BLA"`, want: false},
|
|
{desc: "not equal int 1", expr: `eq 131 313`, want: false},
|
|
{desc: "not equal int 2", expr: `eq -2 2`, want: false},
|
|
{desc: "not equal lists 1", expr: `eq [1 2 3] [1 2]`, want: false},
|
|
{desc: "not equal lists 2", expr: `eq ["123" "234"] [123 234]`, want: false},
|
|
{desc: "not equal hashes 1", expr: `eq ["this":1 "that":"thing"] ["that":"thing"]`, want: false},
|
|
{desc: "not equal hashes 2", expr: `eq ["this":1 "that":"thing"] ["this":1 "that":"thing" "other":"thing"]`, want: false},
|
|
{desc: "not equal hashes 3", expr: `eq ["this":1 "that":"thing"] ["this":"1" "that":"other"]`, want: false},
|
|
|
|
{desc: "not equal types 1", expr: `eq "123" 123`, want: false},
|
|
{desc: "not equal types 2", expr: `eq 0 ""`, want: false},
|
|
{desc: "not equal types 3", expr: `eq [] [:]`, want: false},
|
|
{desc: "not equal types 4", expr: `eq ["23"] "23"`, want: false},
|
|
{desc: "not equal types 5", expr: `eq $true ()`, want: false},
|
|
{desc: "not equal types 6", expr: `eq () $false`, want: false},
|
|
{desc: "not equal types 7", expr: `eq () "yes"`, want: false},
|
|
{desc: "not equal types 8", expr: `eq () $undef`, want: true},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.desc, func(t *testing.T) {
|
|
type testProxyObject struct {
|
|
v string
|
|
}
|
|
|
|
ctx := context.Background()
|
|
outW := bytes.NewBuffer(nil)
|
|
|
|
inst := New(WithOut(outW), WithTestBuiltin())
|
|
inst.SetVar("true", true)
|
|
inst.SetVar("false", false)
|
|
|
|
eqRes, err := inst.Eval(ctx, tt.expr)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, tt.want, eqRes)
|
|
|
|
neRes, err := inst.Eval(ctx, strings.ReplaceAll(tt.expr, "eq", "ne"))
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, !tt.want, neRes)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestBuiltins_Str(t *testing.T) {
|
|
tests := []struct {
|
|
desc string
|
|
expr string
|
|
want string
|
|
}{
|
|
{desc: "str", expr: `str "hello"`, want: "hello"},
|
|
{desc: "int", expr: `str 123`, want: "123"},
|
|
{desc: "bool 1", expr: `str (eq 1 1)`, want: "true"},
|
|
{desc: "bool 2", expr: `str (eq 1 0)`, want: "false"},
|
|
{desc: "list 1", expr: `str [1 2 3]`, want: "[1 2 3]"},
|
|
{desc: "list 2", expr: `str []`, want: "[]"},
|
|
{desc: "dict 1", expr: `str ["hello":"world"]`, want: `[hello:world]`},
|
|
{desc: "dict 2", expr: `str [:]`, want: "[:]"},
|
|
{desc: "nil", expr: `str ()`, want: ""},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.desc, func(t *testing.T) {
|
|
ctx := context.Background()
|
|
outW := bytes.NewBuffer(nil)
|
|
|
|
inst := New(WithOut(outW), WithTestBuiltin())
|
|
|
|
eqRes, err := inst.Eval(ctx, tt.expr)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, tt.want, eqRes)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestBuiltins_Int(t *testing.T) {
|
|
tests := []struct {
|
|
desc string
|
|
expr string
|
|
want int
|
|
wantErr bool
|
|
}{
|
|
{desc: "str 1", expr: `int "123"`, want: 123},
|
|
{desc: "str 2", expr: `int "31452"`, want: 31452},
|
|
{desc: "str 3", expr: `int "-21"`, want: -21},
|
|
{desc: "int 1", expr: `int 123`, want: 123},
|
|
{desc: "int 2", expr: `int -21`, want: -21},
|
|
{desc: "bool 1", expr: `int (eq 1 1)`, want: 1},
|
|
{desc: "bool 2", expr: `int (eq 1 0)`, want: 0},
|
|
{desc: "nil", expr: `int ()`, want: 0},
|
|
|
|
{desc: "list 1", expr: `int [1 2 3]`, wantErr: true},
|
|
{desc: "list 2", expr: `int []`, wantErr: true},
|
|
{desc: "dict 1", expr: `int ["hello":"world"]`, wantErr: true},
|
|
{desc: "dict 2", expr: `int [:]`, wantErr: true},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.desc, func(t *testing.T) {
|
|
ctx := context.Background()
|
|
outW := bytes.NewBuffer(nil)
|
|
|
|
inst := New(WithOut(outW), WithTestBuiltin())
|
|
|
|
eqRes, err := inst.Eval(ctx, tt.expr)
|
|
if tt.wantErr {
|
|
assert.Error(t, err)
|
|
} else {
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, tt.want, eqRes)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestBuiltins_AddSubMupDivMod(t *testing.T) {
|
|
tests := []struct {
|
|
desc string
|
|
expr string
|
|
want int
|
|
wantErr bool
|
|
}{
|
|
{desc: "add 1", expr: `add 1 2`, want: 3},
|
|
{desc: "add 2", expr: `add "3" 5`, want: 8},
|
|
{desc: "add 3", expr: `add 1 "2" 8`, want: 11},
|
|
{desc: "add 4", expr: `add 1`, want: 1},
|
|
{desc: "add 5", expr: `add`, want: 0},
|
|
{desc: "sub 1", expr: `sub 9 3`, want: 6},
|
|
{desc: "sub 2", expr: `sub 2 "5"`, want: -3},
|
|
{desc: "sub 3", expr: `sub 8 1 8`, want: -1},
|
|
{desc: "sub 4", expr: `sub 4`, want: 4},
|
|
{desc: "sub 5", expr: `sub`, want: 0},
|
|
{desc: "mup 1", expr: `mup 2 4`, want: 8},
|
|
{desc: "mup 2", expr: `mup 3 "4" 5`, want: 60},
|
|
{desc: "mup 3", expr: `mup 7`, want: 7},
|
|
{desc: "mup 4", expr: `mup`, want: 1},
|
|
{desc: "div 1", expr: `div 8 4`, want: 2},
|
|
{desc: "div 2", expr: `div "7" 4`, want: 1},
|
|
{desc: "div 3", expr: `div 7`, want: 7},
|
|
{desc: "div 4", expr: `div`, want: 1},
|
|
{desc: "mod 1", expr: `mod 2 3`, want: 2},
|
|
{desc: "mod 2", expr: `mod "7" 4`, want: 3},
|
|
{desc: "mod 3", expr: `mod 8 4`, want: 0},
|
|
{desc: "mod 4", expr: `mod 3`, want: 3},
|
|
{desc: "mod 5", expr: `mod`, want: 0},
|
|
|
|
{desc: "add err", expr: `add [] [:]`, wantErr: true},
|
|
{desc: "sub err", expr: `sub [] [:]`, wantErr: true},
|
|
{desc: "mup err", expr: `mup [] [:]`, wantErr: true},
|
|
{desc: "div err", expr: `div [] [:]`, wantErr: true},
|
|
{desc: "mod err", expr: `mod [] [:]`, wantErr: true},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.desc, func(t *testing.T) {
|
|
ctx := context.Background()
|
|
outW := bytes.NewBuffer(nil)
|
|
|
|
inst := New(WithOut(outW), WithTestBuiltin())
|
|
|
|
eqRes, err := inst.Eval(ctx, tt.expr)
|
|
if tt.wantErr {
|
|
assert.Error(t, err)
|
|
} else {
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, tt.want, eqRes)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestBuiltins_AndOrNot(t *testing.T) {
|
|
tests := []struct {
|
|
desc string
|
|
expr string
|
|
want any
|
|
wantErr bool
|
|
}{
|
|
{desc: "and 1", expr: `and $true $true`, want: true},
|
|
{desc: "and 2", expr: `and $false $true`, want: false},
|
|
{desc: "and 3", expr: `and $false $false`, want: false},
|
|
{desc: "or 1", expr: `or $true $true`, want: true},
|
|
{desc: "or 2", expr: `or $false $true`, want: true},
|
|
{desc: "or 3", expr: `or $false $false`, want: false},
|
|
{desc: "not 1", expr: `not $true`, want: false},
|
|
{desc: "not 2", expr: `not $false`, want: true},
|
|
{desc: "not 3", expr: `not $false $true`, want: true},
|
|
|
|
{desc: "short circuit and 1", expr: `and "hello" "world"`, want: "world"},
|
|
{desc: "short circuit and 2", expr: `and () "world"`, want: nil},
|
|
{desc: "short circuit and 3", expr: `and [] "world"`, want: []any{}},
|
|
{desc: "short circuit or 1", expr: `or "hello" "world"`, want: "hello"},
|
|
{desc: "short circuit or 2", expr: `or () "world"`, want: "world"},
|
|
{desc: "short circuit or 3", expr: `or () []`, want: []any{}},
|
|
|
|
{desc: "bad and 1", expr: `and "one"`, wantErr: true},
|
|
{desc: "bad and 2", expr: `and`, wantErr: true},
|
|
{desc: "bad or 1", expr: `or "one"`, wantErr: true},
|
|
{desc: "bad or 2", expr: `or`, wantErr: true},
|
|
{desc: "bad not 2", expr: `not`, wantErr: true},
|
|
}
|
|
|
|
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.SetVar("true", true)
|
|
inst.SetVar("false", false)
|
|
|
|
eqRes, err := inst.Eval(ctx, tt.expr)
|
|
if tt.wantErr {
|
|
assert.Error(t, err)
|
|
} else {
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, tt.want, eqRes)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestBuiltins_Cat(t *testing.T) {
|
|
tests := []struct {
|
|
desc string
|
|
expr string
|
|
want any
|
|
}{
|
|
{desc: "cat 1", expr: `cat "hello, " "world"`, want: "hello, world"},
|
|
{desc: "cat 2", expr: `cat "hello, " "world " "and stuff"`, want: "hello, world and stuff"},
|
|
{desc: "cat 3", expr: `cat "int = " 123`, want: "int = 123"},
|
|
{desc: "cat 4", expr: `cat "bool = " $true`, want: "bool = true"},
|
|
{desc: "cat 5", expr: `cat "array = " []`, want: "array = []"},
|
|
{desc: "cat 6", expr: `cat "array = " [1 3 2 4]`, want: "array = [1 3 2 4]"},
|
|
{desc: "cat 7", expr: `cat 1 $true 3 [4]`, want: "1true3[4]"},
|
|
{desc: "cat 8", expr: `cat`, want: ""},
|
|
{desc: "cat 9", expr: `$x = ["a" "b" "c"] ; cat "array = " [1 $x.(0) $x.(2) $x.(1)]`, want: "array = [1 a c b]"},
|
|
}
|
|
|
|
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.SetVar("true", true)
|
|
inst.SetVar("false", false)
|
|
|
|
eqRes, err := inst.Eval(ctx, tt.expr)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, tt.want, eqRes)
|
|
})
|
|
}
|
|
}
|
|
|
|
func evalAndDisplay(ctx context.Context, inst *Inst, expr string) error {
|
|
res, err := inst.eval(ctx, expr)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return displayResult(ctx, inst, res)
|
|
}
|
|
|
|
func displayResult(ctx context.Context, inst *Inst, res Object) (err error) {
|
|
switch v := res.(type) {
|
|
case nil:
|
|
if _, err = fmt.Fprintln(inst.out, "(nil)"); err != nil {
|
|
return err
|
|
}
|
|
case Listable:
|
|
for i := 0; i < v.Len(); i++ {
|
|
if err = displayResult(ctx, inst, v.Index(i)); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
default:
|
|
if _, err = fmt.Fprintln(inst.out, v.String()); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|