-
Notifications
You must be signed in to change notification settings - Fork 218
Expand file tree
/
Copy pathvisit.go
More file actions
141 lines (117 loc) · 3.18 KB
/
Copy pathvisit.go
File metadata and controls
141 lines (117 loc) · 3.18 KB
1
2
3
4
5
6
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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
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
package dyn
import (
"errors"
"fmt"
"maps"
"slices"
)
type noSuchKeyError struct {
p Path
}
func (e noSuchKeyError) Error() string {
return fmt.Sprintf("key not found at %q", e.p)
}
func IsNoSuchKeyError(err error) bool {
var target noSuchKeyError
return errors.As(err, &target)
}
type indexOutOfBoundsError struct {
p Path
}
func (e indexOutOfBoundsError) Error() string {
return fmt.Sprintf("index out of bounds at %q", e.p)
}
func IsIndexOutOfBoundsError(err error) bool {
var target indexOutOfBoundsError
return errors.As(err, &target)
}
type visitOptions struct {
// The function to apply to the value once found.
//
// If this function returns the same value as it receives as argument,
// the original visit function call returns the original value unmodified.
//
// If this function returns a new value, the original visit function call
// returns a value with all the intermediate values updated.
//
// If this function returns an error, the original visit function call
// returns this error and the value is left unmodified.
fn func(Path, Value) (Value, error)
}
func visit(v Value, prefix Path, suffix Pattern, opts visitOptions) (Value, error) {
if len(suffix) == 0 {
return opts.fn(prefix, v)
}
// Initialize prefix if it is empty.
// It is pre-allocated to its maximum size to avoid additional allocations.
if len(prefix) == 0 {
prefix = make(Path, 0, len(suffix))
}
component := suffix[0]
suffix = suffix[1:]
// Visit the value with the current component.
return component.visit(v, prefix, suffix, opts)
}
func (component pathComponent) visit(v Value, prefix Path, suffix Pattern, opts visitOptions) (Value, error) {
path := append(prefix, component)
switch {
case component.isKey():
// Expect a map to be set if this is a key.
m, ok := v.AsMap()
if !ok {
return InvalidValue, fmt.Errorf("expected a map to index %q, found %s", path, v.Kind())
}
// Lookup current value in the map.
ev, ok := m[component.key]
if !ok {
return InvalidValue, noSuchKeyError{path}
}
// Recursively transform the value.
nv, err := visit(ev, path, suffix, opts)
if err != nil {
return InvalidValue, err
}
// Return the original value if the value hasn't changed.
if nv.eq(ev) {
return v, nil
}
// Return an updated map value.
m = maps.Clone(m)
m[component.key] = nv
return Value{
v: m,
k: KindMap,
l: v.l,
}, nil
case component.isIndex():
// Expect a sequence to be set if this is an index.
s, ok := v.AsSequence()
if !ok {
return InvalidValue, fmt.Errorf("expected a sequence to index %q, found %s", path, v.Kind())
}
// Lookup current value in the sequence.
if component.index < 0 || component.index >= len(s) {
return InvalidValue, indexOutOfBoundsError{path}
}
// Recursively transform the value.
ev := s[component.index]
nv, err := visit(ev, path, suffix, opts)
if err != nil {
return InvalidValue, err
}
// Return the original value if the value hasn't changed.
if nv.eq(ev) {
return v, nil
}
// Return an updated sequence value.
s = slices.Clone(s)
s[component.index] = nv
return Value{
v: s,
k: KindSequence,
l: v.l,
}, nil
default:
panic("invalid component")
}
}