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