cue/errors: add package
Change-Id: I2f5383e775282a8325201ab08c599f2dfb650857
diff --git a/cue/errors/errors.go b/cue/errors/errors.go
new file mode 100644
index 0000000..87edb74
--- /dev/null
+++ b/cue/errors/errors.go
@@ -0,0 +1,255 @@
+// Copyright 2018 The CUE Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Package errors defines shared types for handling CUE errors.
+package errors // import "cuelang.org/go/cue/errors"
+
+import (
+ "io"
+ "sort"
+
+ "cuelang.org/go/cue/token"
+ "golang.org/x/exp/errors"
+ "golang.org/x/exp/errors/fmt"
+)
+
+// New is a convenience wrapper for errors.New in the core library.
+func New(msg string) error {
+ return errors.New(msg)
+}
+
+// A Handler is a generic error handler used throughout CUE packages.
+//
+// The position points to the beginning of the offending value.
+type Handler func(pos token.Position, msg string)
+
+// Error is the common error message.
+type Error interface {
+ Position() token.Position
+
+ // Error reports the error message without position information.
+ Error() string
+}
+
+// // TODO: make Error an interface that returns a list of positions.
+
+// In an List, an error is represented by an *posError.
+// The position Pos, if valid, points to the beginning of
+// the offending token, and the error condition is described
+// by Msg.
+type posError struct {
+ pos token.Position
+ msg string
+
+ // The underlying error that triggered this one, if any.
+ err error
+}
+
+// E creates a new error.
+func E(args ...interface{}) error {
+ e := &posError{}
+ update(e, args)
+ return e
+}
+
+// Augment adorns an existing error with new information.
+func Augment(err error, args ...interface{}) error {
+ e, ok := err.(*posError)
+ if !ok {
+ e = &posError{err: err}
+ }
+ update(e, args)
+ return e
+}
+
+func update(e *posError, args []interface{}) {
+ err := e.err
+ for _, a := range args {
+ switch x := a.(type) {
+ case string:
+ e.msg = x
+ case token.Position:
+ e.pos = x
+ case []token.Position:
+ // TODO: do something more clever
+ if len(x) > 0 {
+ e.pos = x[0]
+ }
+ case *posError:
+ copy := *x
+ err = ©
+ e.err = combine(e.err, err)
+ case error:
+ e.err = combine(e.err, x)
+ }
+ }
+}
+
+func combine(a, b error) error {
+ switch x := a.(type) {
+ case nil:
+ return b
+ case List:
+ x.add(toErr(b))
+ return x
+ default:
+ return List{toErr(a), toErr(b)}
+ }
+}
+
+func toErr(err error) Error {
+ if e, ok := err.(Error); ok {
+ return e
+ }
+ return &posError{err: err}
+}
+
+func (e posError) Position() token.Position {
+ return e.pos
+}
+
+// Error implements the error interface.
+func (e posError) Error() string { return fmt.Sprint(e) }
+
+func (e posError) Format(p errors.Printer) error {
+ next := e.err
+ if e.msg == "" {
+ next = errFormat(p, e.err)
+ } else {
+ p.Print(e.msg)
+ }
+ if p.Detail() && e.pos.Filename != "" || e.pos.IsValid() {
+ p.Printf("%s", e.pos.String())
+ }
+ return next
+
+}
+
+func errFormat(p errors.Printer, err error) (next error) {
+ switch v := err.(type) {
+ case errors.Formatter:
+ err = v.Format(p)
+ default:
+ p.Print(err)
+ err = nil
+ }
+
+ return err
+}
+
+// List is a list of *posError.
+// The zero value for an List is an empty List ready to use.
+//
+type List []Error
+
+func (p *List) add(err Error) {
+ *p = append(*p, err)
+}
+
+// AddNew adds an Error with given position and error message to an List.
+func (p *List) AddNew(pos token.Position, msg string) {
+ p.add(&posError{pos: pos, msg: msg})
+}
+
+// Add adds an Error with given position and error message to an List.
+func (p *List) Add(err error) {
+ p.add(toErr(err))
+}
+
+// Reset resets an List to no errors.
+func (p *List) Reset() { *p = (*p)[0:0] }
+
+// List implements the sort Interface.
+func (p List) Len() int { return len(p) }
+func (p List) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
+
+func (p List) Less(i, j int) bool {
+ e := p[i].Position()
+ f := p[j].Position()
+ // Note that it is not sufficient to simply compare file offsets because
+ // the offsets do not reflect modified line information (through //line
+ // comments).
+ if e.Filename != f.Filename {
+ return e.Filename < f.Filename
+ }
+ if e.Line != f.Line {
+ return e.Line < f.Line
+ }
+ if e.Column != f.Column {
+ return e.Column < f.Column
+ }
+ return p[i].Error() < p[j].Error()
+}
+
+// Sort sorts an List. *posError entries are sorted by position,
+// other errors are sorted by error message, and before any *posError
+// entry.
+//
+func (p List) Sort() {
+ sort.Sort(p)
+}
+
+// RemoveMultiples sorts an List and removes all but the first error per line.
+func (p *List) RemoveMultiples() {
+ sort.Sort(p)
+ var last token.Position // initial last.Line is != any legal error line
+ i := 0
+ for _, e := range *p {
+ pos := e.Position()
+ if pos.Filename != last.Filename || pos.Line != last.Line {
+ last = pos
+ (*p)[i] = e
+ i++
+ }
+ }
+ (*p) = (*p)[0:i]
+}
+
+// An List implements the error interface.
+func (p List) Error() string {
+ switch len(p) {
+ case 0:
+ return "no errors"
+ case 1:
+ return p[0].Error()
+ }
+ return fmt.Sprintf("%s (and %d more errors)", p[0], len(p)-1)
+}
+
+// Err returns an error equivalent to this error list.
+// If the list is empty, Err returns nil.
+func (p List) Err() error {
+ if len(p) == 0 {
+ return nil
+ }
+ return p
+}
+
+// Print is a utility function that prints a list of errors to w,
+// one error per line, if the err parameter is an List. Otherwise
+// it prints the err string.
+//
+func Print(w io.Writer, err error) {
+ if list, ok := err.(List); ok {
+ for _, e := range list {
+ printError(w, e)
+ }
+ } else if err != nil {
+ printError(w, toErr(err))
+ }
+}
+
+func printError(w io.Writer, err Error) {
+ fmt.Fprintf(w, "%+v\n", err)
+}
diff --git a/cue/errors/errors_test.go b/cue/errors/errors_test.go
new file mode 100644
index 0000000..fb8e7ff
--- /dev/null
+++ b/cue/errors/errors_test.go
@@ -0,0 +1,192 @@
+// Copyright 2018 The CUE Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package errors
+
+import (
+ "bytes"
+ "testing"
+
+ "cuelang.org/go/cue/token"
+)
+
+func TestError_Error(t *testing.T) {
+ tests := []struct {
+ name string
+ e Error
+ want string
+ }{
+ // TODO: Add test cases.
+ }
+ for _, tt := range tests {
+ if got := tt.e.Error(); got != tt.want {
+ t.Errorf("%q. Error.Error() = %v, want %v", tt.name, got, tt.want)
+ }
+ }
+}
+
+func TestErrorList_Add(t *testing.T) {
+ type args struct {
+ pos token.Position
+ msg string
+ }
+ tests := []struct {
+ name string
+ p *List
+ args args
+ }{
+ // TODO: Add test cases.
+ }
+ for _, tt := range tests {
+ tt.p.AddNew(tt.args.pos, tt.args.msg)
+ }
+}
+
+func TestErrorList_Reset(t *testing.T) {
+ tests := []struct {
+ name string
+ p *List
+ }{
+ // TODO: Add test cases.
+ }
+ for _, tt := range tests {
+ tt.p.Reset()
+ }
+}
+
+func TestErrorList_Len(t *testing.T) {
+ tests := []struct {
+ name string
+ p List
+ want int
+ }{
+ // TODO: Add test cases.
+ }
+ for _, tt := range tests {
+ if got := tt.p.Len(); got != tt.want {
+ t.Errorf("%q. List.Len() = %v, want %v", tt.name, got, tt.want)
+ }
+ }
+}
+
+func TestErrorList_Swap(t *testing.T) {
+ type args struct {
+ i int
+ j int
+ }
+ tests := []struct {
+ name string
+ p List
+ args args
+ }{
+ // TODO: Add test cases.
+ }
+ for _, tt := range tests {
+ tt.p.Swap(tt.args.i, tt.args.j)
+ }
+}
+
+func TestErrorList_Less(t *testing.T) {
+ type args struct {
+ i int
+ j int
+ }
+ tests := []struct {
+ name string
+ p List
+ args args
+ want bool
+ }{
+ // TODO: Add test cases.
+ }
+ for _, tt := range tests {
+ if got := tt.p.Less(tt.args.i, tt.args.j); got != tt.want {
+ t.Errorf("%q. List.Less() = %v, want %v", tt.name, got, tt.want)
+ }
+ }
+}
+
+func TestErrorList_Sort(t *testing.T) {
+ tests := []struct {
+ name string
+ p List
+ }{
+ // TODO: Add test cases.
+ }
+ for _, tt := range tests {
+ tt.p.Sort()
+ }
+}
+
+func TestErrorList_RemoveMultiples(t *testing.T) {
+ tests := []struct {
+ name string
+ p *List
+ }{
+ // TODO: Add test cases.
+ }
+ for _, tt := range tests {
+ tt.p.RemoveMultiples()
+ }
+}
+
+func TestErrorList_Error(t *testing.T) {
+ tests := []struct {
+ name string
+ p List
+ want string
+ }{
+ // TODO: Add test cases.
+ }
+ for _, tt := range tests {
+ if got := tt.p.Error(); got != tt.want {
+ t.Errorf("%q. List.Error() = %v, want %v", tt.name, got, tt.want)
+ }
+ }
+}
+
+func TestErrorList_Err(t *testing.T) {
+ tests := []struct {
+ name string
+ p List
+ wantErr bool
+ }{
+ // TODO: Add test cases.
+ }
+ for _, tt := range tests {
+ if err := tt.p.Err(); (err != nil) != tt.wantErr {
+ t.Errorf("%q. List.Err() error = %v, wantErr %v", tt.name, err, tt.wantErr)
+ }
+ }
+}
+
+func TestPrintError(t *testing.T) {
+ type args struct {
+ err error
+ }
+ tests := []struct {
+ name string
+ args args
+ wantW string
+ }{
+ // TODO: Add test cases.
+ }
+ for _, tt := range tests {
+ w := &bytes.Buffer{}
+ Print(w, tt.args.err)
+ if gotW := w.String(); gotW != tt.wantW {
+ t.Errorf("%q. PrintError() = %v, want %v", tt.name, gotW, tt.wantW)
+ }
+ }
+}