Merge pull request 'error-interface' (#23) from error-interface into main
Reviewed-on: sashakoshka/fspl#23
This commit is contained in:
commit
b89bed2c94
|
@ -43,17 +43,17 @@ func testReaderErr (
|
|||
return
|
||||
}
|
||||
|
||||
got := err.(*errors.Error)
|
||||
gotMessage := got.Message
|
||||
gotRow := got.Position.Row + 1
|
||||
gotStart := got.Position.Start + 1
|
||||
got := err.(errors.Error)
|
||||
gotMessage := got.Error()
|
||||
gotRow := got.Position().Row + 1
|
||||
gotStart := got.Position().Start + 1
|
||||
correct :=
|
||||
gotMessage == errMessage &&
|
||||
gotRow == errRow &&
|
||||
gotStart == errStart
|
||||
if !correct {
|
||||
test.Log("errors do not match")
|
||||
test.Log("got:\n" + got.Format())
|
||||
test.Log("got:\n" + errors.Format(got))
|
||||
test.Logf("correct:\n%v:%v: %v", errRow, errStart, errMessage)
|
||||
test.Fail()
|
||||
}
|
||||
|
|
131
errors/errors.go
131
errors/errors.go
|
@ -1,115 +1,50 @@
|
|||
package errors
|
||||
|
||||
import "fmt"
|
||||
import "strings"
|
||||
|
||||
// Position stores the position of something within a file. For memory's sake,
|
||||
// positions in the same file should be created using the same File string, and
|
||||
// positions on the same line should be created using the same Line string.
|
||||
type Position struct {
|
||||
File string
|
||||
Line string
|
||||
|
||||
Row int
|
||||
Start int
|
||||
End int
|
||||
}
|
||||
|
||||
func (pos Position) String () string {
|
||||
return fmt.Sprintf("%s:%d:%d", pos.File, pos.Row + 1, pos.Start + 1)
|
||||
}
|
||||
|
||||
// Format produces a formatted printout of where the position is in its file.
|
||||
func (pos Position) Format () string {
|
||||
line := formatTabs(pos.Line)
|
||||
output := fmt.Sprintf("%-4d | %s\n ", pos.Row + 1, line)
|
||||
|
||||
start := pos.Start
|
||||
end := pos.End
|
||||
if end <= start { end = start + 1 }
|
||||
start = getXInTabbedString(pos.Line, start)
|
||||
end = getXInTabbedString(pos.Line, end)
|
||||
|
||||
for index := range line {
|
||||
if index >= end { break }
|
||||
if index >= start{
|
||||
output += "^"
|
||||
} else {
|
||||
output += " "
|
||||
}
|
||||
}
|
||||
|
||||
return output
|
||||
}
|
||||
|
||||
func getXInTabbedString (tabbed string, column int) int {
|
||||
var x int
|
||||
for index, ch := range tabbed {
|
||||
if index >= column { return x }
|
||||
if ch == '\t' {
|
||||
nextStop := ((x / 8) + 1) * 8
|
||||
x = nextStop - 1
|
||||
}
|
||||
x ++
|
||||
}
|
||||
return x
|
||||
}
|
||||
|
||||
func formatTabs (tabbed string) string {
|
||||
var result string
|
||||
var x int
|
||||
for _, ch := range tabbed {
|
||||
if ch == '\t' {
|
||||
nextStop := ((x / 8) + 1) * 8
|
||||
result += strings.Repeat(" ", nextStop - x)
|
||||
x = nextStop - 1
|
||||
} else {
|
||||
result += string(ch)
|
||||
}
|
||||
x ++
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// Union attempts to return the union of two positions. Since a position cannot
|
||||
// span multiple lines or files, the resulting position will be on the same line
|
||||
// and in the same file as the method reciever.
|
||||
func (pos Position) Union (other Position) Position {
|
||||
switch {
|
||||
case other.File != pos.File:
|
||||
case other.Row < pos.Row: pos.Start = 0
|
||||
case other.Row > pos.Row: pos.End = len(pos.Line)
|
||||
default:
|
||||
if other.Start < pos.Start { pos.Start = other.Start }
|
||||
if other.End > pos.End { pos.End = other.End }
|
||||
}
|
||||
return pos
|
||||
}
|
||||
|
||||
// Error is an error that contains positional information.
|
||||
type Error struct {
|
||||
Position
|
||||
Message string
|
||||
type Error interface {
|
||||
error
|
||||
Position () Position
|
||||
}
|
||||
var _ error = new(Error)
|
||||
|
||||
func (err *Error) Error () string {
|
||||
type errInternal struct {
|
||||
position Position
|
||||
message string
|
||||
}
|
||||
var _ Error = new(errInternal)
|
||||
|
||||
func (err *errInternal) Error () string {
|
||||
return err.String()
|
||||
}
|
||||
|
||||
func (err *Error) String () string {
|
||||
return fmt.Sprintf("%v: %s", err.Position, err.Message)
|
||||
func (err *errInternal) String () string {
|
||||
return err.message
|
||||
}
|
||||
|
||||
// Format produces a formatted printout of the error, and its location.
|
||||
func (err *Error) Format () string {
|
||||
return fmt.Sprintf("%v\n%v", err, err.Position.Format())
|
||||
func (err *errInternal) Position () Position {
|
||||
return err.position
|
||||
}
|
||||
|
||||
// Errorf produces an error with a fmt'd message.
|
||||
func Errorf (position Position, format string, variables ...any) error {
|
||||
return &Error {
|
||||
Position: position,
|
||||
Message: fmt.Sprintf(format, variables...),
|
||||
func Errorf (position Position, format string, variables ...any) Error {
|
||||
return &errInternal {
|
||||
position: position,
|
||||
message: fmt.Sprintf(format, variables...),
|
||||
}
|
||||
}
|
||||
|
||||
// Format returns a formatted string representing an error. This string may take
|
||||
// up multiple lines and contain ANSI escape codes. If err does not fulfill the
|
||||
// Error interface, err.Error() is returned instead.
|
||||
func Format (err error) string {
|
||||
if err, ok := err.(Error); ok {
|
||||
return fmt.Sprintf (
|
||||
"%v: %v\n%v",
|
||||
err.Position(),
|
||||
err.Error(),
|
||||
err.Position().Format())
|
||||
} else {
|
||||
return err.Error()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,16 +8,15 @@ testError (test,
|
|||
`example.fspl:11:7: some error
|
||||
11 | lorem ipsum dolor
|
||||
^^^^^`,
|
||||
&Error {
|
||||
Position: Position {
|
||||
Errorf (
|
||||
Position {
|
||||
File: "example.fspl",
|
||||
Line: "lorem ipsum dolor",
|
||||
Row: 10,
|
||||
Start: 6,
|
||||
End: 11,
|
||||
},
|
||||
Message: "some error",
|
||||
})
|
||||
"some error"))
|
||||
}
|
||||
|
||||
func TestErrorTab (test *testing.T) {
|
||||
|
@ -25,16 +24,15 @@ testError (test,
|
|||
`example.fspl:11:8: some error
|
||||
11 | lorem ipsum dolor
|
||||
^^^^^`,
|
||||
&Error {
|
||||
Position: Position {
|
||||
Errorf (
|
||||
Position {
|
||||
File: "example.fspl",
|
||||
Line: "\tlorem\tipsum\tdolor",
|
||||
Row: 10,
|
||||
Start: 7,
|
||||
End: 12,
|
||||
},
|
||||
Message: "some error",
|
||||
})
|
||||
"some error"))
|
||||
}
|
||||
|
||||
func TestErrorTabInBetween (test *testing.T) {
|
||||
|
@ -42,16 +40,15 @@ testError (test,
|
|||
`example.fspl:11:8: some error
|
||||
11 | lorem ipsum dolor
|
||||
^^^^^^^^^`,
|
||||
&Error {
|
||||
Position: Position {
|
||||
Errorf (
|
||||
Position {
|
||||
File: "example.fspl",
|
||||
Line: "\tlorem\tipsum\tdolor",
|
||||
Row: 10,
|
||||
Start: 7,
|
||||
End: 14,
|
||||
},
|
||||
Message: "some error",
|
||||
})
|
||||
"some error"))
|
||||
}
|
||||
|
||||
func TestGetXInTabbedString (test *testing.T) {
|
||||
|
|
|
@ -0,0 +1,87 @@
|
|||
package errors
|
||||
|
||||
import "fmt"
|
||||
import "strings"
|
||||
|
||||
// Position stores the position of something within a file. For memory's sake,
|
||||
// positions in the same file should be created using the same File string, and
|
||||
// positions on the same line should be created using the same Line string.
|
||||
type Position struct {
|
||||
File string
|
||||
Line string
|
||||
|
||||
Row int
|
||||
Start int
|
||||
End int
|
||||
}
|
||||
|
||||
func (pos Position) String () string {
|
||||
return fmt.Sprintf("%s:%d:%d", pos.File, pos.Row + 1, pos.Start + 1)
|
||||
}
|
||||
|
||||
// Format produces a formatted printout of where the position is in its file.
|
||||
func (pos Position) Format () string {
|
||||
line := formatTabs(pos.Line)
|
||||
output := fmt.Sprintf("%-4d | %s\n ", pos.Row + 1, line)
|
||||
|
||||
start := pos.Start
|
||||
end := pos.End
|
||||
if end <= start { end = start + 1 }
|
||||
start = getXInTabbedString(pos.Line, start)
|
||||
end = getXInTabbedString(pos.Line, end)
|
||||
|
||||
for index := range line {
|
||||
if index >= end { break }
|
||||
if index >= start{
|
||||
output += "^"
|
||||
} else {
|
||||
output += " "
|
||||
}
|
||||
}
|
||||
|
||||
return output
|
||||
}
|
||||
|
||||
func getXInTabbedString (tabbed string, column int) int {
|
||||
var x int
|
||||
for index, ch := range tabbed {
|
||||
if index >= column { return x }
|
||||
if ch == '\t' {
|
||||
nextStop := ((x / 8) + 1) * 8
|
||||
x = nextStop - 1
|
||||
}
|
||||
x ++
|
||||
}
|
||||
return x
|
||||
}
|
||||
|
||||
func formatTabs (tabbed string) string {
|
||||
var result string
|
||||
var x int
|
||||
for _, ch := range tabbed {
|
||||
if ch == '\t' {
|
||||
nextStop := ((x / 8) + 1) * 8
|
||||
result += strings.Repeat(" ", nextStop - x)
|
||||
x = nextStop - 1
|
||||
} else {
|
||||
result += string(ch)
|
||||
}
|
||||
x ++
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// Union attempts to return the union of two positions. Since a position cannot
|
||||
// span multiple lines or files, the resulting position will be on the same line
|
||||
// and in the same file as the method reciever.
|
||||
func (pos Position) Union (other Position) Position {
|
||||
switch {
|
||||
case other.File != pos.File:
|
||||
case other.Row < pos.Row: pos.Start = 0
|
||||
case other.Row > pos.Row: pos.End = len(pos.Line)
|
||||
default:
|
||||
if other.Start < pos.Start { pos.Start = other.Start }
|
||||
if other.End > pos.End { pos.End = other.End }
|
||||
}
|
||||
return pos
|
||||
}
|
|
@ -11,6 +11,6 @@ func testString (test *testing.T, correct string, got string) {
|
|||
testcommon.Compare(test, correct, got)
|
||||
}
|
||||
|
||||
func testError (test *testing.T, correct string, err *Error) {
|
||||
testString(test, correct, err.Format())
|
||||
func testError (test *testing.T, correct string, err Error) {
|
||||
testString(test, correct, Format(err))
|
||||
}
|
||||
|
|
|
@ -30,11 +30,11 @@ func testStringErr (
|
|||
correct ...Token,
|
||||
) {
|
||||
testError := func (err error) bool {
|
||||
got := err.(*errors.Error)
|
||||
gotMessage := got.Message
|
||||
gotLine := got.Line
|
||||
gotRow := got.Position.Row
|
||||
gotStart := got.Position.Start
|
||||
got := err.(errors.Error)
|
||||
gotMessage := got.Error()
|
||||
gotLine := got.Position().Line
|
||||
gotRow := got.Position().Row
|
||||
gotStart := got.Position().Start
|
||||
correct :=
|
||||
gotMessage == errMessage &&
|
||||
gotLine == errLine &&
|
||||
|
@ -42,28 +42,24 @@ func testStringErr (
|
|||
gotStart == errStart
|
||||
if !correct {
|
||||
test.Log("errors do not match")
|
||||
test.Log("got:\n" + got.Format())
|
||||
test.Log("correct:\n" + (&errors.Error {
|
||||
Position: errors.Position {
|
||||
test.Log("got:\n" + errors.Format(got))
|
||||
test.Log("correct:\n" + errors.Format(errors.Errorf (
|
||||
errors.Position {
|
||||
File: "input.fspl",
|
||||
Line: errLine,
|
||||
Row: errRow,
|
||||
Start: errStart,
|
||||
End: errStart,
|
||||
},
|
||||
Message: errMessage,
|
||||
}).Format())
|
||||
errMessage,
|
||||
)))
|
||||
test.Fail()
|
||||
}
|
||||
return correct
|
||||
}
|
||||
|
||||
printUnexpectedErr := func (err error) {
|
||||
if err, ok := err.(*errors.Error); ok {
|
||||
test.Log("lexer returned error:\n", err.Format())
|
||||
} else {
|
||||
test.Log("lexer returned error:", err)
|
||||
}
|
||||
test.Log("lexer returned error:\n" + errors.Format(err))
|
||||
}
|
||||
|
||||
reader := strings.NewReader(input)
|
||||
|
|
|
@ -10,13 +10,7 @@ func testString (test *testing.T, correct string, input string) {
|
|||
ast := Tree { }
|
||||
err := ast.Parse("input.fspl", strings.NewReader(input))
|
||||
if err != nil && err != io.EOF{
|
||||
if err, ok := err.(*errors.Error); ok {
|
||||
test.Log("parser returned error:")
|
||||
test.Log(err.Format())
|
||||
test.Fail()
|
||||
} else {
|
||||
test.Error("parser returned error:", err)
|
||||
}
|
||||
test.Error("parser returned error:\n" + errors.Format(err))
|
||||
return
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue