Use specific error types for parse, compile and eval.

Generic error types in util are ditched.
This commit is contained in:
Qi Xiao 2017-01-28 14:39:52 +00:00
parent 88b912bda4
commit ee81f0680c
13 changed files with 180 additions and 129 deletions

View File

@ -168,14 +168,16 @@ func (ed *Editor) refresh(fullRefresh bool, tips bool) error {
if tips && !atEnd(err, len(src)) {
ed.addTip("compiler error: %s", err)
}
if err, ok := err.(*util.PosError); ok {
p := err.Begin
if err, ok := err.(*eval.CompilationError); ok {
p := err.Context.Begin
for i, token := range ed.tokens {
if token.Node.Begin() <= p && p < token.Node.End() {
ed.tokens[i].MoreStyle = joinStyles(ed.tokens[i].MoreStyle, styleForCompilerError)
break
}
}
} else {
Logger.Printf("Compile returned error of type %T", err)
}
}
}
@ -187,16 +189,17 @@ func (ed *Editor) refresh(fullRefresh bool, tips bool) error {
func atEnd(e error, n int) bool {
switch e := e.(type) {
case *util.PosError:
return e.Begin == n
case *util.Errors:
for _, child := range e.Errors {
if !atEnd(child, n) {
case *eval.CompilationError:
return e.Context.Begin == n
case *parse.ParseError:
for _, entry := range e.Entries {
if entry.Context.Begin != n {
return false
}
}
return true
default:
Logger.Printf("atEnd called with error type %T", e)
return false
}
}

29
eval/compilation-error.go Normal file
View File

@ -0,0 +1,29 @@
package eval
import (
"bytes"
"fmt"
"github.com/elves/elvish/util"
)
// CompilationError represents a compilation error and can pretty print it.
type CompilationError struct {
Message string
Context util.SourceContext
}
func (ce *CompilationError) Error() string {
return fmt.Sprintf("compilation error: %d-%d in %s: %s",
ce.Context.Begin, ce.Context.End, ce.Context.Name, ce.Message)
}
func (ce *CompilationError) Pprint() string {
buf := new(bytes.Buffer)
fmt.Fprintf(buf, "Compilation error: \033[31;1m%s\033[m\n", ce.Message)
fmt.Fprint(buf, " ")
ce.Context.Pprint(buf, " ")
return buf.String()
}

View File

@ -36,7 +36,8 @@ func (cp *compiler) compiling(n parse.Node) {
}
func (cp *compiler) errorpf(begin, end int, format string, args ...interface{}) {
throw(&util.PosError{fmt.Errorf(format, args...), "Compile error", util.Traceback{cp.name, cp.text, begin, end, nil}})
throw(&CompilationError{fmt.Sprintf(format, args...),
util.SourceContext{cp.name, cp.text, begin, end, nil}})
}
func (cp *compiler) errorf(format string, args ...interface{}) {

View File

@ -55,7 +55,7 @@ type EvalCtx struct {
verdict bool
begin, end int
traceback *util.Traceback
traceback *util.SourceContext
background bool
}
@ -280,9 +280,9 @@ func catch(perr *error, ec *EvalCtx) {
}
if exc, ok := r.(util.Exception); ok {
err := exc.Error
if _, ok := err.(*util.TracebackError); !ok {
if _, ok := err.(*Exception); !ok {
if _, ok := err.(flow); !ok {
err = ec.makeTracebackError(err)
err = ec.makeException(err)
}
}
*perr = err
@ -291,12 +291,13 @@ func catch(perr *error, ec *EvalCtx) {
}
}
func (ec *EvalCtx) makeTracebackError(e error) *util.TracebackError {
return &util.TracebackError{Cause: e, Traceback: ec.addTraceback()}
// makeException turns an error into an Exception by adding traceback.
func (ec *EvalCtx) makeException(e error) *Exception {
return &Exception{e, ec.addTraceback()}
}
func (ec *EvalCtx) addTraceback() *util.Traceback {
return &util.Traceback{
func (ec *EvalCtx) addTraceback() *util.SourceContext {
return &util.SourceContext{
Name: ec.srcName, Source: ec.src,
Begin: ec.begin, End: ec.end, Next: ec.traceback,
}

32
eval/exception.go Normal file
View File

@ -0,0 +1,32 @@
package eval
import (
"bytes"
"fmt"
"github.com/elves/elvish/util"
)
// Exception represents an elvish exception.
type Exception struct {
Cause error
Traceback *util.SourceContext
}
func (exc *Exception) Error() string {
return exc.Cause.Error()
}
func (exc *Exception) Pprint() string {
buf := new(bytes.Buffer)
// Error message
fmt.Fprintf(buf, "Exception: \033[31;1m%s\033[m\n", exc.Cause.Error())
buf.WriteString("Traceback:")
for tb := exc.Traceback; tb != nil; tb = tb.Next {
buf.WriteString("\n ")
tb.Pprint(buf, " ")
}
return buf.String()
}

66
parse/parse-error.go Normal file
View File

@ -0,0 +1,66 @@
package parse
import (
"bytes"
"fmt"
"github.com/elves/elvish/util"
)
// ParseErrorEntry represents one parse error.
type ParseErrorEntry struct {
Message string
Context util.SourceContext
}
// ParseError stores multiple ParseErrorEntry's and can pretty print them.
type ParseError struct {
Entries []*ParseErrorEntry
}
func (pe *ParseError) Add(msg string, ctx util.SourceContext) {
pe.Entries = append(pe.Entries, &ParseErrorEntry{msg, ctx})
}
func (pe *ParseError) Error() string {
switch len(pe.Entries) {
case 0:
return "no parse error"
case 1:
e := pe.Entries[0]
return fmt.Sprintf("parse error: %d-%d in %s: %s",
e.Context.Begin, e.Context.End, e.Context.Name, e.Message)
default:
buf := new(bytes.Buffer)
// Contexts of parse error entries all have the same name
fmt.Fprintf(buf, "multiple parse errors in %s: ", pe.Entries[0].Context.Name)
for i, e := range pe.Entries {
if i > 0 {
fmt.Fprint(buf, "; ")
}
fmt.Fprintf(buf, "%d-%d: %s", e.Context.Begin, e.Context.End, e.Message)
}
return buf.String()
}
}
func (pe *ParseError) Pprint() string {
buf := new(bytes.Buffer)
switch len(pe.Entries) {
case 0:
return "no parse error"
case 1:
e := pe.Entries[0]
fmt.Fprintf(buf, "Parse error: \033[31;1m%s\033[m\n ", e.Message)
e.Context.Pprint(buf, " ")
default:
fmt.Fprint(buf, "Multiple parse errors:\n")
for _, e := range pe.Entries {
fmt.Fprintf(buf, " \033[31;1m%s\033[m\n ", e.Message)
e.Context.Pprint(buf, " ")
}
}
return buf.String()
}

View File

@ -13,14 +13,14 @@ import (
// Parse parses elvish source.
func Parse(srcname, src string) (*Chunk, error) {
ps := &parser{srcname, src, 0, 0, []map[rune]int{{}}, 0, nil}
ps := &parser{srcname, src, 0, 0, []map[rune]int{{}}, 0, ParseError{}}
bn := parseChunk(ps)
if ps.pos != len(src) {
ps.error(errUnexpectedRune)
}
var err error
if ps.errors != nil {
err = ps.errors
if len(ps.errors.Entries) > 0 {
err = &ps.errors
}
return bn, err
}
@ -263,10 +263,11 @@ func (fn *Form) tryAssignment(ps *parser) bool {
}
pos := ps.pos
errors := ps.errors
errorEntries := ps.errors.Entries
an := parseAssignment(ps)
if ps.errors != errors {
ps.errors = errors
// If errors were added, revert
if len(ps.errors.Entries) > len(errorEntries) {
ps.errors.Entries = errorEntries
ps.pos = pos
return false
}

View File

@ -4,8 +4,6 @@ import (
"fmt"
"os"
"testing"
"github.com/elves/elvish/util"
)
func a(c ...interface{}) ast {
@ -369,9 +367,9 @@ func TestParseError(t *testing.T) {
t.Errorf("Parse(%q) returns no error", tc.src)
continue
}
posErr0 := err.(*util.Errors).Errors[0].(*util.PosError)
if posErr0.Begin != tc.pos {
t.Errorf("Parse(%q) first error begins at %d, want %d. Errors are:%s\n", tc.src, posErr0.Begin, tc.pos, err)
posErr0 := err.(*ParseError).Entries[0]
if posErr0.Context.Begin != tc.pos {
t.Errorf("Parse(%q) first error begins at %d, want %d. Errors are:%s\n", tc.src, posErr0.Context.Begin, tc.pos, err)
}
}
}

View File

@ -19,7 +19,7 @@ type parser struct {
overEOF int
cutsets []map[rune]int
controls int
errors *util.Errors
errors ParseError
}
const eof rune = -1
@ -93,10 +93,7 @@ func (ps *parser) advance(c int) {
}
func (ps *parser) errorp(begin, end int, e error) {
if ps.errors == nil {
ps.errors = &util.Errors{}
}
ps.errors.Append(&util.PosError{e, "Parse error", util.Traceback{ps.srcName, ps.src, begin, end, nil}})
ps.errors.Add(e.Error(), util.SourceContext{ps.srcName, ps.src, begin, end, nil})
}
func (ps *parser) error(e error) {

View File

@ -129,18 +129,18 @@ func source(ev *eval.Evaler, fname string, notexistok bool) bool {
func sourceText(ev *eval.Evaler, name, src string) bool {
n, err := parse.Parse(name, src)
if err != nil {
printError(err, "Parse error")
printError(err)
return false
}
op, err := ev.Compile(n, name, src)
if err != nil {
printError(err, "Compile error")
printError(err)
return false
}
err = ev.Eval(op, name, src)
if err != nil {
printError(err, "Exception")
printError(err)
return false
}
return true
@ -258,27 +258,15 @@ func newEvalerAndStore() (*eval.Evaler, *store.Store) {
return eval.NewEvaler(st, dataDir), st
}
func printError(err error, errtype string) {
if err == nil {
return
}
switch err := err.(type) {
case *util.Errors:
for _, e := range err.Errors {
printError(e, errtype)
}
case *util.PosError:
fmt.Fprintln(os.Stderr, err.Pprint())
case *util.TracebackError:
fmt.Fprintln(os.Stderr, err.Pprint())
default:
printErrorString(errtype, err.Error())
}
type Pprinter interface {
Pprint() string
}
func printErrorString(errtype, s string) {
if sys.IsATTY(2) {
s = "\033[1;31m" + s + "\033[m"
func printError(err error) {
switch err := err.(type) {
case Pprinter:
fmt.Fprintln(os.Stderr, err.Pprint())
default:
fmt.Fprintf(os.Stderr, "\033[31;1m%s\033[m", err.Error())
}
fmt.Fprintln(os.Stderr, errtype+": "+s)
}

View File

@ -1,36 +0,0 @@
package util
import (
"bytes"
"fmt"
)
// PosError is an error associated with a position range.
type PosError struct {
Err error
Type string
Traceback
}
func (pe *PosError) Error() string {
return fmt.Sprintf("%d-%d: %s", pe.Traceback.Begin, pe.Traceback.End, pe.msg())
}
// Pprint pretty-prints a PosError.
func (pe *PosError) Pprint() string {
buf := new(bytes.Buffer)
// Error message
fmt.Fprintf(buf, "%s: \033[31;1m%s\033[m\n", pe.Type, pe.msg())
// Position
pe.Traceback.Pprint(buf, " ")
return buf.String()
}
func (pe *PosError) msg() string {
if pe.Err != nil {
return pe.Err.Error()
} else {
return "<nil>"
}
}

View File

@ -6,26 +6,26 @@ import (
"strings"
)
type Traceback struct {
type SourceContext struct {
Name string
Source string
Begin int
End int
Next *Traceback
Next *SourceContext
}
var CulpritStyle = "1;4"
func (te *Traceback) Pprint(w io.Writer, sourceIndent string) {
if te.Begin == -1 {
fmt.Fprintf(w, "%s, unknown position", te.Name)
func (sc *SourceContext) Pprint(w io.Writer, sourceIndent string) {
if sc.Begin == -1 {
fmt.Fprintf(w, "%s, unknown position", sc.Name)
return
} else if te.Begin < 0 || te.End > len(te.Source) || te.Begin > te.End {
fmt.Fprintf(w, "%s, invalid position %d-%d", te.Name, te.Begin, te.End)
} else if sc.Begin < 0 || sc.End > len(sc.Source) || sc.Begin > sc.End {
fmt.Fprintf(w, "%s, invalid position %d-%d", sc.Name, sc.Begin, sc.End)
return
}
before, culprit, after := bca(te.Source, te.Begin, te.End)
before, culprit, after := bca(sc.Source, sc.Begin, sc.End)
// Find the part of "before" that is on the same line as the culprit.
lineBefore := lastLine(before)
// Find on which line the culprit begins.
@ -44,9 +44,9 @@ func (te *Traceback) Pprint(w io.Writer, sourceIndent string) {
endLine := beginLine + strings.Count(culprit, "\n")
if beginLine == endLine {
fmt.Fprintf(w, "%s, line %d:\n", te.Name, beginLine)
fmt.Fprintf(w, "%s, line %d:\n", sc.Name, beginLine)
} else {
fmt.Fprintf(w, "%s, line %d-%d:\n", te.Name, beginLine, endLine)
fmt.Fprintf(w, "%s, line %d-%d:\n", sc.Name, beginLine, endLine)
}
fmt.Fprintf(w, "%s%s", sourceIndent, lineBefore)

View File

@ -1,29 +0,0 @@
package util
import (
"bytes"
"fmt"
)
type TracebackError struct {
Cause error
Traceback *Traceback
}
func (te *TracebackError) Error() string {
return te.Cause.Error()
}
func (te *TracebackError) Pprint() string {
buf := new(bytes.Buffer)
// Error message
fmt.Fprintf(buf, "Exception: \033[31;1m%s\033[m\n", te.Cause.Error())
buf.WriteString("Traceback:")
for tb := te.Traceback; tb != nil; tb = tb.Next {
buf.WriteString("\n ")
tb.Pprint(buf, " ")
}
return buf.String()
}