148 lines
3.1 KiB
Go
148 lines
3.1 KiB
Go
|
|
package treerack
|
||
|
|
|
||
|
|
import (
|
||
|
|
"errors"
|
||
|
|
"fmt"
|
||
|
|
"io"
|
||
|
|
"strings"
|
||
|
|
)
|
||
|
|
|
||
|
|
const (
|
||
|
|
maxTraceLineWidth = 108
|
||
|
|
minEventSnippetWidth = 36
|
||
|
|
)
|
||
|
|
|
||
|
|
func traceLevelOffset(tr []TraceEntry) int {
|
||
|
|
min := -1
|
||
|
|
for _, tri := range tr {
|
||
|
|
if min < 0 || tri.Level < min {
|
||
|
|
min = tri.Level
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return min
|
||
|
|
}
|
||
|
|
|
||
|
|
func traceIndent(tr TraceEntry, levelOffset int) string {
|
||
|
|
return strings.Join(make([]string, tr.Level-levelOffset+1), " ")
|
||
|
|
}
|
||
|
|
|
||
|
|
func traceEventSymbol(tr TraceEntry) string {
|
||
|
|
switch tr.Event {
|
||
|
|
case Enter:
|
||
|
|
return ">"
|
||
|
|
case Success:
|
||
|
|
return "<"
|
||
|
|
case Fail:
|
||
|
|
return "!"
|
||
|
|
default:
|
||
|
|
return "?"
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func traceEnterMessage(tr TraceEntry) string {
|
||
|
|
return fmt.Sprintf("parsing %s at %d:%d", tr.Parser, tr.FromLine+1, tr.FromCol+1)
|
||
|
|
}
|
||
|
|
|
||
|
|
func traceSuccessMessage(tr TraceEntry) string {
|
||
|
|
return fmt.Sprintf(
|
||
|
|
"%s success from %d:%d to %d:%d",
|
||
|
|
tr.Parser,
|
||
|
|
tr.FromLine+1, tr.FromCol+1,
|
||
|
|
tr.ToLine+1, tr.ToCol+1,
|
||
|
|
)
|
||
|
|
}
|
||
|
|
|
||
|
|
func traceFailMessage(tr TraceEntry) string {
|
||
|
|
if tr.Reason == "" {
|
||
|
|
return fmt.Sprintf(
|
||
|
|
"%s failed from %d:%d at %d:%d",
|
||
|
|
tr.Parser,
|
||
|
|
tr.FromLine+1, tr.FromCol+1,
|
||
|
|
tr.ToLine+1, tr.ToCol+1,
|
||
|
|
)
|
||
|
|
}
|
||
|
|
|
||
|
|
return fmt.Sprintf(
|
||
|
|
"%s failed from %d:%d at %d:%d, %s",
|
||
|
|
tr.Parser,
|
||
|
|
tr.FromLine+1, tr.FromCol+1,
|
||
|
|
tr.ToLine+1, tr.ToCol+1,
|
||
|
|
tr.Reason,
|
||
|
|
)
|
||
|
|
}
|
||
|
|
|
||
|
|
func traceEventMessage(tr TraceEntry) string {
|
||
|
|
switch tr.Event {
|
||
|
|
case Enter:
|
||
|
|
return traceEnterMessage(tr)
|
||
|
|
case Success:
|
||
|
|
return traceSuccessMessage(tr)
|
||
|
|
case Fail:
|
||
|
|
return traceFailMessage(tr)
|
||
|
|
default:
|
||
|
|
return "?"
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func traceEventLine(tr TraceEntry, levelOffset int) string {
|
||
|
|
i := traceIndent(tr, levelOffset)
|
||
|
|
e := traceEventSymbol(tr)
|
||
|
|
m := traceEventMessage(tr)
|
||
|
|
return fmt.Sprintf("%s%s %s", i, e, m)
|
||
|
|
}
|
||
|
|
|
||
|
|
func traceEventSnippet(pe *ParseError, tr TraceEntry, maxLength int) string {
|
||
|
|
if maxLength < minEventSnippetWidth {
|
||
|
|
maxLength = minEventSnippetWidth
|
||
|
|
}
|
||
|
|
|
||
|
|
switch tr.Event {
|
||
|
|
case Enter, Fail:
|
||
|
|
if len(pe.InputContent(0, -1)) > tr.To {
|
||
|
|
tr.To++
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
minPos := tr.To - maxLength
|
||
|
|
if minPos < 0 {
|
||
|
|
minPos = 0
|
||
|
|
}
|
||
|
|
|
||
|
|
content := pe.InputContent(minPos, tr.To)
|
||
|
|
lines := strings.Split(string(content), "\n")
|
||
|
|
content = []rune(lines[len(lines)-1])
|
||
|
|
if len(content) < maxLength && (len(lines) > 1 || minPos == 0) {
|
||
|
|
return string(content)
|
||
|
|
}
|
||
|
|
|
||
|
|
if len(content)+3 > maxLength {
|
||
|
|
content = content[len(content)+3-maxLength:]
|
||
|
|
}
|
||
|
|
|
||
|
|
return "..." + string(content)
|
||
|
|
}
|
||
|
|
|
||
|
|
// Trace checks if the provided error is of type *ParseError, and if yes, it writes the trace attached to the
|
||
|
|
// error to the provided output. If the err is not of type *ParseError or there is no trace attached, it does
|
||
|
|
// nothing and returns with nil. Note that it is possible and encouraged to generate custom visualizations of
|
||
|
|
// the parsing trace, from the structured trace entries attached to errors of type *ParseError, instead of using
|
||
|
|
// the Trace function.
|
||
|
|
func Trace(out io.Writer, err error) error {
|
||
|
|
var perr *ParseError
|
||
|
|
if !errors.As(err, &perr) {
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
|
||
|
|
levelOffset := traceLevelOffset(perr.Trace)
|
||
|
|
for _, tr := range perr.Trace {
|
||
|
|
l := traceEventLine(tr, levelOffset)
|
||
|
|
s := traceEventSnippet(perr, tr, maxTraceLineWidth-len(l)-4)
|
||
|
|
if _, err := fmt.Fprintf(out, "%s: '%s'\n", l, s); err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return nil
|
||
|
|
}
|