-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathiter.go
More file actions
179 lines (164 loc) · 5.15 KB
/
iter.go
File metadata and controls
179 lines (164 loc) · 5.15 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
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
// Package iter provides primitives for walking arbitrary data structures.
package iter
import (
"fmt"
"reflect"
)
var (
defaultIter = Iter{}
defaultWalker = dfsWalker{Iterator: &defaultIter}
)
// Iterator is a basic interface for iterating elements of a structured type. It
// serves as backing for other traversal methods. Iterators are safe for use by
// multiple Go routines, though the underlying values received in the iteration
// functions may not be.
type Iterator interface {
IterChan(val reflect.Value, f func(seq int, ch reflect.Value) error) error
IterMap(val reflect.Value, f func(key, val reflect.Value) error) error
IterSlice(val reflect.Value, f func(idx int, val reflect.Value) error) error
IterStruct(val reflect.Value,
f func(field reflect.StructField, val reflect.Value) error) error
}
// NewIter returns a new Iter.
func NewIter() Iterator {
return &Iter{}
}
// NewRecoverIter returns the given iterator wrapped so that it will not panic
// under any circumstance, instead returning the panic as an error.
func NewRecoverIter(it Iterator) Iterator {
return &recoverIter{it}
}
// Iter it a basic implementation of Iterator. It is possible for these methods
// to panic under certain circumstances. If you want to disable panics write it
// in a iter.NewrecoverFnIter(Iter{}). Performance cost for Visiting slices is
// negligible relative to iteration using range. About 3x slower for slices with
// less than 1k elements, for slices with more than 2k elements it will be
// around 2x as slow. Iterating maps is only 2-3 times slower for small maps of
// 100-1k elements. When you start to go above that its deficiencies will start
// to take a linear tax on runtime proportianite to the number of elements. It's
// roughly 100x slower at 32k elements. This is because it loads all map keys
// into memory (implementation of reflect.MapKeys).
type Iter struct {
ChanRecv bool
ChanBlock bool
ExcludeAnonymous bool
ExcludeUnexported bool
}
// IterMap will visit each key and value of a map.
func (it Iter) IterMap(val reflect.Value, f func(
key, val reflect.Value) error) error {
kind := val.Kind()
if reflect.Map != kind {
return fmt.Errorf("expected map kind, not %s", kind)
}
for _, key := range val.MapKeys() {
element := val.MapIndex(key)
if err := f(key, element); err != nil {
return err
}
}
return nil
}
// IterSlice will visit each element of an array or slice. Extending the length
// of an Array or Slice during iteration may panic.
func (it Iter) IterSlice(val reflect.Value, f func(
idx int, val reflect.Value) error) error {
kind := val.Kind()
if reflect.Slice != kind && kind != reflect.Array {
return fmt.Errorf("expected array or slice kind, not %s", kind)
}
l := val.Len()
for i := 0; i < l; i++ {
element := val.Index(i)
if err := f(i, element); err != nil {
return err
}
}
return nil
}
// IterStruct will visit each field and value in a struct.
func (it Iter) IterStruct(val reflect.Value, f func(
field reflect.StructField, val reflect.Value) error) error {
kind := val.Kind()
if reflect.Struct != kind {
return fmt.Errorf("expected struct kind, not %s", kind)
}
typ := val.Type()
for i := 0; i < val.NumField(); i++ {
field := typ.Field(i)
if field.Anonymous && it.ExcludeAnonymous {
continue
}
if len(field.PkgPath) > 0 && it.ExcludeUnexported {
continue
}
element := val.Field(i)
if err := f(field, element); err != nil {
return err
}
}
return nil
}
// IterChan will try to receive values from the given channel only if ChanRecv
// is set to true. If ChanBlock is true IterChan will walk values until the
// channel has been clocked, otherwise it will use the behavior described in
// the reflect packages Value.TryRecv. This means when setting ChanBlock it is
// up to the caller to close the channel to prevent a dead lock. A sequential
// counter for this iterations receives is returned for parity with structured
// types.
func (it Iter) IterChan(val reflect.Value, f func(
seq int, recv reflect.Value) error) error {
if !it.ChanRecv {
return nil
}
kind := val.Kind()
if reflect.Chan != kind {
return fmt.Errorf("expected chan kind, not %s", kind)
}
var (
recv reflect.Value
ok bool
)
i := -1
for {
if it.ChanBlock {
recv, ok = val.Recv()
} else {
recv, ok = val.TryRecv()
}
if !ok {
return nil
}
i++
if err := f(i, recv); err != nil {
return err
}
}
}
type recoverIter struct {
Iterator
}
func (it recoverIter) IterMap(val reflect.Value, f func(
key, val reflect.Value) error) (err error) {
return recoverFn(func() error {
return it.Iterator.IterMap(val, f)
})
}
func (it recoverIter) IterSlice(val reflect.Value, f func(
idx int, val reflect.Value) error) (err error) {
return recoverFn(func() error {
return it.Iterator.IterSlice(val, f)
})
}
func (it recoverIter) IterStruct(val reflect.Value, f func(
field reflect.StructField, val reflect.Value) error) (err error) {
return recoverFn(func() error {
return it.Iterator.IterStruct(val, f)
})
}
func (it recoverIter) IterChan(val reflect.Value, f func(
seq int, recv reflect.Value) error) (err error) {
return recoverFn(func() error {
return it.Iterator.IterChan(val, f)
})
}