From 08c645a70f26bd9d6a529aa35365e376d405ea2a Mon Sep 17 00:00:00 2001 From: Leon Mika Date: Sat, 7 Sep 2024 09:14:21 +1000 Subject: [PATCH] issue-1: added and, or, and not --- ucl/builtins.go | 34 ++++++++++++++++++++++++++++ ucl/inst.go | 4 ++++ ucl/testbuiltins_test.go | 49 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 87 insertions(+) diff --git a/ucl/builtins.go b/ucl/builtins.go index b0e1035..f36842e 100644 --- a/ucl/builtins.go +++ b/ucl/builtins.go @@ -264,6 +264,40 @@ func geBuiltin(ctx context.Context, args invocationArgs) (object, error) { return boolObject(isGreater || objectsEqual(args.args[0], args.args[1])), nil } +func andBuiltin(ctx context.Context, args invocationArgs) (object, error) { + if err := args.expectArgn(2); err != nil { + return nil, err + } + + for _, a := range args.args { + if a == nil || !a.Truthy() { + return boolObject(false), nil + } + } + return args.args[len(args.args)-1], nil +} + +func orBuiltin(ctx context.Context, args invocationArgs) (object, error) { + if err := args.expectArgn(2); err != nil { + return nil, err + } + + for _, a := range args.args { + if a != nil && a.Truthy() { + return a, nil + } + } + return boolObject(false), nil +} + +func notBuiltin(ctx context.Context, args invocationArgs) (object, error) { + if err := args.expectArgn(1); err != nil { + return nil, err + } + + return boolObject(!args.args[0].Truthy()), nil +} + var errObjectsNotEqual = errors.New("objects not equal") func objectsEqual(l, r object) bool { diff --git a/ucl/inst.go b/ucl/inst.go index 9ef7585..c19f9cd 100644 --- a/ucl/inst.go +++ b/ucl/inst.go @@ -76,6 +76,10 @@ func New(opts ...InstOption) *Inst { rootEC.addCmd("div", invokableFunc(divBuiltin)) rootEC.addCmd("mod", invokableFunc(modBuiltin)) + rootEC.addCmd("and", invokableFunc(andBuiltin)) + rootEC.addCmd("or", invokableFunc(orBuiltin)) + rootEC.addCmd("not", invokableFunc(notBuiltin)) + rootEC.addCmd("cat", invokableFunc(concatBuiltin)) rootEC.addCmd("break", invokableFunc(breakBuiltin)) rootEC.addCmd("continue", invokableFunc(continueBuiltin)) diff --git a/ucl/testbuiltins_test.go b/ucl/testbuiltins_test.go index 8acd925..8094824 100644 --- a/ucl/testbuiltins_test.go +++ b/ucl/testbuiltins_test.go @@ -1149,3 +1149,52 @@ func TestBuiltins_AddSubMupDivMod(t *testing.T) { }) } } + +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: false}, + {desc: "short circuit or 1", expr: `or "hello" "world"`, want: "hello"}, + {desc: "short circuit or 2", expr: `or () "world"`, want: "world"}, + + {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) + } + }) + } +}