AST Tweaks (#551)

* Use pointers instead of copying around ast.Node

Node is a 56B struct that is constantly in the hot path. Passing nodes
around by copy had a cost that started to add up. This change replaces
them by pointers. Using unsafe pointer arithmetic and converting
sibling/child indexes to relative offsets, it removes the need to carry
around a pointer to the root of the tree. This saves 8B per Node. This
space will be used to store an extra []byte slice to provide contextual
error handling on all nodes, including the ones whose data is different
than the raw input (for example: strings with escaped characters), while
staying under the size of a cache line.

* Remove conditional

* Add Raw to track range in data for parsed values

* Simplify reference tracking
This commit is contained in:
Thomas Pelletier
2021-06-03 21:48:51 -04:00
committed by GitHub
parent f3bb20ea79
commit 618f0181ac
13 changed files with 239 additions and 165 deletions
+16 -16
View File
@@ -156,12 +156,12 @@ Execution time speedup compared to other Go TOML libraries:
<tr><th>Benchmark</th><th>go-toml v1</th><th>BurntSushi/toml</th></tr> <tr><th>Benchmark</th><th>go-toml v1</th><th>BurntSushi/toml</th></tr>
</thead> </thead>
<tbody> <tbody>
<tr><td>Marshal/HugoFrontMatter</td><td>1.9x</td><td>1.9x</td></tr> <tr><td>Marshal/HugoFrontMatter</td><td>2.0x</td><td>2.0x</td></tr>
<tr><td>Marshal/ReferenceFile/map</td><td>1.7x</td><td>1.9x</td></tr> <tr><td>Marshal/ReferenceFile/map</td><td>1.8x</td><td>2.0x</td></tr>
<tr><td>Marshal/ReferenceFile/struct</td><td>2.7x</td><td>2.9x</td></tr> <tr><td>Marshal/ReferenceFile/struct</td><td>2.7x</td><td>2.7x</td></tr>
<tr><td>Unmarshal/HugoFrontMatter</td><td>2.9x</td><td>2.4x</td></tr> <tr><td>Unmarshal/HugoFrontMatter</td><td>3.0x</td><td>2.6x</td></tr>
<tr><td>Unmarshal/ReferenceFile/map</td><td>3.1x</td><td>3.0x</td></tr> <tr><td>Unmarshal/ReferenceFile/map</td><td>3.0x</td><td>3.1x</td></tr>
<tr><td>Unmarshal/ReferenceFile/struct</td><td>5.5x</td><td>5.8x</td></tr> <tr><td>Unmarshal/ReferenceFile/struct</td><td>5.9x</td><td>6.6x</td></tr>
</tbody> </tbody>
</table> </table>
<details><summary>See more</summary> <details><summary>See more</summary>
@@ -174,16 +174,16 @@ provided for completeness.</p>
<tr><th>Benchmark</th><th>go-toml v1</th><th>BurntSushi/toml</th></tr> <tr><th>Benchmark</th><th>go-toml v1</th><th>BurntSushi/toml</th></tr>
</thead> </thead>
<tbody> <tbody>
<tr><td>Marshal/SimpleDocument/map</td><td>1.8x</td><td>2.4x</td></tr> <tr><td>Marshal/SimpleDocument/map</td><td>1.7x</td><td>2.1x</td></tr>
<tr><td>Marshal/SimpleDocument/struct</td><td>2.7x</td><td>3.5x</td></tr> <tr><td>Marshal/SimpleDocument/struct</td><td>2.6x</td><td>2.9x</td></tr>
<tr><td>Unmarshal/SimpleDocument/map</td><td>4.3x</td><td>2.4x</td></tr> <tr><td>Unmarshal/SimpleDocument/map</td><td>4.1x</td><td>2.9x</td></tr>
<tr><td>Unmarshal/SimpleDocument/struct</td><td>5.8x</td><td>3.3x</td></tr> <tr><td>Unmarshal/SimpleDocument/struct</td><td>6.3x</td><td>4.1x</td></tr>
<tr><td>UnmarshalDataset/example</td><td>3.1x</td><td>2.2x</td></tr> <tr><td>UnmarshalDataset/example</td><td>3.5x</td><td>2.4x</td></tr>
<tr><td>UnmarshalDataset/code</td><td>1.8x</td><td>2.1x</td></tr> <tr><td>UnmarshalDataset/code</td><td>2.2x</td><td>2.8x</td></tr>
<tr><td>UnmarshalDataset/twitter</td><td>2.7x</td><td>1.9x</td></tr> <tr><td>UnmarshalDataset/twitter</td><td>2.8x</td><td>2.1x</td></tr>
<tr><td>UnmarshalDataset/citm_catalog</td><td>1.8x</td><td>1.2x</td></tr> <tr><td>UnmarshalDataset/citm_catalog</td><td>2.3x</td><td>1.5x</td></tr>
<tr><td>UnmarshalDataset/config</td><td>3.4x</td><td>2.8x</td></tr> <tr><td>UnmarshalDataset/config</td><td>4.2x</td><td>3.2x</td></tr>
<tr><td>[Geo mean]</td><td>2.8x</td><td>2.5x</td></tr> <tr><td>[Geo mean]</td><td>3.0x</td><td>2.7x</td></tr>
</tbody> </tbody>
</table> </table>
<p>This table can be generated with <code>./ci.sh benchmark -a -html</code>.</p> <p>This table can be generated with <code>./ci.sh benchmark -a -html</code>.</p>
+2 -2
View File
@@ -5,7 +5,7 @@ import (
"strconv" "strconv"
"strings" "strings"
"github.com/pelletier/go-toml/v2/internal/unsafe" "github.com/pelletier/go-toml/v2/internal/danger"
) )
// DecodeError represents an error encountered during the parsing or decoding // DecodeError represents an error encountered during the parsing or decoding
@@ -105,7 +105,7 @@ func (e *DecodeError) Key() Key {
// highlight can be freely deallocated. // highlight can be freely deallocated.
//nolint:funlen //nolint:funlen
func wrapDecodeError(document []byte, de *decodeError) *DecodeError { func wrapDecodeError(document []byte, de *decodeError) *DecodeError {
offset := unsafe.SubsliceOffset(document, de.highlight) offset := danger.SubsliceOffset(document, de.highlight)
errMessage := de.Error() errMessage := de.Error()
errLine, errColumn := positionAtEnd(document[:offset]) errLine, errColumn := positionAtEnd(document[:offset])
+37 -29
View File
@@ -2,6 +2,9 @@ package ast
import ( import (
"fmt" "fmt"
"unsafe"
"github.com/pelletier/go-toml/v2/internal/danger"
) )
// Iterator starts uninitialized, you need to call Next() first. // Iterator starts uninitialized, you need to call Next() first.
@@ -14,7 +17,7 @@ import (
// } // }
type Iterator struct { type Iterator struct {
started bool started bool
node Node node *Node
} }
// Next moves the iterator forward and returns true if points to a node, false // Next moves the iterator forward and returns true if points to a node, false
@@ -31,11 +34,11 @@ func (c *Iterator) Next() bool {
// IsLast returns true if the current node of the iterator is the last one. // IsLast returns true if the current node of the iterator is the last one.
// Subsequent call to Next() will return false. // Subsequent call to Next() will return false.
func (c *Iterator) IsLast() bool { func (c *Iterator) IsLast() bool {
return c.node.next <= 0 return c.node.next == 0
} }
// Node returns a copy of the node pointed at by the iterator. // Node returns a copy of the node pointed at by the iterator.
func (c *Iterator) Node() Node { func (c *Iterator) Node() *Node {
return c.node return c.node
} }
@@ -50,14 +53,13 @@ type Root struct {
func (r *Root) Iterator() Iterator { func (r *Root) Iterator() Iterator {
it := Iterator{} it := Iterator{}
if len(r.nodes) > 0 { if len(r.nodes) > 0 {
it.node = r.nodes[0] it.node = &r.nodes[0]
} }
return it return it
} }
func (r *Root) at(idx int) Node { func (r *Root) at(idx Reference) *Node {
// TODO: unsafe to point to the node directly return &r.nodes[idx]
return r.nodes[idx]
} }
// Arrays have one child per element in the array. // Arrays have one child per element in the array.
@@ -69,42 +71,48 @@ func (r *Root) at(idx int) Node {
// children []Node // children []Node
type Node struct { type Node struct {
Kind Kind Kind Kind
Data []byte // Raw bytes from the input Raw Range // Raw bytes from the input.
Data []byte // Node value (could be either allocated or referencing the input).
// next idx (in the root array). 0 if last of the collection. // References to other nodes, as offsets in the backing array from this
next int // node. References can go backward, so those can be negative.
// child idx (in the root array). 0 if no child. next int // 0 if last element
child int child int // 0 if no child
// pointer to the root array }
root *Root
type Range struct {
Offset uint32
Length uint32
} }
// Next returns a copy of the next node, or an invalid Node if there is no // Next returns a copy of the next node, or an invalid Node if there is no
// next node. // next node.
func (n Node) Next() Node { func (n *Node) Next() *Node {
if n.next <= 0 { if n.next == 0 {
return noNode return nil
} }
return n.root.at(n.next) ptr := unsafe.Pointer(n)
size := unsafe.Sizeof(Node{})
return (*Node)(danger.Stride(ptr, size, n.next))
} }
// Child returns a copy of the first child node of this node. Other children // Child returns a copy of the first child node of this node. Other children
// can be accessed calling Next on the first child. // can be accessed calling Next on the first child.
// Returns an invalid Node if there is none. // Returns an invalid Node if there is none.
func (n Node) Child() Node { func (n *Node) Child() *Node {
if n.child <= 0 { if n.child == 0 {
return noNode return nil
} }
return n.root.at(n.child) ptr := unsafe.Pointer(n)
size := unsafe.Sizeof(Node{})
return (*Node)(danger.Stride(ptr, size, n.child))
} }
// Valid returns true if the node's kind is set (not to Invalid). // Valid returns true if the node's kind is set (not to Invalid).
func (n Node) Valid() bool { func (n *Node) Valid() bool {
return n.Kind != Invalid return n != nil
} }
var noNode = Node{}
// Key returns the child nodes making the Key on a supported node. Panics // Key returns the child nodes making the Key on a supported node. Panics
// otherwise. // otherwise.
// They are guaranteed to be all be of the Kind Key. A simple key would return // They are guaranteed to be all be of the Kind Key. A simple key would return
@@ -127,13 +135,13 @@ func (n *Node) Key() Iterator {
// Value returns a pointer to the value node of a KeyValue. // Value returns a pointer to the value node of a KeyValue.
// Guaranteed to be non-nil. // Guaranteed to be non-nil.
// Panics if not called on a KeyValue node, or if the Children are malformed. // Panics if not called on a KeyValue node, or if the Children are malformed.
func (n Node) Value() Node { func (n *Node) Value() *Node {
assertKind(KeyValue, n) assertKind(KeyValue, *n)
return n.Child() return n.Child()
} }
// Children returns an iterator over a node's children. // Children returns an iterator over a node's children.
func (n Node) Children() Iterator { func (n *Node) Children() Iterator {
return Iterator{node: n.Child()} return Iterator{node: n.Child()}
} }
+11 -20
View File
@@ -1,12 +1,11 @@
package ast package ast
type Reference struct { type Reference int
idx int
set bool const InvalidReference Reference = -1
}
func (r Reference) Valid() bool { func (r Reference) Valid() bool {
return r.set return r != InvalidReference
} }
type Builder struct { type Builder struct {
@@ -18,8 +17,8 @@ func (b *Builder) Tree() *Root {
return &b.tree return &b.tree
} }
func (b *Builder) NodeAt(ref Reference) Node { func (b *Builder) NodeAt(ref Reference) *Node {
return b.tree.at(ref.idx) return b.tree.at(ref)
} }
func (b *Builder) Reset() { func (b *Builder) Reset() {
@@ -28,33 +27,25 @@ func (b *Builder) Reset() {
} }
func (b *Builder) Push(n Node) Reference { func (b *Builder) Push(n Node) Reference {
n.root = &b.tree
b.lastIdx = len(b.tree.nodes) b.lastIdx = len(b.tree.nodes)
b.tree.nodes = append(b.tree.nodes, n) b.tree.nodes = append(b.tree.nodes, n)
return Reference{ return Reference(b.lastIdx)
idx: b.lastIdx,
set: true,
}
} }
func (b *Builder) PushAndChain(n Node) Reference { func (b *Builder) PushAndChain(n Node) Reference {
n.root = &b.tree
newIdx := len(b.tree.nodes) newIdx := len(b.tree.nodes)
b.tree.nodes = append(b.tree.nodes, n) b.tree.nodes = append(b.tree.nodes, n)
if b.lastIdx >= 0 { if b.lastIdx >= 0 {
b.tree.nodes[b.lastIdx].next = newIdx b.tree.nodes[b.lastIdx].next = newIdx - b.lastIdx
} }
b.lastIdx = newIdx b.lastIdx = newIdx
return Reference{ return Reference(b.lastIdx)
idx: b.lastIdx,
set: true,
}
} }
func (b *Builder) AttachChild(parent Reference, child Reference) { func (b *Builder) AttachChild(parent Reference, child Reference) {
b.tree.nodes[parent.idx].child = child.idx b.tree.nodes[parent].child = int(child) - int(parent)
} }
func (b *Builder) Chain(from Reference, to Reference) { func (b *Builder) Chain(from Reference, to Reference) {
b.tree.nodes[from.idx].next = to.idx b.tree.nodes[from].next = int(to) - int(from)
} }
@@ -1,4 +1,4 @@
package unsafe package danger
import ( import (
"fmt" "fmt"
@@ -57,3 +57,9 @@ func BytesRange(start []byte, end []byte) []byte {
return start[:l] return start[:l]
} }
func Stride(ptr unsafe.Pointer, size uintptr, offset int) unsafe.Pointer {
// TODO: replace with unsafe.Add when Go 1.17 is released
// https://github.com/golang/go/issues/40481
return unsafe.Pointer(uintptr(ptr) + uintptr(int(size)*offset))
}
@@ -1,15 +1,16 @@
package unsafe_test package danger_test
import ( import (
"testing" "testing"
"unsafe"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/pelletier/go-toml/v2/internal/unsafe" "github.com/pelletier/go-toml/v2/internal/danger"
) )
func TestUnsafeSubsliceOffsetValid(t *testing.T) { func TestSubsliceOffsetValid(t *testing.T) {
examples := []struct { examples := []struct {
desc string desc string
test func() ([]byte, []byte) test func() ([]byte, []byte)
@@ -28,13 +29,13 @@ func TestUnsafeSubsliceOffsetValid(t *testing.T) {
for _, e := range examples { for _, e := range examples {
t.Run(e.desc, func(t *testing.T) { t.Run(e.desc, func(t *testing.T) {
d, s := e.test() d, s := e.test()
offset := unsafe.SubsliceOffset(d, s) offset := danger.SubsliceOffset(d, s)
assert.Equal(t, e.offset, offset) assert.Equal(t, e.offset, offset)
}) })
} }
} }
func TestUnsafeSubsliceOffsetInvalid(t *testing.T) { func TestSubsliceOffsetInvalid(t *testing.T) {
examples := []struct { examples := []struct {
desc string desc string
test func() ([]byte, []byte) test func() ([]byte, []byte)
@@ -72,13 +73,22 @@ func TestUnsafeSubsliceOffsetInvalid(t *testing.T) {
t.Run(e.desc, func(t *testing.T) { t.Run(e.desc, func(t *testing.T) {
d, s := e.test() d, s := e.test()
require.Panics(t, func() { require.Panics(t, func() {
unsafe.SubsliceOffset(d, s) danger.SubsliceOffset(d, s)
}) })
}) })
} }
} }
func TestUnsafeBytesRange(t *testing.T) { func TestStride(t *testing.T) {
a := []byte{1, 2, 3, 4}
x := &a[1]
n := (*byte)(danger.Stride(unsafe.Pointer(x), unsafe.Sizeof(byte(0)), 1))
require.Equal(t, &a[2], n)
n = (*byte)(danger.Stride(unsafe.Pointer(x), unsafe.Sizeof(byte(0)), -1))
require.Equal(t, &a[0], n)
}
func TestBytesRange(t *testing.T) {
type fn = func() ([]byte, []byte) type fn = func() ([]byte, []byte)
examples := []struct { examples := []struct {
desc string desc string
@@ -157,10 +167,10 @@ func TestUnsafeBytesRange(t *testing.T) {
start, end := e.test() start, end := e.test()
if e.expected == nil { if e.expected == nil {
require.Panics(t, func() { require.Panics(t, func() {
unsafe.BytesRange(start, end) danger.BytesRange(start, end)
}) })
} else { } else {
res := unsafe.BytesRange(start, end) res := danger.BytesRange(start, end)
require.Equal(t, e.expected, res) require.Equal(t, e.expected, res)
} }
}) })
+4 -4
View File
@@ -11,19 +11,19 @@ type KeyTracker struct {
} }
// UpdateTable sets the state of the tracker with the AST table node. // UpdateTable sets the state of the tracker with the AST table node.
func (t *KeyTracker) UpdateTable(node ast.Node) { func (t *KeyTracker) UpdateTable(node *ast.Node) {
t.reset() t.reset()
t.Push(node) t.Push(node)
} }
// UpdateArrayTable sets the state of the tracker with the AST array table node. // UpdateArrayTable sets the state of the tracker with the AST array table node.
func (t *KeyTracker) UpdateArrayTable(node ast.Node) { func (t *KeyTracker) UpdateArrayTable(node *ast.Node) {
t.reset() t.reset()
t.Push(node) t.Push(node)
} }
// Push the given key on the stack. // Push the given key on the stack.
func (t *KeyTracker) Push(node ast.Node) { func (t *KeyTracker) Push(node *ast.Node) {
it := node.Key() it := node.Key()
for it.Next() { for it.Next() {
t.k = append(t.k, string(it.Node().Data)) t.k = append(t.k, string(it.Node().Data))
@@ -31,7 +31,7 @@ func (t *KeyTracker) Push(node ast.Node) {
} }
// Pop key from stack. // Pop key from stack.
func (t *KeyTracker) Pop(node ast.Node) { func (t *KeyTracker) Pop(node *ast.Node) {
it := node.Key() it := node.Key()
for it.Next() { for it.Next() {
t.k = t.k[:len(t.k)-1] t.k = t.k[:len(t.k)-1]
+4 -4
View File
@@ -104,7 +104,7 @@ func (s *SeenTracker) create(parentIdx int, name []byte, kind keyKind, explicit
// CheckExpression takes a top-level node and checks that it does not contain keys // CheckExpression takes a top-level node and checks that it does not contain keys
// that have been seen in previous calls, and validates that types are consistent. // that have been seen in previous calls, and validates that types are consistent.
func (s *SeenTracker) CheckExpression(node ast.Node) error { func (s *SeenTracker) CheckExpression(node *ast.Node) error {
if s.entries == nil { if s.entries == nil {
// s.entries = make([]entry, 0, 8) // s.entries = make([]entry, 0, 8)
// Skip ID = 0 to remove the confusion between nodes whose parent has // Skip ID = 0 to remove the confusion between nodes whose parent has
@@ -125,7 +125,7 @@ func (s *SeenTracker) CheckExpression(node ast.Node) error {
} }
} }
func (s *SeenTracker) checkTable(node ast.Node) error { func (s *SeenTracker) checkTable(node *ast.Node) error {
it := node.Key() it := node.Key()
parentIdx := -1 parentIdx := -1
@@ -169,7 +169,7 @@ func (s *SeenTracker) checkTable(node ast.Node) error {
return nil return nil
} }
func (s *SeenTracker) checkArrayTable(node ast.Node) error { func (s *SeenTracker) checkArrayTable(node *ast.Node) error {
it := node.Key() it := node.Key()
parentIdx := -1 parentIdx := -1
@@ -207,7 +207,7 @@ func (s *SeenTracker) checkArrayTable(node ast.Node) error {
return nil return nil
} }
func (s *SeenTracker) checkKeyValue(node ast.Node) error { func (s *SeenTracker) checkKeyValue(node *ast.Node) error {
it := node.Key() it := node.Key()
parentIdx := s.currentIdx parentIdx := s.currentIdx
+62 -44
View File
@@ -5,6 +5,7 @@ import (
"strconv" "strconv"
"github.com/pelletier/go-toml/v2/internal/ast" "github.com/pelletier/go-toml/v2/internal/ast"
"github.com/pelletier/go-toml/v2/internal/danger"
) )
type parser struct { type parser struct {
@@ -16,9 +17,20 @@ type parser struct {
first bool first bool
} }
func (p *parser) Range(b []byte) ast.Range {
return ast.Range{
Offset: uint32(danger.SubsliceOffset(p.data, b)),
Length: uint32(len(b)),
}
}
func (p *parser) Raw(raw ast.Range) []byte {
return p.data[raw.Offset : raw.Offset+raw.Length]
}
func (p *parser) Reset(b []byte) { func (p *parser) Reset(b []byte) {
p.builder.Reset() p.builder.Reset()
p.ref = ast.Reference{} p.ref = ast.InvalidReference
p.data = b p.data = b
p.left = b p.left = b
p.err = nil p.err = nil
@@ -32,7 +44,7 @@ func (p *parser) NextExpression() bool {
} }
p.builder.Reset() p.builder.Reset()
p.ref = ast.Reference{} p.ref = ast.InvalidReference
for { for {
if len(p.left) == 0 || p.err != nil { if len(p.left) == 0 || p.err != nil {
@@ -61,7 +73,7 @@ func (p *parser) NextExpression() bool {
} }
} }
func (p *parser) Expression() ast.Node { func (p *parser) Expression() *ast.Node {
return p.builder.NodeAt(p.ref) return p.builder.NodeAt(p.ref)
} }
@@ -86,7 +98,7 @@ func (p *parser) parseExpression(b []byte) (ast.Reference, []byte, error) {
// expression = ws [ comment ] // expression = ws [ comment ]
// expression =/ ws keyval ws [ comment ] // expression =/ ws keyval ws [ comment ]
// expression =/ ws table ws [ comment ] // expression =/ ws table ws [ comment ]
var ref ast.Reference ref := ast.InvalidReference
b = p.parseWhitespace(b) b = p.parseWhitespace(b)
@@ -197,7 +209,7 @@ func (p *parser) parseKeyval(b []byte) (ast.Reference, []byte, error) {
key, b, err := p.parseKey(b) key, b, err := p.parseKey(b)
if err != nil { if err != nil {
return ast.Reference{}, nil, err return ast.InvalidReference, nil, err
} }
// keyval-sep = ws %x3D ws ; = // keyval-sep = ws %x3D ws ; =
@@ -205,12 +217,12 @@ func (p *parser) parseKeyval(b []byte) (ast.Reference, []byte, error) {
b = p.parseWhitespace(b) b = p.parseWhitespace(b)
if len(b) == 0 { if len(b) == 0 {
return ast.Reference{}, nil, newDecodeError(b, "expected = after a key, but the document ends there") return ast.InvalidReference, nil, newDecodeError(b, "expected = after a key, but the document ends there")
} }
b, err = expect('=', b) b, err = expect('=', b)
if err != nil { if err != nil {
return ast.Reference{}, nil, err return ast.InvalidReference, nil, err
} }
b = p.parseWhitespace(b) b = p.parseWhitespace(b)
@@ -229,7 +241,7 @@ func (p *parser) parseKeyval(b []byte) (ast.Reference, []byte, error) {
//nolint:cyclop,funlen //nolint:cyclop,funlen
func (p *parser) parseVal(b []byte) (ast.Reference, []byte, error) { func (p *parser) parseVal(b []byte) (ast.Reference, []byte, error) {
// val = string / boolean / array / inline-table / date-time / float / integer // val = string / boolean / array / inline-table / date-time / float / integer
var ref ast.Reference ref := ast.InvalidReference
if len(b) == 0 { if len(b) == 0 {
return ref, nil, newDecodeError(b, "expected value, not eof") return ref, nil, newDecodeError(b, "expected value, not eof")
@@ -240,32 +252,36 @@ func (p *parser) parseVal(b []byte) (ast.Reference, []byte, error) {
switch c { switch c {
case '"': case '"':
var raw []byte
var v []byte var v []byte
if scanFollowsMultilineBasicStringDelimiter(b) { if scanFollowsMultilineBasicStringDelimiter(b) {
v, b, err = p.parseMultilineBasicString(b) raw, v, b, err = p.parseMultilineBasicString(b)
} else { } else {
v, b, err = p.parseBasicString(b) raw, v, b, err = p.parseBasicString(b)
} }
if err == nil { if err == nil {
ref = p.builder.Push(ast.Node{ ref = p.builder.Push(ast.Node{
Kind: ast.String, Kind: ast.String,
Raw: p.Range(raw),
Data: v, Data: v,
}) })
} }
return ref, b, err return ref, b, err
case '\'': case '\'':
var raw []byte
var v []byte var v []byte
if scanFollowsMultilineLiteralStringDelimiter(b) { if scanFollowsMultilineLiteralStringDelimiter(b) {
v, b, err = p.parseMultilineLiteralString(b) raw, v, b, err = p.parseMultilineLiteralString(b)
} else { } else {
v, b, err = p.parseLiteralString(b) raw, v, b, err = p.parseLiteralString(b)
} }
if err == nil { if err == nil {
ref = p.builder.Push(ast.Node{ ref = p.builder.Push(ast.Node{
Kind: ast.String, Kind: ast.String,
Raw: p.Range(raw),
Data: v, Data: v,
}) })
} }
@@ -310,13 +326,13 @@ func atmost(b []byte, n int) []byte {
return b[:n] return b[:n]
} }
func (p *parser) parseLiteralString(b []byte) ([]byte, []byte, error) { func (p *parser) parseLiteralString(b []byte) ([]byte, []byte, []byte, error) {
v, rest, err := scanLiteralString(b) v, rest, err := scanLiteralString(b)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, nil, err
} }
return v[1 : len(v)-1], rest, nil return v, v[1 : len(v)-1], rest, nil
} }
func (p *parser) parseInlineTable(b []byte) (ast.Reference, []byte, error) { func (p *parser) parseInlineTable(b []byte) (ast.Reference, []byte, error) {
@@ -476,10 +492,10 @@ func (p *parser) parseOptionalWhitespaceCommentNewline(b []byte) ([]byte, error)
return b, nil return b, nil
} }
func (p *parser) parseMultilineLiteralString(b []byte) ([]byte, []byte, error) { func (p *parser) parseMultilineLiteralString(b []byte) ([]byte, []byte, []byte, error) {
token, rest, err := scanMultilineLiteralString(b) token, rest, err := scanMultilineLiteralString(b)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, nil, err
} }
i := 3 i := 3
@@ -491,11 +507,11 @@ func (p *parser) parseMultilineLiteralString(b []byte) ([]byte, []byte, error) {
i += 2 i += 2
} }
return token[i : len(token)-3], rest, err return token, token[i : len(token)-3], rest, err
} }
//nolint:funlen,gocognit,cyclop //nolint:funlen,gocognit,cyclop
func (p *parser) parseMultilineBasicString(b []byte) ([]byte, []byte, error) { func (p *parser) parseMultilineBasicString(b []byte) ([]byte, []byte, []byte, error) {
// ml-basic-string = ml-basic-string-delim [ newline ] ml-basic-body // ml-basic-string = ml-basic-string-delim [ newline ] ml-basic-body
// ml-basic-string-delim // ml-basic-string-delim
// ml-basic-string-delim = 3quotation-mark // ml-basic-string-delim = 3quotation-mark
@@ -508,7 +524,7 @@ func (p *parser) parseMultilineBasicString(b []byte) ([]byte, []byte, error) {
// mlb-escaped-nl = escape ws newline *( wschar / newline ) // mlb-escaped-nl = escape ws newline *( wschar / newline )
token, rest, err := scanMultilineBasicString(b) token, rest, err := scanMultilineBasicString(b)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, nil, err
} }
i := 3 i := 3
@@ -529,7 +545,7 @@ func (p *parser) parseMultilineBasicString(b []byte) ([]byte, []byte, error) {
} }
} }
if i == endIdx { if i == endIdx {
return token[startIdx:endIdx], rest, nil return token, token[startIdx:endIdx], rest, nil
} }
var builder bytes.Buffer var builder bytes.Buffer
@@ -579,7 +595,7 @@ func (p *parser) parseMultilineBasicString(b []byte) ([]byte, []byte, error) {
case 'u': case 'u':
x, err := hexToString(atmost(token[i+1:], 4), 4) x, err := hexToString(atmost(token[i+1:], 4), 4)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, nil, err
} }
builder.WriteString(x) builder.WriteString(x)
@@ -587,20 +603,20 @@ func (p *parser) parseMultilineBasicString(b []byte) ([]byte, []byte, error) {
case 'U': case 'U':
x, err := hexToString(atmost(token[i+1:], 8), 8) x, err := hexToString(atmost(token[i+1:], 8), 8)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, nil, err
} }
builder.WriteString(x) builder.WriteString(x)
i += 8 i += 8
default: default:
return nil, nil, newDecodeError(token[i:i+1], "invalid escaped character %#U", c) return nil, nil, nil, newDecodeError(token[i:i+1], "invalid escaped character %#U", c)
} }
} else { } else {
builder.WriteByte(c) builder.WriteByte(c)
} }
} }
return builder.Bytes(), rest, nil return token, builder.Bytes(), rest, nil
} }
func (p *parser) parseKey(b []byte) (ast.Reference, []byte, error) { func (p *parser) parseKey(b []byte) (ast.Reference, []byte, error) {
@@ -612,13 +628,14 @@ func (p *parser) parseKey(b []byte) (ast.Reference, []byte, error) {
// dotted-key = simple-key 1*( dot-sep simple-key ) // dotted-key = simple-key 1*( dot-sep simple-key )
// //
// dot-sep = ws %x2E ws ; . Period // dot-sep = ws %x2E ws ; . Period
key, b, err := p.parseSimpleKey(b) raw, key, b, err := p.parseSimpleKey(b)
if err != nil { if err != nil {
return ast.Reference{}, nil, err return ast.InvalidReference, nil, err
} }
ref := p.builder.Push(ast.Node{ ref := p.builder.Push(ast.Node{
Kind: ast.Key, Kind: ast.Key,
Raw: p.Range(raw),
Data: key, Data: key,
}) })
@@ -627,13 +644,14 @@ func (p *parser) parseKey(b []byte) (ast.Reference, []byte, error) {
if len(b) > 0 && b[0] == '.' { if len(b) > 0 && b[0] == '.' {
b = p.parseWhitespace(b[1:]) b = p.parseWhitespace(b[1:])
key, b, err = p.parseSimpleKey(b) raw, key, b, err = p.parseSimpleKey(b)
if err != nil { if err != nil {
return ref, nil, err return ref, nil, err
} }
p.builder.PushAndChain(ast.Node{ p.builder.PushAndChain(ast.Node{
Kind: ast.Key, Kind: ast.Key,
Raw: p.Range(raw),
Data: key, Data: key,
}) })
} else { } else {
@@ -644,12 +662,12 @@ func (p *parser) parseKey(b []byte) (ast.Reference, []byte, error) {
return ref, b, nil return ref, b, nil
} }
func (p *parser) parseSimpleKey(b []byte) (key, rest []byte, err error) { func (p *parser) parseSimpleKey(b []byte) (raw, key, rest []byte, err error) {
// simple-key = quoted-key / unquoted-key // simple-key = quoted-key / unquoted-key
// unquoted-key = 1*( ALPHA / DIGIT / %x2D / %x5F ) ; A-Z / a-z / 0-9 / - / _ // unquoted-key = 1*( ALPHA / DIGIT / %x2D / %x5F ) ; A-Z / a-z / 0-9 / - / _
// quoted-key = basic-string / literal-string // quoted-key = basic-string / literal-string
if len(b) == 0 { if len(b) == 0 {
return nil, nil, newDecodeError(b, "key is incomplete") return nil, nil, nil, newDecodeError(b, "key is incomplete")
} }
switch { switch {
@@ -659,14 +677,14 @@ func (p *parser) parseSimpleKey(b []byte) (key, rest []byte, err error) {
return p.parseBasicString(b) return p.parseBasicString(b)
case isUnquotedKeyChar(b[0]): case isUnquotedKeyChar(b[0]):
key, rest = scanUnquotedKey(b) key, rest = scanUnquotedKey(b)
return key, rest, nil return key, key, rest, nil
default: default:
return nil, nil, newDecodeError(b[0:1], "invalid character at start of key: %c", b[0]) return nil, nil, nil, newDecodeError(b[0:1], "invalid character at start of key: %c", b[0])
} }
} }
//nolint:funlen,cyclop //nolint:funlen,cyclop
func (p *parser) parseBasicString(b []byte) ([]byte, []byte, error) { func (p *parser) parseBasicString(b []byte) ([]byte, []byte, []byte, error) {
// basic-string = quotation-mark *basic-char quotation-mark // basic-string = quotation-mark *basic-char quotation-mark
// quotation-mark = %x22 ; " // quotation-mark = %x22 ; "
// basic-char = basic-unescaped / escaped // basic-char = basic-unescaped / escaped
@@ -683,7 +701,7 @@ func (p *parser) parseBasicString(b []byte) ([]byte, []byte, error) {
// escape-seq-char =/ %x55 8HEXDIG ; UXXXXXXXX U+XXXXXXXX // escape-seq-char =/ %x55 8HEXDIG ; UXXXXXXXX U+XXXXXXXX
token, rest, err := scanBasicString(b) token, rest, err := scanBasicString(b)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, nil, err
} }
// fast path // fast path
@@ -696,7 +714,7 @@ func (p *parser) parseBasicString(b []byte) ([]byte, []byte, error) {
} }
} }
if i == endIdx { if i == endIdx {
return token[startIdx:endIdx], rest, nil return token, token[startIdx:endIdx], rest, nil
} }
var builder bytes.Buffer var builder bytes.Buffer
@@ -726,7 +744,7 @@ func (p *parser) parseBasicString(b []byte) ([]byte, []byte, error) {
case 'u': case 'u':
x, err := hexToString(token[i+1:len(token)-1], 4) x, err := hexToString(token[i+1:len(token)-1], 4)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, nil, err
} }
builder.WriteString(x) builder.WriteString(x)
@@ -734,20 +752,20 @@ func (p *parser) parseBasicString(b []byte) ([]byte, []byte, error) {
case 'U': case 'U':
x, err := hexToString(token[i+1:len(token)-1], 8) x, err := hexToString(token[i+1:len(token)-1], 8)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, nil, err
} }
builder.WriteString(x) builder.WriteString(x)
i += 8 i += 8
default: default:
return nil, nil, newDecodeError(token[i:i+1], "invalid escaped character %#U", c) return nil, nil, nil, newDecodeError(token[i:i+1], "invalid escaped character %#U", c)
} }
} else { } else {
builder.WriteByte(c) builder.WriteByte(c)
} }
} }
return builder.Bytes(), rest, nil return token, builder.Bytes(), rest, nil
} }
func hexToString(b []byte, length int) (string, error) { func hexToString(b []byte, length int) (string, error) {
@@ -780,7 +798,7 @@ func (p *parser) parseIntOrFloatOrDateTime(b []byte) (ast.Reference, []byte, err
switch b[0] { switch b[0] {
case 'i': case 'i':
if !scanFollowsInf(b) { if !scanFollowsInf(b) {
return ast.Reference{}, nil, newDecodeError(atmost(b, 3), "expected 'inf'") return ast.InvalidReference, nil, newDecodeError(atmost(b, 3), "expected 'inf'")
} }
return p.builder.Push(ast.Node{ return p.builder.Push(ast.Node{
@@ -789,7 +807,7 @@ func (p *parser) parseIntOrFloatOrDateTime(b []byte) (ast.Reference, []byte, err
}), b[3:], nil }), b[3:], nil
case 'n': case 'n':
if !scanFollowsNan(b) { if !scanFollowsNan(b) {
return ast.Reference{}, nil, newDecodeError(atmost(b, 3), "expected 'nan'") return ast.InvalidReference, nil, newDecodeError(atmost(b, 3), "expected 'nan'")
} }
return p.builder.Push(ast.Node{ return p.builder.Push(ast.Node{
@@ -945,7 +963,7 @@ func (p *parser) scanIntOrFloat(b []byte) (ast.Reference, []byte, error) {
}), b[i+3:], nil }), b[i+3:], nil
} }
return ast.Reference{}, nil, newDecodeError(b[i:i+1], "unexpected character 'i' while scanning for a number") return ast.InvalidReference, nil, newDecodeError(b[i:i+1], "unexpected character 'i' while scanning for a number")
} }
if c == 'n' { if c == 'n' {
@@ -956,14 +974,14 @@ func (p *parser) scanIntOrFloat(b []byte) (ast.Reference, []byte, error) {
}), b[i+3:], nil }), b[i+3:], nil
} }
return ast.Reference{}, nil, newDecodeError(b[i:i+1], "unexpected character 'n' while scanning for a number") return ast.InvalidReference, nil, newDecodeError(b[i:i+1], "unexpected character 'n' while scanning for a number")
} }
break break
} }
if i == 0 { if i == 0 {
return ast.Reference{}, b, newDecodeError(b, "incomplete number") return ast.InvalidReference, b, newDecodeError(b, "incomplete number")
} }
kind := ast.Integer kind := ast.Integer
+1 -5
View File
@@ -9,7 +9,6 @@ import (
//nolint:funlen //nolint:funlen
func TestParser_AST_Numbers(t *testing.T) { func TestParser_AST_Numbers(t *testing.T) {
examples := []struct { examples := []struct {
desc string desc string
input string input string
@@ -136,7 +135,6 @@ func TestParser_AST_Numbers(t *testing.T) {
for _, e := range examples { for _, e := range examples {
e := e e := e
t.Run(e.desc, func(t *testing.T) { t.Run(e.desc, func(t *testing.T) {
p := parser{} p := parser{}
p.Reset([]byte(`A = ` + e.input)) p.Reset([]byte(`A = ` + e.input))
p.NextExpression() p.NextExpression()
@@ -167,7 +165,7 @@ type (
} }
) )
func compareNode(t *testing.T, e astNode, n ast.Node) { func compareNode(t *testing.T, e astNode, n *ast.Node) {
t.Helper() t.Helper()
require.Equal(t, e.Kind, n.Kind) require.Equal(t, e.Kind, n.Kind)
require.Equal(t, e.Data, n.Data) require.Equal(t, e.Data, n.Data)
@@ -199,7 +197,6 @@ func compareIterator(t *testing.T, expected []astNode, actual ast.Iterator) {
//nolint:funlen //nolint:funlen
func TestParser_AST(t *testing.T) { func TestParser_AST(t *testing.T) {
examples := []struct { examples := []struct {
desc string desc string
input string input string
@@ -338,7 +335,6 @@ func TestParser_AST(t *testing.T) {
for _, e := range examples { for _, e := range examples {
e := e e := e
t.Run(e.desc, func(t *testing.T) { t.Run(e.desc, func(t *testing.T) {
p := parser{} p := parser{}
p.Reset([]byte(e.input)) p.Reset([]byte(e.input))
p.NextExpression() p.NextExpression()
+9 -9
View File
@@ -2,8 +2,8 @@ package toml
import ( import (
"github.com/pelletier/go-toml/v2/internal/ast" "github.com/pelletier/go-toml/v2/internal/ast"
"github.com/pelletier/go-toml/v2/internal/danger"
"github.com/pelletier/go-toml/v2/internal/tracker" "github.com/pelletier/go-toml/v2/internal/tracker"
"github.com/pelletier/go-toml/v2/internal/unsafe"
) )
type strict struct { type strict struct {
@@ -15,7 +15,7 @@ type strict struct {
missing []decodeError missing []decodeError
} }
func (s *strict) EnterTable(node ast.Node) { func (s *strict) EnterTable(node *ast.Node) {
if !s.Enabled { if !s.Enabled {
return return
} }
@@ -23,7 +23,7 @@ func (s *strict) EnterTable(node ast.Node) {
s.key.UpdateTable(node) s.key.UpdateTable(node)
} }
func (s *strict) EnterArrayTable(node ast.Node) { func (s *strict) EnterArrayTable(node *ast.Node) {
if !s.Enabled { if !s.Enabled {
return return
} }
@@ -31,7 +31,7 @@ func (s *strict) EnterArrayTable(node ast.Node) {
s.key.UpdateArrayTable(node) s.key.UpdateArrayTable(node)
} }
func (s *strict) EnterKeyValue(node ast.Node) { func (s *strict) EnterKeyValue(node *ast.Node) {
if !s.Enabled { if !s.Enabled {
return return
} }
@@ -39,7 +39,7 @@ func (s *strict) EnterKeyValue(node ast.Node) {
s.key.Push(node) s.key.Push(node)
} }
func (s *strict) ExitKeyValue(node ast.Node) { func (s *strict) ExitKeyValue(node *ast.Node) {
if !s.Enabled { if !s.Enabled {
return return
} }
@@ -47,7 +47,7 @@ func (s *strict) ExitKeyValue(node ast.Node) {
s.key.Pop(node) s.key.Pop(node)
} }
func (s *strict) MissingTable(node ast.Node) { func (s *strict) MissingTable(node *ast.Node) {
if !s.Enabled { if !s.Enabled {
return return
} }
@@ -59,7 +59,7 @@ func (s *strict) MissingTable(node ast.Node) {
}) })
} }
func (s *strict) MissingField(node ast.Node) { func (s *strict) MissingField(node *ast.Node) {
if !s.Enabled { if !s.Enabled {
return return
} }
@@ -88,7 +88,7 @@ func (s *strict) Error(doc []byte) error {
return err return err
} }
func keyLocation(node ast.Node) []byte { func keyLocation(node *ast.Node) []byte {
k := node.Key() k := node.Key()
hasOne := k.Next() hasOne := k.Next()
@@ -103,5 +103,5 @@ func keyLocation(node ast.Node) []byte {
end = k.Node().Data end = k.Node().Data
} }
return unsafe.BytesRange(start, end) return danger.BytesRange(start, end)
} }
+19 -22
View File
@@ -106,7 +106,6 @@ func (d *Decoder) Decode(v interface{}) error {
type decoder struct { type decoder struct {
// Which parser instance in use for this decoding session. // Which parser instance in use for this decoding session.
// TODO: Think about removing later.
p *parser p *parser
// Flag indicating that the current expression is stashed. // Flag indicating that the current expression is stashed.
@@ -132,7 +131,7 @@ type decoder struct {
strict strict strict strict
} }
func (d *decoder) expr() ast.Node { func (d *decoder) expr() *ast.Node {
return d.p.Expression() return d.p.Expression()
} }
@@ -208,7 +207,7 @@ Rules for the unmarshal code:
- An "object" is either a struct or a map. - An "object" is either a struct or a map.
*/ */
func (d *decoder) handleRootExpression(expr ast.Node, v reflect.Value) error { func (d *decoder) handleRootExpression(expr *ast.Node, v reflect.Value) error {
var x reflect.Value var x reflect.Value
var err error var err error
@@ -533,7 +532,7 @@ func (d *decoder) handleTablePart(key ast.Iterator, v reflect.Value) (reflect.Va
return d.handleKeyPart(key, v, d.handleTable, makeMapStringInterface) return d.handleKeyPart(key, v, d.handleTable, makeMapStringInterface)
} }
func tryTextUnmarshaler(node ast.Node, v reflect.Value) (bool, error) { func (d *decoder) tryTextUnmarshaler(node *ast.Node, v reflect.Value) (bool, error) {
if v.Kind() != reflect.Struct { if v.Kind() != reflect.Struct {
return false, nil return false, nil
} }
@@ -547,9 +546,7 @@ func tryTextUnmarshaler(node ast.Node, v reflect.Value) (bool, error) {
if v.CanAddr() && v.Addr().Type().Implements(textUnmarshalerType) { if v.CanAddr() && v.Addr().Type().Implements(textUnmarshalerType) {
err := v.Addr().Interface().(encoding.TextUnmarshaler).UnmarshalText(node.Data) err := v.Addr().Interface().(encoding.TextUnmarshaler).UnmarshalText(node.Data)
if err != nil { if err != nil {
return false, fmt.Errorf("toml: error calling UnmarshalText: %w", err) return false, newDecodeError(d.p.Raw(node.Raw), "error calling UnmarshalText: %w", err)
// TODO: same as above
// return false, newDecodeError(node.Data, "error calling UnmarshalText: %w", err)
} }
return true, nil return true, nil
@@ -558,12 +555,12 @@ func tryTextUnmarshaler(node ast.Node, v reflect.Value) (bool, error) {
return false, nil return false, nil
} }
func (d *decoder) handleValue(value ast.Node, v reflect.Value) error { func (d *decoder) handleValue(value *ast.Node, v reflect.Value) error {
for v.Kind() == reflect.Ptr { for v.Kind() == reflect.Ptr {
v = initAndDereferencePointer(v) v = initAndDereferencePointer(v)
} }
ok, err := tryTextUnmarshaler(value, v) ok, err := d.tryTextUnmarshaler(value, v)
if ok || err != nil { if ok || err != nil {
return err return err
} }
@@ -592,7 +589,7 @@ func (d *decoder) handleValue(value ast.Node, v reflect.Value) error {
} }
} }
func (d *decoder) unmarshalArray(array ast.Node, v reflect.Value) error { func (d *decoder) unmarshalArray(array *ast.Node, v reflect.Value) error {
switch v.Kind() { switch v.Kind() {
case reflect.Slice: case reflect.Slice:
if v.IsNil() { if v.IsNil() {
@@ -663,7 +660,7 @@ func (d *decoder) unmarshalArray(array ast.Node, v reflect.Value) error {
return nil return nil
} }
func (d *decoder) unmarshalInlineTable(itable ast.Node, v reflect.Value) error { func (d *decoder) unmarshalInlineTable(itable *ast.Node, v reflect.Value) error {
// Make sure v is an initialized object. // Make sure v is an initialized object.
switch v.Kind() { switch v.Kind() {
case reflect.Map: case reflect.Map:
@@ -699,7 +696,7 @@ func (d *decoder) unmarshalInlineTable(itable ast.Node, v reflect.Value) error {
return nil return nil
} }
func (d *decoder) unmarshalDateTime(value ast.Node, v reflect.Value) error { func (d *decoder) unmarshalDateTime(value *ast.Node, v reflect.Value) error {
dt, err := parseDateTime(value.Data) dt, err := parseDateTime(value.Data)
if err != nil { if err != nil {
return err return err
@@ -709,7 +706,7 @@ func (d *decoder) unmarshalDateTime(value ast.Node, v reflect.Value) error {
return nil return nil
} }
func (d *decoder) unmarshalLocalDate(value ast.Node, v reflect.Value) error { func (d *decoder) unmarshalLocalDate(value *ast.Node, v reflect.Value) error {
ld, err := parseLocalDate(value.Data) ld, err := parseLocalDate(value.Data)
if err != nil { if err != nil {
return err return err
@@ -727,7 +724,7 @@ func (d *decoder) unmarshalLocalDate(value ast.Node, v reflect.Value) error {
return nil return nil
} }
func (d *decoder) unmarshalLocalDateTime(value ast.Node, v reflect.Value) error { func (d *decoder) unmarshalLocalDateTime(value *ast.Node, v reflect.Value) error {
ldt, rest, err := parseLocalDateTime(value.Data) ldt, rest, err := parseLocalDateTime(value.Data)
if err != nil { if err != nil {
return err return err
@@ -749,7 +746,7 @@ func (d *decoder) unmarshalLocalDateTime(value ast.Node, v reflect.Value) error
return nil return nil
} }
func (d *decoder) unmarshalBool(value ast.Node, v reflect.Value) error { func (d *decoder) unmarshalBool(value *ast.Node, v reflect.Value) error {
b := value.Data[0] == 't' b := value.Data[0] == 't'
switch v.Kind() { switch v.Kind() {
@@ -764,7 +761,7 @@ func (d *decoder) unmarshalBool(value ast.Node, v reflect.Value) error {
return nil return nil
} }
func (d *decoder) unmarshalFloat(value ast.Node, v reflect.Value) error { func (d *decoder) unmarshalFloat(value *ast.Node, v reflect.Value) error {
f, err := parseFloat(value.Data) f, err := parseFloat(value.Data)
if err != nil { if err != nil {
return err return err
@@ -787,7 +784,7 @@ func (d *decoder) unmarshalFloat(value ast.Node, v reflect.Value) error {
return nil return nil
} }
func (d *decoder) unmarshalInteger(value ast.Node, v reflect.Value) error { func (d *decoder) unmarshalInteger(value *ast.Node, v reflect.Value) error {
const ( const (
maxInt = int64(^uint(0) >> 1) maxInt = int64(^uint(0) >> 1)
minInt = -maxInt - 1 minInt = -maxInt - 1
@@ -865,7 +862,7 @@ func (d *decoder) unmarshalInteger(value ast.Node, v reflect.Value) error {
return err return err
} }
func (d *decoder) unmarshalString(value ast.Node, v reflect.Value) error { func (d *decoder) unmarshalString(value *ast.Node, v reflect.Value) error {
var err error var err error
switch v.Kind() { switch v.Kind() {
@@ -874,13 +871,13 @@ func (d *decoder) unmarshalString(value ast.Node, v reflect.Value) error {
case reflect.Interface: case reflect.Interface:
v.Set(reflect.ValueOf(string(value.Data))) v.Set(reflect.ValueOf(string(value.Data)))
default: default:
err = fmt.Errorf("toml: cannot store TOML string into a Go %s", v.Kind()) err = newDecodeError(d.p.Raw(value.Raw), "cannot store TOML string into a Go %s", v.Kind())
} }
return err return err
} }
func (d *decoder) handleKeyValue(expr ast.Node, v reflect.Value) (reflect.Value, error) { func (d *decoder) handleKeyValue(expr *ast.Node, v reflect.Value) (reflect.Value, error) {
d.strict.EnterKeyValue(expr) d.strict.EnterKeyValue(expr)
v, err := d.handleKeyValueInner(expr.Key(), expr.Value(), v) v, err := d.handleKeyValueInner(expr.Key(), expr.Value(), v)
@@ -894,7 +891,7 @@ func (d *decoder) handleKeyValue(expr ast.Node, v reflect.Value) (reflect.Value,
return v, err return v, err
} }
func (d *decoder) handleKeyValueInner(key ast.Iterator, value ast.Node, v reflect.Value) (reflect.Value, error) { func (d *decoder) handleKeyValueInner(key ast.Iterator, value *ast.Node, v reflect.Value) (reflect.Value, error) {
if key.Next() { if key.Next() {
// Still scoping the key // Still scoping the key
return d.handleKeyValuePart(key, value, v) return d.handleKeyValuePart(key, value, v)
@@ -904,7 +901,7 @@ func (d *decoder) handleKeyValueInner(key ast.Iterator, value ast.Node, v reflec
return reflect.Value{}, d.handleValue(value, v) return reflect.Value{}, d.handleValue(value, v)
} }
func (d *decoder) handleKeyValuePart(key ast.Iterator, value ast.Node, v reflect.Value) (reflect.Value, error) { func (d *decoder) handleKeyValuePart(key ast.Iterator, value *ast.Node, v reflect.Value) (reflect.Value, error) {
// contains the replacement for v // contains the replacement for v
var rv reflect.Value var rv reflect.Value
+48
View File
@@ -287,6 +287,54 @@ func TestUnmarshal(t *testing.T) {
} }
}, },
}, },
{
desc: "local datetime into time.Time",
input: `a = 1979-05-27T00:32:00`,
gen: func() test {
type doc struct {
A time.Time
}
return test{
target: &doc{},
expected: &doc{
A: time.Date(1979, 5, 27, 0, 32, 0, 0, time.Local),
},
}
},
},
{
desc: "local datetime into interface",
input: `a = 1979-05-27T00:32:00`,
gen: func() test {
type doc struct {
A interface{}
}
return test{
target: &doc{},
expected: &doc{
A: toml.LocalDateTimeOf(time.Date(1979, 5, 27, 0, 32, 0, 0, time.Local)),
},
}
},
},
{
desc: "local date into interface",
input: `a = 1979-05-27`,
gen: func() test {
type doc struct {
A interface{}
}
return test{
target: &doc{},
expected: &doc{
A: toml.LocalDateOf(time.Date(1979, 5, 27, 0, 32, 0, 0, time.Local)),
},
}
},
},
{ {
desc: "issue 475 - space between dots in key", desc: "issue 475 - space between dots in key",
input: `fruit. color = "yellow" input: `fruit. color = "yellow"