From 9f1bedfdac772e56bbd442bf8490d34d4537aa92 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 | 138 +++++++++++++++++++++++++++++++++++++++ ucl/inst.go | 5 ++ ucl/testbuiltins_test.go | 56 ++++++++++++++++ 3 files changed, 199 insertions(+) diff --git a/ucl/builtins.go b/ucl/builtins.go index 571bd9e..14ea2e4 100644 --- a/ucl/builtins.go +++ b/ucl/builtins.go @@ -28,6 +28,144 @@ func echoBuiltin(ctx context.Context, args invocationArgs) (object, error) { return nil, nil } +func addBuiltin(ctx context.Context, args invocationArgs) (object, error) { + if len(args.args) == 0 { + return intObject(0), nil + } + + n := 0 + 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 'add' not convertable to an int", i) + } + n += v + default: + return nil, fmt.Errorf("arg %v of 'add' not convertable to an int", i) + } + } + + 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 649e24f..090dc6d 100644 --- a/ucl/inst.go +++ b/ucl/inst.go @@ -67,6 +67,11 @@ func New(opts ...InstOption) *Inst { rootEC.addCmd("str", invokableFunc(strBuiltin)) rootEC.addCmd("int", invokableFunc(intBuiltin)) + 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)) rootEC.addCmd("continue", invokableFunc(continueBuiltin)) diff --git a/ucl/testbuiltins_test.go b/ucl/testbuiltins_test.go index e594a1c..0a74351 100644 --- a/ucl/testbuiltins_test.go +++ b/ucl/testbuiltins_test.go @@ -1084,3 +1084,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) + } + }) + } +}