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 }