1
0
treerack/trace.go

148 lines
3.1 KiB
Go
Raw Normal View History

2026-06-06 21:19:04 +02:00
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
}