-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathenum_bitflags.go
More file actions
217 lines (168 loc) · 6 KB
/
Copy pathenum_bitflags.go
File metadata and controls
217 lines (168 loc) · 6 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
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
package enum
import (
"fmt"
"github.com/Riven-Spell/generic/enumerable"
"reflect"
"strings"
"github.com/Riven-Spell/enum/v2/internal"
)
type genericBfEnumImpl interface {
bfEnumImpl()
}
// BitflagEnumImpl implements the bitflag enumerator. Paired with BitflagImpl, it allows for a similar structure to EnumImpl.
// To implement it, supply a uint "backing" type for the enum, a BitflagImpl "result" type, and the type encapsulating BitflagEnumImpl.
// BitflagImpl is required to add a fmt.Stringer to satisfy BitflagEnumImpl.
type BitflagEnumImpl[Raw internal.Flaggable, BfImpl genericBfImpl[Raw, Parent], Parent genericBfEnumImpl] struct {
valueNameCache map[Raw]string
nameValueCache map[string]Raw
typeName string
}
// FromRawValue returns the result type from a raw uint. This may not necessarily be a real value in the bitflags, so check your work!
func (e *BitflagEnumImpl[Raw, BfImpl, Parent]) FromRawValue(val Raw) BfImpl {
out := BitflagImpl[Raw, BfImpl, Parent]{}.getParentZeroInstance()
ptr := getBitflagPtr(&out)
ptr.value = val
return out
}
func (BitflagEnumImpl[Raw, BfImpl, Parent]) bfEnumImpl() { panic("do not call, used for contracting") }
func (e *BitflagEnumImpl[Raw, BfImpl, Parent]) generateCaches() {
globalRwLock.RLock() // grab a read lock, guarantee that the caches exist
if e.nameValueCache != nil && e.valueNameCache != nil {
globalRwLock.RUnlock()
return
}
globalRwLock.RUnlock()
// if they do not exist, grab the write lock and create them
globalRwLock.Lock()
defer globalRwLock.Unlock()
e.nameValueCache, e.valueNameCache = generateCaches[Parent, BfImpl, Raw](func(impl BfImpl) Raw {
return impl.Value()
})
if noneName, ok := e.valueNameCache[0]; !ok {
noneName = "None"
e.nameValueCache[noneName] = 0
e.valueNameCache[0] = noneName
}
}
// BitflagSeparatorString defines the default separator string.
// Changing this will only lead to headaches, so it is a const.
const BitflagSeparatorString = ","
// String stringifies the target result type, using options set by ConfigureBitflagStringOptions.
func (e *BitflagEnumImpl[Raw, BfImpl, Parent]) String(t BfImpl) string {
e.generateCaches()
results := make([]string, 0)
for val, name := range e.valueNameCache {
if val == 0 {
continue // don't stringify zero until the end
}
if (t.Value() & val) == val {
results = append(results, name)
}
}
if len(results) == 0 { // If we have nothing, insert the zero value's name.
results = append(results, e.valueNameCache[0])
}
return strings.Join(results, BitflagSeparatorString)
}
// Parse parses a string to the target result type. If options are not provided, falls back to two options:
// First, pulling them from DefaultBitflagStringOptionsGetter (if implemented on the parent struct)
// Second, GlobalDefaultBitflagStringOptions.
func (e *BitflagEnumImpl[Raw, BfImpl, Parent]) Parse(s string, strict bool) (v BfImpl, err error) {
e.generateCaches()
v = BitflagImpl[Raw, BfImpl, Parent]{}.getParentZeroInstance()
bfPtr := getBitflagPtr(&v)
entriesRaw := strings.Split(s, BitflagSeparatorString)
for _, raw := range entriesRaw {
raw = strings.TrimSpace(raw)
raw = strings.ToLower(raw)
toAdd, ok := e.nameValueCache[raw]
if !ok && strict {
err = fmt.Errorf("could not associate input `%s` with a value", s)
return
}
bfPtr.value |= toAdd
}
return
}
func (e *BitflagEnumImpl[Raw, BfImpl, Parent]) Split(in BfImpl) []BfImpl {
e.generateCaches()
out := make([]BfImpl, 0)
inVal := in.Value()
for val, _ := range e.valueNameCache {
if inVal&val == val {
var toAdd BfImpl
getBitflagPtr(&toAdd).value = val
out = append(out, toAdd)
}
}
return out
}
func (e *BitflagEnumImpl[Raw, BfImpl, Parent]) Values() []BfImpl {
e.generateCaches()
return enumerable.Collect(enumerable.Map(enumerable.FromMap(e.valueNameCache), func(i enumerable.AB[Raw, string]) BfImpl {
return e.FromRawValue(i.A)
}))
}
type genericBfImpl[F internal.Flaggable, E genericBfEnumImpl] interface {
// contractual requirements, used for type inference
bfImpl()
enum() E
Value() F
fmt.Stringer
}
func getBitflagPtr[F internal.Flaggable, Enum genericBfEnumImpl, Parent genericBfImpl[F, Enum]](tgt *Parent) (out *BitflagImpl[F, Parent, Enum]) {
derefType := reflect.TypeOf(out).Elem()
parentPtrVal := reflect.ValueOf(tgt).Elem()
nField := parentPtrVal.NumField()
for i := 0; i < nField; i++ {
f := parentPtrVal.Field(i)
if f.Type().AssignableTo(derefType) {
reflect.ValueOf(&out).Elem().Set(f.Addr())
return out
}
}
panic("could not find viable bitflag pointer (is BitflagImpl at the root of your struct?)")
}
// BitflagImpl is the companion type to BitflagEnumImpl. Both should be implemented together. Parameterization is the same.
// BitflagImpl is required to add a fmt.Stringer to satisfy BitflagEnumImpl.
type BitflagImpl[F internal.Flaggable, Parent genericBfImpl[F, Enum], Enum genericBfEnumImpl] struct {
value F
}
func (b BitflagImpl[F, P, E]) bfImpl() { panic("do not call, used for contracting") }
func (b BitflagImpl[F, P, E]) enum() E { panic("do not call, used for type inference") }
func (b BitflagImpl[F, P, E]) getParentZeroInstance() (ret P) {
return
}
// Value returns the raw uint backing value.
func (b BitflagImpl[F, P, E]) Value() F {
return b.value
}
// Add "adds" two or more bitflags together in a binary OR operation.
func (b BitflagImpl[F, P, E]) Add(in ...P) P {
out := b.getParentZeroInstance()
bfPtr := getBitflagPtr(&out)
bfPtr.value = b.value
for _, v := range in {
bfPtr.value |= v.Value()
}
return out
}
// Remove removes bitflags []in from the LHS bitflag and returns it, via bitwise AND + XOR of in.
func (b BitflagImpl[F, P, E]) Remove(in ...P) P {
out := b.getParentZeroInstance()
bfPtr := getBitflagPtr(&out)
bfPtr.value = b.value
for _, v := range in {
bfPtr.value &= ^v.Value()
}
return out
}
// Contains returns whether or not a value exists in this bitflag.
func (b BitflagImpl[F, P, E]) Contains(in ...P) bool {
for _, v := range in {
if (b.value)&v.Value() != v.Value() {
return false
}
}
return true
}