From efaf31c869a4add510aa9104f813c7caf5e25aa8 Mon Sep 17 00:00:00 2001 From: Leon Mika Date: Fri, 6 Sep 2024 20:36:00 +1000 Subject: [PATCH] issue-1: added gt, ge, lt, le --- ucl/builtins.go | 62 +++++++++++++++++++++++++++++++++++ ucl/inst.go | 4 +++ ucl/testbuiltins_test.go | 70 +++++++++++++++++++++++++++++++++++++--- 3 files changed, 132 insertions(+), 4 deletions(-) diff --git a/ucl/builtins.go b/ucl/builtins.go index 4841a3a..ac72f67 100644 --- a/ucl/builtins.go +++ b/ucl/builtins.go @@ -102,6 +102,54 @@ func neBuiltin(ctx context.Context, args invocationArgs) (object, error) { return boolObject(!objectsEqual(l, r)), nil } +func ltBuiltin(ctx context.Context, args invocationArgs) (object, error) { + if err := args.expectArgn(2); err != nil { + return nil, err + } + + isLess, err := objectsLessThan(args.args[0], args.args[1]) + if err != nil { + return nil, err + } + return boolObject(isLess), nil +} + +func leBuiltin(ctx context.Context, args invocationArgs) (object, error) { + if err := args.expectArgn(2); err != nil { + return nil, err + } + + isLess, err := objectsLessThan(args.args[0], args.args[1]) + if err != nil { + return nil, err + } + return boolObject(isLess || objectsEqual(args.args[0], args.args[1])), nil +} + +func gtBuiltin(ctx context.Context, args invocationArgs) (object, error) { + if err := args.expectArgn(2); err != nil { + return nil, err + } + + isGreater, err := objectsLessThan(args.args[1], args.args[0]) + if err != nil { + return nil, err + } + return boolObject(isGreater), nil +} + +func geBuiltin(ctx context.Context, args invocationArgs) (object, error) { + if err := args.expectArgn(2); err != nil { + return nil, err + } + + isGreater, err := objectsLessThan(args.args[1], args.args[0]) + if err != nil { + return nil, err + } + return boolObject(isGreater || objectsEqual(args.args[0], args.args[1])), nil +} + var errObjectsNotEqual = errors.New("objects not equal") func objectsEqual(l, r object) bool { @@ -169,6 +217,20 @@ func objectsEqual(l, r object) bool { return false } +func objectsLessThan(l, r object) (bool, error) { + switch lv := l.(type) { + case strObject: + if rv, ok := r.(strObject); ok { + return lv < rv, nil + } + case intObject: + if rv, ok := r.(intObject); ok { + return lv < rv, nil + } + } + return false, errors.New("objects are not comparable") +} + func concatBuiltin(ctx context.Context, args invocationArgs) (object, error) { var sb strings.Builder diff --git a/ucl/inst.go b/ucl/inst.go index 66c323b..7f38ee8 100644 --- a/ucl/inst.go +++ b/ucl/inst.go @@ -62,6 +62,10 @@ func New(opts ...InstOption) *Inst { rootEC.addCmd("eq", invokableFunc(eqBuiltin)) rootEC.addCmd("ne", invokableFunc(neBuiltin)) + rootEC.addCmd("gt", invokableFunc(gtBuiltin)) + rootEC.addCmd("ge", invokableFunc(geBuiltin)) + rootEC.addCmd("lt", invokableFunc(ltBuiltin)) + rootEC.addCmd("le", invokableFunc(leBuiltin)) rootEC.addCmd("add", invokableFunc(addBuiltin)) diff --git a/ucl/testbuiltins_test.go b/ucl/testbuiltins_test.go index d9133d6..e8b6424 100644 --- a/ucl/testbuiltins_test.go +++ b/ucl/testbuiltins_test.go @@ -892,6 +892,66 @@ func TestBuiltins_Reduce(t *testing.T) { } } +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 @@ -909,8 +969,8 @@ func TestBuiltins_EqNe(t *testing.T) { {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 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}, @@ -931,8 +991,8 @@ func TestBuiltins_EqNe(t *testing.T) { {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 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}, } @@ -949,6 +1009,8 @@ func TestBuiltins_EqNe(t *testing.T) { inst := New(WithOut(outW), WithTestBuiltin()) inst.SetVar("hello", Opaque(testProxyObject{v: "hello"})) inst.SetVar("world", Opaque(testProxyObject{v: "world"})) + inst.SetVar("true", true) + inst.SetVar("false", false) eqRes, err := inst.Eval(ctx, tt.expr) assert.NoError(t, err)