This commit is contained in:
parent
5b913266e9
commit
2f54a9311e
|
@ -68,6 +68,7 @@ type astDot struct {
|
|||
}
|
||||
|
||||
type astCmd struct {
|
||||
Pos lexer.Position
|
||||
Name astDot `parser:"@@"`
|
||||
Args []astDot `parser:"@@*"`
|
||||
}
|
||||
|
|
|
@ -770,17 +770,38 @@ 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) {
|
||||
func tryBuiltin(ctx context.Context, args macroArgs) (_ object, fnErr error) {
|
||||
if args.nargs() < 1 {
|
||||
return nil, errors.New("need at least 1 arguments")
|
||||
}
|
||||
|
||||
var currError errObject
|
||||
defer func() {
|
||||
if errors.As(fnErr, &errBadUsage{}) {
|
||||
return
|
||||
}
|
||||
|
||||
if args.nargs() >= 2 && args.identIs(ctx, args.nargs()-2, "finally") {
|
||||
var blockArgs []object = nil
|
||||
if fnErr != nil {
|
||||
blockArgs = []object{errObject{err: fnErr}}
|
||||
}
|
||||
|
||||
_, err := args.evalBlock(ctx, args.nargs()-1, blockArgs, false)
|
||||
if err != nil && fnErr == nil {
|
||||
fnErr = err
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
res, err := args.evalBlock(ctx, 0, nil, false)
|
||||
args.shift(1)
|
||||
if err == nil {
|
||||
return res, nil
|
||||
}
|
||||
|
||||
args.shift(1)
|
||||
currError = errObject{err: err}
|
||||
|
||||
for args.identIs(ctx, 0, "catch") {
|
||||
args.shift(1)
|
||||
|
||||
|
@ -788,51 +809,20 @@ func tryBuiltin(ctx context.Context, args macroArgs) (object, error) {
|
|||
return nil, errors.New("need at least 1 arguments")
|
||||
}
|
||||
|
||||
res, err := args.evalBlock(ctx, 0, nil, false)
|
||||
res, err := args.evalBlock(ctx, 0, []object{currError}, false)
|
||||
if err == nil {
|
||||
return res, nil
|
||||
}
|
||||
|
||||
args.shift(2)
|
||||
currError = errObject{err: err}
|
||||
args.shift(1)
|
||||
}
|
||||
|
||||
// TODO: handle uncaught error
|
||||
if args.identIs(ctx, 0, "finally") && args.nargs() > 2 {
|
||||
return nil, tooManyFinallyBlocksError(args.ast.Pos)
|
||||
}
|
||||
|
||||
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")
|
||||
*/
|
||||
return nil, currError.err
|
||||
}
|
||||
|
||||
func foreachBuiltin(ctx context.Context, args macroArgs) (object, error) {
|
||||
|
|
37
ucl/errors.go
Normal file
37
ucl/errors.go
Normal file
|
@ -0,0 +1,37 @@
|
|||
package ucl
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/alecthomas/participle/v2/lexer"
|
||||
)
|
||||
|
||||
var (
|
||||
tooManyFinallyBlocksError = newBadUsage("try needs at most 1 finally")
|
||||
)
|
||||
|
||||
type errorWithPos struct {
|
||||
err error
|
||||
pos lexer.Position
|
||||
}
|
||||
|
||||
func (e errorWithPos) Error() string {
|
||||
return fmt.Sprintf("%v:%v - %v", e.pos.Line, e.pos.Offset, e.err.Error())
|
||||
}
|
||||
|
||||
func (e errorWithPos) Unwrap() error {
|
||||
return e.err
|
||||
}
|
||||
|
||||
type errBadUsage struct {
|
||||
msg string
|
||||
}
|
||||
|
||||
func newBadUsage(msg string) func(pos lexer.Position) error {
|
||||
return func(pos lexer.Position) error {
|
||||
return errorWithPos{err: errBadUsage{msg: msg}, pos: pos}
|
||||
}
|
||||
}
|
||||
|
||||
func (e errBadUsage) Error() string {
|
||||
return "bad usage: " + e.msg
|
||||
}
|
10
ucl/objs.go
10
ucl/objs.go
|
@ -303,6 +303,16 @@ func (ma macroArgs) evalBlock(ctx context.Context, n int, args []object, pushSco
|
|||
return nil, errors.New("expected an invokable arg")
|
||||
}
|
||||
|
||||
type errObject struct{ err error }
|
||||
|
||||
func (eo errObject) String() string {
|
||||
return "error:" + eo.err.Error()
|
||||
}
|
||||
|
||||
func (eo errObject) Truthy() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
type invocationArgs struct {
|
||||
eval evaluator
|
||||
inst *Inst
|
||||
|
|
|
@ -202,9 +202,10 @@ func TestBuiltins_If(t *testing.T) {
|
|||
|
||||
func TestBuiltins_Try(t *testing.T) {
|
||||
tests := []struct {
|
||||
desc string
|
||||
expr string
|
||||
want string
|
||||
desc string
|
||||
expr string
|
||||
want string
|
||||
wantErr string
|
||||
}{
|
||||
{desc: "single try - successful", expr: `
|
||||
try {
|
||||
|
@ -215,7 +216,7 @@ func TestBuiltins_Try(t *testing.T) {
|
|||
try {
|
||||
error "bang"
|
||||
}
|
||||
echo "after"`, want: "after\n(nil)\n"},
|
||||
echo "after"`, wantErr: "bang"},
|
||||
{desc: "try with catch - successful", expr: `
|
||||
try {
|
||||
echo "good"
|
||||
|
@ -230,6 +231,149 @@ func TestBuiltins_Try(t *testing.T) {
|
|||
echo "something happened"
|
||||
}
|
||||
echo "after"`, want: "something happened\nafter\n(nil)\n"},
|
||||
{desc: "try with catch with passed in error - unsuccessful", expr: `
|
||||
try {
|
||||
error "bang"
|
||||
} catch { |err|
|
||||
echo (cat "the error was = " $err)
|
||||
}
|
||||
echo "after"`, want: "the error was = error:bang\nafter\n(nil)\n"},
|
||||
{desc: "try with two catch - successful", expr: `
|
||||
try {
|
||||
echo "i'm good"
|
||||
} catch {
|
||||
echo "i'm also good"
|
||||
} catch {
|
||||
echo "wow, we made it here"
|
||||
}
|
||||
echo "after"`, want: "i'm good\nafter\n(nil)\n"},
|
||||
{desc: "try with two catch - first unsuccessful", expr: `
|
||||
try {
|
||||
error "bang"
|
||||
} catch {
|
||||
echo "i'm also good"
|
||||
} catch {
|
||||
echo "wow, we made it here"
|
||||
}
|
||||
echo "after"`, want: "i'm also good\nafter\n(nil)\n"},
|
||||
{desc: "try with two catch - both unsuccessful", expr: `
|
||||
try {
|
||||
error "bang"
|
||||
} catch {
|
||||
error "boom"
|
||||
} catch {
|
||||
echo "wow, we made it here"
|
||||
}
|
||||
echo "after"`, want: "wow, we made it here\nafter\n(nil)\n"},
|
||||
{desc: "return value - single try", expr: `
|
||||
set x (try { error "bang" })
|
||||
$x`, wantErr: "bang"},
|
||||
{desc: "return value - single try", expr: `
|
||||
set x (try { error "bang" } catch { |err| $err })
|
||||
$x`, want: "error:bang\n"},
|
||||
{desc: "return value - try and catch - successful", expr: `
|
||||
set x (try { error "bang" } catch { "hello" })
|
||||
$x`, want: "hello\n"},
|
||||
{desc: "return value - try and catch - unsuccessful", expr: `
|
||||
set x (try { error "bang" } catch { error "boom" })
|
||||
$x`, wantErr: "boom"},
|
||||
{desc: "try with finally - successful", expr: `
|
||||
try {
|
||||
echo "all good"
|
||||
} finally {
|
||||
echo "always at end"
|
||||
}
|
||||
echo "after"`, want: "all good\nalways at end\nafter\n(nil)\n"},
|
||||
{desc: "try with finally - unsuccessful", expr: `
|
||||
try {
|
||||
error "bang"
|
||||
} finally {
|
||||
echo "always at end"
|
||||
}
|
||||
echo "after"`, want: "always at end\n", wantErr: "bang"},
|
||||
{desc: "try with catch and finally - successful", expr: `
|
||||
try {
|
||||
echo "all good"
|
||||
} catch {
|
||||
echo "was caught"
|
||||
} finally {
|
||||
echo "always at end"
|
||||
}
|
||||
echo "after"`, want: "all good\nalways at end\nafter\n(nil)\n"},
|
||||
{desc: "try with catch and finally - unsuccessful", expr: `
|
||||
try {
|
||||
error "bang"
|
||||
} catch {
|
||||
echo "was caught"
|
||||
} finally {
|
||||
echo "always at end"
|
||||
}
|
||||
echo "after"`, want: "was caught\nalways at end\nafter\n(nil)\n"},
|
||||
{desc: "try with catch and finally - catch unsuccessful", expr: `
|
||||
try {
|
||||
error "bang"
|
||||
} catch {
|
||||
error "boom"
|
||||
} finally {
|
||||
echo "always at end"
|
||||
}
|
||||
echo "after"`, want: "always at end\n", wantErr: "boom"},
|
||||
{desc: "try with finally - finally result discarded", expr: `
|
||||
set a (try {
|
||||
"return me"
|
||||
} finally {
|
||||
"not met"
|
||||
})
|
||||
echo $a`, want: "return me\n(nil)\n"},
|
||||
{desc: "try with finally - error discarded if try fails result discarded", expr: `
|
||||
try {
|
||||
error "bang"
|
||||
} finally {
|
||||
error "kaboom"
|
||||
}`, wantErr: "bang"},
|
||||
{desc: "try with finally - error not discarded if try succeeds", expr: `
|
||||
try {
|
||||
echo "all good"
|
||||
} finally {
|
||||
error "kaboom"
|
||||
}`, want: "all good\n", wantErr: "kaboom"},
|
||||
{desc: "try with finally with error - successful", expr: `
|
||||
try {
|
||||
echo "all good"
|
||||
} finally { |err|
|
||||
echo (cat "the error was " $err)
|
||||
if (eq $err ()) { echo "that's nil" }
|
||||
}
|
||||
echo "after"`, want: "all good\nthe error was \nthat's nil\nafter\n(nil)\n"},
|
||||
{desc: "try with finally - unsuccessful", expr: `
|
||||
try {
|
||||
error "bang"
|
||||
} finally { |err|
|
||||
echo (cat "the error was " $err)
|
||||
}
|
||||
echo "after"`, want: "the error was error:bang\n", wantErr: "bang"},
|
||||
{desc: "try with too many finallies - unsuccessful", expr: `
|
||||
try {
|
||||
error "bang"
|
||||
} finally {
|
||||
echo "and do this"
|
||||
} finally { |err|
|
||||
echo (cat "the error was " $err)
|
||||
}
|
||||
echo "after"`, wantErr: "2:4 - bad usage: try needs at most 1 finally"},
|
||||
{desc: "try with finally in catch - unsuccessful", expr: `
|
||||
try {
|
||||
try {
|
||||
error "bang"
|
||||
} finally { |err|
|
||||
echo (cat "the error was " $err)
|
||||
}
|
||||
} catch {
|
||||
echo "outer caught"
|
||||
} finally {
|
||||
echo "outer"
|
||||
}
|
||||
echo "after"`, want: "the error was error:bang\nouter caught\nouter\nafter\n(nil)\n"},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
|
@ -240,7 +384,12 @@ func TestBuiltins_Try(t *testing.T) {
|
|||
inst := New(WithOut(outW), WithTestBuiltin())
|
||||
err := EvalAndDisplay(ctx, inst, tt.expr)
|
||||
|
||||
assert.NoError(t, err)
|
||||
if tt.wantErr != "" {
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, tt.wantErr, err.Error())
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
assert.Equal(t, tt.want, outW.String())
|
||||
})
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue