From bb78a39cdb6fd3adcbf55334104c63b883a095d6 Mon Sep 17 00:00:00 2001 From: Leon Mika Date: Mon, 21 Oct 2024 15:21:28 +1100 Subject: [PATCH] Started working on 'try' statement --- ucl/builtins.go | 65 ++++++++++++++++++++++++++++++++++++++++ ucl/inst.go | 1 + ucl/testbuiltins_test.go | 54 +++++++++++++++++++++++++++++++++ 3 files changed, 120 insertions(+) diff --git a/ucl/builtins.go b/ucl/builtins.go index 3b3a879..9d601ff 100644 --- a/ucl/builtins.go +++ b/ucl/builtins.go @@ -770,6 +770,71 @@ func ifBuiltin(ctx context.Context, args macroArgs) (object, error) { return nil, errors.New("malformed if-elif-else") } +func tryBuiltin(ctx context.Context, args macroArgs) (object, error) { + if args.nargs() < 1 { + return nil, errors.New("need at least 1 arguments") + } + + res, err := args.evalBlock(ctx, 0, nil, false) + if err == nil { + return res, nil + } + + args.shift(1) + for args.identIs(ctx, 0, "catch") { + args.shift(1) + + if args.nargs() < 1 { + return nil, errors.New("need at least 1 arguments") + } + + res, err := args.evalBlock(ctx, 0, nil, false) + if err == nil { + return res, nil + } + + args.shift(2) + } + + // TODO: handle uncaught error + + return nil, nil + + /* + if guard, err := args.evalArg(ctx, 0); err == nil && isTruthy(guard) { + return args.evalBlock(ctx, 1, nil, false) + } else if err != nil { + return nil, err + } + + args.shift(2) + for args.identIs(ctx, 0, "elif") { + args.shift(1) + + if args.nargs() < 2 { + return nil, errors.New("need at least 2 arguments") + } + + if guard, err := args.evalArg(ctx, 0); err == nil && isTruthy(guard) { + return args.evalBlock(ctx, 1, nil, false) + } else if err != nil { + return nil, err + } + + args.shift(2) + } + + if args.identIs(ctx, 0, "else") && args.nargs() > 1 { + return args.evalBlock(ctx, 1, nil, false) + } else if args.nargs() == 0 { + // no elif or else + return nil, nil + } + + return nil, errors.New("malformed if-elif-else") + */ +} + func foreachBuiltin(ctx context.Context, args macroArgs) (object, error) { var ( items object diff --git a/ucl/inst.go b/ucl/inst.go index a5f35ed..5091ac1 100644 --- a/ucl/inst.go +++ b/ucl/inst.go @@ -88,6 +88,7 @@ func New(opts ...InstOption) *Inst { rootEC.addMacro("if", macroFunc(ifBuiltin)) rootEC.addMacro("foreach", macroFunc(foreachBuiltin)) rootEC.addMacro("proc", macroFunc(procBuiltin)) + rootEC.addMacro("try", macroFunc(tryBuiltin)) inst := &Inst{ out: os.Stdout, diff --git a/ucl/testbuiltins_test.go b/ucl/testbuiltins_test.go index 7d0ae1c..684454e 100644 --- a/ucl/testbuiltins_test.go +++ b/ucl/testbuiltins_test.go @@ -3,6 +3,7 @@ package ucl import ( "bytes" "context" + "errors" "fmt" "strings" "testing" @@ -36,6 +37,13 @@ func WithTestBuiltin() InstOption { return listObject(args.args), nil })) + i.rootEC.addCmd("error", invokableFunc(func(ctx context.Context, args invocationArgs) (object, error) { + if len(args.args) == 0 { + return nil, errors.New("an error occurred") + } + return nil, errors.New(args.args[0].String()) + })) + i.rootEC.addCmd("joinpipe", invokableFunc(func(ctx context.Context, args invocationArgs) (object, error) { sb := strings.Builder{} @@ -188,6 +196,52 @@ func TestBuiltins_If(t *testing.T) { } } +func TestBuiltins_Try(t *testing.T) { + tests := []struct { + desc string + expr string + want string + }{ + {desc: "single try - successful", expr: ` + try { + echo "good" + } + echo "after"`, want: "good\nafter\n(nil)\n"}, + {desc: "single try - unsuccessful", expr: ` + try { + error "bang" + } + echo "after"`, want: "after\n(nil)\n"}, + {desc: "try with catch - successful", expr: ` + try { + echo "good" + } catch { + echo "something happened" + } + echo "after"`, want: "good\nafter\n(nil)\n"}, + {desc: "try with catch - unsuccessful", expr: ` + try { + error "bang" + } catch { + echo "something happened" + } + echo "after"`, want: "something happened\nafter\n(nil)\n"}, + } + + for _, tt := range tests { + t.Run(tt.desc, func(t *testing.T) { + ctx := context.Background() + outW := bytes.NewBuffer(nil) + + inst := New(WithOut(outW), WithTestBuiltin()) + err := EvalAndDisplay(ctx, inst, tt.expr) + + assert.NoError(t, err) + assert.Equal(t, tt.want, outW.String()) + }) + } +} + func TestBuiltins_ForEach(t *testing.T) { tests := []struct { desc string