github.com/emcfarlane/larking@v0.0.0-20220605172417-1704b45ee6c3/starlib/starlarkerrors/errors.go (about) 1 // Copyright 2021 Edward McFarlane. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 // Package errors implements functions to manipulate errors. 6 package starlarkerrors 7 8 import ( 9 "errors" 10 "fmt" 11 "regexp" 12 "sort" 13 14 "github.com/emcfarlane/larking/starlib/starext" 15 "go.starlark.net/starlark" 16 "go.starlark.net/starlarkstruct" 17 ) 18 19 func NewModule() *starlarkstruct.Module { 20 return &starlarkstruct.Module{ 21 Name: "errors", 22 Members: starlark.StringDict{ 23 "error": starext.MakeBuiltin("errors.error", MakeError), 24 "catch": starext.MakeBuiltin("errors.catch", MakeCatch), 25 }, 26 } 27 } 28 29 type Error struct { 30 err error 31 } 32 33 func NewError(err error) Error { 34 return Error{err: err} 35 } 36 37 func MakeError(thread *starlark.Thread, _ string, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) { 38 failFn, ok := starlark.Universe["fail"].(*starlark.Builtin) 39 if !ok { 40 return nil, fmt.Errorf("internal builtin fail not found") 41 } 42 if _, err := starlark.Call(thread, failFn, args, kwargs); err != nil { 43 // Error on purpose, capture error printing. 44 return Error{err: err}, nil 45 } 46 return nil, fmt.Errorf("fail failed to error") 47 } 48 49 func (e Error) Err() error { return e.err } 50 func (e Error) Error() string { return e.err.Error() } 51 func (e Error) String() string { return fmt.Sprintf("<error %q>", e.err.Error()) } 52 func (e Error) Type() string { return "errors.error" } 53 func (e Error) Freeze() {} // immutable 54 func (e Error) Truth() starlark.Bool { return starlark.Bool(e.err != nil) } 55 func (e Error) Hash() (uint32, error) { return 0, fmt.Errorf("unhashable type: %s", e.Type()) } 56 57 type errorAttr func(e Error) starlark.Value 58 59 var errorAttrs = map[string]errorAttr{ 60 "matches": func(e Error) starlark.Value { return starext.MakeMethod(e, "matches", e.matches) }, 61 "kind": func(e Error) starlark.Value { return starext.MakeMethod(e, "kind", e.kind) }, 62 } 63 64 func (e Error) Attr(name string) (starlark.Value, error) { 65 if a := errorAttrs[name]; a != nil { 66 return a(e), nil 67 } 68 return nil, nil 69 } 70 func (e Error) AttrNames() []string { 71 names := make([]string, 0, len(errorAttrs)) 72 for name := range errorAttrs { 73 names = append(names, name) 74 } 75 sort.Strings(names) 76 return names 77 } 78 79 func (e Error) matches(_ *starlark.Thread, fnname string, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) { 80 var pattern string 81 if err := starlark.UnpackArgs(fnname, args, kwargs, "pattern", &pattern); err != nil { 82 return nil, err 83 } 84 ok, err := regexp.MatchString(pattern, e.err.Error()) 85 if err != nil { 86 return nil, fmt.Errorf("error.matches: %s", err) 87 } 88 return starlark.Bool(ok), nil 89 } 90 91 // errorKind as "is" is a reserved keyword. 92 func (e Error) kind(_ *starlark.Thread, fnname string, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) { 93 var err Error 94 if err := starlark.UnpackArgs(fnname, args, kwargs, "err", &err); err != nil { 95 return nil, err 96 } 97 ok := errors.Is(e.err, err.err) 98 return starlark.Bool(ok), nil 99 } 100 101 type Result struct { 102 value starlark.Value 103 isErr bool 104 } 105 106 func (v Result) String() string { return fmt.Sprintf("<result %q>", v.value.String()) } 107 func (v Result) Type() string { return "errors.result" } 108 func (v Result) Freeze() {} // immutable 109 func (v Result) Truth() starlark.Bool { return starlark.Bool(!v.isErr) } 110 func (v Result) Hash() (uint32, error) { return 0, fmt.Errorf("unhashable type: %s", v.Type()) } 111 112 // MakeCatch evaluates f() and returns its evaluation error message 113 // if it failed or the value if it succeeded. 114 func MakeCatch(thread *starlark.Thread, _ string, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) { 115 if len(args) == 0 { 116 return Result{value: starlark.None}, nil 117 } 118 119 fn, ok := args[0].(starlark.Callable) 120 if !ok { 121 return nil, fmt.Errorf("errors.call: expected callable got %T", args[0]) 122 } 123 args = args[1:] 124 v, err := starlark.Call(thread, fn, args, kwargs) 125 if err != nil { 126 return Result{ 127 value: NewError(err), 128 isErr: true, 129 }, nil 130 } 131 return Result{value: v}, nil 132 } 133 134 func (v Result) AttrNames() []string { return []string{"err", "val"} } 135 func (v Result) Attr(name string) (starlark.Value, error) { 136 switch name { 137 case "val": 138 if v.isErr { 139 return nil, v.value.(Error) 140 } 141 return v.value, nil 142 case "err": 143 if v.isErr { 144 return v.value, nil 145 } 146 return starlark.None, nil 147 default: 148 return nil, nil 149 } 150 }