From 484631782e47ab29c6f89db721acef4c4e0f0b12 Mon Sep 17 00:00:00 2001 From: Leon Mika Date: Fri, 6 Sep 2024 20:55:19 +1000 Subject: [PATCH] issue-1: Added str and int --- ucl/builtins.go | 40 ++++++++++++++++++++++ ucl/inst.go | 3 ++ ucl/objs.go | 22 +++++++++++-- ucl/testbuiltins_test.go | 71 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 133 insertions(+), 3 deletions(-) diff --git a/ucl/builtins.go b/ucl/builtins.go index ac72f67..333abeb 100644 --- a/ucl/builtins.go +++ b/ucl/builtins.go @@ -231,6 +231,46 @@ func objectsLessThan(l, r object) (bool, error) { return false, errors.New("objects are not comparable") } +func strBuiltin(ctx context.Context, args invocationArgs) (object, error) { + if err := args.expectArgn(1); err != nil { + return nil, err + } + + if args.args[0] == nil { + return strObject(""), nil + } + + return strObject(args.args[0].String()), nil +} + +func intBuiltin(ctx context.Context, args invocationArgs) (object, error) { + if err := args.expectArgn(1); err != nil { + return nil, err + } + + if args.args[0] == nil { + return intObject(0), nil + } + + switch v := args.args[0].(type) { + case intObject: + return v, nil + case strObject: + i, err := strconv.Atoi(string(v)) + if err != nil { + return nil, errors.New("cannot convert to int") + } + return intObject(i), nil + case boolObject: + if v { + return intObject(1), nil + } + return intObject(0), nil + } + + return nil, errors.New("cannot convert to int") +} + func concatBuiltin(ctx context.Context, args invocationArgs) (object, error) { var sb strings.Builder diff --git a/ucl/inst.go b/ucl/inst.go index 7f38ee8..3448e77 100644 --- a/ucl/inst.go +++ b/ucl/inst.go @@ -67,6 +67,9 @@ func New(opts ...InstOption) *Inst { rootEC.addCmd("lt", invokableFunc(ltBuiltin)) rootEC.addCmd("le", invokableFunc(leBuiltin)) + rootEC.addCmd("str", invokableFunc(strBuiltin)) + rootEC.addCmd("int", invokableFunc(intBuiltin)) + rootEC.addCmd("add", invokableFunc(addBuiltin)) rootEC.addCmd("cat", invokableFunc(concatBuiltin)) diff --git a/ucl/objs.go b/ucl/objs.go index 6580c9c..1fa18e0 100644 --- a/ucl/objs.go +++ b/ucl/objs.go @@ -6,6 +6,7 @@ import ( "fmt" "reflect" "strconv" + "strings" "github.com/lmika/gopkgs/fp/slices" ) @@ -51,7 +52,22 @@ func (s listObject) Index(i int) object { type hashObject map[string]object func (s hashObject) String() string { - return fmt.Sprintf("%v", map[string]object(s)) + if len(s) == 0 { + return "[:]" + } + + sb := strings.Builder{} + sb.WriteString("[") + for k, v := range s { + if sb.Len() != 1 { + sb.WriteString(" ") + } + sb.WriteString(k) + sb.WriteString(":") + sb.WriteString(v.String()) + } + sb.WriteString("]") + return sb.String() } func (s hashObject) Truthy() bool { @@ -99,9 +115,9 @@ type boolObject bool func (b boolObject) String() string { if b { - return "(true)" + return "true" } - return "(false))" + return "false" } func (b boolObject) Truthy() bool { diff --git a/ucl/testbuiltins_test.go b/ucl/testbuiltins_test.go index e8b6424..76bac93 100644 --- a/ucl/testbuiltins_test.go +++ b/ucl/testbuiltins_test.go @@ -1022,3 +1022,74 @@ func TestBuiltins_EqNe(t *testing.T) { }) } } + +func TestBuiltins_Str(t *testing.T) { + tests := []struct { + desc string + expr string + want string + }{ + {desc: "str", expr: `str "hello"`, want: "hello"}, + {desc: "int", expr: `str 123`, want: "123"}, + {desc: "bool 1", expr: `str (eq 1 1)`, want: "true"}, + {desc: "bool 2", expr: `str (eq 1 0)`, want: "false"}, + {desc: "list 1", expr: `str [1 2 3]`, want: "[1 2 3]"}, + {desc: "list 2", expr: `str []`, want: "[]"}, + {desc: "dict 1", expr: `str ["hello":"world"]`, want: `[hello:world]`}, + {desc: "dict 2", expr: `str [:]`, want: "[:]"}, + {desc: "nil", expr: `str ()`, want: ""}, + } + + 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) + assert.NoError(t, err) + assert.Equal(t, tt.want, eqRes) + }) + } +} + +func TestBuiltins_Int(t *testing.T) { + tests := []struct { + desc string + expr string + want int + wantErr bool + }{ + {desc: "str 1", expr: `int "123"`, want: 123}, + {desc: "str 2", expr: `int "31452"`, want: 31452}, + {desc: "str 3", expr: `int "-21"`, want: -21}, + {desc: "int 1", expr: `int 123`, want: 123}, + {desc: "int 2", expr: `int -21`, want: -21}, + {desc: "bool 1", expr: `int (eq 1 1)`, want: 1}, + {desc: "bool 2", expr: `int (eq 1 0)`, want: 0}, + {desc: "nil", expr: `int ()`, want: 0}, + + {desc: "list 1", expr: `int [1 2 3]`, wantErr: true}, + {desc: "list 2", expr: `int []`, wantErr: true}, + {desc: "dict 1", expr: `int ["hello":"world"]`, wantErr: true}, + {desc: "dict 2", expr: `int [:]`, 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) + } + }) + } +}