/**
* Variant
* Written by Daniel Keep.
*
* I hereby release this code to the Public Domain; share and enjoy.
*/
module variant;
version( Tango )
{
pragma(msg, `
** WARNING **
The Variant module does not fully support Tango. Tango lacks string
formatters for some basic types, and thus does not pass the unit tests.
`);
import tango.core.Exception : TracedException;
import tango.core.Vararg : va_list;
import tango.text.convert.Float;
import tango.text.convert.Integer;
import tango.text.convert.Layout : Layout;
import tango.text.convert.Utf;
private
{
alias tango.text.convert.Float.toUtf8 toUtf8;
alias tango.text.convert.Integer.toUtf8 toUtf8;
alias tango.text.convert.Utf.toUtf8 toUtf8;
char[] toUtf8(bool v)
{
return v ? "true" : "false";
}
alias toUtf8 toString;
Layout!(char) layout;
}
static this()
{
layout = new Layout!(char);
}
}
else
{
import std.format : doFormat;
import std.stdarg : va_list;
import std.string : toString;
import std.utf : encode;
}
private template maxT(uint a, uint b)
{
const maxT = (a > b) ? a : b;
}
private struct AtomicTypes
{
union
{
bool _bool;
char _char;
wchar _wchar;
dchar _dchar;
byte _byte;
short _short;
int _int;
long _long;
ubyte _ubyte;
ushort _ushort;
uint _uint;
ulong _ulong;
float _float;
double _double;
real _real;
ifloat _ifloat;
idouble _idouble;
ireal _ireal;
void* ptr;
void[] arr;
Object obj;
ubyte[maxT!(_real.sizeof,arr.sizeof)] data;
}
}
private template isAtomicType(T)
{
static if( is( T == bool )
|| is( T == char )
|| is( T == wchar )
|| is( T == dchar )
|| is( T == byte )
|| is( T == short )
|| is( T == int )
|| is( T == long )
|| is( T == ubyte )
|| is( T == ushort )
|| is( T == uint )
|| is( T == ulong )
|| is( T == float )
|| is( T == double )
|| is( T == real )
|| is( T == ifloat )
|| is( T == idouble )
|| is( T == ireal ) )
const isAtomicType = true;
else
const isAtomicType = false;
}
private template isArray(T)
{
static if( is( T U : U[] ) )
const isArray = true;
else
const isArray = false;
}
private template isPointer(T)
{
static if( is( T U : U* ) )
const isPointer = true;
else
const isPointer = false;
}
private template isObject(T)
{
static if( is( T : Object ) )
const isObject = true;
else
const isObject = false;
}
private template isStaticArray(T)
{
static if( is( typeof(T.init)[(T).sizeof / typeof(T.init).sizeof] == T ) )
const isStaticArray = true;
else
const isStaticArray = false;
}
private bool isAny(T,argsT...)(T v, argsT args)
{
foreach( arg ; args )
if( v is arg ) return true;
return false;
}
private
{
const tibool = typeid(bool);
const tichar = typeid(char);
const tiwchar = typeid(wchar);
const tidchar = typeid(dchar);
const tibyte = typeid(byte);
const tishort = typeid(short);
const tiint = typeid(int);
const tilong = typeid(long);
const tiubyte = typeid(ubyte);
const tiushort = typeid(ushort);
const tiuint = typeid(uint);
const tiulong = typeid(ulong);
const tifloat = typeid(float);
const tidouble = typeid(double);
const tireal = typeid(real);
const tiifloat = typeid(ifloat);
const tiidouble = typeid(idouble);
const tiireal = typeid(ireal);
}
private bool canImplicitCastTo(dsttypeT)(TypeInfo srctype)
{
static if( is( dsttypeT == char ) )
return isAny(srctype, tibool, tiubyte);
else static if( is( dsttypeT == wchar ) )
return isAny(srctype, tibool, tiubyte, tiushort, tichar);
else static if( is( dsttypeT == dchar ) )
return isAny(srctype, tibool, tiubyte, tiushort, tiuint, tichar,
tiwchar);
else static if( is( dsttypeT == byte ) )
return isAny(srctype, tibool);
else static if( is( dsttypeT == ubyte ) )
return isAny(srctype, tibool, tichar);
else static if( is( dsttypeT == short ) )
return isAny(srctype, tibool, tibyte, tiubyte, tichar);
else static if( is( dsttypeT == ushort ) )
return isAny(srctype, tibool, tibyte, tiubyte, tichar, tiwchar);
else static if( is( dsttypeT == int ) )
return isAny(srctype, tibool, tibyte, tiubyte, tishort, tiushort,
tichar, tiwchar);
else static if( is( dsttypeT == uint ) )
return isAny(srctype, tibool, tibyte, tiubyte, tishort, tiushort,
tichar, tiwchar, tidchar);
else static if( is( dsttypeT == long ) || is( dsttypeT == ulong ) )
return isAny(srctype, tibool, tibyte, tiubyte, tishort, tiushort,
tiint, tiuint, tichar, tiwchar, tidchar);
else static if( is( dsttypeT == float ) )
return isAny(srctype, tibool, tibyte, tiubyte);
else static if( is( dsttypeT == double ) )
return isAny(srctype, tibool, tibyte, tiubyte, tifloat);
else static if( is( dsttypeT == real ) )
return isAny(srctype, tibool, tibyte, tiubyte, tifloat, tidouble);
else static if( is( dsttypeT == idouble ) )
return isAny(srctype, tiifloat);
else static if( is( dsttypeT == ireal ) )
return isAny(srctype, tiifloat, tiidouble);
else
return false;
}
version( Tango )
alias TracedException BaseException;
else
alias Exception BaseException;
class VariantTypeMismatchException : BaseException
{
this(TypeInfo expected, TypeInfo got)
{
version( Tango )
super("cannot convert "~expected.toUtf8
~" value to a "~got.toUtf8);
else
super("cannot convert "~expected.toString
~" value to a "~got.toString);
}
}
private template storageT(T)
{
static if( isStaticArray!(T) )
alias typeof(T.dup) storageT;
else
alias T storageT;
}
struct Variant
{
TypeInfo type = typeid(void);
AtomicTypes value;
static Variant opCall(T)(T value)
{
Variant _this;
static if( isStaticArray!(T) )
_this = value.dup;
else
_this = value;
return _this;
}
Variant opAssign(T)(T value)
{
static if( isStaticArray!(T) )
{
return (*this = value.dup);
}
else
{
type = typeid(T);
static if( isAtomicType!(T) )
{
mixin("this.value._"~T.stringof~"=value;");
}
else static if( isArray!(T) )
{
this.value.arr = (cast(void*)value.ptr)
[0 .. value.length];
}
else static if( isPointer!(T) )
{
this.value.ptr = cast(void*)T;
}
else static if( isObject!(T) )
{
this.value.obj = T;
}
else
{
if( T.sizeof <= this.value.data.length )
{
this.value.data[0..T.sizeof] =
(cast(ubyte*)&value)[0..T.sizeof];
}
else
{
auto buffer = (cast(ubyte*)&value)[0..T.sizeof].dup;
this.value.arr = cast(void[])buffer;
}
}
return *this;
}
}
bool isA(T)()
{
return cast(bool)(typeid(T) is type);
}
bool isImplicitly(T)()
{
return ( cast(bool)(typeid(T) is type)
|| canImplicitCastTo!(T)(type) );
}
storageT!(S) get(S)()
{
alias storageT!(S) T;
if( type !is typeid(T)
// Let D do runtime check itself
&& !isObject!(T)
// Allow implicit upcasts
&& !canImplicitCastTo!(T)(type)
)
throw new VariantTypeMismatchException(type,typeid(T));
static if( isAtomicType!(T) )
{
if( type is typeid(T) )
{
return mixin("this.value._"~T.stringof);
}
else
{
if( type is tibool ) return cast(T)this.value._bool;
else if( type is tichar ) return cast(T)this.value._char;
else if( type is tiwchar ) return cast(T)this.value._wchar;
else if( type is tidchar ) return cast(T)this.value._dchar;
else if( type is tibyte ) return cast(T)this.value._byte;
else if( type is tishort ) return cast(T)this.value._short;
else if( type is tiint ) return cast(T)this.value._int;
else if( type is tilong ) return cast(T)this.value._long;
else if( type is tiubyte ) return cast(T)this.value._ubyte;
else if( type is tiushort ) return cast(T)this.value._ushort;
else if( type is tiuint ) return cast(T)this.value._uint;
else if( type is tiulong ) return cast(T)this.value._ulong;
else if( type is tifloat ) return cast(T)this.value._float;
else if( type is tidouble ) return cast(T)this.value._double;
else if( type is tireal ) return cast(T)this.value._real;
else if( type is tiifloat ) return cast(T)this.value._ifloat;
else if( type is tiidouble ) return cast(T)this.value._idouble;
else if( type is tiireal ) return cast(T)this.value._ireal;
else
throw new VariantTypeMismatchException(type,typeid(T));
}
}
else static if( isArray!(T) )
{
return (cast(typeof(T[0])*)this.value.arr.ptr)
[0 .. this.value.arr.length];
}
else static if( isPointer!(T) )
{
return cast(T)this.value.ptr;
}
else static if( isObject!(T) )
{
return cast(T)this.value.obj;
}
else
{
if( T.sizeof <= this.value.data.length )
{
T result;
(cast(ubyte*)&result)[0..T.sizeof] =
this.value.data[0..T.sizeof];
return result;
}
else
{
T result;
(cast(ubyte*)&result)[0..T.sizeof] =
(cast(ubyte[])this.value.arr)[0..T.sizeof];
return result;
}
}
assert(false);
}
typeof(T+T) opAdd(T)(T rhs) { return get!(T) + rhs; }
typeof(T+T) opAdd_r(T)(T lhs) { return lhs + get!(T); }
typeof(T-T) opSub(T)(T rhs) { return get!(T) - rhs; }
typeof(T-T) opSub_r(T)(T lhs) { return lhs - get!(T); }
typeof(T*T) opMul(T)(T rhs) { return get!(T) * rhs; }
typeof(T*T) opMul_r(T)(T lhs) { return lhs * get!(T); }
typeof(T/T) opDiv(T)(T rhs) { return get!(T) / rhs; }
typeof(T/T) opDiv_r(T)(T lhs) { return lhs / get!(T); }
typeof(T%T) opMod(T)(T rhs) { return get!(T) % rhs; }
typeof(T%T) opMod_r(T)(T lhs) { return lhs % get!(T); }
typeof(T&T) opAnd(T)(T rhs) { return get!(T) & rhs; }
typeof(T&T) opAnd_r(T)(T lhs) { return lhs & get!(T); }
typeof(T|T) opOr(T)(T rhs) { return get!(T) | rhs; }
typeof(T|T) opOr_r(T)(T lhs) { return lhs | get!(T); }
typeof(T^T) opXor(T)(T rhs) { return get!(T) ^ rhs; }
typeof(T^T) opXor_r(T)(T lhs) { return lhs ^ get!(T); }
typeof(T<<T) opShl(T)(T rhs) { return get!(T) << rhs; }
typeof(T<<T) opShl_r(T)(T lhs) { return lhs << get!(T); }
typeof(T>>T) opShr(T)(T rhs) { return get!(T) >> rhs; }
typeof(T>>T) opShr_r(T)(T lhs) { return lhs >> get!(T); }
typeof(T>>>T) opUShr(T)(T rhs) { return get!(T) >>> rhs; }
typeof(T>>>T) opUShr_r(T)(T lhs){ return lhs >>> get!(T); }
typeof(T~T) opCat(T)(T rhs) { return get!(T) ~ rhs; }
typeof(T~T) opCat_r(T)(T lhs) { return lhs ~ get!(T); }
Variant opAddAssign(T)(T value) { return (*this = get!(T) + value); }
Variant opSubAssign(T)(T value) { return (*this = get!(T) - value); }
Variant opMulAssign(T)(T value) { return (*this = get!(T) * value); }
Variant opDivAssign(T)(T value) { return (*this = get!(T) / value); }
Variant opModAssign(T)(T value) { return (*this = get!(T) % value); }
Variant opAndAssign(T)(T value) { return (*this = get!(T) & value); }
Variant opOrAssign(T)(T value) { return (*this = get!(T) | value); }
Variant opXorAssign(T)(T value) { return (*this = get!(T) ^ value); }
Variant opShlAssign(T)(T value) { return (*this = get!(T) << value); }
Variant opShrAssign(T)(T value) { return (*this = get!(T) >> value); }
Variant opUShrAssign(T)(T value){ return (*this = get!(T) >>> value); }
Variant opCatAssign(T)(T value) { return (*this = get!(T) ~ value); }
int opEquals(T)(T rhs)
{
static if( is( T == Variant ) )
return opEqualsVariant(rhs);
else
return get!(T) == rhs;
}
int opCmp(T)(T rhs)
{
static if( is( T == Variant ) )
return opCmpVariant(rhs);
else
{
auto lhs = get!(T);
return (lhs < rhs) ? -1 : (lhs == rhs) ? 0 : 1;
}
}
hash_t toHash()
{
return type.getHash(data.ptr);
}
private int opEqualsVariant(Variant rhs)
{
if( type != rhs.type ) return false;
return cast(bool) type.equals(data.ptr, rhs.data.ptr);
}
private int opCmpVariant(Variant rhs)
{
if( type != rhs.type )
throw new VariantTypeMismatchException(type, rhs.type);
return type.compare(data.ptr, rhs.data.ptr);
}
private void[] data()
{
if( type.tsize <= value.data.length )
return cast(void[])(value.data);
else
return value.arr;
}
version( Tango )
{
alias toStringImpl toUtf8;
version( PhobosCompatibility )
alias toStringImpl toString;
}
else
alias toStringImpl toString;
private char[] toStringImpl()
{
// Special case: void type
if( type is typeid(void) )
{
return "Variant.init";
}
// Special case: strings (for h3r3tic)
else if( type is typeid(char[]) )
{
return get!(char[]);
}
else if( type is typeid(wchar[]) )
{
return .toString(get!(wchar[]));
}
else if( type is typeid(dchar[]) )
{
return .toString(get!(dchar[]));
}
// Everything else
else
{
version( Tango )
{
TypeInfo[1] tis = [type];
void* args = this.data.ptr;
return layout.convert(tis, args, "{}");
}
else
{
char[] result;
TypeInfo[1] tis = [type];
doFormat((dchar c){encode(result,c);}, tis, this.data.ptr);
return result;
}
}
}
}
version( Tango )
{
version( PhobosCompatibility ) {}
else
version = Tango_NoUnittests;
}
version( Tango_NoUnittests )
{
unittest
{
pragma(msg,
` ** WARNING **
The Variant unittests cannot currently be run under Tango without the
PhobosCompatibility version flag. Why? Because I can't be bothered to write
all the unit tests out twice just because Tango uses toUtf8 instead of
toString.
`);
}
}
else
{
unittest
{
Variant v;
assert( v.isA!(void), v.type.toString );
v = 42;
assert( v.isA!(int), v.type.toString );
assert( v.isImplicitly!(long), v.type.toString );
assert( v.isImplicitly!(ulong), v.type.toString );
assert( !v.isImplicitly!(uint), v.type.toString );
assert( v.get!(int) == 42 );
assert( v.get!(long) == 42L );
assert( v.get!(ulong) == 42uL );
v = "Hello, World!"c;
assert( v.isA!(char[]), v.type.toString );
assert( !v.isImplicitly!(wchar[]), v.type.toString );
assert( v.get!(char[]) == "Hello, World!" );
v = [1,2,3,4,5];
assert( v.isA!(int[]), v.type.toString );
assert( v.get!(int[]) == [1,2,3,4,5] );
v = 3.1413;
assert( v.isA!(double), v.type.toString );
assert( v.isImplicitly!(real), v.type.toString );
assert( !v.isImplicitly!(float), v.type.toString );
assert( v.get!(double) == 3.1413 );
auto u = Variant(v);
assert( u.isA!(double), u.type.toString );
assert( u.get!(double) == 3.1413 );
v = 38;
assert( v + 4 == 42 );
assert( 4 + v == 42 );
assert( v - 4 == 34 );
assert( 4 - v == -34 );
assert( v * 2 == 76 );
assert( 2 * v == 76 );
assert( v / 2 == 19 );
assert( 2 / v == 0 );
assert( v % 2 == 0 );
assert( 2 % v == 2 );
assert( (v & 6) == 6 );
assert( (6 & v) == 6 );
assert( (v | 9) == 47 );
assert( (9 | v) == 47 );
assert( (v ^ 5) == 35 );
assert( (5 ^ v) == 35 );
assert( v << 1 == 76 );
assert( 1 << Variant(2) == 4 );
assert( v >> 1 == 19 );
assert( 4 >> Variant(2) == 1 );
assert( Variant("abc") ~ "def" == "abcdef" );
assert( "abc" ~ Variant("def") == "abcdef" );
v = 38; v += 4; assert( v == 42 );
v = 38; v -= 4; assert( v == 34 );
v = 38; v *= 2; assert( v == 76 );
v = 38; v /= 2; assert( v == 19 );
v = 38; v %= 2; assert( v == 0 );
v = 38; v &= 6; assert( v == 6 );
v = 38; v |= 9; assert( v == 47 );
v = 38; v ^= 5; assert( v == 35 );
v = 38; v <<= 1; assert( v == 76 );
v = 38; v >>= 1; assert( v == 19 );
v = "abc"; v ~= "def"; assert( v == "abcdef" );
v = Variant.init; assert( v.toString == "Variant.init", v.toString );
v = 42; assert( v.toString == "42", v.toString );
v = "abc"; assert( v.toString == "abc", v.toString );
v = [1,2,3]; assert( v.toString == "[1,2,3]", v.toString );
v = 1+2.0i; assert( v.toString == "1+2i", v.toString );
assert( Variant(0) < Variant(42) );
assert( Variant(42) > Variant(0) );
assert( Variant(21) == Variant(21) );
assert( Variant(0) != Variant(42) );
assert( Variant("bar") == Variant("bar") );
assert( Variant("foo") != Variant("bar") );
{
auto v1 = Variant(42);
auto v2 = Variant("foo");
auto v3 = Variant(1+2.0i);
int[Variant] hash;
hash[v1] = 0;
hash[v2] = 1;
hash[v3] = 2;
assert( hash[v1] == 0 );
assert( hash[v2] == 1 );
assert( hash[v3] == 2 );
}
{
int[char[]] hash;
hash["a"] = 1;
hash["b"] = 2;
hash["c"] = 3;
Variant vhash = hash;
assert( vhash.get!(int[char[]])["a"] == 1 );
assert( vhash.get!(int[char[]])["b"] == 2 );
assert( vhash.get!(int[char[]])["c"] == 3 );
}
}
} |