github.com/arnodel/golua@v0.0.0-20230215163904-e0b5347eaaa1/runtime/error.go (about)

     1  package runtime
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"strings"
     7  )
     8  
     9  // Error is the error type that can be produced by running continuations.  Each
    10  // error has a message and a context, which is a slice of continuations.  There
    11  // is no call stack, but you can imagine you "unwind" the call stack by
    12  // iterating over this slice.
    13  type Error struct {
    14  	message Value
    15  	handled bool
    16  	lineno  int
    17  	source  string
    18  }
    19  
    20  // NewError returns a new error with the given message and no context.
    21  func NewError(message Value) *Error {
    22  	return &Error{message: message}
    23  }
    24  
    25  func newHandledError(message Value) *Error {
    26  	return &Error{message: message, handled: true}
    27  }
    28  
    29  // AsError check if err can be converted to an *Error and returns that if
    30  // successful.
    31  func AsError(err error) (rtErr *Error, ok bool) {
    32  	ok = errors.As(err, &rtErr)
    33  	return
    34  }
    35  
    36  // ToError turns err into an *Error instance.
    37  func ToError(err error) *Error {
    38  	if err == nil {
    39  		return nil
    40  	}
    41  	rtErr, ok := AsError(err)
    42  	if ok {
    43  		return rtErr
    44  	}
    45  	return NewError(StringValue(err.Error()))
    46  }
    47  
    48  // ErrorValue extracts a Value from err.  If err is an *Error then it returns
    49  // its Value(), otherwise it builds a Value from the error string.
    50  func ErrorValue(err error) Value {
    51  	if err == nil {
    52  		return NilValue
    53  	}
    54  	rtErr, ok := AsError(err)
    55  	if ok {
    56  		return rtErr.Value()
    57  	}
    58  	return StringValue(err.Error())
    59  }
    60  
    61  // AddContext returns a new error with the lineno / source fields set if not
    62  // already set.
    63  func (e *Error) AddContext(c Cont, depth int) *Error {
    64  	if e.lineno != 0 || e.handled {
    65  		return e
    66  	}
    67  	e = &Error{
    68  		lineno:  -1,
    69  		source:  "?",
    70  		message: e.message,
    71  	}
    72  	if depth == 0 {
    73  		return e
    74  	}
    75  	if depth > 0 {
    76  		for depth > 1 && c != nil {
    77  			c = c.Parent()
    78  			depth--
    79  		}
    80  	} else {
    81  		for c != nil {
    82  			if _, ok := c.(*LuaCont); ok {
    83  				break
    84  			}
    85  			c = c.Parent()
    86  		}
    87  	}
    88  	if c == nil {
    89  		return e
    90  	}
    91  	info := c.DebugInfo()
    92  	if info == nil {
    93  		return e
    94  	}
    95  	if info.CurrentLine != 0 {
    96  		e.lineno = int(info.CurrentLine)
    97  	}
    98  	e.source = info.Source
    99  	s, ok := e.message.TryString()
   100  	if ok && e.lineno > 0 {
   101  		e.message = StringValue(fmt.Sprintf("%s:%d: %s", e.source, e.lineno, s))
   102  	}
   103  	return e
   104  }
   105  
   106  // Value returns the message of the error (which can be any Lua Value).
   107  func (e *Error) Value() Value {
   108  	if e == nil {
   109  		return NilValue
   110  	}
   111  	return e.message
   112  }
   113  
   114  // Handled returns true if the error has been handled (i.e. the message handler
   115  // has processed it).
   116  func (e *Error) Handled() bool {
   117  	return e.handled
   118  }
   119  
   120  // Error implements the error interface.
   121  func (e *Error) Error() string {
   122  	s, _ := e.message.ToString()
   123  	return fmt.Sprintf("error: %s", s)
   124  }
   125  
   126  // Traceback produces a traceback string of the continuation, requiring memory
   127  // for the string.
   128  func (r *Runtime) Traceback(pfx string, c Cont) string {
   129  	sb := strings.Builder{}
   130  	needNewline := false
   131  	if len(pfx) > 0 {
   132  		r.RequireBytes(len(pfx))
   133  		sb.WriteString(pfx)
   134  		needNewline = true
   135  	}
   136  	for c != nil {
   137  		info := c.DebugInfo()
   138  		if info != nil {
   139  			if needNewline {
   140  				r.RequireBytes(1)
   141  				sb.WriteByte('\n')
   142  			}
   143  			sourceInfo := info.Source
   144  			if info.CurrentLine > 0 {
   145  				sourceInfo = fmt.Sprintf("%s:%d", sourceInfo, info.CurrentLine)
   146  			}
   147  			line := fmt.Sprintf("in function %s (file %s)", info.Name, sourceInfo)
   148  			r.RequireBytes(len(line))
   149  			sb.WriteString(line)
   150  			needNewline = true
   151  		}
   152  		c = c.Parent()
   153  	}
   154  	return sb.String()
   155  }