Files
go-toml/unstable/ast.go
T
Claude 5b6828661c Support Unmarshaler interface for tables and array tables (#873)
Extend the unstable.Unmarshaler interface support to work with tables
and array tables, not just single values.

When a type implementing unstable.Unmarshaler is the target of a table
(e.g., [table] or [[array]]), the UnmarshalTOML method receives a
synthetic InlineTable node containing all the key-value pairs belonging
to that table.

Key changes:
- Add handleKeyValuesUnmarshaler to collect and process table content
- Add copyExpressionNodes to deep-copy AST nodes for synthetic tables
- Add helper functions in unstable/ast.go for node manipulation
- Update documentation for EnableUnmarshalerInterface
- Add comprehensive tests for table and array table unmarshaling
2026-01-26 21:30:14 +00:00

192 lines
4.8 KiB
Go

package unstable
import (
"errors"
"fmt"
)
// Iterator over a sequence of nodes.
//
// Starts uninitialized, you need to call Next() first.
//
// For example:
//
// it := n.Children()
// for it.Next() {
// n := it.Node()
// // do something with n
// }
type Iterator struct {
nodes *[]Node
idx int32
started bool
}
// Next moves the iterator forward and returns true if points to a
// node, false otherwise.
func (c *Iterator) Next() bool {
if c.nodes == nil {
return false
}
if !c.started {
c.started = true
} else if c.idx >= 0 {
c.idx = (*c.nodes)[c.idx].next
}
return c.idx >= 0 && int(c.idx) < len(*c.nodes)
}
// IsLast returns true if the current node of the iterator is the last
// one. Subsequent calls to Next() will return false.
func (c *Iterator) IsLast() bool {
return c.nodes == nil || c.idx < 0 || (*c.nodes)[c.idx].next < 0
}
// Node returns a pointer to the node pointed at by the iterator.
func (c *Iterator) Node() *Node {
if c.nodes == nil || c.idx < 0 {
return nil
}
n := &(*c.nodes)[c.idx]
n.nodes = c.nodes
return n
}
// Node in a TOML expression AST.
//
// Depending on Kind, its sequence of children should be interpreted
// differently.
//
// - Array have one child per element in the array.
// - InlineTable have one child per key-value in the table (each of kind
// InlineTable).
// - KeyValue have at least two children. The first one is the value. The rest
// make a potentially dotted key.
// - Table and ArrayTable's children represent a dotted key (same as
// KeyValue, but without the first node being the value).
//
// When relevant, Raw describes the range of bytes this node is referring to in
// the input document. Use Parser.Raw() to retrieve the actual bytes.
type Node struct {
Kind Kind
Raw Range // Raw bytes from the input.
Data []byte // Node value (either allocated or referencing the input).
// Absolute indices into the backing nodes slice. -1 means none.
next int32
child int32
// Reference to the backing nodes slice for navigation.
nodes *[]Node
}
// Range of bytes in the document.
type Range struct {
Offset uint32
Length uint32
}
// Next returns a pointer to the next node, or nil if there is no next node.
func (n *Node) Next() *Node {
if n.next < 0 {
return nil
}
next := &(*n.nodes)[n.next]
next.nodes = n.nodes
return next
}
// Child returns a pointer to the first child node of this node. Other children
// can be accessed calling Next on the first child. Returns nil if this Node
// has no child.
func (n *Node) Child() *Node {
if n.child < 0 {
return nil
}
child := &(*n.nodes)[n.child]
child.nodes = n.nodes
return child
}
// Valid returns true if the node's kind is set (not to Invalid).
func (n *Node) Valid() bool {
return n != nil
}
// Key returns the children nodes making the Key on a supported node. Panics
// otherwise. They are guaranteed to be all be of the Kind Key. A simple key
// would return just one element.
func (n *Node) Key() Iterator {
switch n.Kind {
case KeyValue:
child := n.child
if child < 0 {
panic(errors.New("KeyValue should have at least two children"))
}
valueNode := &(*n.nodes)[child]
return Iterator{nodes: n.nodes, idx: valueNode.next}
case Table, ArrayTable:
return Iterator{nodes: n.nodes, idx: n.child}
default:
panic(fmt.Errorf("Key() is not supported on a %s", n.Kind))
}
}
// Value returns a pointer to the value node of a KeyValue.
// Guaranteed to be non-nil. Panics if not called on a KeyValue node,
// or if the Children are malformed.
func (n *Node) Value() *Node {
return n.Child()
}
// Children returns an iterator over a node's children.
func (n *Node) Children() Iterator {
return Iterator{nodes: n.nodes, idx: n.child}
}
// SetNodeSlice sets the backing nodes slice for a node.
// This is used when building synthetic nodes.
func SetNodeSlice(n *Node, nodes *[]Node) {
n.nodes = nodes
}
// SetNodeChild sets the child index for a node.
// This is used when building synthetic nodes.
func SetNodeChild(n *Node, child int32) {
n.child = child
}
// SetNodeNext sets the next sibling index for a node.
// This is used when building synthetic nodes.
func SetNodeNext(n *Node, next int32) {
n.next = next
}
// GetNodeChild returns the child index for a node.
// This is used when copying nodes.
func GetNodeChild(n *Node) int32 {
return n.child
}
// GetNodeNext returns the next sibling index for a node.
// This is used when copying nodes.
func GetNodeNext(n *Node) int32 {
return n.next
}
// GetNodeIndex returns the index of node n in the backing slice,
// using relativeTo's nodes slice as reference.
// Returns -1 if n is not in the slice.
func GetNodeIndex(n *Node, relativeTo *Node) int32 {
if relativeTo.nodes == nil || n == nil {
return -1
}
nodes := *relativeTo.nodes
for i := range nodes {
if &nodes[i] == n {
return int32(i)
}
}
return -1
}