2024-04-11 12:05:05 +00:00
|
|
|
package cmdlang
|
|
|
|
|
|
|
|
import (
|
2024-04-14 12:09:13 +00:00
|
|
|
"bytes"
|
2024-04-11 12:05:05 +00:00
|
|
|
"context"
|
2024-04-12 23:25:16 +00:00
|
|
|
"fmt"
|
2024-04-14 12:09:13 +00:00
|
|
|
"github.com/stretchr/testify/assert"
|
2024-04-11 12:05:05 +00:00
|
|
|
"strings"
|
2024-04-14 12:09:13 +00:00
|
|
|
"testing"
|
2024-04-11 12:05:05 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
// 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
|
|
|
|
}))
|
|
|
|
|
2024-04-12 23:25:16 +00:00
|
|
|
i.rootEC.addCmd("sjoin", invokableFunc(func(ctx context.Context, args invocationArgs) (object, error) {
|
|
|
|
if len(args.args) == 0 {
|
|
|
|
return strObject(""), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
var line strings.Builder
|
|
|
|
for _, arg := range args.args {
|
|
|
|
if s, ok := arg.(fmt.Stringer); ok {
|
|
|
|
line.WriteString(s.String())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return strObject(line.String()), nil
|
|
|
|
}))
|
|
|
|
|
2024-04-23 12:02:06 +00:00
|
|
|
i.rootEC.addCmd("list", invokableFunc(func(ctx context.Context, args invocationArgs) (object, error) {
|
|
|
|
return listObject(args.args), nil
|
2024-04-11 12:05:05 +00:00
|
|
|
}))
|
|
|
|
|
2024-04-23 12:02:06 +00:00
|
|
|
i.rootEC.addCmd("joinpipe", invokableFunc(func(ctx context.Context, args invocationArgs) (object, error) {
|
2024-04-11 12:05:05 +00:00
|
|
|
sb := strings.Builder{}
|
2024-04-23 12:02:06 +00:00
|
|
|
|
|
|
|
lst, ok := args.args[0].(listable)
|
|
|
|
if !ok {
|
|
|
|
return strObject(""), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
l := lst.Len()
|
|
|
|
for x := 0; x < l; x++ {
|
|
|
|
if x > 0 {
|
2024-04-11 12:05:05 +00:00
|
|
|
sb.WriteString(",")
|
|
|
|
}
|
2024-04-23 12:02:06 +00:00
|
|
|
sb.WriteString(lst.Index(x).String())
|
2024-04-11 12:05:05 +00:00
|
|
|
}
|
|
|
|
return strObject(sb.String()), nil
|
|
|
|
}))
|
|
|
|
|
|
|
|
i.rootEC.setVar("a", strObject("alpha"))
|
|
|
|
i.rootEC.setVar("bee", strObject("buzz"))
|
|
|
|
}
|
|
|
|
}
|
2024-04-14 12:09:13 +00:00
|
|
|
|
|
|
|
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"},
|
|
|
|
}
|
|
|
|
|
|
|
|
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"},
|
|
|
|
}
|
|
|
|
|
|
|
|
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 := inst.EvalAndDisplay(ctx, tt.expr)
|
|
|
|
|
|
|
|
assert.NoError(t, err)
|
|
|
|
assert.Equal(t, tt.want, outW.String())
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
2024-04-16 12:28:12 +00:00
|
|
|
|
|
|
|
func TestBuiltins_ForEach(t *testing.T) {
|
|
|
|
tests := []struct {
|
|
|
|
desc string
|
|
|
|
expr string
|
|
|
|
want string
|
|
|
|
}{
|
|
|
|
{desc: "iterate over list", expr: `
|
|
|
|
foreach ["1" "2" "3"] { |v|
|
|
|
|
echo $v
|
|
|
|
}`, want: "1\n2\n3\n(nil)\n"},
|
2024-04-16 12:29:59 +00:00
|
|
|
// TODO: hash is not sorted, so need to find a way to sort it
|
2024-04-16 12:28:12 +00:00
|
|
|
{desc: "iterate over map", expr: `
|
|
|
|
foreach [a:"1"] { |k v| echo $k "=" $v }`, want: "a=1\n(nil)\n"},
|
2024-04-24 11:09:52 +00:00
|
|
|
{desc: "iterate via pipe", expr: `["2" "4" "6"] | foreach { |x| echo $x }`, want: "2\n4\n6\n(nil)\n"},
|
2024-04-16 12:28:12 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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 := inst.EvalAndDisplay(ctx, tt.expr)
|
|
|
|
|
|
|
|
assert.NoError(t, err)
|
|
|
|
assert.Equal(t, tt.want, outW.String())
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
2024-04-18 12:24:19 +00:00
|
|
|
|
|
|
|
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
|
2024-04-18 12:31:29 +00:00
|
|
|
} else {
|
|
|
|
four4 (cat $xs "x")
|
2024-04-18 12:24:19 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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"},
|
2024-04-18 12:31:29 +00:00
|
|
|
//{desc: "modifying closed over variables", expr: `
|
|
|
|
// proc makeSetter {
|
|
|
|
// set bla "X"
|
|
|
|
// proc appendToBla { |x|
|
|
|
|
// set bla (cat $bla $x)
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// set er (makeSetter)
|
|
|
|
// call $er "xxx"
|
|
|
|
// call $er "yyy"
|
|
|
|
// `, want: "Xxxx\nXxxxyyy(nil)\n"},
|
2024-04-18 12:24:19 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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 := inst.EvalAndDisplay(ctx, 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 })
|
2024-04-23 12:02:06 +00:00
|
|
|
`, want: "[A B C]\n"},
|
2024-04-18 12:24:19 +00:00
|
|
|
{desc: "map list 2", expr: `
|
|
|
|
set makeUpper (proc { |x| $x | toUpper })
|
|
|
|
|
|
|
|
map ["a" "b" "c"] $makeUpper
|
2024-04-23 12:02:06 +00:00
|
|
|
`, want: "[A B C]\n"},
|
|
|
|
{desc: "map list with pipe", expr: `
|
2024-04-18 12:24:19 +00:00
|
|
|
set makeUpper (proc { |x| $x | toUpper })
|
|
|
|
|
|
|
|
["a" "b" "c"] | map $makeUpper
|
2024-04-23 12:02:06 +00:00
|
|
|
`, want: "[A B C]\n"},
|
|
|
|
{desc: "map list with block", expr: `
|
|
|
|
map ["a" "b" "c"] { |x| toUpper $x }
|
|
|
|
`, want: "[A B C]\n"},
|
2024-04-18 12:31:29 +00:00
|
|
|
//{desc: "map list with stream", expr: `
|
|
|
|
// set makeUpper (proc { |x| $x | toUpper })
|
|
|
|
//
|
|
|
|
// set l (["a" "b" "c"] | map $makeUpper)
|
|
|
|
// echo $l
|
|
|
|
// `, want: "[A B C]\n"},
|
2024-04-18 12:24:19 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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 := inst.EvalAndDisplay(ctx, tt.expr)
|
|
|
|
|
|
|
|
assert.NoError(t, err)
|
|
|
|
assert.Equal(t, tt.want, outW.String())
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
2024-04-24 11:09:52 +00:00
|
|
|
|
|
|
|
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 list 1", expr: `goInt | index 1`, want: "5\n"},
|
|
|
|
{desc: "go list 2", expr: `goInt | index 2`, want: "4\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"},
|
|
|
|
}
|
|
|
|
|
|
|
|
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
|
|
|
|
}{
|
|
|
|
Alpha: "foo",
|
|
|
|
Beta: "bar",
|
|
|
|
Gamma: []int{22, 33},
|
|
|
|
}, nil
|
|
|
|
})
|
|
|
|
err := inst.EvalAndDisplay(ctx, tt.expr)
|
|
|
|
|
|
|
|
assert.NoError(t, err)
|
|
|
|
assert.Equal(t, tt.want, outW.String())
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
2024-04-24 11:29:26 +00:00
|
|
|
|
|
|
|
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: "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 := inst.EvalAndDisplay(ctx, tt.expr)
|
|
|
|
|
|
|
|
assert.NoError(t, err)
|
|
|
|
assert.Equal(t, tt.want, outW.String())
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|