1 module reversineer.integers; 2 3 import std.range; 4 import std.traits; 5 6 private struct EndianType(T, bool littleEndian) { 7 ubyte[T.sizeof] raw; 8 alias native this; 9 version(BigEndian) { 10 enum needSwap = littleEndian; 11 } else { 12 enum needSwap = !littleEndian; 13 } 14 T native() const @safe { 15 T result = (cast(T[])(raw[].dup))[0]; 16 static if (needSwap) { 17 swapEndianness(result); 18 } 19 return result; 20 } 21 void native(out T result) const @safe { 22 result = (cast(T[])(raw[].dup))[0]; 23 static if (needSwap) { 24 swapEndianness(result); 25 } 26 } 27 void toString(Range)(Range sink) const if (isOutputRange!(Range, const(char))) { 28 import std.format : formattedWrite; 29 sink.formattedWrite!"%s"(this.native); 30 } 31 void opAssign(ubyte[T.sizeof] input) { 32 raw = input; 33 } 34 void opAssign(ubyte[] input) { 35 assert(input.length == T.sizeof, "Array must be "~T.sizeof.stringof~" bytes long"); 36 raw = input; 37 } 38 void opAssign(T input) @safe { 39 static if (needSwap) { 40 swapEndianness(input); 41 } 42 union Raw { 43 T val; 44 ubyte[T.sizeof] raw; 45 } 46 raw = Raw(input).raw; 47 } 48 } 49 50 void swapEndianness(T)(ref T val) { 51 import std.bitmanip : swapEndian; 52 static if (isIntegral!T || isSomeChar!T || isBoolean!T) { 53 val = swapEndian(val); 54 } else static if (isFloatingPoint!T) { 55 import std.algorithm : reverse; 56 union Raw { 57 T val; 58 ubyte[T.sizeof] raw; 59 } 60 auto raw = Raw(val); 61 reverse(raw.raw[]); 62 val = raw.val; 63 } else static if (is(T == struct)) { 64 foreach (ref field; val.tupleof) { 65 swapEndianness(field); 66 } 67 } else static if (isStaticArray!T) { 68 foreach (ref element; val) { 69 swapEndianness(element); 70 } 71 } else static assert(0, "Unsupported type "~T.stringof); 72 } 73 74 /++ 75 + Represents a little endian type. Most significant bits come last, so 0x4000 76 + is, for example, represented as [00, 04]. 77 +/ 78 alias LittleEndian(T) = EndianType!(T, true); 79 /// 80 @safe unittest { 81 import std.conv : text; 82 LittleEndian!ushort x; 83 x = cast(ubyte[])[0, 2]; 84 assert(x == 0x200); 85 ushort tmp; 86 x.native(tmp); 87 assert(tmp == 512); 88 assert(x.text == "512"); 89 ubyte[] z = [0, 3]; 90 x = z; 91 assert(x == 0x300); 92 assert(x.text == "768"); 93 x = 1024; 94 assert(x.raw == [0, 4]); 95 96 LittleEndian!float f; 97 f = cast(ubyte[])[0, 0, 32, 64]; 98 assert(f == 2.5); 99 100 align(1) static struct Test { 101 align(1): 102 uint a; 103 ushort b; 104 ushort[2] c; 105 } 106 LittleEndian!Test t; 107 t = cast(ubyte[])[3, 2, 1, 0, 2, 1, 6, 5, 8, 7]; 108 assert(t.a == 0x010203); 109 assert(t.b == 0x0102); 110 assert(t.c == [0x0506, 0x0708]); 111 t = Test(42, 42, [10, 20]); 112 assert(t.raw == [42, 0, 0, 0, 42, 0, 10, 0, 20, 0]); 113 114 align(1) static struct Test2 { 115 align(1): 116 ubyte a; 117 char b; 118 ubyte[4] c; 119 } 120 LittleEndian!Test2 t2; 121 t2 = cast(ubyte[])[20, 30, 1, 2, 3, 4]; 122 assert(t2.a == 20); 123 assert(t2.b == 30); 124 assert(t2.c == [1, 2, 3, 4]); 125 } 126 127 /++ 128 + Represents a big endian type. Most significant bits come first, so 0x4000 129 + is, for example, represented as [04, 00]. 130 +/ 131 alias BigEndian(T) = EndianType!(T, false); 132 /// 133 @safe unittest { 134 import std.conv : text; 135 BigEndian!ushort x; 136 x = cast(ubyte[])[2, 0]; 137 assert(x == 0x200); 138 ushort tmp; 139 x.native(tmp); 140 assert(tmp == 512); 141 assert(x.text == "512"); 142 x = 1024; 143 assert(x.raw == [4, 0]); 144 145 BigEndian!float f; 146 f = cast(ubyte[])[64, 32, 0, 0]; 147 assert(f == 2.5); 148 149 align(1) static struct Test { 150 align(1): 151 uint a; 152 ushort b; 153 ushort[2] c; 154 } 155 BigEndian!Test t; 156 t = cast(ubyte[])[0, 1, 2, 3, 1, 2, 5, 6, 7, 8]; 157 assert(t.a == 0x010203); 158 assert(t.b == 0x0102); 159 assert(t.c == [0x0506, 0x0708]); 160 t = Test(42, 42, [10, 20]); 161 assert(t.raw == [0, 0, 0, 42, 0, 42, 0, 10, 0, 20]); 162 163 align(1) static struct Test2 { 164 align(1): 165 ubyte a; 166 char b; 167 ubyte[4] c; 168 } 169 BigEndian!Test2 t2; 170 t2 = cast(ubyte[])[20, 30, 1, 2, 3, 4]; 171 assert(t2.a == 20); 172 assert(t2.b == 30); 173 assert(t2.c == [1, 2, 3, 4]); 174 } 175 176 private T fromPBCD(T)(ubyte[T.sizeof] input) { 177 import std.algorithm : map; 178 import std.array : array; 179 import std.math : pow; 180 import std.range : iota, retro; 181 static immutable ulong[] multipliers = iota(T.sizeof*2).retro.map!(x => pow(10,x)).array; 182 T output; 183 foreach (index, b; input) { 184 output += (b&0xF) * multipliers[index*2+1]; 185 output += ((b&0xF0)>>4) * multipliers[index*2]; 186 } 187 return output; 188 } 189 /// 190 @safe unittest { 191 ubyte[2] data = [0x98, 0x76]; 192 assert(data.fromPBCD!ushort() == 9876); 193 } 194 195 private ubyte[T.sizeof] toPBCD(T)(T input) { 196 import std.algorithm : map; 197 import std.array : array; 198 import std.math : pow; 199 import std.range : iota, retro; 200 static immutable ulong[] multipliers = iota(T.sizeof*2).retro.map!(x => pow(10,x)).array; 201 ubyte[T.sizeof] output; 202 foreach (index, ref b; output) { 203 b += (input/multipliers[index*2+1])%10; 204 b += (input/multipliers[index*2])%10*16; 205 } 206 return output; 207 } 208 /// 209 @safe unittest { 210 assert(9876.toPBCD!ushort == [0x98, 0x76]); 211 } 212 213 /++ 214 + Represents a little endian type in packed binary-coded decimal coding. This 215 + coding is common in old processors, but doesn't see much use in the modern 216 + era. 217 + 218 + See_also: https://en.wikipedia.org/wiki/Binary-coded_decimal 219 +/ 220 struct PackedBCD(T) { 221 ubyte[T.sizeof] raw; 222 alias toInt this; 223 T toInt() const { 224 return fromPBCD!T(raw); 225 } 226 void toString(Range)(Range sink) const if (isOutputRange!(Range, const(char))) { 227 import std.format : formattedWrite; 228 sink.formattedWrite!"%s"(this.toInt()); 229 } 230 void opAssign(ubyte[T.sizeof] input) { 231 raw = input; 232 } 233 void opAssign(ubyte[] input) { 234 raw = input; 235 } 236 void opAssign(const T input) { 237 raw = toPBCD(input); 238 } 239 } 240 241 @safe unittest { 242 PackedBCD!uint x; 243 x = cast(ubyte[])[0x98, 0x76, 0x54, 0x32]; 244 assert(x.toInt == 98765432); 245 x = 44; 246 assert(x.raw == [0, 0, 0, 0x44]); 247 } 248 249 mixin template VerifyOffsets(T, size_t size) { 250 import std.format : format; 251 import std.traits : getSymbolsByUDA, getUDAs; 252 static foreach (field; getSymbolsByUDA!(T, Offset)) { 253 static assert(field.offsetof == getUDAs!(field, Offset)[0].offset, format!"Bad offset for %s.%s: %08X, expecting %08X - adjust previous field by 0x%X"(T.stringof, field.stringof, field.offsetof, getUDAs!(field, Offset)[0].offset, getUDAs!(field, Offset)[0].offset-field.offsetof)); 254 } 255 static assert(T.sizeof == size, format!"Bad size for %s: 0x%08X != 0x%08X"(T.stringof, T.sizeof, size)); 256 }