diff --git a/dyaml/emitter.d b/dyaml/emitter.d index 1f87e0f..57df31e 100644 --- a/dyaml/emitter.d +++ b/dyaml/emitter.d @@ -148,6 +148,9 @@ struct Emitter ScalarStyle style_ = ScalarStyle.Invalid; public: + @disable int opCmp(ref Emitter e); + @disable bool opEquals(ref Emitter e); + /** * Construct an emitter. * @@ -1341,7 +1344,7 @@ struct ScalarWriter startChar_ = endChar_ + 1; } updateBreaks(c, Flag!"UpdateSpaces".yes); - }while(endByte_ < text_.length) + }while(endByte_ < text_.length); emitter_.writeIndicator("\'", false); } @@ -1404,7 +1407,7 @@ struct ScalarWriter ++emitter_.column_; } } - }while(endByte_ < text_.length) + }while(endByte_ < text_.length); emitter_.writeIndicator("\"", false); } @@ -1451,7 +1454,7 @@ struct ScalarWriter if(c == dcharNone){emitter_.writeLineBreak();} } updateBreaks(c, Flag!"UpdateSpaces".yes); - }while(endByte_ < text_.length) + }while(endByte_ < text_.length); } ///Write text as literal block scalar. @@ -1478,7 +1481,7 @@ struct ScalarWriter if(c == dcharNone){emitter_.writeLineBreak();} } updateBreaks(c, Flag!"UpdateSpaces".no); - }while(endByte_ < text_.length) + }while(endByte_ < text_.length); } ///Write text as plain scalar. @@ -1524,7 +1527,7 @@ struct ScalarWriter writeCurrentRange(Flag!"UpdateColumn".yes); } updateBreaks(c, Flag!"UpdateSpaces".yes); - }while(endByte_ < text_.length) + }while(endByte_ < text_.length); } private: diff --git a/dyaml/node.d b/dyaml/node.d index 5798a61..a8b5685 100644 --- a/dyaml/node.d +++ b/dyaml/node.d @@ -20,7 +20,7 @@ import std.stdio; import std.string; import std.traits; import std.typecons; -import std.variant; +import dyaml.std.variant; import dyaml.event; import dyaml.exception; @@ -65,15 +65,15 @@ package abstract class YAMLObject { public: ///Get type of the stored value. - @property TypeInfo type() const; + @property TypeInfo type() const {assert(false);} protected: ///Test for equality with another YAMLObject. - bool equals(const YAMLObject rhs) const; + bool equals(YAMLObject rhs) {assert(false);} } //Stores a user defined YAML data type. -package class YAMLContainer(T) : YAMLObject +package class YAMLContainer(T) if (!Node.Value.allowed!T): YAMLObject { private: //Stored value. @@ -98,10 +98,10 @@ package class YAMLContainer(T) : YAMLObject protected: //Test for equality with another YAMLObject. - override bool equals(const YAMLObject rhs) const + override bool equals(YAMLObject rhs) { if(rhs.type !is typeid(T)){return false;} - return value_ == (cast(YAMLContainer)rhs).value_; + return cast(T)value_ == (cast(YAMLContainer)rhs).value_; } private: @@ -130,13 +130,16 @@ struct Node Node value; public: + @disable bool opEquals(ref Pair); + @disable int opCmp(ref Pair); + ///Construct a Pair from two values. Will be converted to Nodes if needed. this(K, V)(K key, V value) { - static if(is(K == Node)){this.key = key;} - else {this.key = Node(key);} - static if(is(V == Node)){this.value = value;} - else {this.value = Node(value);} + static if(is(Unqual!K == Node)){this.key = key;} + else {this.key = Node(key);} + static if(is(Unqual!V == Node)){this.value = value;} + else {this.value = Node(value);} } ///Equality test with another Pair. @@ -179,6 +182,10 @@ struct Node CollectionStyle collectionStyle = CollectionStyle.Invalid; public: + @disable int opCmp(ref Node); + + @disable bool opEquals(T)(ref T) const if(is(T == const)); + /** * Construct a Node from a value. * @@ -205,7 +212,7 @@ struct Node tag_ = Tag(tag); //No copyconstruction. - static assert(!is(T == Node)); + static assert(!is(Unqual!T == Node)); //We can easily convert ints, floats, strings. static if(isIntegral!T) {value_ = Value(cast(long) value);} @@ -261,12 +268,12 @@ struct Node tag_ = Tag(tag); //Construction from raw node or pair array. - static if(is(T == Node) || is(T == Node.Pair)) + static if(is(Unqual!T == Node) || is(Unqual!T == Node.Pair)) { value_ = Value(array); } //Need to handle byte buffers separately. - else static if(is(T == byte) || is(T == ubyte)) + else static if(is(Unqual!T == byte) || is(Unqual!T == ubyte)) { value_ = Value(cast(ubyte[]) array); } @@ -457,7 +464,7 @@ struct Node * * Returns: true if equal, false otherwise. */ - bool opEquals(T)(ref T rhs) + bool opEquals(T)(ref T rhs) if(!is(T == const)) { return equals!(T, true)(rhs); } @@ -504,10 +511,10 @@ struct Node * Throws: NodeException if unable to convert to specified type, or if * the value is out of range of requested type. */ - @property T get(T)() + @property T get(T)() if(!is(T == const)) { T result; - getToVar(result); + getToVar!T(result); return result; } @@ -521,7 +528,7 @@ struct Node * * Throws: NodeException if unable to convert to specified type. */ - void getToVar(T)(out T target) + void getToVar(T)(out T target) if(!is(T == const)) { if(isType!T) { @@ -529,20 +536,27 @@ struct Node return; } - ///Must go before others, as even string/int/etc could be stored in a YAMLObject. - if(isUserType) + static if(!Value.allowed!T) { - auto object = as!YAMLObject; - if(object.type is typeid(T)) + ///Must go before others, as even string/int/etc could be stored in a YAMLObject. + if(isUserType) { - target = (cast(YAMLContainer!T)object).value_; - return; + auto object = as!YAMLObject; + if(object.type is typeid(T)) + { + target = (cast(YAMLContainer!T)object).value_; + return; + } } } //If we're getting from a mapping and we're not getting Node.Pair[], //we're getting the default value. - if(isMapping){return this["="].as!T;} + if(isMapping) + { + target = this["="].as!T; + return; + } void throwUnexpectedType() { @@ -713,8 +727,8 @@ struct Node static if(isIntegral!K) { auto nodes = value_.get!(Node[]); - static if(is(V == Node)){nodes[index] = value;} - else {nodes[index] = Node(value);} + static if(is(Unqual!V == Node)){nodes[index] = value;} + else {nodes[index] = Node(value);} value_ = Value(nodes); return; } @@ -727,8 +741,8 @@ struct Node else { auto pairs = as!(Node.Pair[])(); - static if(is(V == Node)){pairs[idx].value = value;} - else {pairs[idx].value = Node(value);} + static if(is(Unqual!V == Node)){pairs[idx].value = value;} + else {pairs[idx].value = Node(value);} value_ = Value(pairs); } return; @@ -774,7 +788,7 @@ struct Node int result = 0; foreach(ref node; get!(Node[])) { - static if(is(T == Node)) + static if(is(Unqual!T == Node)) { result = dg(node); } @@ -831,16 +845,16 @@ struct Node int result = 0; foreach(ref pair; get!(Node.Pair[])) { - static if(is(K == Node) && is(V == Node)) + static if(is(Unqual!K == Node) && is(Unqual!V == Node)) { result = dg(pair.key, pair.value); } - else static if(is(K == Node)) + else static if(is(Unqual!K == Node)) { V tempValue = pair.value.as!V; result = dg(pair.key, tempValue); } - else static if(is(V == Node)) + else static if(is(Unqual!V == Node)) { K tempKey = pair.key.as!K; result = dg(tempKey, pair.value); @@ -928,7 +942,7 @@ struct Node new Error("Trying to add an element to a non-sequence node", startMark_)); auto nodes = get!(Node[])(); - static if(is(T == Node)){nodes ~= value;} + static if(is(Unqual!T == Node)){nodes ~= value;} else {nodes ~= Node(value);} value_ = Value(nodes); } @@ -1142,7 +1156,7 @@ struct Node */ bool equals(T, bool useTag)(ref T rhs) { - static if(is(T == Node)) + static if(is(Unqual!T == Node)) { static if(useTag) { @@ -1261,7 +1275,7 @@ struct Node * * This only works for default YAML types, not for user defined types. */ - @property bool isType(T)() const {return value_.type is typeid(T);} + @property bool isType(T)() const {return value_.type is typeid(Unqual!T);} private: //Is the value an integer of some kind? @@ -1274,7 +1288,7 @@ struct Node alias isType!string isString; //Does given node have the same type as this node? - bool hasEqualType(ref Node node) + bool hasEqualType(const ref Node node) const { return value_.type is node.value_.type; } @@ -1308,7 +1322,7 @@ struct Node static if(value){node = &pair.value;} else{node = &pair.key;} - static if(is(T == Node)) + static if(is(Unqual!T == Node)) { if(*node == index){return idx;} } diff --git a/dyaml/representer.d b/dyaml/representer.d index 95050f2..1ebaf86 100644 --- a/dyaml/representer.d +++ b/dyaml/representer.d @@ -340,12 +340,12 @@ final class Representer const keyStyle = value[idx].key.scalarStyle; const valStyle = value[idx].value.scalarStyle; if(!keyScalar || - (keyStyle != ScalarStyle.Invalid && keyStyle != ScalarStyle.Plain))) + (keyStyle != ScalarStyle.Invalid && keyStyle != ScalarStyle.Plain)) { bestStyle = CollectionStyle.Block; } if(!valScalar || - (valStyle != ScalarStyle.Invalid && valStyle != ScalarStyle.Plain))) + (valStyle != ScalarStyle.Invalid && valStyle != ScalarStyle.Plain)) { bestStyle = CollectionStyle.Block; } diff --git a/dyaml/variant.d b/dyaml/variant.d new file mode 100644 index 0000000..32616b4 --- /dev/null +++ b/dyaml/variant.d @@ -0,0 +1,1493 @@ +// Written in the D programming language. + +/** + * This module implements a + * $(LINK2 http://erdani.org/publications/cuj-04-2002.html,discriminated union) + * type (a.k.a. + * $(LINK2 http://en.wikipedia.org/wiki/Tagged_union,tagged union), + * $(LINK2 http://en.wikipedia.org/wiki/Algebraic_data_type,algebraic type)). + * Such types are useful + * for type-uniform binary interfaces, interfacing with scripting + * languages, and comfortable exploratory programming. + * + * Macros: + * WIKI = Phobos/StdVariant + * + * Synopsis: + * + * ---- + * Variant a; // Must assign before use, otherwise exception ensues + * // Initialize with an integer; make the type int + * Variant b = 42; + * assert(b.type == typeid(int)); + * // Peek at the value + * assert(b.peek!(int) !is null && *b.peek!(int) == 42); + * // Automatically convert per language rules + * auto x = b.get!(real); + * // Assign any other type, including other variants + * a = b; + * a = 3.14; + * assert(a.type == typeid(double)); + * // Implicit conversions work just as with built-in types + * assert(a > b); + * // Check for convertibility + * assert(!a.convertsTo!(int)); // double not convertible to int + * // Strings and all other arrays are supported + * a = "now I'm a string"; + * assert(a == "now I'm a string"); + * a = new int[42]; // can also assign arrays + * assert(a.length == 42); + * a[5] = 7; + * assert(a[5] == 7); + * // Can also assign class values + * class Foo {} + * auto foo = new Foo; + * a = foo; + * assert(*a.peek!(Foo) == foo); // and full type information is preserved + * ---- + * + * Credits: + * + * Reviewed by Brad Roberts. Daniel Keep provided a detailed code + * review prompting the following improvements: (1) better support for + * arrays; (2) support for associative arrays; (3) friendlier behavior + * towards the garbage collector. + * + * Copyright: Copyright Andrei Alexandrescu 2007 - 2009. + * License: Boost License 1.0. + * Authors: $(WEB erdani.org, Andrei Alexandrescu) + * Source: $(PHOBOSSRC std/_variant.d) + */ +/* Copyright Andrei Alexandrescu 2007 - 2009. + * Distributed under the Boost Software License, Version 1.0. + * (See accompanying file LICENSE_1_0.txt or copy at + * http://www.boost.org/LICENSE_1_0.txt) + */ +module dyaml.std.variant; + +import std.traits, std.c.string, std.typetuple, std.conv, std.exception; +// version(unittest) +// { + import std.exception, std.stdio; +//} + +private template maxSize(T...) +{ + static if (T.length == 1) + { + enum size_t maxSize = T[0].sizeof; + } + else + { + enum size_t maxSize = T[0].sizeof >= maxSize!(T[1 .. $]) + ? T[0].sizeof : maxSize!(T[1 .. $]); + } +} + +struct This; + +template AssociativeArray(T) +{ + enum bool valid = false; + alias void Key; + alias void Value; +} + +template AssociativeArray(T : V[K], K, V) +{ + enum bool valid = true; + alias K Key; + alias V Value; +} + +template This2Variant(V, T...) +{ + static if (T.length == 0) alias TypeTuple!() This2Variant; + else static if (is(AssociativeArray!(T[0]).Key == This)) + { + static if (is(AssociativeArray!(T[0]).Value == This)) + alias TypeTuple!(V[V], + This2Variant!(V, T[1 .. $])) This2Variant; + else + alias TypeTuple!(AssociativeArray!(T[0]).Value[V], + This2Variant!(V, T[1 .. $])) This2Variant; + } + else static if (is(AssociativeArray!(T[0]).Value == This)) + alias TypeTuple!(V[AssociativeArray!(T[0]).Key], + This2Variant!(V, T[1 .. $])) This2Variant; + else static if (is(T[0] == This[])) + alias TypeTuple!(V[], This2Variant!(V, T[1 .. $])) This2Variant; + else static if (is(T[0] == This*)) + alias TypeTuple!(V*, This2Variant!(V, T[1 .. $])) This2Variant; + else + alias TypeTuple!(T[0], This2Variant!(V, T[1 .. $])) This2Variant; +} + +/** + * $(D_PARAM VariantN) is a back-end type seldom used directly by user + * code. Two commonly-used types using $(D_PARAM VariantN) as + * back-end are: + * + * $(OL $(LI $(B Algebraic): A closed discriminated union with a + * limited type universe (e.g., $(D_PARAM Algebraic!(int, double, + * string)) only accepts these three types and rejects anything + * else).) $(LI $(B Variant): An open discriminated union allowing an + * unbounded set of types. The restriction is that the size of the + * stored type cannot be larger than the largest built-in type. This + * means that $(D_PARAM Variant) can accommodate all primitive types + * and all user-defined types except for large $(D_PARAM struct)s.) ) + * + * Both $(D_PARAM Algebraic) and $(D_PARAM Variant) share $(D_PARAM + * VariantN)'s interface. (See their respective documentations below.) + * + * $(D_PARAM VariantN) is a discriminated union type parameterized + * with the largest size of the types stored ($(D_PARAM maxDataSize)) + * and with the list of allowed types ($(D_PARAM AllowedTypes)). If + * the list is empty, then any type up of size up to $(D_PARAM + * maxDataSize) (rounded up for alignment) can be stored in a + * $(D_PARAM VariantN) object. + * + */ + +struct VariantN(size_t maxDataSize, AllowedTypesX...) +{ + alias This2Variant!(VariantN, AllowedTypesX) AllowedTypes; + +private: + // Compute the largest practical size from maxDataSize + struct SizeChecker + { + int function() fptr; + ubyte[maxDataSize] data; + } + enum size = SizeChecker.sizeof - (int function()).sizeof; + static assert(size >= (void*).sizeof); + + /** Tells whether a type $(D_PARAM T) is statically allowed for + * storage inside a $(D_PARAM VariantN) object by looking + * $(D_PARAM T) up in $(D_PARAM AllowedTypes). If $(D_PARAM + * AllowedTypes) is empty, all types of size up to $(D_PARAM + * maxSize) are allowed. + */ + public template allowed(T) + { + enum bool allowed + = is(T == VariantN) + || + //T.sizeof <= size && + (AllowedTypes.length == 0 || staticIndexOf!(T, AllowedTypes) >= 0); + } + + // Each internal operation is encoded with an identifier. See + // the "handler" function below. + enum OpID { getTypeInfo, get, compare, testConversion, toString, + index, indexAssign, catAssign, copyOut, length, + apply } + + // state + sizediff_t function(OpID selector, ubyte[size]* store, void* data) fptr + = &handler!(void); + union + { + ubyte[size] store; + // conservatively mark the region as pointers + static if (size >= (void*).sizeof) + void* p[size / (void*).sizeof]; + } + + // internals + // Handler for an uninitialized value + static sizediff_t handler(A : void)(OpID selector, ubyte[size]*, void* parm) + { + switch (selector) + { + case OpID.getTypeInfo: + *cast(TypeInfo *) parm = typeid(A); + break; + case OpID.copyOut: + auto target = cast(VariantN *) parm; + target.fptr = &handler!(A); + // no need to copy the data (it's garbage) + break; + case OpID.compare: + auto rhs = cast(VariantN *) parm; + return rhs.peek!(A) + ? 0 // all uninitialized are equal + : int.min; // uninitialized variant is not comparable otherwise + case OpID.toString: + string * target = cast(string*) parm; + *target = ""; + break; + case OpID.get: + case OpID.testConversion: + case OpID.index: + case OpID.indexAssign: + case OpID.catAssign: + case OpID.length: + throw new VariantException( + "Attempt to use an uninitialized VariantN"); + default: assert(false, "Invalid OpID"); + } + return 0; + } + + // Handler for all of a type's operations + static sizediff_t handler(A)(OpID selector, ubyte[size]* pStore, void* parm) + { + static A* getPtr(void* untyped) + { + if (untyped) + { + static if (A.sizeof <= size) + return cast(A*) untyped; + else + return *cast(A**) untyped; + } + return null; + } + auto zis = getPtr(pStore); + // Input: TypeInfo object + // Output: target points to a copy of *me, if me was not null + // Returns: true iff the A can be converted to the type represented + // by the incoming TypeInfo + static bool tryPutting(A* src, TypeInfo targetType, void* target) + { + alias TypeTuple!(A, ImplicitConversionTargets!A) AllTypes; + foreach (T ; AllTypes) + { + if (targetType != typeid(T) && + targetType != typeid(const(T))) + { + static if (isImplicitlyConvertible!(T, immutable(T))) + { + if (targetType != typeid(immutable(T))) + { + continue; + } + } + else + { + continue; + } + } + // found!!! + static if (is(typeof(*cast(T*) target = *src))) + { + auto zat = cast(T*) target; + if (src) + { + assert(target, "target must be non-null"); + *zat = *src; + } + } + else + { + // type is not assignable + if (src) assert(false, A.stringof); + } + return true; + } + return false; + } + + switch (selector) + { + case OpID.getTypeInfo: + *cast(TypeInfo *) parm = typeid(A); + break; + case OpID.copyOut: + auto target = cast(VariantN *) parm; + assert(target); + tryPutting(zis, typeid(A), cast(void*) getPtr(&target.store)) + || assert(false); + target.fptr = &handler!(A); + break; + case OpID.get: + return !tryPutting(zis, *cast(TypeInfo*) parm, parm); + case OpID.testConversion: + return !tryPutting(null, *cast(TypeInfo*) parm, null); + case OpID.compare: + auto rhsP = cast(VariantN *) parm; + auto rhsType = rhsP.type; + // Are we the same? + if (rhsType == typeid(A)) + { + // cool! Same type! + auto rhsPA = getPtr(&rhsP.store); + static if (is(typeof(A.init == A.init))) + { + if (*rhsPA == *zis) + { + return 0; + } + static if (is(typeof(A.init < A.init))) + { + return *zis < *rhsPA ? -1 : 1; + } + } + else + { + // type doesn't support ordering comparisons + return int.min; + } + } else if (rhsType == typeid(void)) + { + // No support for ordering comparisons with + // uninitialized vars + return int.min; + } + VariantN temp; + // Do I convert to rhs? + if (tryPutting(zis, rhsType, &temp.store)) + { + // cool, I do; temp's store contains my data in rhs's type! + // also fix up its fptr + temp.fptr = rhsP.fptr; + // now lhsWithRhsType is a full-blown VariantN of rhs's type + return temp.opCmp(*rhsP); + } + // Does rhs convert to zis? + *cast(TypeInfo*) &temp.store = typeid(A); + if (rhsP.fptr(OpID.get, &rhsP.store, &temp.store) == 0) + { + // cool! Now temp has rhs in my type! + auto rhsPA = getPtr(&temp.store); + static if (is(typeof(A.init == A.init))) + { + if (*rhsPA == *zis) + { + return 0; + } + static if (is(typeof(A.init < A.init))) + { + return *zis < *rhsPA ? -1 : 1; + } + } + else + { + // type doesn't support ordering comparisons + return int.min; + } + } + return int.min; // dunno + case OpID.toString: + auto target = cast(string*) parm; + static if (is(typeof(to!(string)(*zis)))) + { + *target = to!(string)(*zis); + break; + } + // TODO: The following test evaluates to true for shared objects. + // Use __traits for now until this is sorted out. + // else static if (is(typeof((*zis).toString))) + else static if (__traits(compiles, {(*zis).toString;})) + { + *target = (*zis).toString; + break; + } + else + { + throw new VariantException(typeid(A), typeid(string)); + } + + case OpID.index: + // Added allowed!(...) prompted by a bug report by Chris + // Nicholson-Sauls. + static if (isStaticArray!(A) && allowed!(typeof(A.init))) + { + enforce(0, "Not implemented"); + } + static if (isDynamicArray!(A) && allowed!(typeof(A.init[0]))) + { + // array type; input and output are the same VariantN + auto result = cast(VariantN*) parm; + size_t index = result.convertsTo!(int) + ? result.get!(int) : result.get!(size_t); + *result = (*zis)[index]; + break; + } + else static if (isAssociativeArray!(A) + && allowed!(typeof(A.init.values[0]))) + { + auto result = cast(VariantN*) parm; + *result = (*zis)[result.get!(typeof(A.keys[0]))]; + break; + } + else + { + throw new VariantException(typeid(A), typeid(void[])); + } + + case OpID.indexAssign: + static if (isArray!(A) && is(typeof((*zis)[0] = (*zis)[0]))) + { + // array type; result comes first, index comes second + auto args = cast(VariantN*) parm; + size_t index = args[1].convertsTo!(int) + ? args[1].get!(int) : args[1].get!(size_t); + (*zis)[index] = args[0].get!(typeof((*zis)[0])); + break; + } + else static if (isAssociativeArray!(A)) + { + auto args = cast(VariantN*) parm; + (*zis)[args[1].get!(typeof(A.keys[0]))] + = args[0].get!(typeof(A.values[0])); + break; + } + else + { + throw new VariantException(typeid(A), typeid(void[])); + } + + case OpID.catAssign: + static if (is(typeof((*zis)[0])) && is(typeof((*zis) ~= *zis))) + { + // array type; parm is the element to append + auto arg = cast(VariantN*) parm; + alias typeof((*zis)[0]) E; + if (arg[0].convertsTo!(E)) + { + // append one element to the array + (*zis) ~= [ arg[0].get!(E) ]; + } + else + { + // append a whole array to the array + (*zis) ~= arg[0].get!(A); + } + break; + } + else + { + throw new VariantException(typeid(A), typeid(void[])); + } + + case OpID.length: + static if (is(typeof(zis.length))) + { + return zis.length; + } + else + { + throw new VariantException(typeid(A), typeid(void[])); + } + + case OpID.apply: + assert(0); + + default: assert(false); + } + return 0; + } + +public: + /** Constructs a $(D_PARAM VariantN) value given an argument of a + * generic type. Statically rejects disallowed types. + */ + + this(T)(T value) + { + static assert(allowed!(T), "Cannot store a " ~ T.stringof + ~ " in a " ~ VariantN.stringof); + opAssign(value); + } + + /** Assigns a $(D_PARAM VariantN) from a generic + * argument. Statically rejects disallowed types. */ + + VariantN opAssign(T)(T rhs) + { + //writeln(typeid(rhs)); + static assert(allowed!(T), "Cannot store a " ~ T.stringof + ~ " in a " ~ VariantN.stringof ~ ". Valid types are " + ~ AllowedTypes.stringof); + static if (is(T : VariantN)) + { + rhs.fptr(OpID.copyOut, &rhs.store, &this); + } + else static if (is(T : const(VariantN))) + { + static assert(false, + "Assigning Variant objects from const Variant" + " objects is currently not supported."); + } + else + { + static if (T.sizeof <= size) + { + // If T is a class we're only copying the reference, so it + // should be safe to cast away shared so the memcpy will work. + // + // TODO: If a shared class has an atomic reference then using + // an atomic load may be more correct. Just make sure + // to use the fastest approach for the load op. + static if (is(T == class) && is(T == shared)) + memcpy(&store, cast(const(void*)) &rhs, rhs.sizeof); + else + memcpy(&store, &rhs, rhs.sizeof); + } + else + { + static if (__traits(compiles, {new T(rhs);})) + { + auto p = new T(rhs); + } + else + { + auto p = new T; + *p = rhs; + } + memcpy(&store, &p, p.sizeof); + } + fptr = &handler!(T); + } + return this; + } + + /** Returns true if and only if the $(D_PARAM VariantN) object + * holds a valid value (has been initialized with, or assigned + * from, a valid value). + * Example: + * ---- + * Variant a; + * assert(!a.hasValue); + * Variant b; + * a = b; + * assert(!a.hasValue); // still no value + * a = 5; + * assert(a.hasValue); + * ---- + */ + + bool hasValue() const + { + // @@@BUG@@@ in compiler, the cast shouldn't be needed + return cast(typeof(&handler!(void))) fptr != &handler!(void); + } + + /** + * If the $(D_PARAM VariantN) object holds a value of the + * $(I exact) type $(D_PARAM T), returns a pointer to that + * value. Otherwise, returns $(D_PARAM null). In cases + * where $(D_PARAM T) is statically disallowed, $(D_PARAM + * peek) will not compile. + * + * Example: + * ---- + * Variant a = 5; + * auto b = a.peek!(int); + * assert(b !is null); + * *b = 6; + * assert(a == 6); + * ---- + */ + T * peek(T)() + { + static if (!is(T == void)) + static assert(allowed!(T), "Cannot store a " ~ T.stringof + ~ " in a " ~ VariantN.stringof); + return type == typeid(T) ? cast(T*) &store : null; + } + + /** + * Returns the $(D_PARAM typeid) of the currently held value. + */ + + TypeInfo type() const + { + TypeInfo result; + fptr(OpID.getTypeInfo, null, &result); + return result; + } + + /** + * Returns $(D_PARAM true) if and only if the $(D_PARAM VariantN) + * object holds an object implicitly convertible to type $(D_PARAM + * U). Implicit convertibility is defined as per + * $(LINK2 std_traits.html#ImplicitConversionTargets,ImplicitConversionTargets). + */ + + bool convertsTo(T)() + { + TypeInfo info = typeid(T); + return fptr(OpID.testConversion, null, &info) == 0; + } + + // private T[] testing123(T)(T*); + + // /** + // * A workaround for the fact that functions cannot return + // * statically-sized arrays by value. Essentially $(D_PARAM + // * DecayStaticToDynamicArray!(T[N])) is an alias for $(D_PARAM + // * T[]) and $(D_PARAM DecayStaticToDynamicArray!(T)) is an alias + // * for $(D_PARAM T). + // */ + + // template DecayStaticToDynamicArray(T) + // { + // static if (isStaticArray!(T)) + // { + // alias typeof(testing123(&T[0])) DecayStaticToDynamicArray; + // } + // else + // { + // alias T DecayStaticToDynamicArray; + // } + // } + + // static assert(is(DecayStaticToDynamicArray!(immutable(char)[21]) == + // immutable(char)[]), + // DecayStaticToDynamicArray!(immutable(char)[21]).stringof); + + /** + * Returns the value stored in the $(D_PARAM VariantN) object, + * implicitly converted to the requested type $(D_PARAM T), in + * fact $(D_PARAM DecayStaticToDynamicArray!(T)). If an implicit + * conversion is not possible, throws a $(D_PARAM + * VariantException). + */ + + T get(T)() if (!is(T == const)) + { + union Buf + { + @disable bool opEquals(ref Buf); + @disable int opCmp(ref Buf); + + TypeInfo info; + T result; + }; + auto p = *cast(T**) &store; + Buf buf = { typeid(T) }; + if (fptr(OpID.get, &store, &buf)) + { + throw new VariantException(type, typeid(T)); + } + return buf.result; + } + + T get(T)() const if (is(T == const)) + { + union Buf + { + @disable bool opEquals(ref Buf); + @disable int opCmp(ref Buf); + + TypeInfo info; + Unqual!T result; + }; + auto p = *cast(T**) &store; + Buf buf = { typeid(T) }; + if (fptr(OpID.get, cast(ubyte[size]*) &store, &buf)) + { + throw new VariantException(type, typeid(T)); + } + return buf.result; + } + + /** + * Returns the value stored in the $(D_PARAM VariantN) object, + * explicitly converted (coerced) to the requested type $(D_PARAM + * T). If $(D_PARAM T) is a string type, the value is formatted as + * a string. If the $(D_PARAM VariantN) object is a string, a + * parse of the string to type $(D_PARAM T) is attempted. If a + * conversion is not possible, throws a $(D_PARAM + * VariantException). + */ + + T coerce(T)() + { + static if (isNumeric!(T)) + { + if (convertsTo!real()) + { + // maybe optimize this fella; handle ints separately + return to!T(get!real); + } + else if (convertsTo!(const(char)[])) + { + return to!T(get!(const(char)[])); + } + else + { + enforce(false, text("Type ", type(), " does not convert to ", + typeid(T))); + assert(0); + } + } + else static if (is(T : Object)) + { + return to!(T)(get!(Object)); + } + else static if (isSomeString!(T)) + { + return to!(T)(toString); + } + else + { + // Fix for bug 1649 + static assert(false, "unsupported type for coercion"); + } + } + + /** + * Formats the stored value as a string. + */ + + string toString() + { + string result; + fptr(OpID.toString, &store, &result) == 0 || assert(false); + return result; + } + + /** + * Comparison for equality used by the "==" and "!=" operators. + */ + + // returns 1 if the two are equal + bool opEquals(T)(T rhs) + { + static if (is(T == VariantN)) + alias rhs temp; + else + auto temp = Variant(rhs); + return fptr(OpID.compare, &store, &temp) == 0; + } + + /** + * Ordering comparison used by the "<", "<=", ">", and ">=" + * operators. In case comparison is not sensible between the held + * value and $(D_PARAM rhs), an exception is thrown. + */ + + int opCmp(T)(T rhs) + { + static if (is(T == VariantN)) + alias rhs temp; + else + auto temp = Variant(rhs); + auto result = fptr(OpID.compare, &store, &temp); + if (result == sizediff_t.min) + { + throw new VariantException(type, temp.type); + } + + assert(result >= -1 && result <= 1); // Should be true for opCmp. + return cast(int) result; + } + + /** + * Computes the hash of the held value. + */ + + size_t toHash() + { + return type.getHash(&store); + } + + private VariantN opArithmetic(T, string op)(T other) + { + VariantN result; + static if (is(T == VariantN)) + { + if (convertsTo!(uint) && other.convertsTo!(uint)) + result = mixin("get!(uint) " ~ op ~ " other.get!(uint)"); + else if (convertsTo!(int) && other.convertsTo!(int)) + result = mixin("get!(int) " ~ op ~ " other.get!(int)"); + else if (convertsTo!(ulong) && other.convertsTo!(ulong)) + result = mixin("get!(ulong) " ~ op ~ " other.get!(ulong)"); + else if (convertsTo!(long) && other.convertsTo!(long)) + result = mixin("get!(long) " ~ op ~ " other.get!(long)"); + else if (convertsTo!(double) && other.convertsTo!(double)) + result = mixin("get!(double) " ~ op ~ " other.get!(double)"); + else + result = mixin("get!(real) " ~ op ~ " other.get!(real)"); + } + else + { + if (is(typeof(T.max) : uint) && T.min == 0 && convertsTo!(uint)) + result = mixin("get!(uint) " ~ op ~ " other"); + else if (is(typeof(T.max) : int) && T.min < 0 && convertsTo!(int)) + result = mixin("get!(int) " ~ op ~ " other"); + else if (is(typeof(T.max) : ulong) && T.min == 0 + && convertsTo!(ulong)) + result = mixin("get!(ulong) " ~ op ~ " other"); + else if (is(typeof(T.max) : long) && T.min < 0 && convertsTo!(long)) + result = mixin("get!(long) " ~ op ~ " other"); + else if (is(T : double) && convertsTo!(double)) + result = mixin("get!(double) " ~ op ~ " other"); + else + result = mixin("get!(real) " ~ op ~ " other"); + } + return result; + } + + private VariantN opLogic(T, string op)(T other) + { + VariantN result; + static if (is(T == VariantN)) + { + if (convertsTo!(uint) && other.convertsTo!(uint)) + result = mixin("get!(uint) " ~ op ~ " other.get!(uint)"); + else if (convertsTo!(int) && other.convertsTo!(int)) + result = mixin("get!(int) " ~ op ~ " other.get!(int)"); + else if (convertsTo!(ulong) && other.convertsTo!(ulong)) + result = mixin("get!(ulong) " ~ op ~ " other.get!(ulong)"); + else + result = mixin("get!(long) " ~ op ~ " other.get!(long)"); + } + else + { + if (is(typeof(T.max) : uint) && T.min == 0 && convertsTo!(uint)) + result = mixin("get!(uint) " ~ op ~ " other"); + else if (is(typeof(T.max) : int) && T.min < 0 && convertsTo!(int)) + result = mixin("get!(int) " ~ op ~ " other"); + else if (is(typeof(T.max) : ulong) && T.min == 0 + && convertsTo!(ulong)) + result = mixin("get!(ulong) " ~ op ~ " other"); + else + result = mixin("get!(long) " ~ op ~ " other"); + } + return result; + } + + /** + * Arithmetic between $(D_PARAM VariantN) objects and numeric + * values. All arithmetic operations return a $(D_PARAM VariantN) + * object typed depending on the types of both values + * involved. The conversion rules mimic D's built-in rules for + * arithmetic conversions. + */ + + // Adapted from http://www.prowiki.org/wiki4d/wiki.cgi?DanielKeep/Variant + // arithmetic + VariantN opAdd(T)(T rhs) { return opArithmetic!(T, "+")(rhs); } + ///ditto + VariantN opSub(T)(T rhs) { return opArithmetic!(T, "-")(rhs); } + + // Commenteed all _r versions for now because of ambiguities + // arising when two Variants are used + + /////ditto + // VariantN opSub_r(T)(T lhs) + // { + // return VariantN(lhs).opArithmetic!(VariantN, "-")(this); + // } + ///ditto + VariantN opMul(T)(T rhs) { return opArithmetic!(T, "*")(rhs); } + ///ditto + VariantN opDiv(T)(T rhs) { return opArithmetic!(T, "/")(rhs); } + // ///ditto + // VariantN opDiv_r(T)(T lhs) + // { + // return VariantN(lhs).opArithmetic!(VariantN, "/")(this); + // } + ///ditto + VariantN opMod(T)(T rhs) { return opArithmetic!(T, "%")(rhs); } + // ///ditto + // VariantN opMod_r(T)(T lhs) + // { + // return VariantN(lhs).opArithmetic!(VariantN, "%")(this); + // } + ///ditto + VariantN opAnd(T)(T rhs) { return opLogic!(T, "&")(rhs); } + ///ditto + VariantN opOr(T)(T rhs) { return opLogic!(T, "|")(rhs); } + ///ditto + VariantN opXor(T)(T rhs) { return opLogic!(T, "^")(rhs); } + ///ditto + VariantN opShl(T)(T rhs) { return opLogic!(T, "<<")(rhs); } + // ///ditto + // VariantN opShl_r(T)(T lhs) + // { + // return VariantN(lhs).opLogic!(VariantN, "<<")(this); + // } + ///ditto + VariantN opShr(T)(T rhs) { return opLogic!(T, ">>")(rhs); } + // ///ditto + // VariantN opShr_r(T)(T lhs) + // { + // return VariantN(lhs).opLogic!(VariantN, ">>")(this); + // } + ///ditto + VariantN opUShr(T)(T rhs) { return opLogic!(T, ">>>")(rhs); } + // ///ditto + // VariantN opUShr_r(T)(T lhs) + // { + // return VariantN(lhs).opLogic!(VariantN, ">>>")(this); + // } + ///ditto + VariantN opCat(T)(T rhs) + { + auto temp = this; + temp ~= rhs; + return temp; + } + // ///ditto + // VariantN opCat_r(T)(T rhs) + // { + // VariantN temp = rhs; + // temp ~= this; + // return temp; + // } + + ///ditto + VariantN opAddAssign(T)(T rhs) { return this = this + rhs; } + ///ditto + VariantN opSubAssign(T)(T rhs) { return this = this - rhs; } + ///ditto + VariantN opMulAssign(T)(T rhs) { return this = this * rhs; } + ///ditto + VariantN opDivAssign(T)(T rhs) { return this = this / rhs; } + ///ditto + VariantN opModAssign(T)(T rhs) { return this = this % rhs; } + ///ditto + VariantN opAndAssign(T)(T rhs) { return this = this & rhs; } + ///ditto + VariantN opOrAssign(T)(T rhs) { return this = this | rhs; } + ///ditto + VariantN opXorAssign(T)(T rhs) { return this = this ^ rhs; } + ///ditto + VariantN opShlAssign(T)(T rhs) { return this = this << rhs; } + ///ditto + VariantN opShrAssign(T)(T rhs) { return this = this >> rhs; } + ///ditto + VariantN opUShrAssign(T)(T rhs) { return this = this >>> rhs; } + ///ditto + VariantN opCatAssign(T)(T rhs) + { + auto toAppend = VariantN(rhs); + fptr(OpID.catAssign, &store, &toAppend) == 0 || assert(false); + return this; + } + + /** + * Array and associative array operations. If a $(D_PARAM + * VariantN) contains an (associative) array, it can be indexed + * into. Otherwise, an exception is thrown. + * + * Example: + * ---- + * auto a = Variant(new int[10]); + * a[5] = 42; + * assert(a[5] == 42); + * int[int] hash = [ 42:24 ]; + * a = hash; + * assert(a[42] == 24); + * ---- + * + * Caveat: + * + * Due to limitations in current language, read-modify-write + * operations $(D_PARAM op=) will not work properly: + * + * ---- + * Variant a = new int[10]; + * a[5] = 42; + * a[5] += 8; + * assert(a[5] == 50); // fails, a[5] is still 42 + * ---- + */ + VariantN opIndex(K)(K i) + { + auto result = VariantN(i); + fptr(OpID.index, &store, &result) == 0 || assert(false); + return result; + } + + unittest + { + int[int] hash = [ 42:24 ]; + Variant v = hash; + assert(v[42] == 24); + v[42] = 5; + assert(v[42] == 5); + } + + /// ditto + VariantN opIndexAssign(T, N)(T value, N i) + { + VariantN[2] args = [ VariantN(value), VariantN(i) ]; + fptr(OpID.indexAssign, &store, &args) == 0 || assert(false); + return args[0]; + } + + /** If the $(D_PARAM VariantN) contains an (associative) array, + * returns the length of that array. Otherwise, throws an + * exception. + */ + @property size_t length() + { + return cast(size_t) fptr(OpID.length, &store, null); + } + + /** + If the $(D VariantN) contains an array, applies $(D dg) to each + element of the array in turn. Otherwise, throws an exception. + */ + int opApply(Delegate)(scope Delegate dg) if (is(Delegate == delegate)) + { + alias ParameterTypeTuple!(Delegate)[0] A; + if (type() == typeid(A[])) + { + auto arr = get!(A[]); + foreach (ref e; arr) + { + if (dg(e)) return 1; + } + } + else static if (is(A == VariantN)) + { + foreach (i; 0 .. length) + { + // @@@TODO@@@: find a better way to not confuse + // clients who think they change values stored in the + // Variant when in fact they are only changing tmp. + auto tmp = this[i]; + debug scope(exit) assert(tmp == this[i]); + if (dg(tmp)) return 1; + } + } + else + { + enforce(false, text("Variant type ", type(), + " not iterable with values of type ", + A.stringof)); + } + return 0; + } +} + +/** + * Algebraic data type restricted to a closed set of possible + * types. It's an alias for a $(D_PARAM VariantN) with an + * appropriately-constructed maximum size. $(D_PARAM Algebraic) is + * useful when it is desirable to restrict what a discriminated type + * could hold to the end of defining simpler and more efficient + * manipulation. + * + * Future additions to $(D_PARAM Algebraic) will allow compile-time + * checking that all possible types are handled by user code, + * eliminating a large class of errors. + * + * Bugs: + * + * Currently, $(D_PARAM Algebraic) does not allow recursive data + * types. They will be allowed in a future iteration of the + * implementation. + * + * Example: + * ---- + * auto v = Algebraic!(int, double, string)(5); + * assert(v.peek!(int)); + * v = 3.14; + * assert(v.peek!(double)); + * // auto x = v.peek!(long); // won't compile, type long not allowed + * // v = '1'; // won't compile, type char not allowed + * ---- + */ + +template Algebraic(T...) +{ + alias VariantN!(maxSize!(T), T) Algebraic; +} + +/** +$(D_PARAM Variant) is an alias for $(D_PARAM VariantN) instantiated +with the largest of $(D_PARAM creal), $(D_PARAM char[]), and $(D_PARAM +void delegate()). This ensures that $(D_PARAM Variant) is large enough +to hold all of D's predefined types, including all numeric types, +pointers, delegates, and class references. You may want to use +$(D_PARAM VariantN) directly with a different maximum size either for +storing larger types, or for saving memory. + */ + +alias VariantN!(maxSize!(creal, char[], void delegate())) Variant; + +/** + * Returns an array of variants constructed from $(D_PARAM args). + * Example: + * ---- + * auto a = variantArray(1, 3.14, "Hi!"); + * assert(a[1] == 3.14); + * auto b = Variant(a); // variant array as variant + * assert(b[1] == 3.14); + * ---- + * + * Code that needs functionality similar to the $(D_PARAM boxArray) + * function in the $(D_PARAM std.boxer) module can achieve it like this: + * + * ---- + * // old + * Box[] fun(...) + * { + * ... + * return boxArray(_arguments, _argptr); + * } + * // new + * Variant[] fun(T...)(T args) + * { + * ... + * return variantArray(args); + * } + * ---- + * + * This is by design. During construction the $(D_PARAM Variant) needs + * static type information about the type being held, so as to store a + * pointer to function for fast retrieval. + */ + +Variant[] variantArray(T...)(T args) +{ + Variant[] result; + foreach (arg; args) + { + result ~= Variant(arg); + } + return result; +} + +/** + * Thrown in three cases: + * + * $(OL $(LI An uninitialized Variant is used in any way except + * assignment and $(D_PARAM hasValue);) $(LI A $(D_PARAM get) or + * $(D_PARAM coerce) is attempted with an incompatible target type;) + * $(LI A comparison between $(D_PARAM Variant) objects of + * incompatible types is attempted.)) + * + */ + +// @@@ BUG IN COMPILER. THE 'STATIC' BELOW SHOULD NOT COMPILE +static class VariantException : Exception +{ + /// The source type in the conversion or comparison + TypeInfo source; + /// The target type in the conversion or comparison + TypeInfo target; + this(string s) + { + super(s); + } + this(TypeInfo source, TypeInfo target) + { + super("Variant: attempting to use incompatible types " + ~ source.toString + ~ " and " ~ target.toString); + this.source = source; + this.target = target; + } +} + +unittest +{ + alias This2Variant!(char, int, This[int]) W1; + alias TypeTuple!(int, char[int]) W2; + static assert(is(W1 == W2)); + + alias Algebraic!(void, string) var_t; + var_t foo = "quux"; +} + +unittest +{ + // @@@BUG@@@ + // alias Algebraic!(real, This[], This[int], This[This]) A; + // A v1, v2, v3; + // v2 = 5.0L; + // v3 = 42.0L; + // //v1 = [ v2 ][]; + // auto v = v1.peek!(A[]); + // //writeln(v[0]); + // v1 = [ 9 : v3 ]; + // //writeln(v1); + // v1 = [ v3 : v3 ]; + // //writeln(v1); +} + +unittest +{ + // try it with an oddly small size + VariantN!(1) test; + assert(test.size > 1); + + // variantArray tests + auto heterogeneous = variantArray(1, 4.5, "hi"); + assert(heterogeneous.length == 3); + auto variantArrayAsVariant = Variant(heterogeneous); + assert(variantArrayAsVariant[0] == 1); + assert(variantArrayAsVariant.length == 3); + + // array tests + auto arr = Variant([1.2].dup); + auto e = arr[0]; + assert(e == 1.2); + arr[0] = 2.0; + assert(arr[0] == 2); + arr ~= 4.5; + assert(arr[1] == 4.5); + + // general tests + Variant a; + auto b = Variant(5); + assert(!b.peek!(real) && b.peek!(int)); + // assign + a = *b.peek!(int); + // comparison + assert(a == b, a.type.toString ~ " " ~ b.type.toString); + auto c = Variant("this is a string"); + assert(a != c); + // comparison via implicit conversions + a = 42; b = 42.0; assert(a == b); + + // try failing conversions + bool failed = false; + try + { + auto d = c.get!(int); + } + catch (Exception e) + { + //writeln(stderr, e.toString); + failed = true; + } + assert(failed); // :o) + + // toString tests + a = Variant(42); assert(a.toString == "42"); + a = Variant(42.22); assert(a.toString == "42.22"); + + // coerce tests + a = Variant(42.22); assert(a.coerce!(int) == 42); + a = cast(short) 5; assert(a.coerce!(double) == 5); + + // Object tests + class B1 {} + class B2 : B1 {} + a = new B2; + assert(a.coerce!(B1) !is null); + a = new B1; +// BUG: I can't get the following line to pass: +// assert(collectException(a.coerce!(B2) is null)); + a = cast(Object) new B2; // lose static type info; should still work + assert(a.coerce!(B2) !is null); + +// struct Big { int a[45]; } +// a = Big.init; + + // hash + assert(a.toHash != 0); +} + +// tests adapted from +// http://www.dsource.org/projects/tango/browser/trunk/tango/core/Variant.d?rev=2601 +unittest +{ + Variant v; + + assert(!v.hasValue); + v = 42; + assert( v.peek!(int) ); + assert( v.convertsTo!(long) ); + assert( v.get!(int) == 42 ); + assert( v.get!(long) == 42L ); + assert( v.get!(ulong) == 42uL ); + + // should be string... @@@BUG IN COMPILER + v = "Hello, World!"c; + assert( v.peek!(string) ); + + assert( v.get!(string) == "Hello, World!" ); + assert(!is(char[] : wchar[])); + assert( !v.convertsTo!(wchar[]) ); + assert( v.get!(string) == "Hello, World!" ); + + // Literal arrays are dynamically-typed + v = cast(int[5]) [1,2,3,4,5]; + assert( v.peek!(int[5]) ); + assert( v.get!(int[5]) == [1,2,3,4,5] ); + + { + // @@@BUG@@@: array literals should have type T[], not T[5] (I guess) + // v = [1,2,3,4,5]; + // assert( v.peek!(int[]) ); + // assert( v.get!(int[]) == [1,2,3,4,5] ); + } + + v = 3.1413; + assert( v.peek!(double) ); + assert( v.convertsTo!(real) ); + //@@@ BUG IN COMPILER: DOUBLE SHOULD NOT IMPLICITLY CONVERT TO FLOAT + assert( !v.convertsTo!(float) ); + assert( *v.peek!(double) == 3.1413 ); + + auto u = Variant(v); + assert( u.peek!(double) ); + assert( *u.peek!(double) == 3.1413 ); + + // operators + v = 38; + assert( v + 4 == 42 ); + assert( 4 + v == 42 ); + assert( v - 4 == 34 ); + assert( Variant(4) - v == -34 ); + assert( v * 2 == 76 ); + assert( 2 * v == 76 ); + assert( v / 2 == 19 ); + assert( Variant(2) / v == 0 ); + assert( v % 2 == 0 ); + assert( Variant(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( Variant(1) << Variant(2) == 4 ); + assert( v >> 1 == 19 ); + assert( Variant(4) >> Variant(2) == 1 ); + assert( Variant("abc") ~ "def" == "abcdef" ); + assert( Variant("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 = 38; v += 1; assert( v < 40 ); + + v = "abc"; + v ~= "def"; + assert( v == "abcdef", *v.peek!(char[]) ); + assert( Variant(0) < Variant(42) ); + assert( Variant(42) > Variant(0) ); + assert( Variant(42) > Variant(0.1) ); + assert( Variant(42.1) > Variant(1) ); + 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 ); + } + /+ + // @@@BUG@@@ + // dmd: mtype.c:3886: StructDeclaration* TypeAArray::getImpl(): Assertion `impl' failed. + { + 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 ); + } + +/ +} + +unittest +{ + // bug 1558 + Variant va=1; + Variant vb=-2; + assert((va+vb).get!(int) == -1); + assert((va-vb).get!(int) == 3); +} + +unittest +{ + Variant a; + a=5; + Variant b; + b=a; + Variant[] c; + c = variantArray(1, 2, 3.0, "hello", 4); + assert(c[3] == "hello"); +} + +unittest +{ + Variant v = 5; + assert (!__traits(compiles, v.coerce!(bool delegate()))); +} + + +unittest +{ + struct Huge { + real a, b, c, d, e, f, g; + } + + Huge huge; + huge.e = 42; + Variant v; + v = huge; // Compile time error. + assert(v.get!(Huge).e == 42); +} + +unittest +{ + const x = Variant(42); + auto y1 = x.get!(const int)(); + // @@@BUG@@@ + //auto y2 = x.get!(immutable int)(); +} + +// test iteration +unittest +{ + auto v = Variant([ 1, 2, 3, 4 ][]); + auto j = 0; + foreach (int i; v) + { + assert(i == ++j); + } + assert(j == 4); +} + +// test convertibility +unittest +{ + auto v = Variant("abc".dup); + assert(v.convertsTo!(char[])); +} + +// http://d.puremagic.com/issues/show_bug.cgi?id=5424 +unittest +{ + interface A { + void func1(); + } + static class AC: A { + void func1() { + } + } + + A a = new AC(); + a.func1(); + Variant b = Variant(a); +}