goparsify/errors.go
2019-11-29 06:49:16 +01:00

78 lines
1.9 KiB
Go

package goparsify
import (
"bufio"
"fmt"
"strings"
)
// Error represents a parse error. These will often be set, the parser will back up a little and
// find another viable path. In general when combining errors the longest error should be returned.
type Error struct {
pos int
expected string
}
// Pos is the offset into the document the error was found
func (e *Error) Pos() int { return e.pos }
// Error satisfies the golang error interface
func (e *Error) Error() string { return fmt.Sprintf("offset %d: expected %s", e.pos, e.expected) }
// UnparsedInputError is returned by Run when not all of the input was consumed. There may still be a valid result
type UnparsedInputError struct {
Remaining string
}
// Error satisfies the golang error interface
func (e UnparsedInputError) Error() string {
return "left unparsed: " + e.Remaining
}
// LocalError locates the error position in the input string s and returns the
// error description along with a cursor to the input.
func (e *Error) LocateError(s string) string {
if len(s) < e.Pos() {
return e.Error()
}
pos := e.Pos()
// find the line.
var (
lino, prev, off int
line, indent []byte
)
byline := bufio.NewScanner(strings.NewReader(s))
for byline.Scan() {
lino++
line = byline.Bytes()
prev = off
off += len(line) + 1
// log.Printf("lino:%3d len:%4d prev:%4d pos:%4d off:%4d rel:%4d line: %s",
// lino, len(line), prev, pos, off, (pos - prev), string(line))
if prev <= pos && pos <= off {
break
}
}
indent = make([]byte, len(line[:pos-prev]))
for i, c := range indent {
if c != '\t' {
indent[i] = ' '
}
}
off = pos - prev
if off > 40 {
indent = indent[off-30:]
line = line[off-30:]
line[0] = '.'
line[1] = '.'
line[2] = '.'
}
if len(line) > 70 {
line = line[:70]
line[69], line[68], line[67] = '.', '.', '.'
}
return fmt.Sprintf("Parsing error in line %d:\n%s\n%s^\n%v\n", lino, string(line), string(indent), e.Error())
}