Single Fetcher.Fetch on the Guard that holds DenyPrefixes. skipTlsVerification is ignored. No 502. keys add writes the secret only to --out. Release binary has no -test-allow-prefix.
2397 lines
69 KiB
Go
2397 lines
69 KiB
Go
package toml
|
|
|
|
import (
|
|
"encoding"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"math"
|
|
"reflect"
|
|
"strconv"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/pelletier/go-toml/v2/internal/tracker"
|
|
"github.com/pelletier/go-toml/v2/unstable"
|
|
)
|
|
|
|
// decoderPool recycles decoders (and their internal buffers: parser arena,
|
|
// seen-tracker entries, scratch buffers) across calls to Unmarshal and
|
|
// Decode.
|
|
var decoderPool = sync.Pool{
|
|
New: func() interface{} { return &decoder{} },
|
|
}
|
|
|
|
func getDecoder(strictMode, unmarshalerInterface bool) *decoder {
|
|
d := decoderPool.Get().(*decoder)
|
|
d.reset()
|
|
d.strict.Enabled = strictMode
|
|
d.unmarshalerInterface = unmarshalerInterface
|
|
return d
|
|
}
|
|
|
|
func putDecoder(d *decoder) {
|
|
decoderPool.Put(d)
|
|
}
|
|
|
|
// reset clears the per-document state of the decoder, keeping the allocated
|
|
// buffers for reuse.
|
|
func (d *decoder) reset() {
|
|
d.seen.Reset()
|
|
d.tableKey = d.tableKey[:0]
|
|
d.skipUntilTable = false
|
|
d.path = d.path[:0]
|
|
d.captures = d.captures[:0]
|
|
d.captureIdx = -1
|
|
d.segIdx = d.segIdx[:0]
|
|
// Reuse the array-table counter slots across documents instead of
|
|
// deleting them: a zeroed slot is indistinguishable from an absent one,
|
|
// and keeping it alive means setArrayCount does not have to allocate a new
|
|
// *int every time the same path reappears. A safety valve bounds the table
|
|
// for adversarial inputs that introduce unboundedly many distinct paths.
|
|
if len(d.arrayCounts) > 1<<14 {
|
|
d.arrayCounts = nil
|
|
} else {
|
|
for _, p := range d.arrayCounts {
|
|
*p = 0
|
|
}
|
|
}
|
|
d.tableTarget = reflect.Value{}
|
|
d.tableTargetValid = false
|
|
d.tableFlush = d.tableFlush[:0]
|
|
d.tableParentSlot = slotWriter{}
|
|
d.keyParts = d.keyParts[:0]
|
|
d.strict.Reset()
|
|
}
|
|
|
|
// Unmarshal deserializes a TOML document into a Go value.
|
|
//
|
|
// It is a shortcut for Decoder.Decode() with the default options.
|
|
func Unmarshal(data []byte, v interface{}) error {
|
|
d := getDecoder(false, false)
|
|
err := d.unmarshal(data, v)
|
|
putDecoder(d)
|
|
return err
|
|
}
|
|
|
|
// Decoder reads and decode a TOML document from an input stream.
|
|
type Decoder struct {
|
|
// input
|
|
r io.Reader
|
|
|
|
// global settings
|
|
strict bool
|
|
|
|
// toggles unmarshaler interface
|
|
unmarshalerInterface bool
|
|
}
|
|
|
|
// NewDecoder creates a new Decoder that will read from r.
|
|
func NewDecoder(r io.Reader) *Decoder {
|
|
return &Decoder{r: r}
|
|
}
|
|
|
|
// DisallowUnknownFields causes the Decoder to return an error when the
|
|
// destination is a struct and the input contains a key that does not match a
|
|
// non-ignored field.
|
|
//
|
|
// In that case, the Decoder returns a StrictMissingError that can be used to
|
|
// retrieve the individual errors as well as generate a human readable
|
|
// description of the missing fields.
|
|
func (d *Decoder) DisallowUnknownFields() *Decoder {
|
|
d.strict = true
|
|
return d
|
|
}
|
|
|
|
// EnableUnmarshalerInterface allows to enable unmarshaler interface.
|
|
//
|
|
// With this feature enabled, types implementing the unstable.Unmarshaler
|
|
// interface can be decoded from any structure of the document. It allows types
|
|
// that don't have a straightforward TOML representation to provide their own
|
|
// decoding logic.
|
|
//
|
|
// The UnmarshalTOML method receives raw TOML bytes:
|
|
// - For single values: the raw value bytes (e.g., `"hello"` for a string)
|
|
// - For tables: all key-value lines belonging to that table
|
|
// - For inline tables/arrays: the raw bytes of the inline structure
|
|
//
|
|
// The unstable.RawMessage type can be used to capture raw TOML bytes for
|
|
// later processing, similar to json.RawMessage.
|
|
//
|
|
// *Unstable:* This method does not follow the compatibility guarantees of
|
|
// semver. It can be changed or removed without a new major version being
|
|
// issued.
|
|
func (d *Decoder) EnableUnmarshalerInterface() *Decoder {
|
|
d.unmarshalerInterface = true
|
|
return d
|
|
}
|
|
|
|
// Decode the whole content of r into v.
|
|
//
|
|
// By default, values in the document that don't exist in the target Go value
|
|
// are ignored. See Decoder.DisallowUnknownFields() to change this behavior.
|
|
//
|
|
// When a TOML local date, time, or date-time is decoded into a time.Time, its
|
|
// value is represented in time.Local timezone. Otherwise the appropriate Local*
|
|
// structure is used. For time values, precision up to the nanosecond is
|
|
// supported by truncating extra digits.
|
|
//
|
|
// Empty tables decoded in an interface{} create an empty initialized
|
|
// map[string]interface{}.
|
|
//
|
|
// Types implementing the encoding.TextUnmarshaler interface are decoded from a
|
|
// TOML string.
|
|
//
|
|
// When decoding a number, go-toml will return an error if the number is out of
|
|
// bounds for the target type (which includes negative numbers when decoding
|
|
// into an unsigned int).
|
|
//
|
|
// If an error occurs while decoding the content of the document, this function
|
|
// returns a toml.DecodeError, providing context about the issue. When using
|
|
// strict mode and a field is missing, a `toml.StrictMissingError` is
|
|
// returned. In any other case, this function returns a standard Go error.
|
|
//
|
|
// # Type mapping
|
|
//
|
|
// List of supported TOML types and their associated accepted Go types:
|
|
//
|
|
// String -> string
|
|
// Integer -> uint*, int*, depending on size
|
|
// Float -> float*, depending on size
|
|
// Boolean -> bool
|
|
// Offset Date-Time -> time.Time
|
|
// Local Date-time -> LocalDateTime, time.Time
|
|
// Local Date -> LocalDate, time.Time
|
|
// Local Time -> LocalTime, time.Time
|
|
// Array -> slice and array, depending on elements types
|
|
// Table -> map and struct
|
|
// Inline Table -> same as Table
|
|
// Array of Tables -> same as Array and Table
|
|
func (d *Decoder) Decode(v interface{}) error {
|
|
b, err := io.ReadAll(d.r)
|
|
if err != nil {
|
|
return fmt.Errorf("toml: %w", err)
|
|
}
|
|
|
|
dec := getDecoder(d.strict, d.unmarshalerInterface)
|
|
err = dec.unmarshal(b, v)
|
|
putDecoder(dec)
|
|
return err
|
|
}
|
|
|
|
// pathPart is one part of the key path leading to a value. Parts that come
|
|
// from the current table header only carry a name; parts that come from the
|
|
// key of the current key-value expression also carry the AST node, and their
|
|
// name is materialized lazily to avoid allocations.
|
|
type pathPart struct {
|
|
name string
|
|
node *unstable.Node
|
|
}
|
|
|
|
// bytes returns the raw bytes of the key part.
|
|
func (p *pathPart) bytes() []byte {
|
|
if p.node != nil {
|
|
return p.node.Data
|
|
}
|
|
return []byte(p.name)
|
|
}
|
|
|
|
// str returns the key part as a string, possibly allocating.
|
|
func (p *pathPart) str() string {
|
|
if p.node != nil {
|
|
return string(p.node.Data)
|
|
}
|
|
return p.name
|
|
}
|
|
|
|
// rawCapture accumulates the raw bytes fed to a type implementing
|
|
// unstable.Unmarshaler for a table target. The target is identified by the
|
|
// parts of its key and the array-table indexes in effect when the capture
|
|
// was created, so that it can be located again once the whole document has
|
|
// been processed (the address of the target may change as slices grow).
|
|
type rawCapture struct {
|
|
names []string
|
|
// indexes[i] is the index to use when reaching a slice or array right
|
|
// before consuming names[i]. indexes[len(names)] is the index of the
|
|
// element when the target is an element of an array table. -1 when not
|
|
// relevant.
|
|
indexes []int
|
|
buf []byte
|
|
}
|
|
|
|
type decoder struct {
|
|
p unstable.Parser
|
|
|
|
// strict mode
|
|
strict strict
|
|
|
|
// toggles unmarshaler interface
|
|
unmarshalerInterface bool
|
|
|
|
// tracks the duplicate and type consistency of the keys
|
|
seen tracker.SeenTracker
|
|
|
|
// path of the current table header, as copied strings
|
|
tableKey []string
|
|
|
|
// true when the expressions under the current table header cannot be
|
|
// stored anywhere and should be skipped
|
|
skipUntilTable bool
|
|
|
|
// scratch buffer for the key path of the current expression
|
|
path []pathPart
|
|
|
|
// raw captures for the unmarshaler interface, in order of first
|
|
// appearance. captureIdx is the index of the capture the current table
|
|
// belongs to, or -1.
|
|
captures []rawCapture
|
|
captureIdx int
|
|
|
|
// segIdx[i] records the array element index used when traversing a
|
|
// slice or array right before consuming the i-th part of the current
|
|
// table key. Reset for each table expression.
|
|
segIdx []int
|
|
|
|
// arrayCounts tracks the number of elements appended to fixed-size
|
|
// arrays used as array tables, keyed by the NUL-joined key parts.
|
|
// Values are pointer slots so that updating an existing path does not
|
|
// allocate a new key string.
|
|
arrayCounts map[string]*int
|
|
|
|
// Cached target of the current table, so that key-values do not need to
|
|
// walk the document structure from the root for every expression.
|
|
// tableFlush holds the write-backs to perform when leaving the table
|
|
// (for targets reached through map values, which are copies).
|
|
// tableParentSlot stores a replacement of the target itself (e.g. a nil
|
|
// map that was allocated) into its parent.
|
|
tableTarget reflect.Value
|
|
tableTargetValid bool
|
|
tableFlush []flushOp
|
|
tableParentSlot slotWriter
|
|
|
|
// strKey is a reusable string value used as map key, so that map
|
|
// operations with string keys do not need to allocate a boxed key for
|
|
// every access. It must be refreshed with stringMapKey immediately
|
|
// before each use: any recursive call may overwrite it.
|
|
strKey reflect.Value
|
|
|
|
// interned de-duplicates key strings: documents repeat the same keys
|
|
// over and over, and the table survives pooling, so repeated decodes
|
|
// of similar documents stop allocating key strings altogether.
|
|
interned map[string]string
|
|
|
|
// pathScratch is the buffer used by joinPath.
|
|
pathScratch []byte
|
|
|
|
// keyParts is the reusable buffer holding the decoded parts of the key of
|
|
// the current expression in the fused generic decode path.
|
|
keyParts [][]byte
|
|
}
|
|
|
|
// slotWriter remembers how to store a value at some location of the target
|
|
// structure. Implemented as a struct instead of a closure to avoid
|
|
// allocations.
|
|
type slotWriter struct {
|
|
kind uint8 // 0: none, 1: slot.Set, 2: m.SetMapIndex(k, ...), 3: m.SetMapIndex(string key ks, ...)
|
|
slot reflect.Value
|
|
m reflect.Value
|
|
k reflect.Value
|
|
ks string
|
|
}
|
|
|
|
func (d *decoder) storeSlot(s *slotWriter, nv reflect.Value) {
|
|
switch s.kind {
|
|
case 1:
|
|
if s.slot.CanSet() {
|
|
s.slot.Set(nv)
|
|
}
|
|
case 2:
|
|
s.m.SetMapIndex(s.k, nv)
|
|
case 3:
|
|
s.m.SetMapIndex(d.stringMapKey(s.ks), nv)
|
|
}
|
|
}
|
|
|
|
// flushOp stores val using w when the table is flushed.
|
|
type flushOp struct {
|
|
w slotWriter
|
|
val reflect.Value
|
|
}
|
|
|
|
// flushTable performs the pending write-backs of the cached table target, in
|
|
// reverse order so that inner copies land before their parents are stored.
|
|
func (d *decoder) flushTable() {
|
|
for i := len(d.tableFlush) - 1; i >= 0; i-- {
|
|
d.storeSlot(&d.tableFlush[i].w, d.tableFlush[i].val)
|
|
}
|
|
d.tableFlush = d.tableFlush[:0]
|
|
d.tableTargetValid = false
|
|
d.tableParentSlot = slotWriter{}
|
|
d.tableTarget = reflect.Value{}
|
|
}
|
|
|
|
// intern returns the string corresponding to the given bytes, reusing a
|
|
// previous allocation when the same key has been seen before.
|
|
func (d *decoder) intern(b []byte) string {
|
|
if s, ok := d.interned[string(b)]; ok { // does not allocate
|
|
return s
|
|
}
|
|
if d.interned == nil {
|
|
d.interned = make(map[string]string, 64)
|
|
} else if len(d.interned) >= 1<<14 {
|
|
// Safety valve for adversarial inputs: do not let the table grow
|
|
// without bounds.
|
|
for k := range d.interned {
|
|
delete(d.interned, k)
|
|
}
|
|
}
|
|
s := string(b)
|
|
d.interned[s] = s
|
|
return s
|
|
}
|
|
|
|
// partString returns the name of a path part, interning it when it comes
|
|
// from the document.
|
|
func (d *decoder) partString(p *pathPart) string {
|
|
if p.node != nil {
|
|
return d.intern(p.node.Data)
|
|
}
|
|
return p.name
|
|
}
|
|
|
|
// stringMapKey returns a reflect.Value holding the given string, reusing the
|
|
// same allocation every time. The result must be used (the map operation
|
|
// performed) before any recursive call, which may overwrite the buffer.
|
|
func (d *decoder) stringMapKey(s string) reflect.Value {
|
|
if !d.strKey.IsValid() {
|
|
d.strKey = reflect.New(stringType).Elem()
|
|
}
|
|
d.strKey.SetString(s)
|
|
return d.strKey
|
|
}
|
|
|
|
// joinPath builds the NUL-joined representation of a key path in the
|
|
// decoder's scratch buffer. The result is only valid until the next call.
|
|
func (d *decoder) joinPath(parts []string) []byte {
|
|
d.pathScratch = d.pathScratch[:0]
|
|
for i, p := range parts {
|
|
if i > 0 {
|
|
d.pathScratch = append(d.pathScratch, 0)
|
|
}
|
|
d.pathScratch = append(d.pathScratch, p...)
|
|
}
|
|
return d.pathScratch
|
|
}
|
|
|
|
// arrayCount returns the number of elements appended so far to the array
|
|
// table at the given path.
|
|
func (d *decoder) arrayCount(key []byte) int {
|
|
if d.arrayCounts == nil {
|
|
return 0
|
|
}
|
|
if p := d.arrayCounts[string(key)]; p != nil { // does not allocate
|
|
return *p
|
|
}
|
|
return 0
|
|
}
|
|
|
|
func (d *decoder) setArrayCount(key []byte, n int) {
|
|
if d.arrayCounts == nil {
|
|
d.arrayCounts = map[string]*int{}
|
|
}
|
|
if p := d.arrayCounts[string(key)]; p != nil { // does not allocate
|
|
*p = n
|
|
return
|
|
}
|
|
v := n
|
|
d.arrayCounts[string(key)] = &v
|
|
}
|
|
|
|
// resetChildArrayCounts forgets the counts of all the array tables under
|
|
// the given path, so that a new element starts fresh.
|
|
func (d *decoder) resetChildArrayCounts(key []byte) {
|
|
if len(d.arrayCounts) == 0 {
|
|
return
|
|
}
|
|
for k, p := range d.arrayCounts {
|
|
// Prefix match without building the prefix string: same bytes as
|
|
// key, followed by the NUL separator.
|
|
if len(k) > len(key) && k[len(key)] == 0 && k[:len(key)] == string(key) {
|
|
// Zero instead of delete: the next element of the parent table
|
|
// will reuse the slot without allocating a new key.
|
|
*p = 0
|
|
}
|
|
}
|
|
}
|
|
|
|
func (d *decoder) typeMismatchError(toml string, target reflect.Type, highlight []byte) error {
|
|
return &typeMismatchError{
|
|
toml: toml,
|
|
target: target,
|
|
highlight: highlight,
|
|
}
|
|
}
|
|
|
|
type typeMismatchError struct {
|
|
toml string
|
|
target reflect.Type
|
|
highlight []byte
|
|
// key is the TOML key being processed when the mismatch occurred. It is
|
|
// populated lazily as the error propagates back up to the key-value
|
|
// handler (see contextualizeError).
|
|
key Key
|
|
}
|
|
|
|
func (e *typeMismatchError) Error() string {
|
|
return fmt.Sprintf("cannot decode TOML %s into %s", e.toml, e.target)
|
|
}
|
|
|
|
// contextualizeError attaches the TOML key currently being processed to errors
|
|
// raised while decoding a key-value expression, so that DecodeError.Key()
|
|
// reports the offending key (e.g. on type mismatch errors). The current key is
|
|
// reconstructed from d.path; when the table target is cached, d.path holds only
|
|
// the key-value parts, so the table key prefix is prepended. This only runs on
|
|
// the error path and adds no cost to successful decodes.
|
|
func (d *decoder) contextualizeError(err error, withTableKey bool) error {
|
|
var mm *typeMismatchError
|
|
if errors.As(err, &mm) {
|
|
if mm.key == nil {
|
|
mm.key = d.currentKey(withTableKey)
|
|
}
|
|
return err
|
|
}
|
|
var perr *unstable.ParserError
|
|
if errors.As(err, &perr) {
|
|
if perr.Key == nil {
|
|
perr.Key = d.currentKey(withTableKey)
|
|
}
|
|
}
|
|
return err
|
|
}
|
|
|
|
// currentKey reconstructs the full TOML key being processed from the decoder's
|
|
// path. When withTableKey is true, d.path contains only the key-value parts
|
|
// (the table target is cached) and the table key is prepended.
|
|
func (d *decoder) currentKey(withTableKey bool) Key {
|
|
n := len(d.path)
|
|
if withTableKey {
|
|
n += len(d.tableKey)
|
|
}
|
|
key := make(Key, 0, n)
|
|
if withTableKey {
|
|
key = append(key, d.tableKey...)
|
|
}
|
|
for i := range d.path {
|
|
key = append(key, d.path[i].str())
|
|
}
|
|
return key
|
|
}
|
|
|
|
func (d *decoder) unmarshal(data []byte, v interface{}) error {
|
|
r := reflect.ValueOf(v)
|
|
if r.Kind() != reflect.Ptr {
|
|
return fmt.Errorf("toml: decoding can only be performed into a pointer, not %s", r.Kind())
|
|
}
|
|
if r.IsNil() {
|
|
return errors.New("toml: decoding pointer target cannot be nil")
|
|
}
|
|
|
|
root := r.Elem()
|
|
|
|
d.captureIdx = -1
|
|
d.p.Reset(data)
|
|
|
|
// Fully generic targets (interface{} or map[string]interface{}) are
|
|
// decoded straight into native Go maps and slices, with no reflection on
|
|
// the document structure at all. This covers the common "decode arbitrary
|
|
// TOML into a map" case, including every standard benchmark dataset.
|
|
if !d.unmarshalerInterface {
|
|
if k := root.Kind(); k == reflect.Interface || (k == reflect.Map && root.Type() == mapStringInterfaceType) {
|
|
return d.unmarshalFused(root, data)
|
|
}
|
|
}
|
|
|
|
// When the root target itself implements the Unmarshaler interface, the
|
|
// whole document decodes into it. Open a capture spanning the entire
|
|
// document up front: the top-level key-values then flow through the
|
|
// existing capture branch and any tables attach to it via resumeCapture,
|
|
// so UnmarshalTOML receives the assembled document exactly once.
|
|
if d.unmarshalerInterface && hasUnmarshaler(root) {
|
|
d.startRootCapture()
|
|
}
|
|
|
|
for d.p.NextExpression() {
|
|
err := d.handleRootExpression(d.p.Expression(), root)
|
|
if err != nil {
|
|
return d.wrapError(data, err)
|
|
}
|
|
}
|
|
if err := d.p.Error(); err != nil {
|
|
var perr *unstable.ParserError
|
|
if errors.As(err, &perr) {
|
|
return wrapDecodeError(data, perr)
|
|
}
|
|
return err
|
|
}
|
|
|
|
d.flushTable()
|
|
|
|
// Deliver the accumulated raw documents to the unmarshaler-interface
|
|
// targets.
|
|
for i := range d.captures {
|
|
nv, err := d.resolveCapture(root, &d.captures[i], 0, false)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if nv.IsValid() {
|
|
root.Set(nv)
|
|
}
|
|
}
|
|
|
|
// An empty document into a generic target still initializes it.
|
|
switch root.Kind() {
|
|
case reflect.Map:
|
|
if root.IsNil() {
|
|
root.Set(reflect.MakeMap(root.Type()))
|
|
}
|
|
case reflect.Interface:
|
|
if root.IsNil() {
|
|
root.Set(reflect.ValueOf(map[string]interface{}{}))
|
|
}
|
|
default:
|
|
}
|
|
|
|
return d.strict.Error(data)
|
|
}
|
|
|
|
// setAnyKey assigns the value of a key-value into the native map m, following
|
|
// the (possibly dotted) key and creating intermediate maps as needed.
|
|
func (d *decoder) setAnyKey(m map[string]interface{}, key unstable.Iterator, value *unstable.Node) error {
|
|
cur := m
|
|
for key.Next() {
|
|
name := d.intern(key.Node().Data)
|
|
if key.IsLast() {
|
|
av, err := d.decodeAny(value)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
cur[name] = av
|
|
return nil
|
|
}
|
|
cur = d.anyChildTable(cur, name)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// anyChildTable returns the child table at name within cur, creating it if
|
|
// absent and descending into the current (last) element when an array table
|
|
// occupies the slot. A non-container in the slot cannot occur for a document
|
|
// the seen-tracker has accepted.
|
|
func (d *decoder) anyChildTable(cur map[string]interface{}, name string) map[string]interface{} {
|
|
switch v := cur[name].(type) {
|
|
case map[string]interface{}:
|
|
return v
|
|
case []interface{}:
|
|
if len(v) > 0 {
|
|
if last, ok := v[len(v)-1].(map[string]interface{}); ok {
|
|
return last
|
|
}
|
|
}
|
|
}
|
|
nm := map[string]interface{}{}
|
|
cur[name] = nm
|
|
return nm
|
|
}
|
|
|
|
// wrapError gives document context to errors generated while processing an
|
|
// expression.
|
|
func (d *decoder) wrapError(data []byte, err error) error {
|
|
var perr *unstable.ParserError
|
|
if errors.As(err, &perr) {
|
|
return wrapDecodeError(data, perr)
|
|
}
|
|
var mm *typeMismatchError
|
|
if errors.As(err, &mm) {
|
|
return wrapDecodeError(data, &unstable.ParserError{
|
|
Highlight: mm.highlight,
|
|
Message: mm.Error(),
|
|
Key: mm.key,
|
|
})
|
|
}
|
|
return err
|
|
}
|
|
|
|
// keyedError turns a bare error raised while processing an expression into a
|
|
// ParserError carrying the position and key of that expression, so that it is
|
|
// reported as a DecodeError with context. It is used for the errors returned
|
|
// by SeenTracker.CheckExpression (redefinitions and duplicate keys, see issue
|
|
// #668) and for table placement errors raised by walkTable (see issue #806).
|
|
//
|
|
// The highlight spans the expression's key. Unlike Node.Raw, key nodes always
|
|
// carry a Raw range, so this works for tables and array tables too (whose own
|
|
// Raw range is not set by the parser). For a duplicate detected inside an
|
|
// inline table, node is the enclosing key-value expression, so the error
|
|
// points at that expression's key.
|
|
func (d *decoder) keyedError(node *unstable.Node, err error) error {
|
|
if err == nil {
|
|
return nil
|
|
}
|
|
|
|
var key Key
|
|
var start, end unstable.Range
|
|
it := node.Key()
|
|
for it.Next() {
|
|
n := it.Node()
|
|
key = append(key, string(n.Data))
|
|
if len(key) == 1 {
|
|
start = n.Raw
|
|
}
|
|
end = n.Raw
|
|
}
|
|
|
|
var highlight []byte
|
|
if len(key) > 0 {
|
|
highlight = d.p.Raw(unstable.Range{
|
|
Offset: start.Offset,
|
|
Length: end.Offset + end.Length - start.Offset,
|
|
})
|
|
}
|
|
|
|
return &unstable.ParserError{
|
|
Highlight: highlight,
|
|
Message: strings.TrimPrefix(err.Error(), "toml: "),
|
|
Key: key,
|
|
}
|
|
}
|
|
|
|
func (d *decoder) handleRootExpression(expr *unstable.Node, root reflect.Value) error {
|
|
first, err := d.seen.CheckExpression(expr)
|
|
if err != nil {
|
|
return d.keyedError(expr, err)
|
|
}
|
|
|
|
switch expr.Kind {
|
|
case unstable.KeyValue:
|
|
if d.skipUntilTable {
|
|
return nil
|
|
}
|
|
if d.captureIdx >= 0 {
|
|
d.captureKeyValue(expr)
|
|
return nil
|
|
}
|
|
return d.handleKeyValueExpression(expr, root)
|
|
case unstable.Table:
|
|
d.flushTable()
|
|
d.skipUntilTable = false
|
|
d.captureIdx = -1
|
|
d.strict.EnterTable(expr)
|
|
return d.handleTableExpression(expr, root, false, first)
|
|
case unstable.ArrayTable:
|
|
d.flushTable()
|
|
d.skipUntilTable = false
|
|
d.captureIdx = -1
|
|
d.strict.EnterTable(expr)
|
|
return d.handleTableExpression(expr, root, true, first)
|
|
default:
|
|
return unstable.NewParserError(expr.Data, "unsupported expression kind %s", expr.Kind)
|
|
}
|
|
}
|
|
|
|
// updateTableKey copies the parts of the key of a table expression into
|
|
// tableKey.
|
|
func (d *decoder) updateTableKey(expr *unstable.Node) {
|
|
d.tableKey = d.tableKey[:0]
|
|
it := expr.Key()
|
|
for it.Next() {
|
|
d.tableKey = append(d.tableKey, d.intern(it.Node().Data))
|
|
}
|
|
}
|
|
|
|
func (d *decoder) handleTableExpression(expr *unstable.Node, root reflect.Value, isArrayTable bool, first bool) error {
|
|
d.updateTableKey(expr)
|
|
|
|
// Check whether this table belongs to an exisiting raw capture (split
|
|
// tables, or children of a table assigned to an Unmarshaler).
|
|
if d.unmarshalerInterface {
|
|
if d.resumeCapture(expr) {
|
|
return nil
|
|
}
|
|
}
|
|
|
|
// Reset the per-segment array indexes.
|
|
d.segIdx = d.segIdx[:0]
|
|
for i := 0; i <= len(d.tableKey); i++ {
|
|
d.segIdx = append(d.segIdx, -1)
|
|
}
|
|
|
|
return d.walkTable(root, expr, isArrayTable, first)
|
|
}
|
|
|
|
// newContainerElem returns a fresh element for a slice of the given element
|
|
// type. Plain interface elements start out as an empty table.
|
|
func newContainerElem(et reflect.Type) reflect.Value {
|
|
if et == interfaceType {
|
|
return reflect.ValueOf(map[string]interface{}{})
|
|
}
|
|
return reflect.New(et).Elem()
|
|
}
|
|
|
|
// walkTable processes a [table] or [[array table]] header: it creates the
|
|
// intermediate containers, appends array-table elements, applies the strict
|
|
// policy, registers unmarshaler-interface captures, and caches the target
|
|
// container so that the key-values that follow are stored directly.
|
|
//
|
|
// Map values are not addressable: when one needs in-place mutations (struct
|
|
// or array values), a copy is made and registered to be stored back when the
|
|
// table changes (see flushTable). Maps and slices are references and are
|
|
// traversed without copies.
|
|
func (d *decoder) walkTable(root reflect.Value, expr *unstable.Node, isArrayTable bool, first bool) error {
|
|
v := root
|
|
pf := slotWriter{kind: 1, slot: root}
|
|
idx := 0
|
|
|
|
walk:
|
|
for {
|
|
// Dereference pointers in place.
|
|
for v.Kind() == reflect.Ptr {
|
|
if v.IsNil() {
|
|
v.Set(reflect.New(v.Type().Elem()))
|
|
}
|
|
elem := v.Elem()
|
|
pf = slotWriter{kind: 1, slot: elem}
|
|
v = elem
|
|
}
|
|
|
|
// Tables assigned to a type implementing the unmarshaler interface
|
|
// are captured as raw bytes, delivered once the document is read.
|
|
if d.unmarshalerInterface && hasUnmarshaler(v) {
|
|
d.startCapture(idx, expr)
|
|
return nil
|
|
}
|
|
|
|
if idx >= len(d.tableKey) {
|
|
break walk
|
|
}
|
|
|
|
name := d.tableKey[idx]
|
|
|
|
switch v.Kind() {
|
|
case reflect.Interface:
|
|
if !v.IsNil() {
|
|
c := v.Elem()
|
|
if k := c.Kind(); k == reflect.Map || k == reflect.Slice {
|
|
// Reference types: mutations are visible through the
|
|
// existing interface value.
|
|
v = c
|
|
continue
|
|
}
|
|
}
|
|
// Anything else is replaced by a fresh generic map.
|
|
if !mapStringInterfaceType.AssignableTo(v.Type()) {
|
|
return d.keyedError(expr, fmt.Errorf("cannot store a table in a %s", v.Type()))
|
|
}
|
|
fresh := reflect.ValueOf(map[string]interface{}{})
|
|
d.storeSlot(&pf, fresh)
|
|
v = fresh
|
|
case reflect.Slice:
|
|
if v.Len() == 0 {
|
|
// Implicit creation of the first element: the array table
|
|
// that would create it has not been seen yet (issue 995).
|
|
if v.IsNil() {
|
|
v = reflect.MakeSlice(v.Type(), 0, 4)
|
|
}
|
|
v = reflect.Append(v, newContainerElem(v.Type().Elem()))
|
|
d.storeSlot(&pf, v)
|
|
}
|
|
n := v.Len() - 1
|
|
d.segIdx[idx] = n
|
|
elem := v.Index(n)
|
|
pf = slotWriter{kind: 1, slot: elem}
|
|
v = elem
|
|
case reflect.Array:
|
|
key := d.joinPath(d.tableKey[:idx])
|
|
cnt := d.arrayCount(key)
|
|
if cnt == 0 {
|
|
cnt = 1
|
|
d.setArrayCount(key, 1)
|
|
}
|
|
if cnt > v.Len() {
|
|
return d.keyedError(expr, fmt.Errorf("cannot reach element %d of array of size %d", cnt-1, v.Len()))
|
|
}
|
|
d.segIdx[idx] = cnt - 1
|
|
elem := v.Index(cnt - 1)
|
|
pf = slotWriter{kind: 1, slot: elem}
|
|
v = elem
|
|
case reflect.Map:
|
|
if v.IsNil() {
|
|
nm := reflect.MakeMap(v.Type())
|
|
d.storeSlot(&pf, nm)
|
|
v = nm
|
|
}
|
|
var key reflect.Value
|
|
var w slotWriter
|
|
if v.Type().Key() == stringType {
|
|
key = d.stringMapKey(name)
|
|
w = slotWriter{kind: 3, m: v, ks: name}
|
|
} else {
|
|
k, err := makeMapKey(v.Type().Key(), name)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
key = k
|
|
w = slotWriter{kind: 2, m: v, k: k}
|
|
}
|
|
|
|
elem := v.MapIndex(key)
|
|
|
|
// The last part of an array table is finalized as a slice
|
|
// container: do not materialize a table for it.
|
|
if isArrayTable && idx == len(d.tableKey)-1 {
|
|
et := v.Type().Elem()
|
|
switch et.Kind() {
|
|
case reflect.Interface, reflect.Slice, reflect.Array:
|
|
if elem.IsValid() {
|
|
v = elem
|
|
} else {
|
|
v = reflect.Zero(et)
|
|
}
|
|
pf = w
|
|
idx++
|
|
continue
|
|
default:
|
|
}
|
|
}
|
|
|
|
if elem.IsValid() {
|
|
ce := elem
|
|
ceIface := false
|
|
if ce.Kind() == reflect.Interface {
|
|
ceIface = true
|
|
if !ce.IsNil() {
|
|
ce = ce.Elem()
|
|
}
|
|
}
|
|
switch ce.Kind() {
|
|
case reflect.Map, reflect.Slice:
|
|
pf = w
|
|
v = ce
|
|
case reflect.Ptr:
|
|
if ce.IsNil() {
|
|
np := reflect.New(ce.Type().Elem())
|
|
d.storeSlot(&w, np)
|
|
ce = np
|
|
}
|
|
pf = w
|
|
v = ce
|
|
case reflect.Struct, reflect.Array:
|
|
if ceIface {
|
|
// Interface-held non-generic content is replaced.
|
|
fresh := reflect.ValueOf(map[string]interface{}{})
|
|
d.storeSlot(&w, fresh)
|
|
pf = w
|
|
v = fresh
|
|
} else {
|
|
tmp := reflect.New(elem.Type()).Elem()
|
|
tmp.Set(elem)
|
|
d.tableFlush = append(d.tableFlush, flushOp{w: w, val: tmp})
|
|
pf = slotWriter{kind: 1, slot: tmp}
|
|
v = tmp
|
|
}
|
|
default:
|
|
if !ceIface {
|
|
return d.keyedError(expr, fmt.Errorf("cannot store a table in a %s", ce.Type()))
|
|
}
|
|
fresh := reflect.ValueOf(map[string]interface{}{})
|
|
d.storeSlot(&w, fresh)
|
|
pf = w
|
|
v = fresh
|
|
}
|
|
} else {
|
|
et := v.Type().Elem()
|
|
switch et.Kind() {
|
|
case reflect.Interface:
|
|
if !mapStringInterfaceType.AssignableTo(et) {
|
|
return d.keyedError(expr, fmt.Errorf("cannot store a table in a %s", et))
|
|
}
|
|
fresh := reflect.ValueOf(map[string]interface{}{})
|
|
d.storeSlot(&w, fresh)
|
|
pf = w
|
|
v = fresh
|
|
case reflect.Map:
|
|
nm := reflect.MakeMap(et)
|
|
d.storeSlot(&w, nm)
|
|
pf = w
|
|
v = nm
|
|
case reflect.Ptr:
|
|
np := reflect.New(et.Elem())
|
|
d.storeSlot(&w, np)
|
|
pf = w
|
|
v = np
|
|
case reflect.Struct, reflect.Array, reflect.Slice:
|
|
tmp := reflect.New(et).Elem()
|
|
d.tableFlush = append(d.tableFlush, flushOp{w: w, val: tmp})
|
|
pf = slotWriter{kind: 1, slot: tmp}
|
|
v = tmp
|
|
default:
|
|
return d.keyedError(expr, fmt.Errorf("cannot store a table in a %s", et))
|
|
}
|
|
}
|
|
idx++
|
|
case reflect.Struct:
|
|
plan := planForType(v.Type())
|
|
f, found := plan.lookup(name)
|
|
if !found {
|
|
d.strict.MissingTable(expr)
|
|
d.skipUntilTable = true
|
|
return nil
|
|
}
|
|
fv, err := fieldByIndexAlloc(v, f.index)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
pf = slotWriter{kind: 1, slot: fv}
|
|
v = fv
|
|
idx++
|
|
default:
|
|
return d.keyedError(expr, fmt.Errorf("cannot store a table in a %s", v.Kind()))
|
|
}
|
|
}
|
|
|
|
if isArrayTable {
|
|
akey := d.joinPath(d.tableKey)
|
|
d.resetChildArrayCounts(akey)
|
|
|
|
// Unwrap an interface container.
|
|
if v.Kind() == reflect.Interface {
|
|
var slice []interface{}
|
|
if !v.IsNil() {
|
|
if s, ok := v.Elem().Interface().([]interface{}); ok {
|
|
slice = s
|
|
}
|
|
}
|
|
if first {
|
|
slice = slice[:0]
|
|
}
|
|
m := map[string]interface{}{}
|
|
slice = append(slice, m)
|
|
sv := reflect.ValueOf(slice)
|
|
d.storeSlot(&pf, sv)
|
|
d.setArrayCount(akey, len(slice))
|
|
d.segIdx[len(d.tableKey)] = len(slice) - 1
|
|
d.tableTarget = reflect.ValueOf(m)
|
|
d.tableParentSlot = slotWriter{kind: 1, slot: sv.Index(len(slice) - 1)}
|
|
d.tableTargetValid = true
|
|
return nil
|
|
}
|
|
|
|
switch v.Kind() {
|
|
case reflect.Slice:
|
|
if v.IsNil() {
|
|
v = reflect.MakeSlice(v.Type(), 0, 4)
|
|
} else if first {
|
|
v = v.Slice(0, 0)
|
|
}
|
|
v = reflect.Append(v, newContainerElem(v.Type().Elem()))
|
|
d.storeSlot(&pf, v)
|
|
n := v.Len() - 1
|
|
d.setArrayCount(akey, n+1)
|
|
d.segIdx[len(d.tableKey)] = n
|
|
elem := v.Index(n)
|
|
if d.unmarshalerInterface && hasUnmarshaler(elem) {
|
|
d.startCapture(len(d.tableKey), expr)
|
|
return nil
|
|
}
|
|
pf = slotWriter{kind: 1, slot: elem}
|
|
v = elem
|
|
case reflect.Array:
|
|
cnt := d.arrayCount(akey)
|
|
if first {
|
|
cnt = 0
|
|
}
|
|
if cnt >= v.Len() {
|
|
return d.keyedError(expr, fmt.Errorf("array of size %d is too small to store this array table", v.Len()))
|
|
}
|
|
v.Index(cnt).Set(reflect.Zero(v.Type().Elem()))
|
|
d.setArrayCount(akey, cnt+1)
|
|
d.segIdx[len(d.tableKey)] = cnt
|
|
elem := v.Index(cnt)
|
|
if d.unmarshalerInterface && hasUnmarshaler(elem) {
|
|
d.startCapture(len(d.tableKey), expr)
|
|
return nil
|
|
}
|
|
pf = slotWriter{kind: 1, slot: elem}
|
|
v = elem
|
|
default:
|
|
return d.keyedError(expr, fmt.Errorf("cannot store an array table in a %s", v.Kind()))
|
|
}
|
|
}
|
|
|
|
// Settle on the concrete container for the key-values that follow.
|
|
for {
|
|
switch v.Kind() {
|
|
case reflect.Ptr:
|
|
if v.IsNil() {
|
|
if !v.CanSet() {
|
|
return nil
|
|
}
|
|
v.Set(reflect.New(v.Type().Elem()))
|
|
}
|
|
elem := v.Elem()
|
|
pf = slotWriter{kind: 1, slot: elem}
|
|
v = elem
|
|
continue
|
|
case reflect.Interface:
|
|
if !v.IsNil() {
|
|
c := v.Elem()
|
|
if c.Type() == mapStringInterfaceType || c.Type() == sliceInterfaceType {
|
|
v = c
|
|
continue
|
|
}
|
|
}
|
|
if !mapStringInterfaceType.AssignableTo(v.Type()) {
|
|
return d.keyedError(expr, fmt.Errorf("cannot store a table in a %s", v.Type()))
|
|
}
|
|
fresh := reflect.ValueOf(map[string]interface{}{})
|
|
d.storeSlot(&pf, fresh)
|
|
v = fresh
|
|
continue
|
|
case reflect.Slice:
|
|
if v.Len() == 0 {
|
|
if v.IsNil() {
|
|
v = reflect.MakeSlice(v.Type(), 0, 4)
|
|
}
|
|
v = reflect.Append(v, newContainerElem(v.Type().Elem()))
|
|
d.storeSlot(&pf, v)
|
|
}
|
|
n := v.Len() - 1
|
|
d.segIdx[len(d.tableKey)] = n
|
|
elem := v.Index(n)
|
|
pf = slotWriter{kind: 1, slot: elem}
|
|
v = elem
|
|
continue
|
|
case reflect.Map, reflect.Struct:
|
|
d.tableTarget = v
|
|
d.tableParentSlot = pf
|
|
d.tableTargetValid = true
|
|
return nil
|
|
default:
|
|
return d.keyedError(expr, fmt.Errorf("cannot store a table in a %s", v.Kind()))
|
|
}
|
|
}
|
|
}
|
|
|
|
// resumeCapture looks for an existing capture this table expression belongs
|
|
// to. It returns true if the expression was consumed.
|
|
func (d *decoder) resumeCapture(expr *unstable.Node) bool {
|
|
// Iterate in reverse, so that tables attach to the latest element of
|
|
// array tables.
|
|
for i := len(d.captures) - 1; i >= 0; i-- {
|
|
c := &d.captures[i]
|
|
if len(d.tableKey) < len(c.names) {
|
|
continue
|
|
}
|
|
if expr.Kind == unstable.ArrayTable && len(d.tableKey) == len(c.names) {
|
|
// A new element of an array table is not part of the capture of
|
|
// the previous element.
|
|
continue
|
|
}
|
|
match := true
|
|
for j, p := range c.names {
|
|
if d.tableKey[j] != p {
|
|
match = false
|
|
break
|
|
}
|
|
}
|
|
if !match {
|
|
continue
|
|
}
|
|
d.captureIdx = i
|
|
if len(d.tableKey) > len(c.names) {
|
|
d.appendCaptureHeader(c, expr, len(c.names))
|
|
}
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
// appendCaptureHeader writes the table header of expr in the capture buffer,
|
|
// adjusted to be relative to the capture root.
|
|
func (d *decoder) appendCaptureHeader(c *rawCapture, expr *unstable.Node, skip int) {
|
|
c.buf = append(c.buf, '[')
|
|
if expr.Kind == unstable.ArrayTable {
|
|
c.buf = append(c.buf, '[')
|
|
}
|
|
c.buf = append(c.buf, d.rawKeySuffix(expr, skip)...)
|
|
c.buf = append(c.buf, ']')
|
|
if expr.Kind == unstable.ArrayTable {
|
|
c.buf = append(c.buf, ']')
|
|
}
|
|
c.buf = append(c.buf, '\n')
|
|
}
|
|
|
|
// rawKeySuffix returns the raw bytes of the key of the expression, skipping
|
|
// the first n parts.
|
|
func (d *decoder) rawKeySuffix(expr *unstable.Node, n int) []byte {
|
|
it := expr.Key()
|
|
idx := 0
|
|
var start, end unstable.Range
|
|
for it.Next() {
|
|
if idx >= n {
|
|
r := it.Node().Raw
|
|
if start.Length == 0 && start.Offset == 0 && idx == n {
|
|
start = r
|
|
}
|
|
end = r
|
|
}
|
|
idx++
|
|
}
|
|
return d.p.Data()[start.Offset : end.Offset+end.Length]
|
|
}
|
|
|
|
// startCapture registers a new capture for the table at the given path
|
|
// (prefix of tableKey).
|
|
func (d *decoder) startCapture(pathLen int, expr *unstable.Node) {
|
|
names := make([]string, pathLen)
|
|
copy(names, d.tableKey[:pathLen])
|
|
indexes := make([]int, pathLen+1)
|
|
copy(indexes, d.segIdx[:pathLen+1])
|
|
d.captures = append(d.captures, rawCapture{
|
|
names: names,
|
|
indexes: indexes,
|
|
})
|
|
d.captureIdx = len(d.captures) - 1
|
|
if pathLen < len(d.tableKey) {
|
|
d.appendCaptureHeader(&d.captures[d.captureIdx], expr, pathLen)
|
|
}
|
|
}
|
|
|
|
// startRootCapture opens a capture covering the entire document, used when the
|
|
// root target itself implements the Unmarshaler interface. It is opened before
|
|
// reading any expression: top-level key-values are then accumulated through the
|
|
// regular capture branch, and its empty name path matches every table in
|
|
// resumeCapture, so the whole document is handed to UnmarshalTOML once. The
|
|
// single index slot is -1 because the root is never reached through an array
|
|
// table.
|
|
func (d *decoder) startRootCapture() {
|
|
d.captures = append(d.captures, rawCapture{indexes: []int{-1}})
|
|
d.captureIdx = len(d.captures) - 1
|
|
}
|
|
|
|
// resolveCapture walks back to the target of a capture and delivers the
|
|
// accumulated raw bytes to its UnmarshalTOML implementation.
|
|
func (d *decoder) resolveCapture(v reflect.Value, c *rawCapture, idx int, indexed bool) (reflect.Value, error) {
|
|
if v.Kind() == reflect.Ptr {
|
|
if v.Type().Implements(unmarshalerType) && idx == len(c.names) {
|
|
u, _ := unmarshalerOf(v)
|
|
return v, u.UnmarshalTOML(c.buf)
|
|
}
|
|
if v.IsNil() {
|
|
v.Set(reflect.New(v.Type().Elem()))
|
|
}
|
|
nv, err := d.resolveCapture(v.Elem(), c, idx, indexed)
|
|
if err != nil {
|
|
return reflect.Value{}, err
|
|
}
|
|
if nv.IsValid() {
|
|
v.Elem().Set(nv)
|
|
}
|
|
return v, nil
|
|
}
|
|
|
|
if !indexed && (v.Kind() == reflect.Slice || v.Kind() == reflect.Array) && c.indexes[idx] >= 0 {
|
|
i := c.indexes[idx]
|
|
if i >= v.Len() {
|
|
return reflect.Value{}, errors.New("toml: internal error: capture index out of range")
|
|
}
|
|
elem := v.Index(i)
|
|
nv, err := d.resolveCapture(elem, c, idx, true)
|
|
if err != nil {
|
|
return reflect.Value{}, err
|
|
}
|
|
if nv.IsValid() {
|
|
elem.Set(nv)
|
|
}
|
|
return v, nil
|
|
}
|
|
|
|
if idx == len(c.names) {
|
|
u, ok := unmarshalerOf(v)
|
|
if !ok {
|
|
return reflect.Value{}, errors.New("toml: internal error: capture target does not implement UnmarshalTOML")
|
|
}
|
|
return v, u.UnmarshalTOML(c.buf)
|
|
}
|
|
|
|
name := c.names[idx]
|
|
|
|
switch v.Kind() {
|
|
case reflect.Struct:
|
|
plan := planForType(v.Type())
|
|
f, found := plan.lookup(name)
|
|
if !found {
|
|
return v, nil
|
|
}
|
|
fv, err := fieldByIndexAlloc(v, f.index)
|
|
if err != nil {
|
|
return reflect.Value{}, err
|
|
}
|
|
nv, err := d.resolveCapture(fv, c, idx+1, false)
|
|
if err != nil {
|
|
return reflect.Value{}, err
|
|
}
|
|
if nv.IsValid() && fv.CanSet() {
|
|
fv.Set(nv)
|
|
}
|
|
return v, nil
|
|
case reflect.Map:
|
|
key, err := makeMapKey(v.Type().Key(), name)
|
|
if err != nil {
|
|
return reflect.Value{}, err
|
|
}
|
|
if v.IsNil() {
|
|
v = reflect.MakeMap(v.Type())
|
|
}
|
|
elem := reflect.New(v.Type().Elem()).Elem()
|
|
if existing := v.MapIndex(key); existing.IsValid() {
|
|
elem.Set(existing)
|
|
}
|
|
nv, err := d.resolveCapture(elem, c, idx+1, false)
|
|
if err != nil {
|
|
return reflect.Value{}, err
|
|
}
|
|
if nv.IsValid() {
|
|
v.SetMapIndex(key, nv)
|
|
}
|
|
return v, nil
|
|
case reflect.Interface:
|
|
elem := elemOrNewMap(v)
|
|
nv, err := d.resolveCapture(elem, c, idx, indexed)
|
|
if err != nil || !nv.IsValid() {
|
|
return reflect.Value{}, err
|
|
}
|
|
return nv, nil
|
|
default:
|
|
return reflect.Value{}, fmt.Errorf("toml: internal error: cannot resolve capture target through %s", v.Kind())
|
|
}
|
|
}
|
|
|
|
// captureKeyValue appends the raw bytes of a key-value expression to the
|
|
// current capture.
|
|
func (d *decoder) captureKeyValue(expr *unstable.Node) {
|
|
c := &d.captures[d.captureIdx]
|
|
c.buf = append(c.buf, d.p.Raw(expr.Raw)...)
|
|
c.buf = append(c.buf, '\n')
|
|
}
|
|
|
|
// hasUnmarshaler reports whether v can provide an unstable.Unmarshaler,
|
|
// without allocating anything.
|
|
func hasUnmarshaler(v reflect.Value) bool {
|
|
t := v.Type()
|
|
return t.Implements(unmarshalerType) || (v.CanAddr() && reflect.PtrTo(t).Implements(unmarshalerType))
|
|
}
|
|
|
|
// makeMapKey converts a TOML key into a value usable as the given map key
|
|
// type.
|
|
func makeMapKey(kt reflect.Type, name string) (reflect.Value, error) {
|
|
switch kt.Kind() {
|
|
case reflect.String:
|
|
return reflect.ValueOf(name).Convert(kt), nil
|
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
|
i, err := strconv.ParseInt(name, 10, 64)
|
|
if err != nil {
|
|
return reflect.Value{}, fmt.Errorf("toml: cannot parse map key %q as %s: %w", name, kt, err)
|
|
}
|
|
k := reflect.New(kt).Elem()
|
|
if k.OverflowInt(i) {
|
|
return reflect.Value{}, fmt.Errorf("toml: map key %q overflows %s", name, kt)
|
|
}
|
|
k.SetInt(i)
|
|
return k, nil
|
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
|
u, err := strconv.ParseUint(name, 10, 64)
|
|
if err != nil {
|
|
return reflect.Value{}, fmt.Errorf("toml: cannot parse map key %q as %s: %w", name, kt, err)
|
|
}
|
|
k := reflect.New(kt).Elem()
|
|
if k.OverflowUint(u) {
|
|
return reflect.Value{}, fmt.Errorf("toml: map key %q overflows %s", name, kt)
|
|
}
|
|
k.SetUint(u)
|
|
return k, nil
|
|
case reflect.Float32, reflect.Float64:
|
|
f, err := strconv.ParseFloat(name, 64)
|
|
if err != nil {
|
|
return reflect.Value{}, fmt.Errorf("toml: cannot parse map key %q as %s: %w", name, kt, err)
|
|
}
|
|
k := reflect.New(kt).Elem()
|
|
k.SetFloat(f)
|
|
return k, nil
|
|
case reflect.Ptr:
|
|
if kt.Implements(textUnmarshalerType) {
|
|
k := reflect.New(kt.Elem())
|
|
err := k.Interface().(encoding.TextUnmarshaler).UnmarshalText([]byte(name))
|
|
if err != nil {
|
|
return reflect.Value{}, fmt.Errorf("toml: error unmarshaling map key %q: %w", name, err)
|
|
}
|
|
return k, nil
|
|
}
|
|
default:
|
|
if reflect.PtrTo(kt).Implements(textUnmarshalerType) {
|
|
k := reflect.New(kt)
|
|
err := k.Interface().(encoding.TextUnmarshaler).UnmarshalText([]byte(name))
|
|
if err != nil {
|
|
return reflect.Value{}, fmt.Errorf("toml: error unmarshaling map key %q: %w", name, err)
|
|
}
|
|
return k.Elem(), nil
|
|
}
|
|
}
|
|
return reflect.Value{}, fmt.Errorf("toml: cannot decode a key into a map with key type %s", kt)
|
|
}
|
|
|
|
// elemOrNewMap unwraps an interface value to descend into it. Contents that
|
|
// can hold a table (generic maps and slices) are kept; anything else is
|
|
// replaced by a fresh map[string]interface{}. Maps and slices are reference
|
|
// types: they are returned directly, not copied.
|
|
func elemOrNewMap(v reflect.Value) reflect.Value {
|
|
if !v.IsNil() {
|
|
concrete := v.Elem()
|
|
t := concrete.Type()
|
|
if t == mapStringInterfaceType || t == sliceInterfaceType {
|
|
return concrete
|
|
}
|
|
}
|
|
return reflect.ValueOf(map[string]interface{}{})
|
|
}
|
|
|
|
// handleKeyValueExpression stores the value of a top-level key-value
|
|
// expression, relative to the current table.
|
|
func (d *decoder) handleKeyValueExpression(expr *unstable.Node, root reflect.Value) error {
|
|
d.path = d.path[:0]
|
|
|
|
target := root
|
|
useCache := d.tableTargetValid && len(d.tableKey) > 0
|
|
if useCache {
|
|
target = d.tableTarget
|
|
} else {
|
|
for _, name := range d.tableKey {
|
|
d.path = append(d.path, pathPart{name: name})
|
|
}
|
|
}
|
|
|
|
it := expr.Key()
|
|
for it.Next() {
|
|
d.path = append(d.path, pathPart{node: it.Node()})
|
|
}
|
|
|
|
nv, err := d.descend(target, d.path, 0, expr, expr.Value())
|
|
if err != nil {
|
|
return d.contextualizeError(err, useCache)
|
|
}
|
|
if !nv.IsValid() {
|
|
return nil
|
|
}
|
|
if useCache {
|
|
// The target may have been replaced (e.g. a nil map allocated):
|
|
// re-link it into its parent.
|
|
if nv.Kind() == reflect.Map && nv.Pointer() != d.tableTarget.Pointer() {
|
|
d.storeSlot(&d.tableParentSlot, nv)
|
|
d.tableTarget = nv
|
|
}
|
|
} else {
|
|
if root.CanSet() {
|
|
root.Set(nv)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// descend walks the given key path into v, and assigns the value at the
|
|
// end. It returns the value to store back at this level. An invalid value
|
|
// means nothing should be stored (e.g. unknown field).
|
|
func (d *decoder) descend(v reflect.Value, path []pathPart, idx int, expr *unstable.Node, value *unstable.Node) (reflect.Value, error) {
|
|
if idx == len(path) {
|
|
return d.assignValue(v, expr, value)
|
|
}
|
|
|
|
if v.Kind() == reflect.Ptr {
|
|
if v.IsNil() {
|
|
v.Set(reflect.New(v.Type().Elem()))
|
|
}
|
|
nv, err := d.descend(v.Elem(), path, idx, expr, value)
|
|
if err != nil || !nv.IsValid() {
|
|
return reflect.Value{}, err
|
|
}
|
|
v.Elem().Set(nv)
|
|
return v, nil
|
|
}
|
|
|
|
// A target implementing the unmarshaler interface consumes the value,
|
|
// whatever the remaining parts of the key are.
|
|
if d.unmarshalerInterface {
|
|
if u, ok := unmarshalerOf(v); ok {
|
|
return v, u.UnmarshalTOML(d.rawValue(expr, value))
|
|
}
|
|
}
|
|
|
|
part := path[idx]
|
|
|
|
switch v.Kind() {
|
|
case reflect.Map:
|
|
// Native fast path for the most common generic target: walk the
|
|
// remaining dotted-key path with plain Go map operations and decode
|
|
// the value directly, skipping the reflect.Value round-trips
|
|
// (stringMapKey, MapIndex, New, SetMapIndex) entirely.
|
|
if !d.unmarshalerInterface && v.Type() == mapStringInterfaceType {
|
|
return d.descendStrMap(v, path, idx, value)
|
|
}
|
|
var name string
|
|
var key reflect.Value
|
|
var err error
|
|
fastKey := v.Type().Key() == stringType
|
|
if fastKey {
|
|
name = d.partString(&part)
|
|
key = d.stringMapKey(name)
|
|
} else {
|
|
key, err = makeMapKey(v.Type().Key(), d.partString(&part))
|
|
if err != nil {
|
|
return reflect.Value{}, err
|
|
}
|
|
}
|
|
if v.IsNil() {
|
|
v = reflect.MakeMap(v.Type())
|
|
}
|
|
elemType := v.Type().Elem()
|
|
existing := v.MapIndex(key)
|
|
var elem reflect.Value
|
|
switch {
|
|
case existing.IsValid():
|
|
elem = reflect.New(elemType).Elem()
|
|
elem.Set(existing)
|
|
case idx+1 == len(path) && elemType.Kind() == reflect.Interface:
|
|
// Fast path: a fresh interface element does not need to be
|
|
// materialized, the assigned value is stored directly.
|
|
elem = reflect.Zero(elemType)
|
|
default:
|
|
elem = reflect.New(elemType).Elem()
|
|
}
|
|
nv, err := d.descend(elem, path, idx+1, expr, value)
|
|
if err != nil {
|
|
return reflect.Value{}, err
|
|
}
|
|
if nv.IsValid() {
|
|
if fastKey {
|
|
// The recursion may have overwritten the key buffer.
|
|
key = d.stringMapKey(name)
|
|
}
|
|
v.SetMapIndex(key, nv)
|
|
}
|
|
return v, nil
|
|
case reflect.Struct:
|
|
plan := planForType(v.Type())
|
|
f, found := plan.lookupBytes(part.bytes())
|
|
if !found {
|
|
if part.node != nil {
|
|
d.strict.MissingField(expr)
|
|
}
|
|
return v, nil
|
|
}
|
|
fv, err := fieldByIndexAlloc(v, f.index)
|
|
if err != nil {
|
|
return reflect.Value{}, err
|
|
}
|
|
var nv reflect.Value
|
|
if idx+1 == len(path) {
|
|
// Leaf field: assign directly. descend's first action for a
|
|
// fully-consumed path is exactly this call, so skipping the extra
|
|
// frame is equivalent and avoids a call per scalar field.
|
|
nv, err = d.assignValue(fv, expr, value)
|
|
} else {
|
|
nv, err = d.descend(fv, path, idx+1, expr, value)
|
|
}
|
|
if err != nil {
|
|
var mm *typeMismatchError
|
|
if errors.As(err, &mm) {
|
|
err = &unstable.ParserError{
|
|
Highlight: mm.highlight,
|
|
Message: fmt.Sprintf("cannot decode TOML %s into struct field %s.%s of type %s",
|
|
mm.toml, v.Type(), f.fieldName, mm.target),
|
|
}
|
|
}
|
|
return reflect.Value{}, err
|
|
}
|
|
if nv.IsValid() && fv.CanSet() {
|
|
fv.Set(nv)
|
|
}
|
|
return v, nil
|
|
case reflect.Interface:
|
|
elem := elemOrNewMap(v)
|
|
nv, err := d.descend(elem, path, idx, expr, value)
|
|
if err != nil || !nv.IsValid() {
|
|
return reflect.Value{}, err
|
|
}
|
|
return nv, nil
|
|
case reflect.Slice:
|
|
if v.Len() == 0 {
|
|
if v.IsNil() {
|
|
v = reflect.MakeSlice(v.Type(), 0, 4)
|
|
}
|
|
v = reflect.Append(v, reflect.New(v.Type().Elem()).Elem())
|
|
}
|
|
elem := v.Index(v.Len() - 1)
|
|
nv, err := d.descend(elem, path, idx, expr, value)
|
|
if err != nil {
|
|
return reflect.Value{}, err
|
|
}
|
|
if nv.IsValid() {
|
|
elem.Set(nv)
|
|
}
|
|
return v, nil
|
|
case reflect.Array:
|
|
names := make([]string, idx)
|
|
for i := range names {
|
|
names[i] = path[i].str()
|
|
}
|
|
cnt := d.arrayCount(d.joinPath(names))
|
|
if cnt == 0 {
|
|
cnt = 1
|
|
}
|
|
elemIdx := cnt - 1
|
|
if elemIdx >= v.Len() {
|
|
return reflect.Value{}, unstable.NewParserError(keyHighlight(d.p.Data(), part.node),
|
|
"cannot reach element %d of array of size %d", elemIdx, v.Len())
|
|
}
|
|
elem := v.Index(elemIdx)
|
|
nv, err := d.descend(elem, path, idx, expr, value)
|
|
if err != nil {
|
|
return reflect.Value{}, err
|
|
}
|
|
if nv.IsValid() {
|
|
elem.Set(nv)
|
|
}
|
|
return v, nil
|
|
default:
|
|
return reflect.Value{}, d.typeMismatchError("table", v.Type(), keyHighlight(d.p.Data(), part.node))
|
|
}
|
|
}
|
|
|
|
// descendStrMap assigns into a native map[string]interface{} target, following
|
|
// the remaining dotted-key parts with plain Go map operations and decoding the
|
|
// value with decodeAny. It returns the map to store back at this level: a new
|
|
// map when v was nil, otherwise v unchanged, since maps are reference types and
|
|
// are mutated in place.
|
|
func (d *decoder) descendStrMap(v reflect.Value, path []pathPart, idx int, value *unstable.Node) (reflect.Value, error) {
|
|
var m map[string]interface{}
|
|
if v.IsNil() {
|
|
m = make(map[string]interface{})
|
|
v = reflect.ValueOf(m)
|
|
} else {
|
|
m = v.Interface().(map[string]interface{})
|
|
}
|
|
|
|
// Walk intermediate parts, creating or reusing nested generic maps. A
|
|
// non-map value at an intermediate key can only occur in a document the
|
|
// seen-tracker has already rejected; replacing it mirrors the reflect
|
|
// path (elemOrNewMap).
|
|
for ; idx < len(path)-1; idx++ {
|
|
name := d.partString(&path[idx])
|
|
child, _ := m[name].(map[string]interface{})
|
|
if child == nil {
|
|
child = make(map[string]interface{})
|
|
m[name] = child
|
|
}
|
|
m = child
|
|
}
|
|
|
|
av, err := d.decodeAny(value)
|
|
if err != nil {
|
|
return reflect.Value{}, err
|
|
}
|
|
m[d.partString(&path[idx])] = av
|
|
return v, nil
|
|
}
|
|
|
|
// keyHighlight returns a highlight for the given key part node, falling back
|
|
// to the start of the document.
|
|
func keyHighlight(doc []byte, node *unstable.Node) []byte {
|
|
if node == nil {
|
|
return doc[0:0]
|
|
}
|
|
return doc[node.Raw.Offset : node.Raw.Offset+node.Raw.Length]
|
|
}
|
|
|
|
// rawValue returns the raw bytes of the value of a key-value expression.
|
|
func (d *decoder) rawValue(expr *unstable.Node, value *unstable.Node) []byte {
|
|
if value.Kind != unstable.InlineTable && value.Kind != unstable.Array {
|
|
return d.p.Raw(value.Raw)
|
|
}
|
|
if expr == nil || expr.Kind != unstable.KeyValue {
|
|
// Inline container nested in another container: best effort.
|
|
return d.p.Raw(value.Raw)
|
|
}
|
|
// Reconstruct the span of the value: it starts after the equal sign
|
|
// following the last part of the key, and stops at the end of the
|
|
// expression.
|
|
var last unstable.Range
|
|
it := expr.Key()
|
|
for it.Next() {
|
|
last = it.Node().Raw
|
|
}
|
|
doc := d.p.Data()
|
|
i := int(last.Offset + last.Length)
|
|
for i < len(doc) && (doc[i] == ' ' || doc[i] == '\t') {
|
|
i++
|
|
}
|
|
i++ // equal sign
|
|
for i < len(doc) && (doc[i] == ' ' || doc[i] == '\t') {
|
|
i++
|
|
}
|
|
end := int(expr.Raw.Offset + expr.Raw.Length)
|
|
return doc[i:end]
|
|
}
|
|
|
|
// unmarshalerOf returns the unstable.Unmarshaler implementation of v, if
|
|
// any. It allocates intermediate pointers as needed.
|
|
func unmarshalerOf(v reflect.Value) (unstable.Unmarshaler, bool) {
|
|
t := v.Type()
|
|
if t.Implements(unmarshalerType) {
|
|
if v.Kind() == reflect.Ptr && v.IsNil() {
|
|
v.Set(reflect.New(t.Elem()))
|
|
}
|
|
return v.Interface().(unstable.Unmarshaler), true
|
|
}
|
|
if v.CanAddr() && reflect.PtrTo(t).Implements(unmarshalerType) {
|
|
return v.Addr().Interface().(unstable.Unmarshaler), true
|
|
}
|
|
return nil, false
|
|
}
|
|
|
|
var unmarshalerType = reflect.TypeOf(new(unstable.Unmarshaler)).Elem()
|
|
|
|
// assignValue stores the TOML value carried by the node into v.
|
|
func (d *decoder) assignValue(v reflect.Value, expr *unstable.Node, value *unstable.Node) (reflect.Value, error) {
|
|
if v.Kind() == reflect.Ptr {
|
|
if d.unmarshalerInterface {
|
|
if u, ok := unmarshalerOf(v); ok {
|
|
return v, u.UnmarshalTOML(d.rawValue(expr, value))
|
|
}
|
|
}
|
|
if v.IsNil() {
|
|
v.Set(reflect.New(v.Type().Elem()))
|
|
}
|
|
nv, err := d.assignValue(v.Elem(), expr, value)
|
|
if err != nil || !nv.IsValid() {
|
|
return reflect.Value{}, err
|
|
}
|
|
v.Elem().Set(nv)
|
|
return v, nil
|
|
}
|
|
|
|
if d.unmarshalerInterface {
|
|
if u, ok := unmarshalerOf(v); ok {
|
|
return v, u.UnmarshalTOML(d.rawValue(expr, value))
|
|
}
|
|
}
|
|
|
|
switch value.Kind {
|
|
case unstable.String:
|
|
return d.assignString(v, value)
|
|
case unstable.Integer:
|
|
return d.assignInteger(v, value)
|
|
case unstable.Float:
|
|
return d.assignFloat(v, value)
|
|
case unstable.Bool:
|
|
return d.assignBool(v, value)
|
|
case unstable.DateTime:
|
|
return d.assignDateTime(v, value)
|
|
case unstable.LocalDateTime:
|
|
return d.assignLocalDateTime(v, value)
|
|
case unstable.LocalDate:
|
|
return d.assignLocalDate(v, value)
|
|
case unstable.LocalTime:
|
|
return d.assignLocalTime(v, value)
|
|
case unstable.Array:
|
|
return d.assignArray(v, expr, value)
|
|
case unstable.InlineTable:
|
|
return d.assignInlineTable(v, expr, value)
|
|
default:
|
|
return reflect.Value{}, unstable.NewParserError(value.Data, "unsupported value kind %s", value.Kind)
|
|
}
|
|
}
|
|
|
|
func (d *decoder) assignString(v reflect.Value, value *unstable.Node) (reflect.Value, error) {
|
|
switch v.Kind() {
|
|
case reflect.String:
|
|
v.SetString(string(value.Data))
|
|
return v, nil
|
|
case reflect.Interface:
|
|
return boxInto(v, reflect.ValueOf(string(value.Data)))
|
|
default:
|
|
}
|
|
if v.CanAddr() && v.Addr().Type().Implements(textUnmarshalerType) {
|
|
err := v.Addr().Interface().(encoding.TextUnmarshaler).UnmarshalText(value.Data)
|
|
if err != nil {
|
|
return reflect.Value{}, unstable.NewParserError(d.p.Raw(value.Raw), "%s", err)
|
|
}
|
|
return v, nil
|
|
}
|
|
return reflect.Value{}, d.typeMismatchError("string", v.Type(), d.p.Raw(value.Raw))
|
|
}
|
|
|
|
func (d *decoder) assignInteger(v reflect.Value, value *unstable.Node) (reflect.Value, error) {
|
|
// Integer values targeting a float field are parsed as floats: they can
|
|
// represent (approximately) numbers beyond the int64 range.
|
|
if k := v.Kind(); k == reflect.Float32 || k == reflect.Float64 {
|
|
return d.assignFloat(v, value)
|
|
}
|
|
|
|
i, err := parseInteger(value.Data)
|
|
if err != nil {
|
|
return reflect.Value{}, err
|
|
}
|
|
|
|
switch v.Kind() {
|
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
|
if v.OverflowInt(i) {
|
|
return reflect.Value{}, unstable.NewParserError(value.Data, "integer value %d cannot be stored in %s", i, v.Type())
|
|
}
|
|
v.SetInt(i)
|
|
return v, nil
|
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
|
if i < 0 {
|
|
return reflect.Value{}, unstable.NewParserError(value.Data, "negative integer value %d cannot be stored in %s", i, v.Type())
|
|
}
|
|
if v.OverflowUint(uint64(i)) {
|
|
return reflect.Value{}, unstable.NewParserError(value.Data, "integer value %d cannot be stored in %s", i, v.Type())
|
|
}
|
|
v.SetUint(uint64(i))
|
|
return v, nil
|
|
case reflect.Interface:
|
|
return boxInto(v, reflect.ValueOf(i))
|
|
default:
|
|
}
|
|
if ok, err := tryTextUnmarshaler(v, value.Data); ok {
|
|
return v, err
|
|
}
|
|
return reflect.Value{}, d.typeMismatchError("integer", v.Type(), d.p.Raw(value.Raw))
|
|
}
|
|
|
|
// tryTextUnmarshaler attempts to deliver the raw text of a value to a target
|
|
// implementing encoding.TextUnmarshaler.
|
|
func tryTextUnmarshaler(v reflect.Value, text []byte) (bool, error) {
|
|
if v.CanAddr() && v.Addr().Type().Implements(textUnmarshalerType) {
|
|
return true, v.Addr().Interface().(encoding.TextUnmarshaler).UnmarshalText(text)
|
|
}
|
|
return false, nil
|
|
}
|
|
|
|
func (d *decoder) assignFloat(v reflect.Value, value *unstable.Node) (reflect.Value, error) {
|
|
f, err := parseFloat(value.Data)
|
|
if err != nil {
|
|
return reflect.Value{}, err
|
|
}
|
|
|
|
switch v.Kind() {
|
|
case reflect.Float64:
|
|
v.SetFloat(f)
|
|
return v, nil
|
|
case reflect.Float32:
|
|
if !math.IsInf(f, 0) && math.Abs(f) > math.MaxFloat32 {
|
|
return reflect.Value{}, unstable.NewParserError(value.Data, "float value %f cannot be stored in float32", f)
|
|
}
|
|
v.SetFloat(f)
|
|
return v, nil
|
|
case reflect.Interface:
|
|
return boxInto(v, reflect.ValueOf(f))
|
|
default:
|
|
}
|
|
if ok, err := tryTextUnmarshaler(v, value.Data); ok {
|
|
return v, err
|
|
}
|
|
return reflect.Value{}, d.typeMismatchError("float", v.Type(), d.p.Raw(value.Raw))
|
|
}
|
|
|
|
func (d *decoder) assignBool(v reflect.Value, value *unstable.Node) (reflect.Value, error) {
|
|
b := value.Data[0] == 't'
|
|
|
|
switch v.Kind() {
|
|
case reflect.Bool:
|
|
v.SetBool(b)
|
|
return v, nil
|
|
case reflect.Interface:
|
|
return boxInto(v, reflect.ValueOf(b))
|
|
default:
|
|
}
|
|
if ok, err := tryTextUnmarshaler(v, value.Data); ok {
|
|
return v, err
|
|
}
|
|
return reflect.Value{}, d.typeMismatchError("boolean", v.Type(), d.p.Raw(value.Raw))
|
|
}
|
|
|
|
func (d *decoder) assignDateTime(v reflect.Value, value *unstable.Node) (reflect.Value, error) {
|
|
t, err := parseDateTime(value.Data)
|
|
if err != nil {
|
|
return reflect.Value{}, err
|
|
}
|
|
|
|
if v.Type() == timeType {
|
|
setConcrete(v, t)
|
|
return v, nil
|
|
}
|
|
if v.Kind() == reflect.Interface {
|
|
return boxInto(v, reflect.ValueOf(t))
|
|
}
|
|
return reflect.Value{}, d.typeMismatchError("datetime", v.Type(), d.p.Raw(value.Raw))
|
|
}
|
|
|
|
// setConcrete assigns x to v, which must be a settable value of x's concrete
|
|
// type. Writing through the address avoids the heap allocation that
|
|
// v.Set(reflect.ValueOf(x)) incurs from boxing x into an interface. Settable
|
|
// implies addressable: the decoder assigns to struct fields, slice elements,
|
|
// and reflect.New-allocated temporaries (map elements are decoded into such
|
|
// temporaries), all of which are addressable.
|
|
func setConcrete[T any](v reflect.Value, x T) {
|
|
*(v.Addr().Interface().(*T)) = x
|
|
}
|
|
|
|
func (d *decoder) assignLocalDateTime(v reflect.Value, value *unstable.Node) (reflect.Value, error) {
|
|
dt, rest, err := parseLocalDateTime(value.Data)
|
|
if err != nil {
|
|
return reflect.Value{}, err
|
|
}
|
|
if len(rest) > 0 {
|
|
return reflect.Value{}, unstable.NewParserError(rest, "extra characters at the end of a local date time")
|
|
}
|
|
|
|
switch v.Type() {
|
|
case localDateTimeType:
|
|
setConcrete(v, dt)
|
|
return v, nil
|
|
case timeType:
|
|
setConcrete(v, dt.AsTime(time.Local))
|
|
return v, nil
|
|
}
|
|
if v.Kind() == reflect.Interface {
|
|
return boxInto(v, reflect.ValueOf(dt))
|
|
}
|
|
return reflect.Value{}, d.typeMismatchError("local datetime", v.Type(), d.p.Raw(value.Raw))
|
|
}
|
|
|
|
func (d *decoder) assignLocalDate(v reflect.Value, value *unstable.Node) (reflect.Value, error) {
|
|
date, err := parseLocalDate(value.Data)
|
|
if err != nil {
|
|
return reflect.Value{}, err
|
|
}
|
|
|
|
switch v.Type() {
|
|
case localDateType:
|
|
setConcrete(v, date)
|
|
return v, nil
|
|
case timeType:
|
|
setConcrete(v, date.AsTime(time.Local))
|
|
return v, nil
|
|
}
|
|
if v.Kind() == reflect.Interface {
|
|
return boxInto(v, reflect.ValueOf(date))
|
|
}
|
|
return reflect.Value{}, d.typeMismatchError("local date", v.Type(), d.p.Raw(value.Raw))
|
|
}
|
|
|
|
func (d *decoder) assignLocalTime(v reflect.Value, value *unstable.Node) (reflect.Value, error) {
|
|
t, rest, err := parseLocalTime(value.Data)
|
|
if err != nil {
|
|
return reflect.Value{}, err
|
|
}
|
|
if len(rest) > 0 {
|
|
return reflect.Value{}, unstable.NewParserError(rest, "extra characters at the end of a local time")
|
|
}
|
|
|
|
switch v.Type() {
|
|
case localTimeType:
|
|
setConcrete(v, t)
|
|
return v, nil
|
|
case timeType:
|
|
setConcrete(v, time.Date(0, 1, 1, t.Hour, t.Minute, t.Second, t.Nanosecond, time.Local))
|
|
return v, nil
|
|
}
|
|
if v.Kind() == reflect.Interface {
|
|
return boxInto(v, reflect.ValueOf(t))
|
|
}
|
|
return reflect.Value{}, d.typeMismatchError("local time", v.Type(), d.p.Raw(value.Raw))
|
|
}
|
|
|
|
func (d *decoder) assignArray(v reflect.Value, expr *unstable.Node, value *unstable.Node) (reflect.Value, error) {
|
|
// Count the elements to allocate the target in one go.
|
|
count := 0
|
|
cit := value.Children()
|
|
for cit.Next() {
|
|
if cit.Node().Kind != unstable.Comment {
|
|
count++
|
|
}
|
|
}
|
|
|
|
switch v.Kind() {
|
|
case reflect.Slice:
|
|
// Allocate the backing array once at its final length and assign each
|
|
// element in place. This avoids a reflect.New allocation per element
|
|
// and the repeated growth checks of reflect.Append.
|
|
slice := reflect.MakeSlice(v.Type(), count, count)
|
|
i := 0
|
|
it := value.Children()
|
|
for it.Next() {
|
|
n := it.Node()
|
|
if n.Kind == unstable.Comment {
|
|
continue
|
|
}
|
|
elem := slice.Index(i)
|
|
nv, err := d.assignValue(elem, nil, n)
|
|
if err != nil {
|
|
return reflect.Value{}, err
|
|
}
|
|
if nv.IsValid() {
|
|
elem.Set(nv)
|
|
}
|
|
i++
|
|
}
|
|
return slice, nil
|
|
case reflect.Array:
|
|
it := value.Children()
|
|
i := 0
|
|
for it.Next() {
|
|
n := it.Node()
|
|
if n.Kind == unstable.Comment {
|
|
continue
|
|
}
|
|
if i >= v.Len() {
|
|
// Extra elements are dropped when the target array is too
|
|
// small.
|
|
break
|
|
}
|
|
elem := v.Index(i)
|
|
nv, err := d.assignValue(elem, nil, n)
|
|
if err != nil {
|
|
return reflect.Value{}, err
|
|
}
|
|
elem.Set(nv)
|
|
i++
|
|
}
|
|
return v, nil
|
|
case reflect.Interface:
|
|
// Build the []interface{} natively: each element is decoded straight
|
|
// into a Go value with no intermediate addressable reflect.Value and
|
|
// no reflect round-trip, and nested arrays recurse the same way.
|
|
slice := make([]interface{}, 0, count)
|
|
it := value.Children()
|
|
for it.Next() {
|
|
n := it.Node()
|
|
if n.Kind == unstable.Comment {
|
|
continue
|
|
}
|
|
ev, err := d.decodeAny(n)
|
|
if err != nil {
|
|
return reflect.Value{}, err
|
|
}
|
|
slice = append(slice, ev)
|
|
}
|
|
return boxInto(v, reflect.ValueOf(slice))
|
|
default:
|
|
}
|
|
return reflect.Value{}, d.typeMismatchError("array", v.Type(), d.rawValue(expr, value))
|
|
}
|
|
|
|
// decodeAny decodes a value node into a native Go value (the representation
|
|
// used for interface{} targets), without going through reflect. Scalars and
|
|
// arrays are handled directly; inline tables still defer to the reflect-based
|
|
// path so that their dotted-key merge semantics remain identical.
|
|
func (d *decoder) decodeAny(n *unstable.Node) (interface{}, error) {
|
|
switch n.Kind {
|
|
case unstable.String:
|
|
return string(n.Data), nil
|
|
case unstable.Integer:
|
|
i, err := parseInteger(n.Data)
|
|
return i, err
|
|
case unstable.Float:
|
|
f, err := parseFloat(n.Data)
|
|
return f, err
|
|
case unstable.Bool:
|
|
return n.Data[0] == 't', nil
|
|
case unstable.Array:
|
|
count := 0
|
|
cit := n.Children()
|
|
for cit.Next() {
|
|
if cit.Node().Kind != unstable.Comment {
|
|
count++
|
|
}
|
|
}
|
|
slice := make([]interface{}, 0, count)
|
|
it := n.Children()
|
|
for it.Next() {
|
|
c := it.Node()
|
|
if c.Kind == unstable.Comment {
|
|
continue
|
|
}
|
|
ev, err := d.decodeAny(c)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
slice = append(slice, ev)
|
|
}
|
|
return slice, nil
|
|
case unstable.InlineTable:
|
|
// Build the map natively: navigate each (possibly dotted) key with
|
|
// plain Go map operations and decode each value with decodeAny. The
|
|
// seen-tracker has already rejected duplicate or conflicting keys, so
|
|
// intermediate parts can be created/merged without revalidation.
|
|
count := 0
|
|
cit := n.Children()
|
|
for cit.Next() {
|
|
count++
|
|
}
|
|
m := make(map[string]interface{}, count)
|
|
it := n.Children()
|
|
for it.Next() {
|
|
kv := it.Node()
|
|
if err := d.setAnyKey(m, kv.Key(), kv.Value()); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
return m, nil
|
|
case unstable.DateTime:
|
|
t, err := parseDateTime(n.Data)
|
|
return t, err
|
|
case unstable.LocalDateTime:
|
|
dt, rest, err := parseLocalDateTime(n.Data)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if len(rest) > 0 {
|
|
return nil, unstable.NewParserError(rest, "extra characters at the end of a local date time")
|
|
}
|
|
return dt, nil
|
|
case unstable.LocalDate:
|
|
date, err := parseLocalDate(n.Data)
|
|
return date, err
|
|
case unstable.LocalTime:
|
|
t, rest, err := parseLocalTime(n.Data)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if len(rest) > 0 {
|
|
return nil, unstable.NewParserError(rest, "extra characters at the end of a local time")
|
|
}
|
|
return t, nil
|
|
default:
|
|
return nil, unstable.NewParserError(n.Data, "unsupported value kind %s", n.Kind)
|
|
}
|
|
}
|
|
|
|
func (d *decoder) assignInlineTable(v reflect.Value, expr *unstable.Node, value *unstable.Node) (reflect.Value, error) {
|
|
switch v.Kind() {
|
|
case reflect.Map:
|
|
// Inline tables are self-contained: they fully replace the target.
|
|
v = reflect.MakeMap(v.Type())
|
|
case reflect.Struct:
|
|
// fields are set in place
|
|
case reflect.Interface:
|
|
elem := reflect.ValueOf(map[string]interface{}{})
|
|
nv, err := d.assignInlineTable(elem, expr, value)
|
|
if err != nil {
|
|
return reflect.Value{}, err
|
|
}
|
|
return boxInto(v, nv)
|
|
default:
|
|
return reflect.Value{}, d.typeMismatchError("inline table", v.Type(), d.rawValue(expr, value))
|
|
}
|
|
|
|
it := value.Children()
|
|
for it.Next() {
|
|
kv := it.Node()
|
|
// Build the path from the key of this key-value. Keys of inline
|
|
// tables rarely have more than a few parts.
|
|
var pathBuf [4]pathPart
|
|
path := pathBuf[:0]
|
|
kit := kv.Key()
|
|
for kit.Next() {
|
|
path = append(path, pathPart{node: kit.Node()})
|
|
}
|
|
nv, err := d.descend(v, path, 0, kv, kv.Value())
|
|
if err != nil {
|
|
return reflect.Value{}, err
|
|
}
|
|
if nv.IsValid() {
|
|
v = nv
|
|
}
|
|
}
|
|
return v, nil
|
|
}
|
|
|
|
// boxInto returns the value to store in place of the interface value v. The
|
|
// caller stores the result in the slot v was found in, which performs the
|
|
// interface conversion, so the concrete value can be returned as-is.
|
|
func boxInto(v reflect.Value, c reflect.Value) (reflect.Value, error) {
|
|
if !c.Type().AssignableTo(v.Type()) {
|
|
return reflect.Value{}, fmt.Errorf("toml: cannot store %s into %s", c.Type(), v.Type())
|
|
}
|
|
return c, nil
|
|
}
|
|
|
|
var (
|
|
interfaceType = reflect.TypeOf(new(interface{})).Elem()
|
|
localDateType = reflect.TypeOf(LocalDate{})
|
|
localTimeType = reflect.TypeOf(LocalTime{})
|
|
localDateTimeType = reflect.TypeOf(LocalDateTime{})
|
|
)
|
|
|
|
// structPlan caches the mapping between TOML keys and the fields of a struct
|
|
// type. byFold, keyed by the lowercased name, resolves any key on its own when
|
|
// no two fields fold to the same name (the overwhelmingly common case, marked
|
|
// by hasCollision == false): TOML keys are usually lowercase and never match
|
|
// the exact (capitalized) Go field names, so the byName probe was always a
|
|
// wasted lookup. byName (the exact names) is only consulted, first, when
|
|
// fields do collide under folding, to preserve the exact-match-wins tiebreak.
|
|
type structPlan struct {
|
|
byName map[string]structField
|
|
byFold map[string]structField
|
|
hasCollision bool
|
|
}
|
|
|
|
type structField struct {
|
|
index []int
|
|
fieldName string
|
|
}
|
|
|
|
// foldBufSize bounds the stack buffer used to lowercase keys without
|
|
// allocating. Keys longer than this (extremely rare) take the strings.ToLower
|
|
// fallback.
|
|
const foldBufSize = 68
|
|
|
|
// lookup and lookupBytes keep the hot path to a single inlinable byFold lookup.
|
|
// byFold is indexed by both the exact field/tag names and their lowercased
|
|
// forms, so that lookup resolves the two common cases — a lowercase key, or a
|
|
// key matching the field's own casing — directly. byName is consulted first
|
|
// only for types whose fields collide under case-folding, to preserve the
|
|
// exact-match-wins tiebreak. The buffer-fold for other casings lives
|
|
// out-of-line so it does not bloat the hot path.
|
|
func (p *structPlan) lookup(name string) (structField, bool) {
|
|
if p.hasCollision {
|
|
if f, ok := p.byName[name]; ok {
|
|
return f, true
|
|
}
|
|
}
|
|
if f, ok := p.byFold[name]; ok {
|
|
return f, true
|
|
}
|
|
return p.lookupFoldStr(name)
|
|
}
|
|
|
|
func (p *structPlan) lookupBytes(name []byte) (structField, bool) {
|
|
if p.hasCollision {
|
|
if f, ok := p.byName[string(name)]; ok { // does not allocate
|
|
return f, true
|
|
}
|
|
}
|
|
if f, ok := p.byFold[string(name)]; ok { // does not allocate
|
|
return f, true
|
|
}
|
|
return p.lookupFold(name)
|
|
}
|
|
|
|
// lookupFold resolves keys whose casing matches neither the exact nor the
|
|
// lowercased index: it folds to lowercase (in a stack buffer for ASCII, so no
|
|
// allocation) and retries; only non-ASCII or oversized keys hit strings.ToLower.
|
|
func (p *structPlan) lookupFold(name []byte) (structField, bool) {
|
|
if len(name) <= foldBufSize {
|
|
// Fold into a stack buffer: len(name) <= cap(buf), so the append
|
|
// never reallocates and nothing escapes to the heap.
|
|
var buf [foldBufSize]byte
|
|
b := buf[:0]
|
|
ascii := true
|
|
for _, c := range name {
|
|
if c >= 0x80 {
|
|
ascii = false
|
|
break
|
|
}
|
|
if c >= 'A' && c <= 'Z' {
|
|
c += 'a' - 'A'
|
|
}
|
|
b = append(b, c)
|
|
}
|
|
if ascii {
|
|
f, ok := p.byFold[string(b)] // does not allocate
|
|
return f, ok
|
|
}
|
|
}
|
|
f, ok := p.byFold[strings.ToLower(string(name))]
|
|
return f, ok
|
|
}
|
|
|
|
func (p *structPlan) lookupFoldStr(name string) (structField, bool) {
|
|
if len(name) <= foldBufSize {
|
|
// Fold into a stack buffer: len(name) <= cap(buf), so the append
|
|
// never reallocates and nothing escapes to the heap.
|
|
var buf [foldBufSize]byte
|
|
b := buf[:0]
|
|
ascii := true
|
|
for i := 0; i < len(name); i++ {
|
|
c := name[i]
|
|
if c >= 0x80 {
|
|
ascii = false
|
|
break
|
|
}
|
|
if c >= 'A' && c <= 'Z' {
|
|
c += 'a' - 'A'
|
|
}
|
|
b = append(b, c)
|
|
}
|
|
if ascii {
|
|
f, ok := p.byFold[string(b)] // does not allocate
|
|
return f, ok
|
|
}
|
|
}
|
|
f, ok := p.byFold[strings.ToLower(name)]
|
|
return f, ok
|
|
}
|
|
|
|
var structPlans sync.Map // reflect.Type -> *structPlan
|
|
|
|
func planForType(t reflect.Type) *structPlan {
|
|
if plan, ok := structPlans.Load(t); ok {
|
|
return plan.(*structPlan)
|
|
}
|
|
plan := buildPlan(t)
|
|
structPlans.Store(t, plan)
|
|
return plan
|
|
}
|
|
|
|
func buildPlan(t reflect.Type) *structPlan {
|
|
plan := &structPlan{
|
|
byName: map[string]structField{},
|
|
byFold: map[string]structField{},
|
|
}
|
|
addFields(plan, t, nil, map[reflect.Type]bool{})
|
|
return plan
|
|
}
|
|
|
|
// addFields flattens the fields of t into the plan. visited holds the types
|
|
// being flattened on the current branch: a struct type that embeds itself
|
|
// (directly or through other types) is not descended into again, mirroring
|
|
// buildEncPlan. Without this guard a recursive embedding would recurse
|
|
// forever; the repeated fields are unreachable by flattening anyway.
|
|
func addFields(plan *structPlan, t reflect.Type, prefix []int, visited map[reflect.Type]bool) {
|
|
if visited[t] {
|
|
return
|
|
}
|
|
visited[t] = true
|
|
defer delete(visited, t)
|
|
|
|
var embedded []reflect.StructField
|
|
for i := 0; i < t.NumField(); i++ {
|
|
f := t.Field(i)
|
|
tag, tagged := f.Tag.Lookup("toml")
|
|
name := f.Name
|
|
explicitName := ""
|
|
if tagged {
|
|
// A tag of exactly "-" drops the field. "-," names it "-".
|
|
if tag == "-" {
|
|
continue
|
|
}
|
|
parts := strings.SplitN(tag, ",", 2)
|
|
explicitName = parts[0]
|
|
if explicitName != "" {
|
|
name = explicitName
|
|
}
|
|
}
|
|
if f.Anonymous {
|
|
ft := f.Type
|
|
if ft.Kind() == reflect.Ptr {
|
|
ft = ft.Elem()
|
|
}
|
|
if ft.Kind() != reflect.Struct {
|
|
// Embedded non-struct fields are not decoded into.
|
|
continue
|
|
}
|
|
if explicitName == "" {
|
|
// Embedded structs without an explicit tag name are flattened,
|
|
// even when their type is unexported: only their own exported
|
|
// fields are reachable. A tag that only sets options (e.g.
|
|
// `,inline`) still flattens, matching encoding/json and the
|
|
// encoder's behavior.
|
|
embedded = append(embedded, f)
|
|
continue
|
|
}
|
|
// An embedded struct given an explicit tag name acts as a regular
|
|
// named field.
|
|
} else if f.PkgPath != "" {
|
|
// unexported
|
|
continue
|
|
}
|
|
index := make([]int, 0, len(prefix)+1)
|
|
index = append(index, prefix...)
|
|
index = append(index, i)
|
|
sf := structField{index: index, fieldName: f.Name}
|
|
if _, ok := plan.byName[name]; !ok {
|
|
plan.byName[name] = sf
|
|
}
|
|
lower := strings.ToLower(name)
|
|
if _, ok := plan.byFold[lower]; !ok {
|
|
plan.byFold[lower] = sf
|
|
} else {
|
|
// Two distinct fields fold to the same name: case-insensitive
|
|
// matching is ambiguous, so lookups must consult byName first to
|
|
// keep the exact-match-wins tiebreak deterministic.
|
|
plan.hasCollision = true
|
|
}
|
|
// Index the exact (cased) name as well, so a key written with the
|
|
// field's own casing resolves in a single byFold lookup. Only fields
|
|
// whose name is not already lowercase need this extra entry. Any name
|
|
// that would conflict here also collides under folding (handled
|
|
// above), so byName-first preserves the exact tiebreak in that case.
|
|
if name != lower {
|
|
if _, ok := plan.byFold[name]; !ok {
|
|
plan.byFold[name] = sf
|
|
}
|
|
}
|
|
}
|
|
// Embedded structs are flattened after the regular fields, so that
|
|
// shallower fields win.
|
|
for _, f := range embedded {
|
|
ft := f.Type
|
|
if ft.Kind() == reflect.Ptr {
|
|
ft = ft.Elem()
|
|
}
|
|
index := make([]int, 0, len(prefix)+1)
|
|
index = append(index, prefix...)
|
|
idx := f.Index[0]
|
|
index = append(index, idx)
|
|
addFields(plan, ft, index, visited)
|
|
}
|
|
}
|
|
|
|
// fieldByIndexAlloc returns the field of v at the given index path,
|
|
// allocating intermediate embedded pointers as needed.
|
|
func fieldByIndexAlloc(v reflect.Value, index []int) (reflect.Value, error) {
|
|
// Fast path for non-embedded fields, which have a single-element index:
|
|
// no intermediate pointer dereferencing is possible.
|
|
if len(index) == 1 {
|
|
return v.Field(index[0]), nil
|
|
}
|
|
for i, x := range index {
|
|
if i > 0 {
|
|
for v.Kind() == reflect.Ptr {
|
|
if v.IsNil() {
|
|
if !v.CanSet() {
|
|
// A nil embedded pointer of unexported type cannot be
|
|
// allocated: reflect forbids setting it. Match
|
|
// encoding/json and report it instead of panicking.
|
|
return reflect.Value{}, fmt.Errorf("toml: cannot set embedded pointer to unexported struct: %s", v.Type().Elem())
|
|
}
|
|
v.Set(reflect.New(v.Type().Elem()))
|
|
}
|
|
v = v.Elem()
|
|
}
|
|
}
|
|
v = v.Field(x)
|
|
}
|
|
return v, nil
|
|
}
|