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 }