Path: blob/main/vendor/github.com/pelletier/go-toml/v2/internal/tracker/seen.go
2898 views
package tracker12import (3"bytes"4"fmt"5"sync"67"github.com/pelletier/go-toml/v2/unstable"8)910type keyKind uint81112const (13invalidKind keyKind = iota14valueKind15tableKind16arrayTableKind17)1819func (k keyKind) String() string {20switch k {21case invalidKind:22return "invalid"23case valueKind:24return "value"25case tableKind:26return "table"27case arrayTableKind:28return "array table"29}30panic("missing keyKind string mapping")31}3233// SeenTracker tracks which keys have been seen with which TOML type to flag34// duplicates and mismatches according to the spec.35//36// Each node in the visited tree is represented by an entry. Each entry has an37// identifier, which is provided by a counter. Entries are stored in the array38// entries. As new nodes are discovered (referenced for the first time in the39// TOML document), entries are created and appended to the array. An entry40// points to its parent using its id.41//42// To find whether a given key (sequence of []byte) has already been visited,43// the entries are linearly searched, looking for one with the right name and44// parent id.45//46// Given that all keys appear in the document after their parent, it is47// guaranteed that all descendants of a node are stored after the node, this48// speeds up the search process.49//50// When encountering [[array tables]], the descendants of that node are removed51// to allow that branch of the tree to be "rediscovered". To maintain the52// invariant above, the deletion process needs to keep the order of entries.53// This results in more copies in that case.54type SeenTracker struct {55entries []entry56currentIdx int57}5859var pool = sync.Pool{60New: func() interface{} {61return &SeenTracker{}62},63}6465func (s *SeenTracker) reset() {66// Always contains a root element at index 0.67s.currentIdx = 068if len(s.entries) == 0 {69s.entries = make([]entry, 1, 2)70} else {71s.entries = s.entries[:1]72}73s.entries[0].child = -174s.entries[0].next = -175}7677type entry struct {78// Use -1 to indicate no child or no sibling.79child int80next int8182name []byte83kind keyKind84explicit bool85kv bool86}8788// Find the index of the child of parentIdx with key k. Returns -1 if89// it does not exist.90func (s *SeenTracker) find(parentIdx int, k []byte) int {91for i := s.entries[parentIdx].child; i >= 0; i = s.entries[i].next {92if bytes.Equal(s.entries[i].name, k) {93return i94}95}96return -197}9899// Remove all descendants of node at position idx.100func (s *SeenTracker) clear(idx int) {101if idx >= len(s.entries) {102return103}104105for i := s.entries[idx].child; i >= 0; {106next := s.entries[i].next107n := s.entries[0].next108s.entries[0].next = i109s.entries[i].next = n110s.entries[i].name = nil111s.clear(i)112i = next113}114115s.entries[idx].child = -1116}117118func (s *SeenTracker) create(parentIdx int, name []byte, kind keyKind, explicit bool, kv bool) int {119e := entry{120child: -1,121next: s.entries[parentIdx].child,122123name: name,124kind: kind,125explicit: explicit,126kv: kv,127}128var idx int129if s.entries[0].next >= 0 {130idx = s.entries[0].next131s.entries[0].next = s.entries[idx].next132s.entries[idx] = e133} else {134idx = len(s.entries)135s.entries = append(s.entries, e)136}137138s.entries[parentIdx].child = idx139140return idx141}142143func (s *SeenTracker) setExplicitFlag(parentIdx int) {144for i := s.entries[parentIdx].child; i >= 0; i = s.entries[i].next {145if s.entries[i].kv {146s.entries[i].explicit = true147s.entries[i].kv = false148}149s.setExplicitFlag(i)150}151}152153// CheckExpression takes a top-level node and checks that it does not contain154// keys that have been seen in previous calls, and validates that types are155// consistent. It returns true if it is the first time this node's key is seen.156// Useful to clear array tables on first use.157func (s *SeenTracker) CheckExpression(node *unstable.Node) (bool, error) {158if s.entries == nil {159s.reset()160}161switch node.Kind {162case unstable.KeyValue:163return s.checkKeyValue(node)164case unstable.Table:165return s.checkTable(node)166case unstable.ArrayTable:167return s.checkArrayTable(node)168default:169panic(fmt.Errorf("this should not be a top level node type: %s", node.Kind))170}171}172173func (s *SeenTracker) checkTable(node *unstable.Node) (bool, error) {174if s.currentIdx >= 0 {175s.setExplicitFlag(s.currentIdx)176}177178it := node.Key()179180parentIdx := 0181182// This code is duplicated in checkArrayTable. This is because factoring183// it in a function requires to copy the iterator, or allocate it to the184// heap, which is not cheap.185for it.Next() {186if it.IsLast() {187break188}189190k := it.Node().Data191192idx := s.find(parentIdx, k)193194if idx < 0 {195idx = s.create(parentIdx, k, tableKind, false, false)196} else {197entry := s.entries[idx]198if entry.kind == valueKind {199return false, fmt.Errorf("toml: expected %s to be a table, not a %s", string(k), entry.kind)200}201}202parentIdx = idx203}204205k := it.Node().Data206idx := s.find(parentIdx, k)207208first := false209if idx >= 0 {210kind := s.entries[idx].kind211if kind != tableKind {212return false, fmt.Errorf("toml: key %s should be a table, not a %s", string(k), kind)213}214if s.entries[idx].explicit {215return false, fmt.Errorf("toml: table %s already exists", string(k))216}217s.entries[idx].explicit = true218} else {219idx = s.create(parentIdx, k, tableKind, true, false)220first = true221}222223s.currentIdx = idx224225return first, nil226}227228func (s *SeenTracker) checkArrayTable(node *unstable.Node) (bool, error) {229if s.currentIdx >= 0 {230s.setExplicitFlag(s.currentIdx)231}232233it := node.Key()234235parentIdx := 0236237for it.Next() {238if it.IsLast() {239break240}241242k := it.Node().Data243244idx := s.find(parentIdx, k)245246if idx < 0 {247idx = s.create(parentIdx, k, tableKind, false, false)248} else {249entry := s.entries[idx]250if entry.kind == valueKind {251return false, fmt.Errorf("toml: expected %s to be a table, not a %s", string(k), entry.kind)252}253}254255parentIdx = idx256}257258k := it.Node().Data259idx := s.find(parentIdx, k)260261firstTime := idx < 0262if firstTime {263idx = s.create(parentIdx, k, arrayTableKind, true, false)264} else {265kind := s.entries[idx].kind266if kind != arrayTableKind {267return false, fmt.Errorf("toml: key %s already exists as a %s, but should be an array table", kind, string(k))268}269s.clear(idx)270}271272s.currentIdx = idx273274return firstTime, nil275}276277func (s *SeenTracker) checkKeyValue(node *unstable.Node) (bool, error) {278parentIdx := s.currentIdx279it := node.Key()280281for it.Next() {282k := it.Node().Data283284idx := s.find(parentIdx, k)285286if idx < 0 {287idx = s.create(parentIdx, k, tableKind, false, true)288} else {289entry := s.entries[idx]290if it.IsLast() {291return false, fmt.Errorf("toml: key %s is already defined", string(k))292} else if entry.kind != tableKind {293return false, fmt.Errorf("toml: expected %s to be a table, not a %s", string(k), entry.kind)294} else if entry.explicit {295return false, fmt.Errorf("toml: cannot redefine table %s that has already been explicitly defined", string(k))296}297}298299parentIdx = idx300}301302s.entries[parentIdx].kind = valueKind303304value := node.Value()305306switch value.Kind {307case unstable.InlineTable:308return s.checkInlineTable(value)309case unstable.Array:310return s.checkArray(value)311}312313return false, nil314}315316func (s *SeenTracker) checkArray(node *unstable.Node) (first bool, err error) {317it := node.Children()318for it.Next() {319n := it.Node()320switch n.Kind {321case unstable.InlineTable:322first, err = s.checkInlineTable(n)323if err != nil {324return false, err325}326case unstable.Array:327first, err = s.checkArray(n)328if err != nil {329return false, err330}331}332}333return first, nil334}335336func (s *SeenTracker) checkInlineTable(node *unstable.Node) (first bool, err error) {337s = pool.Get().(*SeenTracker)338s.reset()339340it := node.Children()341for it.Next() {342n := it.Node()343first, err = s.checkKeyValue(n)344if err != nil {345return false, err346}347}348349// As inline tables are self-contained, the tracker does not350// need to retain the details of what they contain. The351// keyValue element that creates the inline table is kept to352// mark the presence of the inline table and prevent353// redefinition of its keys: check* functions cannot walk into354// a value.355pool.Put(s)356return first, nil357}358359360