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  }