walk.go 7.61 KB
Newer Older
1 2 3
package traversal

import (
hannahhoward's avatar
hannahhoward committed
4 5
	"fmt"

tavit ohanian's avatar
tavit ohanian committed
6
	ld "gitlab.dms3.io/ld/go-ld-prime"
tavit ohanian's avatar
tavit ohanian committed
7
	"gitlab.dms3.io/ld/go-ld-prime/traversal/selector"
8 9
)

Eric Myhre's avatar
Eric Myhre committed
10 11 12 13 14 15 16
// WalkMatching walks a graph of Nodes, deciding which to visit by applying a Selector,
// and calling the given VisitFn on those that the Selector deems a match.
//
// This function is a helper function which starts a new walk with default configuration.
// It cannot cross links automatically (since this requires configuration).
// Use the equivalent WalkMatching function on the Progress structure
// for more advanced and configurable walks.
tavit ohanian's avatar
tavit ohanian committed
17
func WalkMatching(n ld.Node, s selector.Selector, fn VisitFn) error {
18
	return Progress{}.WalkMatching(n, s, fn)
19 20
}

Eric Myhre's avatar
Eric Myhre committed
21 22 23 24 25 26 27 28
// WalkAdv is identical to WalkMatching, except it is called for *all* nodes
// visited (not just matching nodes), together with the reason for the visit.
// An AdvVisitFn is used instead of a VisitFn, so that the reason can be provided.
//
// This function is a helper function which starts a new walk with default configuration.
// It cannot cross links automatically (since this requires configuration).
// Use the equivalent WalkAdv function on the Progress structure
// for more advanced and configurable walks.
tavit ohanian's avatar
tavit ohanian committed
29
func WalkAdv(n ld.Node, s selector.Selector, fn AdvVisitFn) error {
30
	return Progress{}.WalkAdv(n, s, fn)
31 32
}

Eric Myhre's avatar
Eric Myhre committed
33 34 35 36 37 38 39 40
// WalkTransforming walks a graph of Nodes, deciding which to alter by applying a Selector,
// and calls the given TransformFn to decide what new node to replace the visited node with.
// A new Node tree will be returned (the original is unchanged).
//
// This function is a helper function which starts a new walk with default configuration.
// It cannot cross links automatically (since this requires configuration).
// Use the equivalent WalkTransforming function on the Progress structure
// for more advanced and configurable walks.
tavit ohanian's avatar
tavit ohanian committed
41
func WalkTransforming(n ld.Node, s selector.Selector, fn TransformFn) (ld.Node, error) {
42
	return Progress{}.WalkTransforming(n, s, fn)
43 44
}

Eric Myhre's avatar
Eric Myhre committed
45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
// WalkMatching walks a graph of Nodes, deciding which to visit by applying a Selector,
// and calling the given VisitFn on those that the Selector deems a match.
//
// WalkMatching is a read-only traversal.
// See WalkTransforming if looking for a way to do "updates" to a tree of nodes.
//
// Provide configuration to this process using the Config field in the Progress object.
//
// This walk will automatically cross links, but requires some configuration
// with link loading functions to do so.
//
// Traversals are defined as visiting a (node,path) tuple.
// This is important to note because when walking DAGs with Links,
// it means you may visit the same node multiple times
// due to having reached it via a different path.
60 61
// (You can prevent this by using a LinkLoader function which memoizes a set of
// already-visited Links, and returns a SkipMe when encountering them again.)
Eric Myhre's avatar
Eric Myhre committed
62 63 64 65 66 67
//
// WalkMatching (and the other traversal functions) can be used again again inside the VisitFn!
// By using the traversal.Progress handed to the VisitFn,
// the Path recorded of the traversal so far will continue to be extended,
// and thus continued nested uses of Walk and Focus will see the fully contextualized Path.
//
tavit ohanian's avatar
tavit ohanian committed
68
func (prog Progress) WalkMatching(n ld.Node, s selector.Selector, fn VisitFn) error {
Eric Myhre's avatar
Eric Myhre committed
69
	prog.init()
tavit ohanian's avatar
tavit ohanian committed
70
	return prog.walkAdv(n, s, func(prog Progress, n ld.Node, tr VisitReason) error {
71
		if tr != VisitReason_SelectionMatch {
72 73
			return nil
		}
Eric Myhre's avatar
Eric Myhre committed
74
		return fn(prog, n)
75 76 77
	})
}

Eric Myhre's avatar
Eric Myhre committed
78 79 80 81
// WalkAdv is identical to WalkMatching, except it is called for *all* nodes
// visited (not just matching nodes), together with the reason for the visit.
// An AdvVisitFn is used instead of a VisitFn, so that the reason can be provided.
//
tavit ohanian's avatar
tavit ohanian committed
82
func (prog Progress) WalkAdv(n ld.Node, s selector.Selector, fn AdvVisitFn) error {
Eric Myhre's avatar
Eric Myhre committed
83 84
	prog.init()
	return prog.walkAdv(n, s, fn)
Eric Myhre's avatar
Eric Myhre committed
85 86
}

tavit ohanian's avatar
tavit ohanian committed
87
func (prog Progress) walkAdv(n ld.Node, s selector.Selector, fn AdvVisitFn) error {
Eric Myhre's avatar
Eric Myhre committed
88
	if s.Decide(n) {
Eric Myhre's avatar
Eric Myhre committed
89
		if err := fn(prog, n, VisitReason_SelectionMatch); err != nil {
Eric Myhre's avatar
Eric Myhre committed
90 91 92
			return err
		}
	} else {
Eric Myhre's avatar
Eric Myhre committed
93
		if err := fn(prog, n, VisitReason_SelectionCandidate); err != nil {
Eric Myhre's avatar
Eric Myhre committed
94 95 96
			return err
		}
	}
97
	nk := n.Kind()
Eric Myhre's avatar
Eric Myhre committed
98
	switch nk {
tavit ohanian's avatar
tavit ohanian committed
99
	case ld.Kind_Map, ld.Kind_List: // continue
Eric Myhre's avatar
Eric Myhre committed
100 101 102
	default:
		return nil
	}
103 104
	attn := s.Interests()
	if attn == nil {
Eric Myhre's avatar
Eric Myhre committed
105
		return prog.walkAdv_iterateAll(n, s, fn)
106
	}
Eric Myhre's avatar
Eric Myhre committed
107
	return prog.walkAdv_iterateSelective(n, attn, s, fn)
108 109 110

}

tavit ohanian's avatar
tavit ohanian committed
111
func (prog Progress) walkAdv_iterateAll(n ld.Node, s selector.Selector, fn AdvVisitFn) error {
112 113 114 115 116 117 118
	for itr := selector.NewSegmentIterator(n); !itr.Done(); {
		ps, v, err := itr.Next()
		if err != nil {
			return err
		}
		sNext := s.Explore(n, ps)
		if sNext != nil {
Eric Myhre's avatar
Eric Myhre committed
119
			progNext := prog
120
			progNext.Path = prog.Path.AppendSegment(ps)
tavit ohanian's avatar
tavit ohanian committed
121
			if v.Kind() == ld.Kind_Link {
122
				lnk, _ := v.AsLink()
Eric Myhre's avatar
Eric Myhre committed
123 124 125
				progNext.LastBlock.Path = progNext.Path
				progNext.LastBlock.Link = lnk
				v, err = progNext.loadLink(v, n)
126
				if err != nil {
127 128 129
					if _, ok := err.(SkipMe); ok {
						return nil
					}
130
					return err
hannahhoward's avatar
hannahhoward committed
131
				}
132
			}
133

Eric Myhre's avatar
Eric Myhre committed
134
			err = progNext.walkAdv(v, sNext, fn)
135
			if err != nil {
136
				return err
137
			}
138 139 140 141 142
		}
	}
	return nil
}

tavit ohanian's avatar
tavit ohanian committed
143
func (prog Progress) walkAdv_iterateSelective(n ld.Node, attn []ld.PathSegment, s selector.Selector, fn AdvVisitFn) error {
144
	for _, ps := range attn {
145
		v, err := n.LookupBySegment(ps)
146 147 148 149 150
		if err != nil {
			continue
		}
		sNext := s.Explore(n, ps)
		if sNext != nil {
Eric Myhre's avatar
Eric Myhre committed
151
			progNext := prog
152
			progNext.Path = prog.Path.AppendSegment(ps)
tavit ohanian's avatar
tavit ohanian committed
153
			if v.Kind() == ld.Kind_Link {
154
				lnk, _ := v.AsLink()
Eric Myhre's avatar
Eric Myhre committed
155 156 157
				progNext.LastBlock.Path = progNext.Path
				progNext.LastBlock.Link = lnk
				v, err = progNext.loadLink(v, n)
hannahhoward's avatar
hannahhoward committed
158
				if err != nil {
159 160 161
					if _, ok := err.(SkipMe); ok {
						return nil
					}
162
					return err
hannahhoward's avatar
hannahhoward committed
163 164
				}
			}
165

Eric Myhre's avatar
Eric Myhre committed
166
			err = progNext.walkAdv(v, sNext, fn)
167 168 169
			if err != nil {
				return err
			}
170 171 172 173
		}
	}
	return nil
}
hannahhoward's avatar
hannahhoward committed
174

tavit ohanian's avatar
tavit ohanian committed
175
func (prog Progress) loadLink(v ld.Node, parent ld.Node) (ld.Node, error) {
176 177 178
	lnk, err := v.AsLink()
	if err != nil {
		return nil, err
Eric Myhre's avatar
Eric Myhre committed
179
	}
tavit ohanian's avatar
tavit ohanian committed
180
	lnkCtx := ld.LinkContext{
Eric Myhre's avatar
Eric Myhre committed
181
		Ctx:        prog.Cfg.Ctx,
Eric Myhre's avatar
Eric Myhre committed
182
		LinkPath:   prog.Path,
183 184
		LinkNode:   v,
		ParentNode: parent,
185
	}
186
	// Pick what in-memory format we will build.
187
	np, err := prog.Cfg.LinkTargetNodePrototypeChooser(lnk, lnkCtx)
188 189 190
	if err != nil {
		return nil, fmt.Errorf("error traversing node at %q: could not load link %q: %s", prog.Path, lnk, err)
	}
191
	// Load link!
Eric Myhre's avatar
Eric Myhre committed
192
	n, err := prog.Cfg.LinkSystem.Load(lnkCtx, lnk, np)
193
	if err != nil {
194 195 196
		if _, ok := err.(SkipMe); ok {
			return nil, err
		}
Eric Myhre's avatar
Eric Myhre committed
197
		return nil, fmt.Errorf("error traversing node at %q: could not load link %q: %s", prog.Path, lnk, err)
198
	}
Eric Myhre's avatar
Eric Myhre committed
199
	return n, nil
200 201
}

Eric Myhre's avatar
Eric Myhre committed
202 203 204 205 206 207 208 209 210 211 212 213 214
// WalkTransforming walks a graph of Nodes, deciding which to alter by applying a Selector,
// and calls the given TransformFn to decide what new node to replace the visited node with.
// A new Node tree will be returned (the original is unchanged).
//
// If the TransformFn returns the same Node which it was called with,
// then the transform is a no-op; if every visited node is a no-op,
// then the root node returned from the walk as a whole will also be
// the same as its starting Node (no new memory will be used).
//
// When a Node is replaced, no further recursion of this walk will occur on its contents.
// (You can certainly do a additional traversals, including transforms,
// from inside the TransformFn while building the replacement node.)
//
215 216
// The prototype (that is, implementation) of Node returned will be the same as the
// prototype of the Nodes at the same positions in the existing tree
Eric Myhre's avatar
Eric Myhre committed
217
// (literally, builders used to construct any new needed intermediate nodes
218
// are chosen by asking the existing nodes about their prototype).
Eric Myhre's avatar
Eric Myhre committed
219 220
//
// This feature is not yet implemented.
tavit ohanian's avatar
tavit ohanian committed
221
func (prog Progress) WalkTransforming(n ld.Node, s selector.Selector, fn TransformFn) (ld.Node, error) {
222 223
	panic("TODO")
}