Created
April 24, 2009 02:10
-
-
Save DanielKeep/100885 to your computer and use it in GitHub Desktop.
Simple binary serialisation of value types for D.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Simple binary serialisation of value types (oh, and arrays and AAs). | |
* | |
* Copyright © 2007 Daniel Keep | |
* License: http://www.opensource.org/licenses/zlib-license.php | |
*/ | |
module serial; | |
version(Serial_NoReals) | |
{ | |
version = NoReals; | |
} | |
else version(Serial_ConvertRealsToDoubles) | |
{ | |
version = ConvertRealsToDoubles; | |
} | |
else | |
{ | |
version = NoReals; | |
} | |
import std.stream: MemoryStream; | |
import std.stream: OutputStream; | |
import std.stream: InputStream; | |
ubyte[] toData(T_Type)(T_Type value) | |
{ | |
scope stream = new MemoryStream; | |
toStream(stream, value); | |
return stream.data; | |
} | |
void toStream(T_Type)(OutputStream stream, T_Type value) | |
{ | |
static if( is(T_Type == byte) || is(T_Type == ubyte) ) | |
{ | |
stream.write(value); | |
} | |
else static if( is(T_Type == bool) ) | |
{ | |
stream.write(value ? ~cast(ubyte) 0 : cast(ubyte) 0); | |
} | |
else static if( is(T_Type == short) || is(T_Type == ushort) | |
|| is(T_Type == int) || is(T_Type == uint) || is(T_Type == long) | |
|| is(T_Type == ulong) || is(T_Type == float) | |
|| is(T_Type == double) ) | |
{ | |
stream.write(nboSwap(value)); | |
} | |
else static if( is(T_Type == real) ) | |
{ | |
version(NoReals) | |
{ | |
static assert( 0, "Support for serialising real values has been disabled." ); | |
} | |
else version(ConvertRealsToDoubles) | |
{ | |
stream.write(nboSwap(cast(double) value)); | |
} | |
else | |
{ | |
static assert( 0, "Error: no real handling mode set!" ); | |
} | |
} | |
else static if( is(T_Type == ifloat) || is(T_Type == idouble) | |
|| is(T_Type == ireal) ) | |
{ | |
stream.write(nboSwap(value.im)); | |
} | |
else static if( is(T_Type == cfloat) || is(T_Type == cdouble) | |
|| is(T_Type == creal) ) | |
{ | |
stream.write(nboSwap(value.re)); | |
stream.write(nboSwap(value.im)); | |
} | |
else static if( is(T_Type == char) ) | |
{ | |
stream.write(value); | |
} | |
else static if( is(T_Type == wchar) || is(T_Type == dchar) ) | |
{ | |
stream.write(nboSwap(value)); | |
} | |
else static if( is(T_Type == size_t) ) | |
{ | |
// Obviously, this doesn't work with 64-bit archs. If there are going | |
// to be 64-bit machines used with this, ALL size_ts and pointers | |
// need to use the largest size. | |
if( value > uint.max ) | |
throw new SerialOverflowException(value); | |
else | |
stream.write(nboSwap(cast(uint) value)); | |
} | |
else static if( is(T_Type == void[]) ) | |
{ | |
toStream(stream, (cast(ubyte*) value.ptr)[0 .. value.length]); | |
} | |
else static if( is(T_Type T_Inner : T_Inner[]) ) | |
{ | |
toStream(stream, value.length); | |
foreach( element; value ) | |
toStream(stream, element); | |
} | |
else static if( isAssociativeArray!(T_Type) ) | |
{ | |
auto keys = value.keys; | |
auto values = value.values; | |
assert( keys.length == values.length ); | |
toStream(stream, keys.length); | |
for( size_t i = 0; i < keys.length; i++ ) | |
{ | |
toStream(stream, keys[i]); | |
toStream(stream, values[i]); | |
} | |
} | |
else static if( is(typeof(value.toStream(stream))) ) | |
{ | |
value.toStream(stream); | |
} | |
else static if( is(typeof(value.toData()) == ubyte[]) ) | |
{ | |
toStream(stream, value.toData()); | |
} | |
else static if( is(T_Type == struct) ) | |
{ | |
foreach( field; value.tupleof ) | |
toStream(stream, field); | |
} | |
else static if( is(T_Type T_BaseType == typedef) ) | |
{ | |
toStream(stream, cast(T_BaseType) value); | |
} | |
else | |
{ | |
static assert( 0, "toStream!(" ~ T_Type.stringof ~ ") not supported." ); | |
} | |
} | |
T_Type fromData(T_Type)(ubyte[] data) | |
{ | |
scope stream = new MemoryStream(data); | |
T_Type result; | |
fromStream(stream, result); | |
return result; | |
} | |
void fromStream(T_Type)(InputStream stream, out T_Type result) | |
{ | |
static if( is(T_Type == byte) || is(T_Type == ubyte) ) | |
{ | |
stream.read(result); | |
} | |
else static if( is(T_Type == bool) ) | |
{ | |
ubyte temp; | |
stream.read(temp); | |
result = (temp != 0); | |
} | |
else static if( is(T_Type == short) || is(T_Type == ushort) || is(T_Type == int) || is(T_Type == uint) || is(T_Type == long) || is(T_Type == ulong) || is(T_Type == float) || is(T_Type == double) ) | |
{ | |
T_Type temp; | |
stream.read(temp); | |
result = nboSwap(temp); | |
} | |
else static if( is(T_Type == real) ) | |
{ | |
version(NoReals) | |
{ | |
static assert( 0, "Support for de-serialising real values has been disabled." ); | |
} | |
else version(ConvertRealsToDoubles) | |
{ | |
double temp; | |
stream.read(temp); | |
result = cast(real) nboSwap(temp); | |
} | |
else | |
{ | |
static assert( 0, "Error: no real handling mode set!" ); | |
} | |
} | |
else static if( is(T_Type == ifloat) || is(T_Type == idouble) || is(T_Type == ireal) ) | |
{ | |
T_Type temp; | |
stream.read(temp); | |
result = nboSwap(temp) * 1.0i; | |
} | |
else static if( is(T_Type == cfloat) || is(T_Type == cdouble) || is(T_Type == creal) ) | |
{ | |
typeof(result.re) temp_re, | |
temp_im; | |
stream.read(temp_re); | |
stream.read(temp_im); | |
result = nboSwap(temp_re) + nboSwap(temp_im) * 1.0i; | |
} | |
else static if( is(T_Type == char) ) | |
{ | |
stream.read(result); | |
} | |
else static if( is(T_Type == wchar) || is(T_Type == dchar) ) | |
{ | |
T_Type temp; | |
stream.read(temp); | |
result = nboSwap(temp); | |
} | |
else static if( is(T_Type == size_t) ) | |
{ | |
T_Type temp; | |
stream.read(temp); | |
result = nboSwap(temp); | |
} | |
else static if( is(T_Type == void[]) ) | |
{ | |
ubyte[] temp; | |
fromStream(stream, temp); | |
result = (cast(void*) temp.ptr)[0 .. temp.length]; | |
} | |
else static if( is(T_Type T_Inner : T_Inner[]) ) | |
{ | |
size_t length; | |
fromStream(stream, length); | |
result.length = length; | |
for( size_t i = 0; i < length; i++ ) | |
fromStream(stream, result[i]); | |
} | |
else static if( isAssociativeArray!(T_Type) ) | |
{ | |
alias typeof(result.keys.init[0]) T_Key; | |
alias typeof(result.values.init[0]) T_Value; | |
size_t length; | |
fromStream(stream, length); | |
for( size_t i = 0; i < length; i++ ) | |
{ | |
T_Key key; | |
T_Value value; | |
fromStream(stream, key); | |
fromStream(stream, value); | |
result[key] = value; | |
} | |
} | |
else static if( is(typeof(T_Type.fromStream(stream, result))) ) | |
{ | |
T_Type.fromStream(stream, result); | |
} | |
else static if( is(typeof(&T_Type.fromData) == T_Type function(ubyte[])) ) | |
{ | |
ubyte[] temp; | |
fromStream(stream, temp); | |
scope( exit ) | |
temp.length = 0; | |
result = T_Type.fromData(temp); | |
} | |
else static if( is(T_Type == struct) ) | |
{ | |
T_Type temp; | |
foreach( i, field; temp.tupleof ) | |
{ | |
typeof(field) fv; | |
fromStream(stream, fv); | |
temp.tupleof[i] = fv; | |
} | |
result = temp; | |
} | |
else static if( is(T_Type T_BaseType == typedef) ) | |
{ | |
T_BaseType temp; | |
fromStream(stream, temp); | |
result = cast(T_Type) temp; | |
} | |
else | |
{ | |
static assert( 0, "fromStream!(" ~ T_Type.stringof ~ ") not supported." ); | |
} | |
} | |
T_Type nboSwap2(T_Type)(T_Type value) | |
{ | |
static assert( T_Type.sizeof == 2u ); | |
version(BigEndian) | |
{ | |
return value; | |
} | |
else | |
{ | |
T_Type temp = value; | |
ushort v = *(cast(ushort*) (&temp)); | |
ubyte l = cast(ubyte) (v & 0x00ff); | |
ubyte h = cast(ubyte) ((v >>> 8) & 0x00ff); | |
v = cast(ushort) ((cast(ushort) h) | ((cast(ushort) l) << 8)); | |
return *(cast(T_Type*) (&v)); | |
} | |
} | |
T_Type nboSwap4(T_Type)(T_Type value) | |
{ | |
static assert( T_Type.sizeof == 4u ); | |
version(BigEndian) | |
{ | |
return value; | |
} | |
else | |
{ | |
T_Type temp = value; | |
uint v = *(cast(uint*) (&temp)); | |
ushort l = cast(ushort) (v & 0x0000ffff); | |
ushort h = cast(ushort) ((v >>> 16) & 0x0000ffff); | |
l = nboSwap2(l); | |
h = nboSwap2(h); | |
v = cast(uint) h | (cast(uint) l << 16); | |
return *(cast(T_Type*) (&v)); | |
} | |
} | |
T_Type nboSwap8(T_Type)(T_Type value) | |
{ | |
static assert( T_Type.sizeof == 8u ); | |
version(BigEndian) | |
{ | |
return value; | |
} | |
else | |
{ | |
T_Type temp = value; | |
ulong v = *(cast(ulong*) (&temp)); | |
uint l = cast(uint) (v & 0xffffffff); | |
uint h = cast(uint) ((v >>> 32) & 0xffffffff); | |
l = nboSwap4(l); | |
h = nboSwap4(h); | |
v = (cast(ulong) h) | ((cast(ulong) l) << 32); | |
return *(cast(T_Type*) (&v)); | |
} | |
} | |
alias nboSwap2!(short) nboSwap; | |
alias nboSwap2!(ushort) nboSwap; | |
alias nboSwap2!(wchar) nboSwap; | |
alias nboSwap4!(int) nboSwap; | |
alias nboSwap4!(uint) nboSwap; | |
alias nboSwap4!(dchar) nboSwap; | |
alias nboSwap4!(float) nboSwap; | |
alias nboSwap8!(long) nboSwap; | |
alias nboSwap8!(ulong) nboSwap; | |
alias nboSwap8!(double) nboSwap; | |
private | |
{ | |
template isAssociativeArray(T) | |
{ | |
static if( T.mangleof[0] == 'H' ) | |
const isAssociativeArray = true; | |
else | |
const isAssociativeArray = false; | |
} | |
} | |
unittest { | |
short a = -0x765; | |
ushort b = 0x8765; | |
wchar c = 'c'; | |
int d = -0x7654321; | |
uint e = 0x87654321; | |
dchar f = 'f'; | |
float g = -3.1413; | |
long h = -0x765432187654321; | |
ulong i = 0x8765432187654321; | |
double j = -3.1413; | |
assert( nboSwap(nboSwap(a)) == a ); | |
assert( nboSwap(nboSwap(b)) == b ); | |
assert( nboSwap(nboSwap(c)) == c ); | |
assert( nboSwap(nboSwap(d)) == d ); | |
assert( nboSwap(nboSwap(e)) == e ); | |
assert( nboSwap(nboSwap(f)) == f ); | |
assert( nboSwap(nboSwap(g)) == g ); | |
assert( nboSwap(nboSwap(h)) == h ); | |
assert( nboSwap(nboSwap(i)) == i ); | |
assert( nboSwap(nboSwap(j)) == j ); | |
} | |
version(serial_main) | |
{ | |
import std.stdio; | |
void main() | |
{ | |
writefln("All tests passed!"); | |
} | |
} | |
version(Unittest) | |
{ | |
void test_value(T_Type)(T_Type orig) | |
{ | |
scope got = fromData!(T_Type)(toData(orig)); | |
assert( orig == got, T_Type.stringof ); | |
} | |
void test_array(T_Type)(T_Type orig) | |
{ | |
scope got = fromData!(T_Type)(toData(orig)); | |
assert( orig.length == got.length, T_Type.stringof ); | |
foreach( i, e; orig ) | |
assert( e == got[i], T_Type.stringof ); | |
} | |
void test_map(T_Type)(T_Type orig) | |
{ | |
scope got = fromData!(T_Type)(toData(orig)); | |
assert( orig.keys.length == got.keys.length, T_Type.stringof ); | |
foreach( k, v; orig ) | |
assert( v == got[k], T_Type.stringof ); | |
} | |
unittest { | |
struct point_t | |
{ | |
float x, | |
y; | |
} | |
typedef uint id_t; | |
byte a = 1; | |
ubyte b = 2; | |
bool c = true; | |
char d = 'd'; | |
short e = 5; | |
ushort f = 6; | |
wchar g = 'g'; | |
int h = 8; | |
uint i = 9; | |
dchar j = 'j'; | |
float k = 10.0; | |
long l = 11; | |
ulong m = 12; | |
double n = 13.0; | |
point_t o; | |
o.x = 3.1413; | |
o.y = 2.13; | |
id_t p = cast(id_t) 0x81726354; | |
int[] aa = [1, 2, 3, 4, 5]; | |
char[] ab = "Hello, World!"; | |
int[char[]] ma; | |
ma["life"] = 42; | |
ma["age"] = 21; | |
test_value(a); | |
test_value(b); | |
test_value(c); | |
test_value(d); | |
test_value(e); | |
test_value(f); | |
test_value(g); | |
test_value(h); | |
test_value(i); | |
test_value(j); | |
test_value(k); | |
test_value(l); | |
test_value(m); | |
test_value(n); | |
test_value(o); | |
test_value(p); | |
test_array(aa); | |
test_array(ab); | |
test_map(ma); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment