95 lines
2.6 KiB
Go
95 lines
2.6 KiB
Go
package engine
|
|
|
|
import "gitea.maximumdirect.net/eric/ConfigKit/internal/document"
|
|
|
|
// walkDocument visits value nodes in preorder, preserving stored mapping order
|
|
// and sequence index order. Mapping keys are not separate nodes in this walk.
|
|
// The callback borrows paths and nodes; clone any mutable state it retains.
|
|
// Specialized origin, pruning, and budget traversals retain their own semantics.
|
|
func walkDocument(node *document.Node, path Path, visit func(Path, *document.Node)) {
|
|
if node == nil {
|
|
return
|
|
}
|
|
visit(path, node)
|
|
switch node.Kind {
|
|
case document.Mapping:
|
|
for _, entry := range node.Mapping {
|
|
walkDocument(entry.Value, path.Key(entry.Key), visit)
|
|
}
|
|
case document.Sequence:
|
|
for index, child := range node.Sequence {
|
|
walkDocument(child, path.Index(index), visit)
|
|
}
|
|
}
|
|
}
|
|
|
|
// indexDocument borrows nodes from its caller-owned tree.
|
|
func indexDocument(node *document.Node, path Path, index map[string]*document.Node) {
|
|
walkDocument(node, path, func(path Path, node *document.Node) {
|
|
index[path.String()] = node
|
|
})
|
|
}
|
|
|
|
func documentAtPath(root *document.Node, path Path) (*document.Node, bool) {
|
|
return documentAtPathWithLookup(root, path, nil)
|
|
}
|
|
|
|
func documentAtPathWithLookup(root *document.Node, path Path, lookup *documentLookup) (*document.Node, bool) {
|
|
if root == nil || !path.valid() {
|
|
return nil, false
|
|
}
|
|
current := root
|
|
for _, segment := range path.segments {
|
|
switch current.Kind {
|
|
case document.Mapping:
|
|
if segment.kind == pathSegmentIndex {
|
|
return nil, false
|
|
}
|
|
entryIndex := -1
|
|
if lookup != nil {
|
|
entryIndex = lookup.find(current, segment.key)
|
|
} else {
|
|
for index, entry := range current.Mapping {
|
|
if entry.Key == segment.key {
|
|
entryIndex = index
|
|
break
|
|
}
|
|
}
|
|
}
|
|
if entryIndex < 0 || entryIndex >= len(current.Mapping) {
|
|
return nil, false
|
|
}
|
|
current = current.Mapping[entryIndex].Value
|
|
case document.Sequence:
|
|
index, ok := schemaIndex(segment)
|
|
if !ok || index >= len(current.Sequence) {
|
|
return nil, false
|
|
}
|
|
current = current.Sequence[index]
|
|
default:
|
|
return nil, false
|
|
}
|
|
}
|
|
return current, true
|
|
}
|
|
|
|
// countInputNodes includes mapping-key nodes, unlike walkDocument's value-only
|
|
// traversal. Aggregate source budgets charge every input occurrence.
|
|
func countInputNodes(node *document.Node) int {
|
|
if node == nil {
|
|
return 0
|
|
}
|
|
count := 1
|
|
if node.Kind == document.Mapping {
|
|
count += len(node.Mapping)
|
|
for _, entry := range node.Mapping {
|
|
count += countInputNodes(entry.Value)
|
|
}
|
|
} else if node.Kind == document.Sequence {
|
|
for _, child := range node.Sequence {
|
|
count += countInputNodes(child)
|
|
}
|
|
}
|
|
return count
|
|
}
|