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  }