3 // Walk visits all of the values in a possibly-complex structure, calling
4 // a given function for each value.
6 // For example, given a list of strings the callback would first be called
7 // with the whole list and then called once for each element of the list.
9 // The callback function may prevent recursive visits to child values by
10 // returning false. The callback function my halt the walk altogether by
11 // returning a non-nil error. If the returned error is about the element
12 // currently being visited, it is recommended to use the provided path
13 // value to produce a PathError describing that context.
15 // The path passed to the given function may not be used after that function
16 // returns, since its backing array is re-used for other calls.
17 func Walk(val Value, cb func(Path, Value) (bool, error)) error {
19 return walk(path, val, cb)
22 func walk(path Path, val Value, cb func(Path, Value) (bool, error)) error {
23 deeper, err := cb(path, val)
31 if val.IsNull() || !val.IsKnown() {
32 // Can't recurse into null or unknown values, regardless of type
38 case ty.IsObjectType():
39 for it := val.ElementIterator(); it.Next(); {
40 nameVal, av := it.Element()
41 path := append(path, GetAttrStep{
42 Name: nameVal.AsString(),
44 err := walk(path, av, cb)
49 case val.CanIterateElements():
50 for it := val.ElementIterator(); it.Next(); {
51 kv, ev := it.Element()
52 path := append(path, IndexStep{
55 err := walk(path, ev, cb)
64 // Transform visits all of the values in a possibly-complex structure,
65 // calling a given function for each value which has an opportunity to
66 // replace that value.
68 // Unlike Walk, Transform visits child nodes first, so for a list of strings
69 // it would first visit the strings and then the _new_ list constructed
70 // from the transformed values of the list items.
72 // This is useful for creating the effect of being able to make deep mutations
73 // to a value even though values are immutable. However, it's the responsibility
74 // of the given function to preserve expected invariants, such as homogenity of
75 // element types in collections; this function can panic if such invariants
76 // are violated, just as if new values were constructed directly using the
77 // value constructor functions. An easy way to preserve invariants is to
78 // ensure that the transform function never changes the value type.
80 // The callback function my halt the walk altogether by
81 // returning a non-nil error. If the returned error is about the element
82 // currently being visited, it is recommended to use the provided path
83 // value to produce a PathError describing that context.
85 // The path passed to the given function may not be used after that function
86 // returns, since its backing array is re-used for other calls.
87 func Transform(val Value, cb func(Path, Value) (Value, error)) (Value, error) {
89 return transform(path, val, cb)
92 func transform(path Path, val Value, cb func(Path, Value) (Value, error)) (Value, error) {
98 case val.IsNull() || !val.IsKnown():
99 // Can't recurse into null or unknown values, regardless of type
102 case ty.IsListType() || ty.IsSetType() || ty.IsTupleType():
106 // No deep transform for an empty sequence
109 elems := make([]Value, 0, l)
110 for it := val.ElementIterator(); it.Next(); {
111 kv, ev := it.Element()
112 path := append(path, IndexStep{
115 newEv, err := transform(path, ev, cb)
117 return DynamicVal, err
119 elems = append(elems, newEv)
122 case ty.IsListType():
123 newVal = ListVal(elems)
125 newVal = SetVal(elems)
126 case ty.IsTupleType():
127 newVal = TupleVal(elems)
129 panic("unknown sequence type") // should never happen because of the case we are in
137 // No deep transform for an empty map
140 elems := make(map[string]Value)
141 for it := val.ElementIterator(); it.Next(); {
142 kv, ev := it.Element()
143 path := append(path, IndexStep{
146 newEv, err := transform(path, ev, cb)
148 return DynamicVal, err
150 elems[kv.AsString()] = newEv
152 newVal = MapVal(elems)
155 case ty.IsObjectType():
157 case ty.Equals(EmptyObject):
158 // No deep transform for an empty object
161 atys := ty.AttributeTypes()
162 newAVs := make(map[string]Value)
163 for name := range atys {
164 av := val.GetAttr(name)
165 path := append(path, GetAttrStep{
168 newAV, err := transform(path, av, cb)
170 return DynamicVal, err
174 newVal = ObjectVal(newAVs)
181 return cb(path, newVal)