1 module mecca.lib.integers;
2 
3 // Licensed under the Boost license. Full copyright information in the AUTHORS file
4 
5 import std.traits;
6 import std.meta: AliasSeq;
7 import std.conv;
8 import std..string;
9 import std.algorithm: among;
10 
11 import mecca.lib.exception : DBG_ASSERT;
12 
13 version(unittest) import mecca.lib.exception : assertEQ, assertThrows;
14 
15 import mecca.log;
16 import mecca.lib.consts;
17 
18 version(LDC) {
19     ubyte bswap(ubyte x) pure @nogc {
20         pragma(inline, true);
21         return x;
22     }
23     ushort bswap(ushort x) pure @nogc {
24         pragma(inline, true);
25         if (__ctfe) {
26             return cast(ushort)((x << 8) | (x >> 8));
27         }
28         else {
29             import ldc.intrinsics: llvm_bswap;
30             return llvm_bswap(x);
31         }
32     }
33     uint bswap(uint x) pure @nogc {
34         pragma(inline, true);
35         if (__ctfe) {
36             import core.bitop: bswap;
37             return bswap(x);
38         }
39         else {
40             import ldc.intrinsics: llvm_bswap;
41             return llvm_bswap(x);
42         }
43     }
44     ulong bswap(ulong x) pure @nogc {
45         pragma(inline, true);
46         if (__ctfe) {
47             import core.bitop: bswap;
48             return bswap(x);
49         }
50         else {
51             import ldc.intrinsics: llvm_bswap;
52             return llvm_bswap(x);
53         }
54     }
55 }
56 else {
57     public import core.bitop: bswap;
58     ubyte bswap(ubyte x) pure @nogc {return x;}
59     ushort bswap(ushort x) pure @nogc {return cast(ushort)((x << 8) | (x >> 8));}
60 }
61 
62 unittest {
63     assert(bswap(ubyte(0x12)) == 0x12);
64     assert(bswap(ushort(0x1122)) == 0x2211);
65     assert(bswap(uint(0x11223344)) == 0x44332211);
66     assert(bswap(ulong(0x1122334455667788UL)) == 0x8877665544332211UL);
67 }
68 
69 version (LittleEndian) {
70     enum hostOrder = 'L';
71 }
72 else version (BigEndian) {
73     enum hostOrder = 'B';
74 }
75 else {
76     static assert (false);
77 }
78 enum networkOrder = 'B';
79 
80 private U toEndianity(char E1, char E2, U)(U val) pure @nogc
81         if (isIntegral!U && (E1 == 'L' || E1 == 'B') && (E2 == 'L' || E2 == 'B')) {
82     pragma(inline, true);
83     static if (E1 == E2) {
84         return val;
85     }
86     else {
87         return bswap(val);
88     }
89 }
90 private U endianOp(char E1, string op, char E2, U)(U lhs, U rhs) {
91     pragma(inline, true);
92     return cast(U)(mixin("toEndianity!(E1, hostOrder)(lhs)" ~ op ~ "toEndianity!(E2, hostOrder)(rhs)"));
93 }
94 
95 
96 struct Integer(T, char E) {
97     static assert (E == 'L' || E == 'B');
98     static assert (is(T == byte) || is(T == ubyte) || is(T == short) || is(T == ushort) ||
99         is(T == int) || is(T == uint) || is(T == long) || is(T == ulong), T);
100     enum signed = isSigned!T;
101     enum bits = T.sizeof * 8;
102     enum name = (signed ? "S" : "U") ~ E ~ bits.to!string;
103     enum Integer min = Integer(T.min);
104     enum Integer max = Integer(T.max);
105 
106     T value;
107 
108     static assert (this.sizeof == T.sizeof);
109 
110     this(T val) {
111         value = toEndianity!(E, hostOrder)(val);
112     }
113     this(U, char E2)(Integer!(U, E2) val) {
114         pragma(inline, true);
115         opAssign!(U, E2)(val);
116     }
117     @property T inHostOrder() const pure @nogc {
118         pragma(inline, true);
119         return toEndianity!(E, hostOrder, T)(value);
120     }
121 
122     U opCast(U)() {
123         static if (is(U == bool)) {
124             return value != 0;
125         }
126         static if (isIntegral!U) {
127             return cast(U)value;
128         }
129         else if (isInstanceOf!(Integer, U)) {
130             return U(cast(S)toHostOrder);
131         }
132         else {
133             static assert (false, U);
134         }
135     }
136 
137     auto ref opAssign(T rhs) {
138         value = toEndianity!(E, hostOrder)(rhs);
139         return this;
140     }
141     auto ref opAssign(U, char E2)(Integer!(U, E2) rhs) if (signed == rhs.signed) {
142         static assert (U.sizeof <= T.sizeof);
143         value = toEndianity!(E, E2)(rhs.value);
144         return this;
145     }
146 
147     bool opEquals(T rhs) {
148         pragma(inline, true);
149         return inHostOrder == rhs;
150     }
151     bool opEquals(U, char E2)(Integer!(U, E2) rhs) const if (signed == rhs.signed) {
152         pragma(inline, true);
153         return inHostOrder == rhs.inHostOrder;
154     }
155 
156     long opCmp(T rhs) {
157         pragma(inline, true);
158         return inHostOrder < rhs ? -1 : 1;
159     }
160     long opCmp(U, char E2)(Integer!(U, E2) rhs) const if (signed == rhs.signed) {
161         pragma(inline, true);
162         return inHostOrder < rhs.inHostOrder ? -1 : 1;
163     }
164 
165     auto ref opUnary(string op: "++")() {
166         pragma(inline, true);
167         static if (E == hostOrder) {
168             ++value;
169         }
170         else {
171             opAssign(endianOp!(E, "+", hostOrder)(value, cast(T)1));
172         }
173         return this;
174     }
175     auto ref opUnary(string op: "--")() {
176         static if (E == hostOrder) {
177             --value;
178         }
179         else {
180             opAssign(endianOp!(E, "+", hostOrder)(value, cast(T)1));
181         }
182         return this;
183     }
184 
185     static if (signed) {
186         auto opUnary(string op)() const if (op == "+" || op == "-") {
187             pragma(inline, true);
188             // Make the integer promotion cast explicit, to avoid compiler deprecation messages.
189             static if (bits < 32) {
190                 enum promotionCast = "cast(int)";
191             }
192             else {
193                 enum promotionCast = "";
194             }
195             return Integer(cast(T) mixin(op ~ promotionCast ~ "inHostOrder"));
196         }
197 
198         private alias binOps = AliasSeq!("+", "-", "*", "/", "%", "^");
199     }
200     else {
201         auto opUnary(string op)() const if (op == "~") {
202             pragma(inline, true);
203             // Make the integer promotion cast explicit, to avoid compiler deprecation messages.
204             static if (bits < 32) {
205                 enum promotionCast = "cast(uint)";
206             }
207             else {
208                 enum promotionCast = "";
209             }
210             return Integer(cast(T) mixin(op ~ promotionCast ~ "inHostOrder"));
211         }
212 
213         private alias binOps = AliasSeq!("+", "-", "*", "/", "%", "^", "&", "|", "^", "<<", ">>", ">>>");
214     }
215 
216     auto opBinary(string op)(T rhs) const if (op.among(binOps)) {
217         pragma(inline, true);
218         return Integer(endianOp!(E, op, hostOrder)(value, rhs));
219     }
220     auto opBinary(string op, U, char E2)(Integer!(U, E2) rhs) const if (signed == rhs.signed && op.among(binOps)) {
221         pragma(inline, true);
222         static if (U.sizeof <= T.sizeof) {
223             return Integer(endianOp!(E, op, E2)(value, rhs.value));
224         }
225         else {
226             return Integer!(U, E)(endianOp!(E, op, E2)(cast(U)value, rhs.value));
227         }
228     }
229 
230     string toString() const {
231         return "%s#%s".format(value, name);
232     }
233 }
234 
235 alias U8  = Integer!(ubyte,  hostOrder);
236 alias U16 = Integer!(ushort, hostOrder);
237 alias U32 = Integer!(uint,   hostOrder);
238 alias U64 = Integer!(ulong,  hostOrder);
239 
240 alias S8  = Integer!(byte,   hostOrder);
241 alias S16 = Integer!(short,  hostOrder);
242 alias S32 = Integer!(int,    hostOrder);
243 alias S64 = Integer!(long,   hostOrder);
244 
245 unittest {
246     auto x = 17.U8;
247     auto y = 17.S8;
248     auto z = 17.U16;
249     auto x64 = 17.U64;
250     auto y64 = 17.S64;
251     auto w = (-128).S8;
252 
253     static assert (!is(typeof(x + y)));
254     static assert (!is(typeof(x > y)));
255     static assert (is(typeof(x > z) == bool));
256     static assert (is(typeof(~x)));
257     static assert (is(typeof(x ^ z)));
258     static assert (is(typeof(-y)));
259     static assert (!is(typeof(x ^ y)));
260     static assert (!is(typeof(~y)));
261 
262     assert (x + z == 34);
263     assert (x * 2 == 34);
264     assert (y * 2 == 34);
265     assert (z * 2 == 34);
266     assert (-y == -17);
267     assert (~x64 == 0xFFFF_FFFF_FFFF_FFEE);
268     assert (-y64 == -long(17) );
269     assert (-w == -128); // We don't want the result to be promoted to int (==128).
270 
271     x++;
272     y++;
273     assert (x == 18);
274 }
275 
276 alias NetU8  = Integer!(ubyte,  networkOrder);
277 alias NetU16 = Integer!(ushort, networkOrder);
278 alias NetU32 = Integer!(uint,   networkOrder);
279 alias NetU64 = Integer!(ulong,  networkOrder);
280 
281 alias NetS8  = Integer!(byte,   networkOrder);
282 alias NetS16 = Integer!(short,  networkOrder);
283 alias NetS32 = Integer!(int,    networkOrder);
284 alias NetS64 = Integer!(long,   networkOrder);
285 
286 unittest {
287     import std.stdio;
288 
289     NetU16 x = 17;
290     U16 y = x;
291     assert (x == y);
292     assert (x.value == 0x1100);
293     assert (y.value == 0x0011);
294 
295     assert (x.toString == "4352#UB16");
296     assert (y.toString == "17#UL16");
297 
298     auto z = x + y;
299     static assert (is(typeof(z) == NetU16));
300     assert (z.value == 0x2200);
301     auto w = x * 2;
302     static assert (is(typeof(w) == NetU16));
303 
304     assert (y * 2 == 34);
305     assert (x * 2 == w);
306 
307     w++;
308     assert (w.value == 0x2300);
309 }
310 
311 
312 struct SerialInteger(T) {
313     static assert (isUnsigned!T, "Type must be an unsigned integer");
314 
315     alias Type = T;
316     enum min = T.min;
317     enum max = T.max;
318     enum T midpoint = (2 ^^ (BITS_IN_BYTE * T.sizeof - 1));
319     T value;
320 
321     this(T value) nothrow @nogc {
322         this.value = value;
323     }
324 
325     U opCast(U)() nothrow @nogc if (isUnsigned!U) {
326         return this.value;
327     }
328 
329     ref SerialInteger opAssign(SerialInteger rhs) nothrow @nogc {pragma(inline, true);
330         this.value = rhs.value;
331         return this;
332     }
333     ref SerialInteger opAssign(T rhs) nothrow @nogc {pragma(inline, true);
334         this.value = rhs;
335         return this;
336     }
337 
338     ref SerialInteger opUnary(string op)() nothrow @nogc if (op == "++" || op == "--") {pragma(inline, true);
339         mixin(op ~ "value;");
340         return this;
341     }
342     SerialInteger opBinary(string op)(T rhs) const pure nothrow @nogc if (op == "+" || op == "-") {pragma(inline, true);
343         return SerialInteger(cast(T)mixin("value " ~ op ~ " rhs"));
344     }
345     SerialInteger opBinary(string op)(SerialInteger rhs) const pure nothrow @nogc if (op == "+" || op == "-") {pragma(inline, true);
346         return SerialInteger(cast(T)mixin("value " ~ op ~ " rhs.value"));
347     }
348 
349     ref SerialInteger opOpAssign(string op)(T rhs) nothrow @nogc if (op == "+" || op == "-") {pragma(inline, true);
350         mixin("value " ~ op ~ "= rhs;");
351         return this;
352     }
353     ref SerialInteger opOpAssign(string op)(SerialInteger rhs) nothrow @nogc if (op == "+" || op == "-") {pragma(inline, true);
354         mixin("value " ~ op ~ "= rhs.value;");
355         return this;
356     }
357 
358     bool opEquals(const T rhs) const pure nothrow @nogc {pragma(inline, true);
359         return value == rhs;
360     }
361     bool opEquals(const SerialInteger rhs) const pure nothrow @nogc {pragma(inline, true);
362         return value == rhs.value;
363     }
364 
365     int opCmp(const SerialInteger rhs) const pure nothrow @nogc {pragma(inline, true);
366         return opCmp(rhs.value);
367     }
368     int opCmp(const T rhs) const {pragma(inline, true);
369         pragma(inline, true);
370         // see https://en.wikipedia.org/wiki/Serial_number_arithmetic
371         // note that if the two numbers are on the circumference, then both (a < b) and (b < a) are true
372         // but it's too expensive to assert on it
373         static if (is(T == ubyte)) {
374             return cast(byte)(value - rhs);
375         }
376         else static if (is(T == ushort)) {
377             return cast(short)(value - rhs);
378         }
379         else static if (is(T == uint)) {
380             return cast(int)(value - rhs);
381         }
382         else {
383             static assert (false, "not implemented");
384         }
385     }
386 
387     static assert (this.sizeof == T.sizeof);
388 }
389 
390 alias Serial8  = SerialInteger!ubyte;
391 alias Serial16 = SerialInteger!ushort;
392 alias Serial32 = SerialInteger!uint;
393 
394 unittest {
395     auto a = Serial16(2);
396     a = 7;
397     assert(a < 100);
398     assert(Serial16(65530) < 100);
399     assert(100 > Serial16(65530));
400     assert(a == 7);
401     a++;
402     a += 7;
403     assert(a == 15);
404     assert(a < 100);
405     assert(a > 10);
406     assert(Serial16(0) > Serial16(65534));
407 
408     // subtraction
409     a = Serial16(2);
410     assert( (a - cast(ushort)(-1)).value == 3 );
411     assert( (a - cast(ushort)(-1)).value == cast(ushort)(a.value + 1) );
412 
413     assert( (a - 5).value == 65533 );
414     assert( cast(short)((a - 5).value) == cast(short)-3 );
415 }
416 
417 /// Flip each bit of the input. Returns the same type as the input.
418 /// See https://dlang.org/changelog/2.078.0.html#fix16997
419 T bitComplement(T)(T val) pure nothrow @nogc {
420     static if (T.sizeof < int.sizeof) {
421         return cast(T)( ~ cast(int)val );
422     } else {
423         return ~val;
424     }
425 }
426 
427 unittest {
428     {
429         ulong sum = 1;
430         ubyte val = 0xFE;
431         sum += bitComplement(val);
432         assert(sum == 2);
433     }
434     {
435         ushort s1 = 0x0101;
436         ushort s2 = 0xF00F;
437         s1 &= bitComplement(s2);
438         assert(s1 == 0x0100);
439     }
440 
441     static assert( bitComplement(0x0F) == 0xFFFF_FFF0 );
442     static assert( bitComplement(ushort(0x0F)) == 0xFFF0 );
443 }
444 
445 /// Return the number of bits sufficient to cover a value
446 ubyte bitsInValue(ulong value) pure nothrow @safe @nogc {
447     ubyte ret;
448 
449     while( value!=0 ) {
450         ret++;
451         value>>=1;
452     }
453 
454     return ret;
455 }
456 
457 unittest {
458     assertEQ(bitsInValue(1), 1);
459     assertEQ(bitsInValue(2), 2);
460     assertEQ(bitsInValue(0), 0);
461     assertEQ(bitsInValue(3), 2);
462     assertEQ(bitsInValue(5), 3);
463 }
464 
465 /// Create a mask with `bits` set bits
466 @notrace T createBitMask(T = ulong)(uint numBits) if(isIntegral!T && isUnsigned!T)
467 {
468     DBG_ASSERT!"numBits=%s"(numBits <= (T.sizeof * 8), numBits);
469 
470     // Avoid the corner cases that would require an "if" to support
471     static if( T.sizeof == ulong.sizeof ) {
472         DBG_ASSERT!"An 'all ones' mask is not supported by this function"(numBits < (T.sizeof * 8));
473     }
474 
475     return cast(T)((cast(T)1 << numBits) - 1);
476 }
477 
478 unittest {
479     import std.exception;
480     import core.exception;
481 
482     assertEQ(createBitMask(7), 0x7f);
483     assertEQ(createBitMask(8), 0xff);
484     assertEQ(createBitMask!ubyte(8), 0xff);
485     assertEQ(createBitMask(32), 0xffffffff);
486     assertThrown!AssertError(createBitMask!ulong(64));
487     assertThrown!AssertError(createBitMask!ubyte(9));
488 }