feature/issue-1: built out the core libraries #3

Merged
lmika merged 8 commits from feature/issue-1 into main 2024-09-06 23:38:05 +00:00
4 changed files with 169 additions and 18 deletions
Showing only changes of commit c689814ba2 - Show all commits

View File

@ -88,17 +88,85 @@ func eqBuiltin(ctx context.Context, args invocationArgs) (object, error) {
l := args.args[0]
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) {
case strObject:
if rv, ok := r.(strObject); ok {
return boolObject(lv == rv), nil
return lv == rv
}
case intObject:
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) {

View File

@ -61,6 +61,8 @@ func New(opts ...InstOption) *Inst {
rootEC.addCmd("reduce", invokableFunc(reduceBuiltin))
rootEC.addCmd("eq", invokableFunc(eqBuiltin))
rootEC.addCmd("ne", invokableFunc(neBuiltin))
rootEC.addCmd("add", invokableFunc(addBuiltin))
rootEC.addCmd("cat", invokableFunc(concatBuiltin))
@ -88,6 +90,14 @@ func New(opts ...InstOption) *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 {
if inst.out == nil {
return os.Stdout

View File

@ -118,6 +118,8 @@ func toGoValue(obj object) (interface{}, bool) {
return string(v), true
case intObject:
return int(v), true
case boolObject:
return bool(v), true
case listObject:
xs := make([]interface{}, 0, len(v))
for _, va := range v {
@ -159,6 +161,8 @@ func fromGoValue(v any) (object, error) {
return strObject(t), nil
case int:
return intObject(t), nil
case bool:
return boolObject(t), nil
}
return fromGoReflectValue(reflect.ValueOf(v))

View File

@ -229,16 +229,16 @@ func TestBuiltins_Break(t *testing.T) {
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 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|
@ -246,11 +246,11 @@ func TestBuiltins_Break(t *testing.T) {
if (eq $v "2") { break }
}
}`, want: "a1\na2\nb1\nb2\n(nil)\n"},
//{desc: "break returning value", 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", expr: `
echo (foreach ["1" "2" "3"] { |v|
echo $v
if (eq $v "2") { break "hello" }
})`, want: "1\n2\nhello\n(nil)\n"},
}
for _, tt := range tests {
@ -891,3 +891,72 @@ func TestBuiltins_Reduce(t *testing.T) {
})
}
}
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())
inst.SetVar("hello", Opaque(testProxyObject{v: "hello"}))
inst.SetVar("world", Opaque(testProxyObject{v: "world"}))
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)
})
}
}