-
Notifications
You must be signed in to change notification settings - Fork 50
/
bitmask.hpp
338 lines (295 loc) · 9.94 KB
/
bitmask.hpp
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
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
#ifndef _C4_BITMASK_HPP_
#define _C4_BITMASK_HPP_
/** @file bitmask.hpp bitmask utilities */
#include <cstring>
#include <type_traits>
#include "c4/enum.hpp"
#include "c4/format.hpp"
#if defined(_MSC_VER)
# pragma warning(push)
# pragma warning(disable : 4996) // 'strncpy', fopen, etc: This function or variable may be unsafe
#endif
#if defined(__clang__)
# pragma clang diagnostic push
# pragma clang diagnostic ignored "-Wold-style-cast"
#elif defined(__GNUC__)
# pragma GCC diagnostic push
# pragma GCC diagnostic ignored "-Wold-style-cast"
# if __GNUC__ >= 8
# pragma GCC diagnostic ignored "-Wstringop-truncation"
# pragma GCC diagnostic ignored "-Wstringop-overflow"
# endif
#endif
namespace c4 {
//-----------------------------------------------------------------------------
/** write a bitmask to a stream, formatted as a string */
template<class Enum, class Stream>
Stream& bm2stream(Stream &s, typename std::underlying_type<Enum>::type bits, EnumOffsetType offst=EOFFS_PFX)
{
using I = typename std::underlying_type<Enum>::type;
bool written = false;
auto const& pairs = esyms<Enum>();
// write non null value
if(bits)
{
// do reverse iteration to give preference to composite enum symbols,
// which are likely to appear at the end of the enum sequence
for(size_t i = pairs.size() - 1; i != size_t(-1); --i)
{
auto p = pairs[i];
I b(static_cast<I>(p.value));
if(b && (bits & b) == b)
{
if(written) s << '|'; // append bit-or character
written = true;
s << p.name_offs(offst); // append bit string
bits &= ~b;
}
}
return s;
}
else
{
// write a null value
for(size_t i = pairs.size() - 1; i != size_t(-1); --i)
{
auto p = pairs[i];
I b(static_cast<I>(p.value));
if(b == 0)
{
s << p.name_offs(offst);
written = true;
break;
}
}
}
if(!written)
{
s << '0';
}
return s;
}
template<class Enum, class Stream>
typename std::enable_if<is_scoped_enum<Enum>::value, Stream&>::type
bm2stream(Stream &s, Enum value, EnumOffsetType offst=EOFFS_PFX)
{
using I = typename std::underlying_type<Enum>::type;
return bm2stream<Enum>(s, static_cast<I>(value), offst);
}
//-----------------------------------------------------------------------------
// some utility macros, undefed below
/// @cond dev
/* Execute `code` if the `num` of characters is available in the str
* buffer. This macro simplifies the code for bm2str().
* @todo improve performance by writing from the end and moving only once. */
#define _c4prependchars(code, num) \
if(str && (pos + num <= sz)) \
{ \
/* move the current string to the right */ \
memmove(str + num, str, pos); \
/* now write in the beginning of the string */ \
code; \
} \
else if(str && sz) \
{ \
C4_ERROR("cannot write to string pos=%d num=%d sz=%d", \
(int)pos, (int)num, (int)sz); \
} \
pos += num
/* Execute `code` if the `num` of characters is available in the str
* buffer. This macro simplifies the code for bm2str(). */
#define _c4appendchars(code, num) \
if(str && (pos + num <= sz)) \
{ \
code; \
} \
else if(str && sz) \
{ \
C4_ERROR("cannot write to string pos=%d num=%d sz=%d", \
(int)pos, (int)num, (int)sz); \
} \
pos += num
/// @endcond
/** convert a bitmask to string.
* return the number of characters written. To find the needed size,
* call first with str=nullptr and sz=0 */
template<class Enum>
size_t bm2str
(
typename std::underlying_type<Enum>::type bits,
char *str=nullptr,
size_t sz=0,
EnumOffsetType offst=EOFFS_PFX
)
{
using I = typename std::underlying_type<Enum>::type;
C4_ASSERT((str == nullptr) == (sz == 0));
auto syms = esyms<Enum>();
size_t pos = 0;
typename EnumSymbols<Enum>::Sym const* C4_RESTRICT zero = nullptr;
// do reverse iteration to give preference to composite enum symbols,
// which are likely to appear later in the enum sequence
for(size_t i = syms.size()-1; i != size_t(-1); --i)
{
auto const &C4_RESTRICT p = syms[i]; // do not copy, we are assigning to `zero`
I b = static_cast<I>(p.value);
if(b == 0)
{
zero = &p; // save this symbol for later
}
else if((bits & b) == b)
{
bits &= ~b;
// append bit-or character
if(pos > 0)
{
_c4prependchars(*str = '|', 1);
}
// append bit string
const char *pname = p.name_offs(offst);
size_t len = strlen(pname);
_c4prependchars(strncpy(str, pname, len), len);
}
}
C4_CHECK_MSG(bits == 0, "could not find all bits");
if(pos == 0) // make sure at least something is written
{
if(zero) // if we have a zero symbol, use that
{
const char *pname = zero->name_offs(offst);
size_t len = strlen(pname);
_c4prependchars(strncpy(str, pname, len), len);
}
else // otherwise just write an integer zero
{
_c4prependchars(*str = '0', 1);
}
}
_c4appendchars(str[pos] = '\0', 1);
return pos;
}
// cleanup!
#undef _c4appendchars
#undef _c4prependchars
/** scoped enums do not convert automatically to their underlying type,
* so this SFINAE overload will accept scoped enum symbols and cast them
* to the underlying type */
template<class Enum>
typename std::enable_if<is_scoped_enum<Enum>::value, size_t>::type
bm2str
(
Enum bits,
char *str=nullptr,
size_t sz=0,
EnumOffsetType offst=EOFFS_PFX
)
{
using I = typename std::underlying_type<Enum>::type;
return bm2str<Enum>(static_cast<I>(bits), str, sz, offst);
}
//-----------------------------------------------------------------------------
namespace detail {
#ifdef __clang__
# pragma clang diagnostic push
#elif defined(__GNUC__)
# pragma GCC diagnostic push
# if __GNUC__ >= 6
# pragma GCC diagnostic ignored "-Wnull-dereference"
# endif
#endif
template<class Enum>
typename std::underlying_type<Enum>::type str2bm_read_one(const char *str, size_t sz, bool alnum)
{
using I = typename std::underlying_type<Enum>::type;
auto pairs = esyms<Enum>();
if(alnum)
{
auto *p = pairs.find(str, sz);
C4_CHECK_MSG(p != nullptr, "no valid enum pair name for '%.*s'", (int)sz, str);
return static_cast<I>(p->value);
}
I tmp{0};
size_t len = uncat(csubstr(str, sz), tmp);
C4_CHECK_MSG(len != csubstr::npos, "could not read string as an integral type: '%.*s'", (int)sz, str);
return tmp;
}
#ifdef __clang__
# pragma clang diagnostic pop
#elif defined(__GNUC__)
# pragma GCC diagnostic pop
#endif
} // namespace detail
/** convert a string to a bitmask */
template<class Enum>
typename std::underlying_type<Enum>::type str2bm(const char *str, size_t sz)
{
using I = typename std::underlying_type<Enum>::type;
I val = 0;
bool started = false;
bool alnum = false, num = false;
const char *f = nullptr, *pc = str;
for( ; pc < str+sz; ++pc)
{
const char c = *pc;
if((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_')
{
C4_CHECK(( ! num) || ((pc - f) == 1 && (c == 'x' || c == 'X'))); // accept hexadecimal numbers
if( ! started)
{
f = pc;
alnum = started = true;
}
}
else if(c >= '0' && c <= '9')
{
C4_CHECK( ! alnum);
if(!started)
{
f = pc;
num = started = true;
}
}
else if(c == ':' || c == ' ')
{
// skip this char
}
else if(c == '|' || c == '\0')
{
C4_ASSERT(num != alnum);
C4_ASSERT(pc >= f);
val |= detail::str2bm_read_one<Enum>(f, static_cast<size_t>(pc-f), alnum);
started = num = alnum = false;
if(c == '\0')
{
return val;
}
}
else
{
C4_ERROR("bad character '%c' in bitmask string", c);
}
}
if(f)
{
C4_ASSERT(num != alnum);
C4_ASSERT(pc >= f);
val |= detail::str2bm_read_one<Enum>(f, static_cast<size_t>(pc-f), alnum);
}
return val;
}
/** convert a string to a bitmask */
template<class Enum>
typename std::underlying_type<Enum>::type str2bm(const char *str)
{
return str2bm<Enum>(str, strlen(str));
}
} // namespace c4
#ifdef _MSC_VER
# pragma warning(pop)
#endif
#if defined(__clang__)
# pragma clang diagnostic pop
#elif defined(__GNUC__)
# pragma GCC diagnostic pop
#endif
#endif // _C4_BITMASK_HPP_