From 1169f82f7bd96835ad12ff08bc873c9d74d55270 Mon Sep 17 00:00:00 2001 From: Leon Mika Date: Sat, 7 Sep 2024 09:00:40 +1000 Subject: [PATCH] issue-1: added add, sub, mup, div, and mod --- ucl/builtins.go | 114 +++++++++++++++++++++++++++++++++++++++ ucl/inst.go | 4 ++ ucl/testbuiltins_test.go | 56 +++++++++++++++++++ 3 files changed, 174 insertions(+) diff --git a/ucl/builtins.go b/ucl/builtins.go index 333abeb..b0e1035 100644 --- a/ucl/builtins.go +++ b/ucl/builtins.go @@ -53,6 +53,120 @@ func addBuiltin(ctx context.Context, args invocationArgs) (object, error) { return intObject(n), nil } +func subBuiltin(ctx context.Context, args invocationArgs) (object, error) { + if len(args.args) == 0 { + return intObject(0), nil + } + + n := 0 + for i, a := range args.args { + var p int + switch t := a.(type) { + case intObject: + p = int(t) + case strObject: + v, err := strconv.Atoi(string(t)) + if err != nil { + return nil, fmt.Errorf("arg %v of 'sub' not convertable to an int", i) + } + p = v + default: + return nil, fmt.Errorf("arg %v of 'sub' not convertable to an int", i) + } + if i == 0 { + n = p + } else { + n -= p + } + } + + return intObject(n), nil +} + +func mupBuiltin(ctx context.Context, args invocationArgs) (object, error) { + if len(args.args) == 0 { + return intObject(1), nil + } + + n := 1 + for i, a := range args.args { + switch t := a.(type) { + case intObject: + n *= int(t) + case strObject: + v, err := strconv.Atoi(string(t)) + if err != nil { + return nil, fmt.Errorf("arg %v of 'mup' not convertable to an int", i) + } + n *= v + default: + return nil, fmt.Errorf("arg %v of 'mup' not convertable to an int", i) + } + } + + return intObject(n), nil +} + +func divBuiltin(ctx context.Context, args invocationArgs) (object, error) { + if len(args.args) == 0 { + return intObject(1), nil + } + + n := 1 + for i, a := range args.args { + var p int + switch t := a.(type) { + case intObject: + p = int(t) + case strObject: + v, err := strconv.Atoi(string(t)) + if err != nil { + return nil, fmt.Errorf("arg %v of 'div' not convertable to an int", i) + } + p = v + default: + return nil, fmt.Errorf("arg %v of 'div' not convertable to an int", i) + } + if i == 0 { + n = p + } else { + n /= p + } + } + + return intObject(n), nil +} + +func modBuiltin(ctx context.Context, args invocationArgs) (object, error) { + if len(args.args) == 0 { + return intObject(0), nil + } + + n := 0 + for i, a := range args.args { + var p int + switch t := a.(type) { + case intObject: + p = int(t) + case strObject: + v, err := strconv.Atoi(string(t)) + if err != nil { + return nil, fmt.Errorf("arg %v of 'mod' not convertable to an int", i) + } + p = v + default: + return nil, fmt.Errorf("arg %v of 'mod' not convertable to an int", i) + } + if i == 0 { + n = p + } else { + n %= p + } + } + + return intObject(n), nil +} + func setBuiltin(ctx context.Context, args invocationArgs) (object, error) { if err := args.expectArgn(2); err != nil { return nil, err diff --git a/ucl/inst.go b/ucl/inst.go index 3448e77..9ef7585 100644 --- a/ucl/inst.go +++ b/ucl/inst.go @@ -71,6 +71,10 @@ func New(opts ...InstOption) *Inst { rootEC.addCmd("int", invokableFunc(intBuiltin)) rootEC.addCmd("add", invokableFunc(addBuiltin)) + rootEC.addCmd("sub", invokableFunc(subBuiltin)) + rootEC.addCmd("mup", invokableFunc(mupBuiltin)) + rootEC.addCmd("div", invokableFunc(divBuiltin)) + rootEC.addCmd("mod", invokableFunc(modBuiltin)) rootEC.addCmd("cat", invokableFunc(concatBuiltin)) rootEC.addCmd("break", invokableFunc(breakBuiltin)) diff --git a/ucl/testbuiltins_test.go b/ucl/testbuiltins_test.go index 76bac93..8acd925 100644 --- a/ucl/testbuiltins_test.go +++ b/ucl/testbuiltins_test.go @@ -1093,3 +1093,59 @@ func TestBuiltins_Int(t *testing.T) { }) } } + +func TestBuiltins_AddSubMupDivMod(t *testing.T) { + tests := []struct { + desc string + expr string + want int + wantErr bool + }{ + {desc: "add 1", expr: `add 1 2`, want: 3}, + {desc: "add 2", expr: `add "3" 5`, want: 8}, + {desc: "add 3", expr: `add 1 "2" 8`, want: 11}, + {desc: "add 4", expr: `add 1`, want: 1}, + {desc: "add 5", expr: `add`, want: 0}, + {desc: "sub 1", expr: `sub 9 3`, want: 6}, + {desc: "sub 2", expr: `sub 2 "5"`, want: -3}, + {desc: "sub 3", expr: `sub 8 1 8`, want: -1}, + {desc: "sub 4", expr: `sub 4`, want: 4}, + {desc: "sub 5", expr: `sub`, want: 0}, + {desc: "mup 1", expr: `mup 2 4`, want: 8}, + {desc: "mup 2", expr: `mup 3 "4" 5`, want: 60}, + {desc: "mup 3", expr: `mup 7`, want: 7}, + {desc: "mup 4", expr: `mup`, want: 1}, + {desc: "div 1", expr: `div 8 4`, want: 2}, + {desc: "div 2", expr: `div "7" 4`, want: 1}, + {desc: "div 3", expr: `div 7`, want: 7}, + {desc: "div 4", expr: `div`, want: 1}, + {desc: "mod 1", expr: `mod 2 3`, want: 2}, + {desc: "mod 2", expr: `mod "7" 4`, want: 3}, + {desc: "mod 3", expr: `mod 8 4`, want: 0}, + {desc: "mod 4", expr: `mod 3`, want: 3}, + {desc: "mod 5", expr: `mod`, want: 0}, + + {desc: "add err", expr: `add [] [:]`, wantErr: true}, + {desc: "sub err", expr: `sub [] [:]`, wantErr: true}, + {desc: "mup err", expr: `mup [] [:]`, wantErr: true}, + {desc: "div err", expr: `div [] [:]`, wantErr: true}, + {desc: "mod err", expr: `mod [] [:]`, 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()) + + 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) + } + }) + } +}