github.com/mem/u-root@v2.0.1-0.20181004165302-9b18b4636a33+incompatible/cmds/elvish/eval/exception.go (about) 1 package eval 2 3 import ( 4 "bytes" 5 "fmt" 6 "strconv" 7 "strings" 8 "syscall" 9 "unsafe" 10 11 "github.com/u-root/u-root/cmds/elvish/eval/vals" 12 "github.com/u-root/u-root/cmds/elvish/parse" 13 "github.com/u-root/u-root/cmds/elvish/util" 14 "github.com/xiaq/persistent/hash" 15 ) 16 17 // Exception represents an elvish exception. It is both a Value accessible to 18 // elvishscript, and the type of error returned by public facing evaluation 19 // methods like (*Evaler)PEval. 20 type Exception struct { 21 Cause error 22 Traceback *stackTrace 23 } 24 25 // OK is a pointer to the zero value of Exception, representing the absence of 26 // exception. 27 var OK = &Exception{} 28 29 func (exc *Exception) Error() string { 30 return exc.Cause.Error() 31 } 32 33 func (exc *Exception) Pprint(indent string) string { 34 buf := new(bytes.Buffer) 35 36 var causeDescription string 37 if pprinter, ok := exc.Cause.(util.Pprinter); ok { 38 causeDescription = pprinter.Pprint(indent) 39 } else { 40 causeDescription = "\033[31;1m" + exc.Cause.Error() + "\033[m" 41 } 42 fmt.Fprintf(buf, "Exception: %s\n", causeDescription) 43 44 if exc.Traceback.next == nil { 45 buf.WriteString(exc.Traceback.entry.PprintCompact(indent)) 46 } else { 47 buf.WriteString(indent + "Traceback:") 48 for tb := exc.Traceback; tb != nil; tb = tb.next { 49 buf.WriteString("\n" + indent + " ") 50 buf.WriteString(tb.entry.Pprint(indent + " ")) 51 } 52 } 53 54 if pipeExcs, ok := exc.Cause.(PipelineError); ok { 55 buf.WriteString("\n" + indent + "Caused by:") 56 for _, e := range pipeExcs.Errors { 57 if e == OK { 58 continue 59 } 60 buf.WriteString("\n" + indent + " " + e.Pprint(indent+" ")) 61 } 62 } 63 64 return buf.String() 65 } 66 67 func (exc *Exception) Kind() string { 68 return "exception" 69 } 70 71 func (exc *Exception) Repr(indent int) string { 72 if exc.Cause == nil { 73 return "$ok" 74 } 75 if r, ok := exc.Cause.(vals.Reprer); ok { 76 return r.Repr(indent) 77 } 78 return "?(fail " + parse.Quote(exc.Cause.Error()) + ")" 79 } 80 81 // Equal compares by identity. 82 func (exc *Exception) Equal(rhs interface{}) bool { 83 return exc == rhs 84 } 85 86 func (exc *Exception) Hash() uint32 { 87 return hash.Pointer(unsafe.Pointer(exc)) 88 } 89 90 func (exc *Exception) Bool() bool { 91 return exc.Cause == nil 92 } 93 94 func (exc *Exception) Index(k interface{}) (interface{}, bool) { 95 // TODO: Access to Traceback 96 switch k { 97 case "cause": 98 return exc.Cause, true 99 default: 100 return nil, false 101 } 102 } 103 104 func (exc *Exception) IterateKeys(f func(interface{}) bool) { 105 vals.Feed(f, "cause") 106 } 107 108 // PipelineError represents the errors of pipelines, in which multiple commands 109 // may error. 110 type PipelineError struct { 111 Errors []*Exception 112 } 113 114 func (pe PipelineError) Repr(indent int) string { 115 // TODO Make a more generalized ListReprBuilder and use it here. 116 b := new(bytes.Buffer) 117 b.WriteString("?(multi-error") 118 elemIndent := indent + len("?(multi-error ") 119 for _, e := range pe.Errors { 120 if indent > 0 { 121 b.WriteString("\n" + strings.Repeat(" ", elemIndent)) 122 } else { 123 b.WriteString(" ") 124 } 125 b.WriteString(e.Repr(elemIndent)) 126 } 127 b.WriteString(")") 128 return b.String() 129 } 130 131 func (pe PipelineError) Error() string { 132 b := new(bytes.Buffer) 133 b.WriteString("(") 134 for i, e := range pe.Errors { 135 if i > 0 { 136 b.WriteString(" | ") 137 } 138 if e == nil || e.Cause == nil { 139 b.WriteString("<nil>") 140 } else { 141 b.WriteString(e.Error()) 142 } 143 } 144 b.WriteString(")") 145 return b.String() 146 } 147 148 // ComposeExceptionsFromPipeline takes a slice of Exception pointers and 149 // composes a suitable error. If all elements of the slice are either nil or OK, 150 // a nil is returned. If there is exactly non-nil non-OK Exception, it is 151 // returned. Otherwise, a PipelineError built from the slice is returned, with 152 // nil items turned into OK's for easier access from elvishscript. 153 func ComposeExceptionsFromPipeline(excs []*Exception) error { 154 newexcs := make([]*Exception, len(excs)) 155 notOK, lastNotOK := 0, 0 156 for i, e := range excs { 157 if e == nil { 158 newexcs[i] = OK 159 } else { 160 newexcs[i] = e 161 if e.Cause != nil { 162 notOK++ 163 lastNotOK = i 164 } 165 } 166 } 167 switch notOK { 168 case 0: 169 return nil 170 case 1: 171 return newexcs[lastNotOK] 172 default: 173 return PipelineError{newexcs} 174 } 175 } 176 177 // Flow is a special type of error used for control flows. 178 type Flow uint 179 180 // Control flows. 181 const ( 182 Return Flow = iota 183 Break 184 Continue 185 ) 186 187 var flowNames = [...]string{ 188 "return", "break", "continue", 189 } 190 191 func (f Flow) Repr(int) string { 192 return "?(" + f.Error() + ")" 193 } 194 195 func (f Flow) Error() string { 196 if f >= Flow(len(flowNames)) { 197 return fmt.Sprintf("!(BAD FLOW: %v)", f) 198 } 199 return flowNames[f] 200 } 201 202 func (f Flow) Pprint(string) string { 203 return "\033[33;1m" + f.Error() + "\033[m" 204 } 205 206 // ExternalCmdExit contains the exit status of external commands. If the 207 // command was stopped rather than terminated, the Pid field contains the pid 208 // of the process. 209 type ExternalCmdExit struct { 210 syscall.WaitStatus 211 CmdName string 212 Pid int 213 } 214 215 func NewExternalCmdExit(name string, ws syscall.WaitStatus, pid int) error { 216 if ws.Exited() && ws.ExitStatus() == 0 { 217 return nil 218 } 219 if !ws.Stopped() { 220 pid = 0 221 } 222 return ExternalCmdExit{ws, name, pid} 223 } 224 225 func (exit ExternalCmdExit) Error() string { 226 ws := exit.WaitStatus 227 quotedName := parse.Quote(exit.CmdName) 228 switch { 229 case ws.Exited(): 230 return quotedName + " exited with " + strconv.Itoa(ws.ExitStatus()) 231 case ws.Signaled(): 232 causeDescription := quotedName + " killed by signal " + ws.Signal().String() 233 if ws.CoreDump() { 234 causeDescription += " (core dumped)" 235 } 236 return causeDescription 237 case ws.Stopped(): 238 causeDescription := quotedName + " stopped by signal " + fmt.Sprintf("%s (pid=%d)", ws.StopSignal(), exit.Pid) 239 trap := ws.TrapCause() 240 if trap != -1 { 241 causeDescription += fmt.Sprintf(" (trapped %v)", trap) 242 } 243 return causeDescription 244 default: 245 return fmt.Sprint(quotedName, " has unknown WaitStatus ", ws) 246 } 247 }