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  }