Completed 'eq' cases and added 'ne'
This commit is contained in:
parent
89ec653eb0
commit
1c3346947b
|
@ -63,17 +63,85 @@ func eqBuiltin(ctx context.Context, args invocationArgs) (object, error) {
|
||||||
l := args.args[0]
|
l := args.args[0]
|
||||||
r := args.args[1]
|
r := args.args[1]
|
||||||
|
|
||||||
|
return boolObject(objectsEqual(l, r)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func neBuiltin(ctx context.Context, args invocationArgs) (object, error) {
|
||||||
|
if err := args.expectArgn(2); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
l := args.args[0]
|
||||||
|
r := args.args[1]
|
||||||
|
|
||||||
|
return boolObject(!objectsEqual(l, r)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var errObjectsNotEqual = errors.New("objects not equal")
|
||||||
|
|
||||||
|
func objectsEqual(l, r object) bool {
|
||||||
|
if l == nil || r == nil {
|
||||||
|
return l == nil && r == nil
|
||||||
|
}
|
||||||
|
|
||||||
switch lv := l.(type) {
|
switch lv := l.(type) {
|
||||||
case strObject:
|
case strObject:
|
||||||
if rv, ok := r.(strObject); ok {
|
if rv, ok := r.(strObject); ok {
|
||||||
return boolObject(lv == rv), nil
|
return lv == rv
|
||||||
}
|
}
|
||||||
case intObject:
|
case intObject:
|
||||||
if rv, ok := r.(intObject); ok {
|
if rv, ok := r.(intObject); ok {
|
||||||
return boolObject(lv == rv), nil
|
return lv == rv
|
||||||
}
|
}
|
||||||
|
case boolObject:
|
||||||
|
if rv, ok := r.(boolObject); ok {
|
||||||
|
return lv == rv
|
||||||
|
}
|
||||||
|
case listable:
|
||||||
|
rv, ok := r.(listable)
|
||||||
|
if !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if lv.Len() != rv.Len() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for i := 0; i < lv.Len(); i++ {
|
||||||
|
if !objectsEqual(lv.Index(i), rv.Index(i)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
case hashable:
|
||||||
|
rv, ok := r.(hashable)
|
||||||
|
if !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if lv.Len() != rv.Len() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if err := lv.Each(func(k string, lkv object) error {
|
||||||
|
rkv := rv.Value(k)
|
||||||
|
if rkv == nil {
|
||||||
|
return errObjectsNotEqual
|
||||||
|
} else if !objectsEqual(lkv, rkv) {
|
||||||
|
return errObjectsNotEqual
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}); err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
case OpaqueObject:
|
||||||
|
rv, ok := r.(OpaqueObject)
|
||||||
|
if !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return lv.v == rv.v
|
||||||
}
|
}
|
||||||
return boolObject(false), nil
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func concatBuiltin(ctx context.Context, args invocationArgs) (object, error) {
|
func concatBuiltin(ctx context.Context, args invocationArgs) (object, error) {
|
||||||
|
|
10
ucl/inst.go
10
ucl/inst.go
|
@ -58,6 +58,8 @@ func New(opts ...InstOption) *Inst {
|
||||||
rootEC.addCmd("head", invokableFunc(firstBuiltin))
|
rootEC.addCmd("head", invokableFunc(firstBuiltin))
|
||||||
|
|
||||||
rootEC.addCmd("eq", invokableFunc(eqBuiltin))
|
rootEC.addCmd("eq", invokableFunc(eqBuiltin))
|
||||||
|
rootEC.addCmd("ne", invokableFunc(neBuiltin))
|
||||||
|
|
||||||
rootEC.addCmd("cat", invokableFunc(concatBuiltin))
|
rootEC.addCmd("cat", invokableFunc(concatBuiltin))
|
||||||
rootEC.addCmd("break", invokableFunc(breakBuiltin))
|
rootEC.addCmd("break", invokableFunc(breakBuiltin))
|
||||||
rootEC.addCmd("continue", invokableFunc(continueBuiltin))
|
rootEC.addCmd("continue", invokableFunc(continueBuiltin))
|
||||||
|
@ -83,6 +85,14 @@ func New(opts ...InstOption) *Inst {
|
||||||
return inst
|
return inst
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (inst *Inst) SetVar(name string, value any) {
|
||||||
|
obj, err := fromGoValue(value)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
inst.rootEC.setOrDefineVar(name, obj)
|
||||||
|
}
|
||||||
|
|
||||||
func (inst *Inst) Out() io.Writer {
|
func (inst *Inst) Out() io.Writer {
|
||||||
if inst.out == nil {
|
if inst.out == nil {
|
||||||
return os.Stdout
|
return os.Stdout
|
||||||
|
|
|
@ -107,6 +107,8 @@ func toGoValue(obj object) (interface{}, bool) {
|
||||||
return string(v), true
|
return string(v), true
|
||||||
case intObject:
|
case intObject:
|
||||||
return int(v), true
|
return int(v), true
|
||||||
|
case boolObject:
|
||||||
|
return bool(v), true
|
||||||
case listObject:
|
case listObject:
|
||||||
xs := make([]interface{}, 0, len(v))
|
xs := make([]interface{}, 0, len(v))
|
||||||
for _, va := range v {
|
for _, va := range v {
|
||||||
|
@ -146,6 +148,8 @@ func fromGoValue(v any) (object, error) {
|
||||||
return strObject(t), nil
|
return strObject(t), nil
|
||||||
case int:
|
case int:
|
||||||
return intObject(t), nil
|
return intObject(t), nil
|
||||||
|
case bool:
|
||||||
|
return boolObject(t), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
resVal := reflect.ValueOf(v)
|
resVal := reflect.ValueOf(v)
|
||||||
|
|
|
@ -228,16 +228,16 @@ func TestBuiltins_Break(t *testing.T) {
|
||||||
expr string
|
expr string
|
||||||
want string
|
want string
|
||||||
}{
|
}{
|
||||||
//{desc: "break unconditionally returning nothing", expr: `
|
{desc: "break unconditionally returning nothing", expr: `
|
||||||
// foreach ["1" "2" "3"] { |v|
|
foreach ["1" "2" "3"] { |v|
|
||||||
// break
|
break
|
||||||
// echo $v
|
echo $v
|
||||||
// }`, want: "(nil)\n"},
|
}`, want: "(nil)\n"},
|
||||||
//{desc: "break conditionally returning nothing", expr: `
|
{desc: "break conditionally returning nothing", expr: `
|
||||||
// foreach ["1" "2" "3"] { |v|
|
foreach ["1" "2" "3"] { |v|
|
||||||
// echo $v
|
echo $v
|
||||||
// if (eq $v "2") { break }
|
if (eq $v "2") { break }
|
||||||
// }`, want: "1\n2\n(nil)\n"},
|
}`, want: "1\n2\n(nil)\n"},
|
||||||
{desc: "break inner loop only returning nothing", expr: `
|
{desc: "break inner loop only returning nothing", expr: `
|
||||||
foreach ["a" "b"] { |u|
|
foreach ["a" "b"] { |u|
|
||||||
foreach ["1" "2" "3"] { |v|
|
foreach ["1" "2" "3"] { |v|
|
||||||
|
@ -245,11 +245,11 @@ func TestBuiltins_Break(t *testing.T) {
|
||||||
if (eq $v "2") { break }
|
if (eq $v "2") { break }
|
||||||
}
|
}
|
||||||
}`, want: "a1\na2\nb1\nb2\n(nil)\n"},
|
}`, want: "a1\na2\nb1\nb2\n(nil)\n"},
|
||||||
//{desc: "break returning value", expr: `
|
{desc: "break returning value", expr: `
|
||||||
// echo (foreach ["1" "2" "3"] { |v|
|
echo (foreach ["1" "2" "3"] { |v|
|
||||||
// echo $v
|
echo $v
|
||||||
// if (eq $v "2") { break "hello" }
|
if (eq $v "2") { break "hello" }
|
||||||
// })`, want: "1\n2\nhello\n(nil)\n"},
|
})`, want: "1\n2\nhello\n(nil)\n"},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
|
@ -393,6 +393,36 @@ func TestBuiltins_Return(t *testing.T) {
|
||||||
expr string
|
expr string
|
||||||
want 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: `
|
{desc: "nil return", expr: `
|
||||||
proc greet {
|
proc greet {
|
||||||
echo "Hello"
|
echo "Hello"
|
||||||
|
@ -402,6 +432,27 @@ func TestBuiltins_Return(t *testing.T) {
|
||||||
|
|
||||||
greet
|
greet
|
||||||
`, want: "Hello\n(nil)\n"},
|
`, 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: `
|
{desc: "simple return", expr: `
|
||||||
proc greet {
|
proc greet {
|
||||||
return "Hello, world"
|
return "Hello, world"
|
||||||
|
@ -410,6 +461,7 @@ func TestBuiltins_Return(t *testing.T) {
|
||||||
|
|
||||||
greet
|
greet
|
||||||
`, want: "Hello, world\n"},
|
`, want: "Hello, world\n"},
|
||||||
|
|
||||||
{desc: "only return current frame", expr: `
|
{desc: "only return current frame", expr: `
|
||||||
proc greetWhat {
|
proc greetWhat {
|
||||||
echo "Greet the"
|
echo "Greet the"
|
||||||
|
@ -618,6 +670,16 @@ func TestBuiltins_Index(t *testing.T) {
|
||||||
inst.SetBuiltin("goInt", func(ctx context.Context, args CallArgs) (any, error) {
|
inst.SetBuiltin("goInt", func(ctx context.Context, args CallArgs) (any, error) {
|
||||||
return []int{6, 5, 4}, nil
|
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) {
|
inst.SetBuiltin("goStruct", func(ctx context.Context, args CallArgs) (any, error) {
|
||||||
type nested struct {
|
type nested struct {
|
||||||
This string
|
This string
|
||||||
|
@ -710,3 +772,182 @@ func TestBuiltins_Len(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 map 1", expr: `filter [alpha:"hello" bravo:"world"] { |k v| eq $k "alpha" }`, want: map[string]any{
|
||||||
|
"alpha": "hello",
|
||||||
|
}},
|
||||||
|
{desc: "filter map 2", expr: `filter [alpha:"hello" bravo:"world"] { |k v| eq $v "world" }`, want: map[string]any{
|
||||||
|
"bravo": "world",
|
||||||
|
}},
|
||||||
|
{desc: "filter map 3", expr: `filter [alpha:"hello" bravo:"world"] { |k v| eq $v "alpha" }`, want: map[string]any{}},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.desc, func(t *testing.T) {
|
||||||
|
ctx := context.Background()
|
||||||
|
outW := bytes.NewBuffer(nil)
|
||||||
|
|
||||||
|
inst := New(WithOut(outW), WithTestBuiltin())
|
||||||
|
|
||||||
|
res, err := inst.Eval(ctx, tt.expr)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, tt.want, res)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBuiltins_Reduce(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
desc string
|
||||||
|
expr string
|
||||||
|
want any
|
||||||
|
}{
|
||||||
|
{desc: "reduce list 1", expr: `reduce [1 1 1] { |x a| add $x $a }`, want: 3},
|
||||||
|
{desc: "reduce list 2", expr: `reduce [1 1 1] 20 { |x a| add $x $a }`, want: 23},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.desc, func(t *testing.T) {
|
||||||
|
ctx := context.Background()
|
||||||
|
outW := bytes.NewBuffer(nil)
|
||||||
|
|
||||||
|
inst := New(WithOut(outW), WithTestBuiltin())
|
||||||
|
|
||||||
|
res, err := inst.Eval(ctx, tt.expr)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, tt.want, res)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
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)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue