From 07901bb91b19c5e899fcdcbafb7ab74e334de70e Mon Sep 17 00:00:00 2001 From: Michael McLoughlin Date: Sun, 6 Jan 2019 13:32:09 -0800 Subject: [PATCH] build: return ErrorList type Adds a very similar interface to go/scanner package for returning a list of errors. Updates #34 --- build/cli.go | 28 +++++++++++++++++-------- build/context.go | 9 ++++---- build/error.go | 53 ++++++++++++++++++++++++++++++++++++++++++++++++ pass/reg_test.go | 9 ++++---- 4 files changed, 80 insertions(+), 19 deletions(-) diff --git a/build/cli.go b/build/cli.go index 7f191a4..7474cbc 100644 --- a/build/cli.go +++ b/build/cli.go @@ -14,6 +14,7 @@ import ( // Config contains options for an avo main function. type Config struct { ErrOut io.Writer + MaxErrors int // max errors to report; 0 means unlimited CPUProfile io.WriteCloser Passes []pass.Interface } @@ -33,11 +34,9 @@ func Main(cfg *Config, context *Context) int { defer pprof.StopCPUProfile() } - f, errs := context.Result() - if errs != nil { - for _, err := range errs { - diag.Println(err) - } + f, err := context.Result() + if err != nil { + LogError(diag, err, cfg.MaxErrors) return 1 } @@ -52,9 +51,10 @@ func Main(cfg *Config, context *Context) int { // Flags represents CLI flags for an avo program. type Flags struct { - errout *outputValue - cpuprof *outputValue - printers []*printerValue + errout *outputValue + allerrors bool + cpuprof *outputValue + printers []*printerValue } // NewFlags initializes avo flags for the given FlagSet. @@ -64,6 +64,8 @@ func NewFlags(fs *flag.FlagSet) *Flags { f.errout = newOutputValue(os.Stderr) fs.Var(f.errout, "log", "diagnostics output") + fs.BoolVar(&f.allerrors, "e", false, "no limit on number of errors reported") + f.cpuprof = newOutputValue(nil) fs.Var(f.cpuprof, "cpuprofile", "write cpu profile to `file`") @@ -88,11 +90,19 @@ func (f *Flags) Config() *Config { passes = append(passes, p) } } - return &Config{ + + cfg := &Config{ ErrOut: f.errout.w, + MaxErrors: 10, CPUProfile: f.cpuprof.w, Passes: passes, } + + if f.allerrors { + cfg.MaxErrors = 0 + } + + return cfg } type outputValue struct { diff --git a/build/context.go b/build/context.go index a895d1a..ab36b29 100644 --- a/build/context.go +++ b/build/context.go @@ -19,7 +19,7 @@ type Context struct { file *avo.File function *avo.Function global *avo.Global - errs []error + errs ErrorList reg.Collection } @@ -176,8 +176,7 @@ func (c *Context) activeglobal() *avo.Global { } func (c *Context) adderror(err error) { - e := exterr(err) - c.errs = append(c.errs, e) + c.errs.addext(err) } func (c *Context) adderrormessage(msg string) { @@ -185,6 +184,6 @@ func (c *Context) adderrormessage(msg string) { } // Result returns the built file and any accumulated errors. -func (c *Context) Result() (*avo.File, []error) { - return c.file, c.errs +func (c *Context) Result() (*avo.File, error) { + return c.file, c.errs.Err() } diff --git a/build/error.go b/build/error.go index 80753cb..bbe38c5 100644 --- a/build/error.go +++ b/build/error.go @@ -1,6 +1,9 @@ package build import ( + "fmt" + "log" + "github.com/mmcloughlin/avo/internal/stack" "github.com/mmcloughlin/avo/src" ) @@ -28,3 +31,53 @@ func (e Error) Error() string { } return msg } + +// ErrorList is a collection of errors for a source file. +type ErrorList []Error + +// Add appends an error to the list. +func (e *ErrorList) Add(err Error) { + *e = append(*e, err) +} + +// addext appends an error to the list, tagged with the +func (e *ErrorList) addext(err error) { + e.Add(exterr(err)) +} + +// Err returns an error equivalent to this error list. +// If the list is empty, Err returns nil. +func (e ErrorList) Err() error { + if len(e) == 0 { + return nil + } + return e +} + +// An ErrorList implements the error interface. +func (e ErrorList) Error() string { + switch len(e) { + case 0: + return "no errors" + case 1: + return e[0].Error() + } + return fmt.Sprintf("%s (and %d more errors)", e[0], len(e)-1) +} + +// LogError logs a list of errors, one error per line, if the err parameter is +// an ErrorList. Otherwise it just logs the err string. Reports at most max +// errors, or unlimited if max is 0. +func LogError(l *log.Logger, err error, max int) { + if list, ok := err.(ErrorList); ok { + for i, e := range list { + l.Printf("%s\n", e) + if max > 0 && i == max { + l.Print("too many errors") + return + } + } + } else if err != nil { + l.Printf("%s\n", err) + } +} diff --git a/pass/reg_test.go b/pass/reg_test.go index b8664b7..fbfe6e5 100644 --- a/pass/reg_test.go +++ b/pass/reg_test.go @@ -3,6 +3,7 @@ package pass_test import ( "testing" + "github.com/mmcloughlin/avo/internal/test" "github.com/mmcloughlin/avo/reg" "github.com/mmcloughlin/avo/pass" @@ -57,11 +58,9 @@ func AssertRegistersMatchSet(t *testing.T, rs []reg.Register, s reg.Set) { } func ConstructLiveness(t *testing.T, ctx *build.Context) *avo.Function { - f, errs := ctx.Result() - if errs != nil { - for _, err := range errs { - t.Error(err) - } + f, err := ctx.Result() + if err != nil { + build.LogError(test.Logger(t), err, 0) t.FailNow() }