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 }