exploreRecursive.go 7.28 KB
Newer Older
1 2 3 4 5
package selector

import (
	"fmt"

tavit ohanian's avatar
tavit ohanian committed
6
	ld "gitlab.dms3.io/ld/go-ld-prime"
7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
)

// ExploreRecursive traverses some structure recursively.
// To guide this exploration, it uses a "sequence", which is another Selector
// tree; some leaf node in this sequence should contain an ExploreRecursiveEdge
// selector, which denotes the place recursion should occur.
//
// In implementation, whenever evaluation reaches an ExploreRecursiveEdge marker
// in the recursion sequence's Selector tree, the implementation logically
// produces another new Selector which is a copy of the original
// ExploreRecursive selector, but with a decremented maxDepth parameter, and
// continues evaluation thusly.
//
// It is not valid for an ExploreRecursive selector's sequence to contain
// no instances of ExploreRecursiveEdge; it *is* valid for it to contain
// more than one ExploreRecursiveEdge.
//
// ExploreRecursive can contain a nested ExploreRecursive!
// This is comparable to a nested for-loop.
// In these cases, any ExploreRecursiveEdge instance always refers to the
// nearest parent ExploreRecursive (in other words, ExploreRecursiveEdge can
// be thought of like the 'continue' statement, or end of a for-loop body;
// it is *not* a 'goto' statement).
//
// Be careful when using ExploreRecursive with a large maxDepth parameter;
// it can easily cause very large traversals (especially if used in combination
// with selectors like ExploreAll inside the sequence).
type ExploreRecursive struct {
35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55
	sequence Selector       // selector for element we're interested in
	current  Selector       // selector to apply to the current node
	limit    RecursionLimit // the limit for this recursive selector
}

// RecursionLimit_Mode is an enum that represents the type of a recursion limit
// -- either "depth" or "none" for now
type RecursionLimit_Mode uint8

const (
	// RecursionLimit_None means there is no recursion limit
	RecursionLimit_None RecursionLimit_Mode = 0
	// RecursionLimit_Depth mean recursion stops after the recursive selector
	// is copied to a given depth
	RecursionLimit_Depth RecursionLimit_Mode = 1
)

// RecursionLimit is a union type that captures all data about the recursion
// limit (both its type and data specific to the type)
type RecursionLimit struct {
	mode  RecursionLimit_Mode
56
	depth int64
57 58 59 60 61 62 63 64
}

// Mode returns the type for this recursion limit
func (rl RecursionLimit) Mode() RecursionLimit_Mode {
	return rl.mode
}

// Depth returns the depth for a depth recursion limit, or 0 otherwise
65
func (rl RecursionLimit) Depth() int64 {
66 67 68 69 70 71 72
	if rl.mode != RecursionLimit_Depth {
		return 0
	}
	return rl.depth
}

// RecursionLimitDepth returns a depth limited recursion to the given depth
73
func RecursionLimitDepth(depth int64) RecursionLimit {
74 75 76 77 78 79
	return RecursionLimit{RecursionLimit_Depth, depth}
}

// RecursionLimitNone return recursion with no limit
func RecursionLimitNone() RecursionLimit {
	return RecursionLimit{RecursionLimit_None, 0}
80 81 82
}

// Interests for ExploreRecursive is empty (meaning traverse everything)
tavit ohanian's avatar
tavit ohanian committed
83
func (s ExploreRecursive) Interests() []ld.PathSegment {
84 85 86 87
	return s.current.Interests()
}

// Explore returns the node's selector for all fields
tavit ohanian's avatar
tavit ohanian committed
88
func (s ExploreRecursive) Explore(n ld.Node, p ld.PathSegment) Selector {
89
	nextSelector := s.current.Explore(n, p)
90
	limit := s.limit
91

92 93 94
	if nextSelector == nil {
		return nil
	}
95
	if !s.hasRecursiveEdge(nextSelector) {
96
		return ExploreRecursive{s.sequence, nextSelector, limit}
97
	}
98 99 100
	switch limit.mode {
	case RecursionLimit_Depth:
		if limit.depth < 2 {
101 102
			return s.replaceRecursiveEdge(nextSelector, nil)
		}
103 104 105 106 107
		return ExploreRecursive{s.sequence, s.replaceRecursiveEdge(nextSelector, s.sequence), RecursionLimit{RecursionLimit_Depth, limit.depth - 1}}
	case RecursionLimit_None:
		return ExploreRecursive{s.sequence, s.replaceRecursiveEdge(nextSelector, s.sequence), limit}
	default:
		panic("Unsupported recursion limit type")
108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147
	}
}

func (s ExploreRecursive) hasRecursiveEdge(nextSelector Selector) bool {
	_, isRecursiveEdge := nextSelector.(ExploreRecursiveEdge)
	if isRecursiveEdge {
		return true
	}
	exploreUnion, isUnion := nextSelector.(ExploreUnion)
	if isUnion {
		for _, selector := range exploreUnion.Members {
			if s.hasRecursiveEdge(selector) {
				return true
			}
		}
	}
	return false
}

func (s ExploreRecursive) replaceRecursiveEdge(nextSelector Selector, replacement Selector) Selector {
	_, isRecursiveEdge := nextSelector.(ExploreRecursiveEdge)
	if isRecursiveEdge {
		return replacement
	}
	exploreUnion, isUnion := nextSelector.(ExploreUnion)
	if isUnion {
		replacementMembers := make([]Selector, 0, len(exploreUnion.Members))
		for _, selector := range exploreUnion.Members {
			newSelector := s.replaceRecursiveEdge(selector, replacement)
			if newSelector != nil {
				replacementMembers = append(replacementMembers, newSelector)
			}
		}
		if len(replacementMembers) == 0 {
			return nil
		}
		if len(replacementMembers) == 1 {
			return replacementMembers[0]
		}
		return ExploreUnion{replacementMembers}
148
	}
149
	return nextSelector
150 151 152
}

// Decide always returns false because this is not a matcher
tavit ohanian's avatar
tavit ohanian committed
153
func (s ExploreRecursive) Decide(n ld.Node) bool {
154 155 156
	return s.current.Decide(n)
}

157 158 159 160 161 162 163 164 165 166 167 168
type exploreRecursiveContext struct {
	edgesFound int
}

func (erc *exploreRecursiveContext) Link(s Selector) bool {
	_, ok := s.(ExploreRecursiveEdge)
	if ok {
		erc.edgesFound++
	}
	return ok
}

169
// ParseExploreRecursive assembles a Selector from a ExploreRecursive selector node
tavit ohanian's avatar
tavit ohanian committed
170 171
func (pc ParseContext) ParseExploreRecursive(n ld.Node) (Selector, error) {
	if n.Kind() != ld.Kind_Map {
172 173 174
		return nil, fmt.Errorf("selector spec parse rejected: selector body must be a map")
	}

175
	limitNode, err := n.LookupByString(SelectorKey_Limit)
176
	if err != nil {
177
		return nil, fmt.Errorf("selector spec parse rejected: limit field must be present in ExploreRecursive selector")
178
	}
179
	limit, err := parseLimit(limitNode)
180
	if err != nil {
181
		return nil, err
182
	}
183
	sequence, err := n.LookupByString(SelectorKey_Sequence)
184 185 186
	if err != nil {
		return nil, fmt.Errorf("selector spec parse rejected: sequence field must be present in ExploreRecursive selector")
	}
187
	erc := &exploreRecursiveContext{}
188
	selector, err := pc.PushParent(erc).ParseSelector(sequence)
189 190 191
	if err != nil {
		return nil, err
	}
192 193 194
	if erc.edgesFound == 0 {
		return nil, fmt.Errorf("selector spec parse rejected: ExploreRecursive must have at least one ExploreRecursiveEdge")
	}
195
	return ExploreRecursive{selector, selector, limit}, nil
196 197
}

tavit ohanian's avatar
tavit ohanian committed
198 199
func parseLimit(n ld.Node) (RecursionLimit, error) {
	if n.Kind() != ld.Kind_Map {
200
		return RecursionLimit{}, fmt.Errorf("selector spec parse rejected: limit in ExploreRecursive is a keyed union and thus must be a map")
201 202
	}
	if n.Length() != 1 {
203
		return RecursionLimit{}, fmt.Errorf("selector spec parse rejected: limit in ExploreRecursive is a keyed union and thus must be a single-entry map")
204 205 206 207 208 209 210
	}
	kn, v, _ := n.MapIterator().Next()
	kstr, _ := kn.AsString()
	switch kstr {
	case SelectorKey_LimitDepth:
		maxDepthValue, err := v.AsInt()
		if err != nil {
211
			return RecursionLimit{}, fmt.Errorf("selector spec parse rejected: limit field of type depth must be a number in ExploreRecursive selector")
212
		}
213
		return RecursionLimit{RecursionLimit_Depth, maxDepthValue}, nil
214
	case SelectorKey_LimitNone:
215
		return RecursionLimit{RecursionLimit_None, 0}, nil
216
	default:
217
		return RecursionLimit{}, fmt.Errorf("selector spec parse rejected: %q is not a known member of the limit union in ExploreRecursive", kstr)
218
	}
219
}