ucl/ucl/testbuiltins_test.go
Leon Mika 142abeb990 Added iterators
Iterators are an unbounded sequence of elements that can only be consumed one-by-one.
2025-03-23 12:00:58 +11:00

1470 lines
42 KiB
Go

package ucl
import (
"bytes"
"context"
"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: `set what "world" ; echo "Hello, $what"`, want: "Hello, world\n"},
{desc: "interpolated string 2", expr: `set 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: `set what "Hello" ; set 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: `
set x "Hello"
if $x {
echo "true"
}`, want: "true\n(nil)\n"},
{desc: "single then and else", expr: `
set x "Hello"
if $x {
echo "true"
} else {
echo "false"
}`, want: "true\n(nil)\n"},
{desc: "single then, elif and else", expr: `
set 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: `
set 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: `
set 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: `set 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: `set i (itr) ; if $i { echo "more" } else { echo "none" }`, want: "more\n(nil)\n"},
{desc: "if of itr 2", expr: `set 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: `set 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: `set 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: `set i (itr | filter { |x| () }) ; if $i { echo "more" } else { echo "none" }`, want: "none\n(nil)\n"},
{desc: "if of itr 6", expr: `set 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_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
}
}
set helloGreater (makeGreeter "Hello")
$helloGreater "world"
set 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 {
set bla "X"
proc appendToBla { |x|
set bla (cat $bla $x)
}
}
set 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 {
set 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|
set 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|
set myProc (proc { echo $x })
proc { do-thing $myProc }
}
}
set 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 {
set f 0
[1 2 3] | map { |x|
set myProc (proc { echo $f })
set f (add $f 1)
proc { do-thing $myProc }
}
}
set 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 {
set f 1
[1 2 3] | map { |x|
set g $f
set myProc (proc { echo $g })
set f (add $f 1)
proc { do-thing $myProc }
}
}
set 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: `
set makeUpper (proc { |x| $x | toUpper })
map ["a" "b" "c"] $makeUpper
`, want: "A\nB\nC\n"},
{desc: "map list with pipe", expr: `
set 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: `
set makeUpper (proc { |x| toUpper $x })
set l (["a" "b" "c"] | map $makeUpper)
echo $l
`, want: "[A B C]\n(nil)\n"},
{desc: "map itr stream", expr: `
set add2 (proc { |x| add $x 2 })
set 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: `set s "" ; itr | filter { |x| ne $x 2 } | foreach { |x| set 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: `set h (itr) ; head $h`, want: 1},
{desc: "head itr 3", expr: `set h (itr) ; head $h ; head $h`, want: 2},
{desc: "head itr 4", expr: `set h (itr) ; head $h ; head $h ; head $h`, want: 3},
{desc: "head itr 5", expr: `set 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 opaque 1", expr: `eq $hello $hello`, want: true},
{desc: "equal opaque 2", expr: `eq $world $world`, 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 opaque 1", expr: `eq $hello $world`, want: false},
{desc: "not equal opaque 2", expr: `eq $hello "hello"`, 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 () $world`, want: false},
}
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())
// Removed code I don't have the rights to
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: `set 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
}