diff --git a/decode.go b/decode.go index 7aaf462..ff9609c 100644 --- a/decode.go +++ b/decode.go @@ -540,12 +540,13 @@ func (md *MetaData) badtype(dst string, data any) error { func (md *MetaData) parseErr(err error) error { k := md.context.String() + d := string(md.data) return ParseError{ LastKey: k, - Position: md.keyInfo[k].pos, + Position: md.keyInfo[k].pos.withCol(d), Line: md.keyInfo[k].pos.Line, err: err, - input: string(md.data), + input: d, } } diff --git a/error.go b/error.go index b45a3f4..93a870c 100644 --- a/error.go +++ b/error.go @@ -67,8 +67,28 @@ type ParseError struct { // Position of an error. type Position struct { Line int // Line number, starting at 1. + Col int // Error column, starting at 1. Start int // Start of error, as byte offset starting at 0. - Len int // Lenght in bytes. + Len int // Lenght of the error in bytes. +} + +func (p Position) withCol(tomlFile string) Position { + var ( + pos int + lines = strings.Split(tomlFile, "\n") + ) + for i := range lines { + ll := len(lines[i]) + 1 // +1 for the removed newline + if pos+ll >= p.Start { + p.Col = p.Start - pos + 1 + if p.Col < 1 { // Should never happen, but just in case. + p.Col = 1 + } + break + } + pos += ll + } + return p } func (pe ParseError) Error() string { @@ -94,7 +114,6 @@ func (pe ParseError) ErrorWithPosition() string { var ( lines = strings.Split(pe.input, "\n") - col = pe.column(lines) b = new(strings.Builder) ) @@ -108,10 +127,10 @@ func (pe ParseError) ErrorWithPosition() string { if pe.Position.Len == 1 { fmt.Fprintf(b, "toml: error: %s\n\nAt line %d, column %d:\n\n", - msg, pe.Position.Line, col+1) + msg, pe.Position.Line, pe.Position.Col) } else { fmt.Fprintf(b, "toml: error: %s\n\nAt line %d, column %d-%d:\n\n", - msg, pe.Position.Line, col, col+pe.Position.Len) + msg, pe.Position.Line, pe.Position.Col, pe.Position.Col+pe.Position.Len-1) } if pe.Position.Line > 2 { fmt.Fprintf(b, "% 7d | %s\n", pe.Position.Line-2, expandTab(lines[pe.Position.Line-3])) @@ -129,7 +148,7 @@ func (pe ParseError) ErrorWithPosition() string { diff := len(expanded) - len(lines[pe.Position.Line-1]) fmt.Fprintf(b, "% 7d | %s\n", pe.Position.Line, expanded) - fmt.Fprintf(b, "% 10s%s%s\n", "", strings.Repeat(" ", col+diff), strings.Repeat("^", pe.Position.Len)) + fmt.Fprintf(b, "% 10s%s%s\n", "", strings.Repeat(" ", pe.Position.Col-1+diff), strings.Repeat("^", pe.Position.Len)) return b.String() } @@ -151,23 +170,6 @@ func (pe ParseError) ErrorWithUsage() string { return m } -func (pe ParseError) column(lines []string) int { - var pos, col int - for i := range lines { - ll := len(lines[i]) + 1 // +1 for the removed newline - if pos+ll >= pe.Position.Start { - col = pe.Position.Start - pos - if col < 0 { // Should never happen, but just in case. - col = 0 - } - break - } - pos += ll - } - - return col -} - func expandTab(s string) string { var ( b strings.Builder diff --git a/error_test.go b/error_test.go index ce2171f..4fd7250 100644 --- a/error_test.go +++ b/error_test.go @@ -37,7 +37,7 @@ At line 1, column 23: {"array/tables-2.toml", ` toml: error: Key 'fruit.variety' has already been defined. -At line 9, column 3-8: +At line 9, column 4-8: 7 | 8 | # This table conflicts with the previous table @@ -46,7 +46,7 @@ At line 9, column 3-8: {"local-date/trailing-t.toml", ` toml: error: invalid datetime: "2006-01-30T" -At line 2, column 4-15: +At line 2, column 5-15: 1 | # Date cannot end with trailing T 2 | d = 2006-01-30T @@ -92,7 +92,7 @@ func TestParseError(t *testing.T) { "Int = 200", `| toml: error: 200 is out of range for int8 | - | At line 1, column 6-9: + | At line 1, column 7-9: | | 1 | Int = 200 | ^^^ @@ -122,7 +122,7 @@ func TestParseError(t *testing.T) { fmt.Sprintf("Int = %d", uint64(math.MaxInt64+1)), `| toml: error: 9223372036854775808 is out of range for int64 | - | At line 1, column 6-25: + | At line 1, column 7-25: | | 1 | Int = 9223372036854775808 | ^^^^^^^^^^^^^^^^^^^ @@ -153,7 +153,7 @@ func TestParseError(t *testing.T) { ` | toml: error: 1.1e+99 is out of range for float32 | - | At line 1, column 8-14: + | At line 1, column 9-14: | | 1 | Float = 1.1e99 | ^^^^^^ @@ -185,7 +185,7 @@ func TestParseError(t *testing.T) { ` | toml: error: invalid duration: "99 bottles" | - | At line 1, column 5-15: + | At line 1, column 6-15: | | 1 | D = "99 bottles" | ^^^^^^^^^^ @@ -211,7 +211,7 @@ func TestParseError(t *testing.T) { ` | toml: error: invalid datetime: "2006-01-99" | - | At line 1, column 4-14: + | At line 1, column 5-14: | | 1 | D = 2006-01-99 | ^^^^^^^^^^ @@ -297,7 +297,7 @@ func TestUnmarshalTypeError(t *testing.T) { want := `toml: error: invalid value: "invalid" -At line 3, column 6-13: +At line 3, column 7-13: 1 | k1 = 'asd' 2 | k2 = 'ok' @@ -339,7 +339,7 @@ func TestMarhsalError(t *testing.T) { want := `toml: error: invalid value: "invalid" -At line 3, column 6-13: +At line 3, column 7-13: 1 | k1 = 'asd' 2 | k2 = 'ok' @@ -369,7 +369,7 @@ func TestErrorIndent(t *testing.T) { err := getErr(t, "\tspaces = xxx") want := `toml: error: expected value but found "xxx" instead -At line 1, column 10-13: +At line 1, column 11-13: 1 | spaces = xxx ^^^ @@ -382,7 +382,7 @@ At line 1, column 10-13: err = getErr(t, "\tspaces\t=\txxx") want = `toml: error: expected value but found "xxx" instead -At line 1, column 10-13: +At line 1, column 11-13: 1 | spaces = xxx ^^^ @@ -394,7 +394,7 @@ At line 1, column 10-13: err = getErr(t, "\txxx \t = \t 1\n\tspaces\t=\txxx") want = `toml: error: expected value but found "xxx" instead -At line 2, column 10-13: +At line 2, column 11-13: 1 | xxx = 1 2 | spaces = xxx diff --git a/parse.go b/parse.go index 11ac310..33fb0a8 100644 --- a/parse.go +++ b/parse.go @@ -65,7 +65,7 @@ func parse(data string) (p *parser, err error) { if i := strings.IndexRune(data[:ex], 0); i > -1 { return nil, ParseError{ Message: "files cannot contain NULL bytes; probably using UTF-16; TOML files must be UTF-8", - Position: Position{Line: 1, Start: i, Len: 1}, + Position: Position{Line: 1, Col: 1, Start: i, Len: 1}, Line: 1, input: data, } @@ -93,7 +93,7 @@ func parse(data string) (p *parser, err error) { func (p *parser) panicErr(it item, err error) { panic(ParseError{ err: err, - Position: it.pos, + Position: it.pos.withCol(p.lx.input), Line: it.pos.Len, LastKey: p.current(), }) @@ -102,7 +102,7 @@ func (p *parser) panicErr(it item, err error) { func (p *parser) panicItemf(it item, format string, v ...any) { panic(ParseError{ Message: fmt.Sprintf(format, v...), - Position: it.pos, + Position: it.pos.withCol(p.lx.input), Line: it.pos.Len, LastKey: p.current(), }) @@ -111,7 +111,7 @@ func (p *parser) panicItemf(it item, format string, v ...any) { func (p *parser) panicf(format string, v ...any) { panic(ParseError{ Message: fmt.Sprintf(format, v...), - Position: p.pos, + Position: p.pos.withCol(p.lx.input), Line: p.pos.Line, LastKey: p.current(), }) @@ -123,7 +123,7 @@ func (p *parser) next() item { if it.typ == itemError { if it.err != nil { panic(ParseError{ - Position: it.pos, + Position: it.pos.withCol(p.lx.input), Line: it.pos.Line, LastKey: p.current(), err: it.err,