ea225df3ed
``` name old time/op new time/op delta UnmarshalDataset/config-32 86.7ms ± 2% 87.5ms ± 2% ~ (p=0.113 n=9+10) UnmarshalDataset/canada-32 129ms ± 4% 106ms ± 3% -17.94% (p=0.000 n=10+10) UnmarshalDataset/citm_catalog-32 59.4ms ± 5% 58.7ms ± 5% ~ (p=0.393 n=10+10) UnmarshalDataset/twitter-32 27.0ms ± 7% 26.9ms ± 6% ~ (p=0.720 n=10+9) UnmarshalDataset/code-32 326ms ± 4% 322ms ± 7% ~ (p=0.661 n=9+10) UnmarshalDataset/example-32 510µs ±11% 526µs ± 7% ~ (p=0.182 n=10+9) UnmarshalSimple-32 1.41µs ± 6% 1.41µs ± 4% ~ (p=0.736 n=10+9) ReferenceFile-32 45.6µs ± 3% 43.9µs ±10% ~ (p=0.089 n=10+10) name old speed new speed delta UnmarshalDataset/config-32 12.1MB/s ± 2% 12.0MB/s ± 2% ~ (p=0.108 n=9+10) UnmarshalDataset/canada-32 17.1MB/s ± 4% 20.9MB/s ± 3% +21.86% (p=0.000 n=10+10) UnmarshalDataset/citm_catalog-32 9.41MB/s ± 5% 9.51MB/s ± 5% ~ (p=0.362 n=10+10) UnmarshalDataset/twitter-32 16.4MB/s ± 8% 16.5MB/s ± 6% ~ (p=0.704 n=10+9) UnmarshalDataset/code-32 8.24MB/s ± 4% 8.34MB/s ± 7% ~ (p=0.675 n=9+10) UnmarshalDataset/example-32 15.9MB/s ±11% 15.4MB/s ± 7% ~ (p=0.182 n=10+9) ReferenceFile-32 115MB/s ± 4% 120MB/s ±10% ~ (p=0.085 n=10+10) name old alloc/op new alloc/op delta UnmarshalDataset/config-32 16.9MB ± 0% 16.9MB ± 0% -0.02% (p=0.000 n=10+10) UnmarshalDataset/canada-32 76.8MB ± 0% 74.3MB ± 0% -3.31% (p=0.000 n=10+10) UnmarshalDataset/citm_catalog-32 37.3MB ± 0% 37.1MB ± 0% -0.60% (p=0.000 n=9+10) UnmarshalDataset/twitter-32 15.6MB ± 0% 15.6MB ± 0% -0.09% (p=0.000 n=10+10) UnmarshalDataset/code-32 60.2MB ± 0% 59.3MB ± 0% -1.51% (p=0.000 n=10+9) UnmarshalDataset/example-32 238kB ± 0% 238kB ± 0% -0.18% (p=0.000 n=10+10) ReferenceFile-32 11.8kB ± 0% 11.8kB ± 0% ~ (all equal) name old allocs/op new allocs/op delta UnmarshalDataset/config-32 653k ± 0% 645k ± 0% -1.20% (p=0.000 n=10+6) UnmarshalDataset/canada-32 1.01M ± 0% 0.90M ± 0% -11.04% (p=0.000 n=9+10) UnmarshalDataset/citm_catalog-32 384k ± 0% 370k ± 0% -3.75% (p=0.000 n=10+10) UnmarshalDataset/twitter-32 160k ± 0% 157k ± 0% -1.32% (p=0.000 n=10+10) UnmarshalDataset/code-32 2.97M ± 0% 2.91M ± 0% -2.15% (p=0.000 n=10+7) UnmarshalDataset/example-32 3.69k ± 0% 3.63k ± 0% -1.52% (p=0.000 n=10+10) ReferenceFile-32 253 ± 0% 253 ± 0% ~ (all equal) ```
264 lines
5.9 KiB
Go
264 lines
5.9 KiB
Go
package toml
|
|
|
|
import (
|
|
"fmt"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/pelletier/go-toml/v2/internal/unsafe"
|
|
)
|
|
|
|
// DecodeError represents an error encountered during the parsing or decoding
|
|
// of a TOML document.
|
|
//
|
|
// In addition to the error message, it contains the position in the document
|
|
// where it happened, as well as a human-readable representation that shows
|
|
// where the error occurred in the document.
|
|
type DecodeError struct {
|
|
message string
|
|
line int
|
|
column int
|
|
key Key
|
|
|
|
human string
|
|
}
|
|
|
|
// StrictMissingError occurs in a TOML document that does not have a
|
|
// corresponding field in the target value. It contains all the missing fields
|
|
// in Errors.
|
|
//
|
|
// Emitted by Decoder when SetStrict(true) was called.
|
|
type StrictMissingError struct {
|
|
// One error per field that could not be found.
|
|
Errors []DecodeError
|
|
}
|
|
|
|
// Error returns the canonical string for this error.
|
|
func (s *StrictMissingError) Error() string {
|
|
return "strict mode: fields in the document are missing in the target struct"
|
|
}
|
|
|
|
// String returns a human readable description of all errors.
|
|
func (s *StrictMissingError) String() string {
|
|
var buf strings.Builder
|
|
|
|
for i, e := range s.Errors {
|
|
if i > 0 {
|
|
buf.WriteString("\n---\n")
|
|
}
|
|
|
|
buf.WriteString(e.String())
|
|
}
|
|
|
|
return buf.String()
|
|
}
|
|
|
|
type Key []string
|
|
|
|
// internal version of DecodeError that is used as the base to create a
|
|
// DecodeError with full context.
|
|
type decodeError struct {
|
|
highlight []byte
|
|
message string
|
|
key Key // optional
|
|
}
|
|
|
|
func (de *decodeError) Error() string {
|
|
return de.message
|
|
}
|
|
|
|
func newDecodeError(highlight []byte, format string, args ...interface{}) error {
|
|
return &decodeError{
|
|
highlight: highlight,
|
|
message: fmt.Errorf(format, args...).Error(),
|
|
}
|
|
}
|
|
|
|
// Error returns the error message contained in the DecodeError.
|
|
func (e *DecodeError) Error() string {
|
|
return "toml: " + e.message
|
|
}
|
|
|
|
// String returns the human-readable contextualized error. This string is multi-line.
|
|
func (e *DecodeError) String() string {
|
|
return e.human
|
|
}
|
|
|
|
// Position returns the (line, column) pair indicating where the error
|
|
// occurred in the document. Positions are 1-indexed.
|
|
func (e *DecodeError) Position() (row int, column int) {
|
|
return e.line, e.column
|
|
}
|
|
|
|
// Key that was being processed when the error occurred.
|
|
func (e *DecodeError) Key() Key {
|
|
return e.key
|
|
}
|
|
|
|
// decodeErrorFromHighlight creates a DecodeError referencing to a highlighted
|
|
// range of bytes from document.
|
|
//
|
|
// highlight needs to be a sub-slice of document, or this function panics.
|
|
//
|
|
// The function copies all bytes used in DecodeError, so that document and
|
|
// highlight can be freely deallocated.
|
|
//nolint:funlen
|
|
func wrapDecodeError(document []byte, de *decodeError) *DecodeError {
|
|
if de == nil {
|
|
return nil
|
|
}
|
|
|
|
offset := unsafe.SubsliceOffset(document, de.highlight)
|
|
|
|
errMessage := de.message
|
|
errLine, errColumn := positionAtEnd(document[:offset])
|
|
before, after := linesOfContext(document, de.highlight, offset, 3)
|
|
|
|
var buf strings.Builder
|
|
|
|
maxLine := errLine + len(after) - 1
|
|
lineColumnWidth := len(strconv.Itoa(maxLine))
|
|
|
|
for i := len(before) - 1; i > 0; i-- {
|
|
line := errLine - i
|
|
buf.WriteString(formatLineNumber(line, lineColumnWidth))
|
|
buf.WriteString("|")
|
|
|
|
if len(before[i]) > 0 {
|
|
buf.WriteString(" ")
|
|
buf.Write(before[i])
|
|
}
|
|
|
|
buf.WriteRune('\n')
|
|
}
|
|
|
|
buf.WriteString(formatLineNumber(errLine, lineColumnWidth))
|
|
buf.WriteString("| ")
|
|
|
|
if len(before) > 0 {
|
|
buf.Write(before[0])
|
|
}
|
|
|
|
buf.Write(de.highlight)
|
|
|
|
if len(after) > 0 {
|
|
buf.Write(after[0])
|
|
}
|
|
|
|
buf.WriteRune('\n')
|
|
buf.WriteString(strings.Repeat(" ", lineColumnWidth))
|
|
buf.WriteString("| ")
|
|
|
|
if len(before) > 0 {
|
|
buf.WriteString(strings.Repeat(" ", len(before[0])))
|
|
}
|
|
|
|
buf.WriteString(strings.Repeat("~", len(de.highlight)))
|
|
|
|
if len(errMessage) > 0 {
|
|
buf.WriteString(" ")
|
|
buf.WriteString(errMessage)
|
|
}
|
|
|
|
for i := 1; i < len(after); i++ {
|
|
buf.WriteRune('\n')
|
|
line := errLine + i
|
|
buf.WriteString(formatLineNumber(line, lineColumnWidth))
|
|
buf.WriteString("|")
|
|
|
|
if len(after[i]) > 0 {
|
|
buf.WriteString(" ")
|
|
buf.Write(after[i])
|
|
}
|
|
}
|
|
|
|
return &DecodeError{
|
|
message: errMessage,
|
|
line: errLine,
|
|
column: errColumn,
|
|
key: de.key,
|
|
human: buf.String(),
|
|
}
|
|
}
|
|
|
|
func formatLineNumber(line int, width int) string {
|
|
format := "%" + strconv.Itoa(width) + "d"
|
|
|
|
return fmt.Sprintf(format, line)
|
|
}
|
|
|
|
func linesOfContext(document []byte, highlight []byte, offset int, linesAround int) ([][]byte, [][]byte) {
|
|
return beforeLines(document, offset, linesAround), afterLines(document, highlight, offset, linesAround)
|
|
}
|
|
|
|
func beforeLines(document []byte, offset int, linesAround int) [][]byte {
|
|
var beforeLines [][]byte
|
|
|
|
// Walk the document backward from the highlight to find previous lines
|
|
// of context.
|
|
rest := document[:offset]
|
|
backward:
|
|
for o := len(rest) - 1; o >= 0 && len(beforeLines) <= linesAround && len(rest) > 0; {
|
|
switch {
|
|
case rest[o] == '\n':
|
|
// handle individual lines
|
|
beforeLines = append(beforeLines, rest[o+1:])
|
|
rest = rest[:o]
|
|
o = len(rest) - 1
|
|
case o == 0:
|
|
// add the first line only if it's non-empty
|
|
beforeLines = append(beforeLines, rest)
|
|
|
|
break backward
|
|
default:
|
|
o--
|
|
}
|
|
}
|
|
|
|
return beforeLines
|
|
}
|
|
|
|
func afterLines(document []byte, highlight []byte, offset int, linesAround int) [][]byte {
|
|
var afterLines [][]byte
|
|
|
|
// Walk the document forward from the highlight to find the following
|
|
// lines of context.
|
|
rest := document[offset+len(highlight):]
|
|
forward:
|
|
for o := 0; o < len(rest) && len(afterLines) <= linesAround; {
|
|
switch {
|
|
case rest[o] == '\n':
|
|
// handle individual lines
|
|
afterLines = append(afterLines, rest[:o])
|
|
rest = rest[o+1:]
|
|
o = 0
|
|
|
|
case o == len(rest)-1 && o > 0:
|
|
// add last line only if it's non-empty
|
|
afterLines = append(afterLines, rest)
|
|
|
|
break forward
|
|
default:
|
|
o++
|
|
}
|
|
}
|
|
|
|
return afterLines
|
|
}
|
|
|
|
func positionAtEnd(b []byte) (row int, column int) {
|
|
row = 1
|
|
column = 1
|
|
|
|
for _, c := range b {
|
|
if c == '\n' {
|
|
row++
|
|
column = 1
|
|
} else {
|
|
column++
|
|
}
|
|
}
|
|
|
|
return
|
|
}
|