Skip to content

Instantly share code, notes, and snippets.

@DanielKeep
Created April 24, 2009 02:10
Show Gist options
  • Save DanielKeep/100885 to your computer and use it in GitHub Desktop.
Save DanielKeep/100885 to your computer and use it in GitHub Desktop.
Simple binary serialisation of value types for D.
/**
* 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