1 /**
2  * This module implements the ScriptAny struct, which can hold any value type usable in the scripting language.
3  */
4 module mildew.types.any;
5 
6 import std.conv: to;
7 import std.traits;
8 
9 /**
10  * This variant holds primitive values as well as ScriptObject types.
11  */
12 struct ScriptAny
13 {
14     import mildew.types.object: ScriptObject;
15     import mildew.types.func: ScriptFunction;
16 
17 public:
18     /**
19      * Enumeration of what type is held by a ScriptAny. Note that a function, array, or string can be used
20      * as an object.
21      */
22     enum Type 
23     {
24         /// primitives
25         NULL=0, UNDEFINED, BOOLEAN, INTEGER, DOUBLE, 
26         /// objects
27         OBJECT, ARRAY, FUNCTION, STRING
28     }
29 
30     /**
31      * Constructs a new ScriptAny based on the value given.
32      * Params:
33      *  value = This can be any valid type such as null, bool, signed or unsigned ints or longs, floats,
34      *          or doubles, strings, even primitive D arrays, as well as ScriptObject or ScriptFunction
35      */
36     this(T)(T value)
37     {
38         setValue!T(value);
39     }
40 
41     /**
42      * opCast will now use toValue
43      */
44     T opCast(T)() const
45     {
46         return toValue!T();
47     }
48 
49     /**
50      * Assigns a value.
51      */
52     auto opAssign(T)(T value)
53     {
54         setValue(value);
55         return this;
56     }
57 
58     /**
59      * Implements binary math operations between two ScriptAnys and returns a ScriptAny. For
60      * certain operations that make no sense the result will be NaN or UNDEFINED
61      */
62     auto opBinary(string op)(auto ref const ScriptAny rhs) const
63     {
64         // if either value is undefined return undefined
65         if(_type == Type.UNDEFINED || rhs._type == Type.UNDEFINED)
66             return UNDEFINED;
67         
68         static if(op == "+")
69         {
70             // if either is string convert both to string and concatenate
71             if(_type == Type.STRING || rhs._type == Type.STRING)
72             {
73                 return ScriptAny(toString() ~ rhs.toString());
74             }
75             
76             // if they are both numerical
77             if(this.isNumber && rhs.isNumber)
78             {
79                 // if either is floating point convert both to floating point and add
80                 if(_type == Type.DOUBLE || rhs._type == Type.DOUBLE)
81                     return ScriptAny(toValue!double + rhs.toValue!double);
82                 // else an integer addition is fine
83                 else
84                     return ScriptAny(toValue!long + rhs.toValue!long);
85             }
86             // else this makes no sense so concatenate strings
87             else return ScriptAny(toString() ~ rhs.toString());
88         }
89         else static if(op == "-" || op == "*" || op == "%") // values can stay int here as well
90         {
91             // only makes sense for numbers
92             if(!(this.isNumber && rhs.isNumber))
93                 return ScriptAny(double.nan);
94             // if either is floating point convert both to float
95             if(_type == Type.DOUBLE || rhs._type == Type.DOUBLE)
96                 mixin("return ScriptAny(toValue!double" ~ op ~ "rhs.toValue!double);");
97             else // int is fine
98                 mixin("return ScriptAny(toValue!long" ~ op ~ "rhs.toValue!long);");
99         }
100         else static if(op == "/" || op == "^^") // must be doubles
101         {
102             // both must be casted to double
103             if(!(this.isNumber && rhs.isNumber))
104                 return ScriptAny(double.nan);
105             mixin("return ScriptAny(toValue!double" ~ op ~ "rhs.toValue!double);");
106         }
107         // for the bitwise operations the values MUST be cast to long
108         else static if(op == "&" || op == "|" || op == "^" || op == "<<" || op == ">>" || op == ">>>")
109         {
110             if(!(this.isNumber && rhs.isNumber))
111                 return ScriptAny(0);
112             mixin("return ScriptAny(toValue!long" ~ op ~ "rhs.toValue!long);");
113         }
114         else
115             static assert(false, "The binary operation " ~ op ~ " is not supported for this type");
116     }
117 
118     /**
119      * A method so that undefined || 22 results in 22.
120      */
121     ScriptAny orOp(ScriptAny other)
122     {
123         if(!cast(bool)this)
124             return other;
125         else
126             return this;
127     }
128 
129     /**
130      * Depending on the type of index, if it is a string it accesses a field of the object, otherwise
131      * if it is numerical, attempts to access an index of an array object.
132      */
133     ScriptAny lookupField(T)(T index, out bool success)
134     {
135         import mildew.types..string: ScriptString;
136         import mildew.types.array: ScriptArray;
137 
138         success = false;
139 
140         // number index only makes sense for strings and arrays
141         static if(isIntegral!T)
142         {
143             if(!(_type == Type.ARRAY || _type == Type.STRING))
144             {
145                 return UNDEFINED;
146             }
147             else if(_type == Type.ARRAY)
148             {
149                 auto arr = cast(ScriptArray)_asObject;
150                 auto found = arr[index];
151                 if(found == null)
152                     return UNDEFINED;
153                 success = true;
154                 return *found;
155             }
156             else if(_type == Type.STRING)
157             {
158                 auto str = (cast(ScriptString)_asObject).toString();
159                 if(index < 0 || index >= str.length)
160                     return UNDEFINED;
161                 success = true;
162                 return ScriptAny([str[index]]);
163             }
164             return ScriptAny.UNDEFINED;
165         }
166         // else it is a string so we are accessing an object property
167         else static if(isSomeString!T)
168         {
169             if(!isObject)
170                 return UNDEFINED;
171             success = true;
172             return _asObject.lookupField(index.to!string);
173         }
174         else
175             static assert(false, "Invalid index type");
176     }
177 
178     /**
179      * Overload to ignore the bool
180      */
181     ScriptAny lookupField(T)(T index)
182     {
183         bool ignore; // @suppress(dscanner.suspicious.unmodified)
184         return lookupField(index, ignore);
185     }
186 
187     /**
188      * Attempt to assign a field of a complex object. This can be used to assign array indexes if
189      * the index is a number.
190      */
191     ScriptAny assignField(T)(T index, ScriptAny value, out bool success)
192     {
193         import mildew.types..string: ScriptString;
194         import mildew.types.array: ScriptArray;
195 
196         success = false;
197 
198         // number index only makes sense for arrays
199         static if(isIntegral!T)
200         {
201             if(_type != Type.ARRAY)
202             {
203                 return UNDEFINED;
204             }
205             else
206             {
207                 auto arr = cast(ScriptArray)_asObject;
208                 auto found = (arr[index] = value);
209                 if(found == null)
210                     return UNDEFINED;
211                 success = true;
212                 return *found;
213             }
214         }
215         // else it is a string so we are accessing an object property
216         else static if(isSomeString!T)
217         {
218             if(!isObject)
219                 return UNDEFINED;
220             success = true;
221             return _asObject.assignField(index.to!string, value);
222         }
223         else
224             static assert(false, "Invalid index type");
225     }
226 
227     /**
228      * Overload to ignore the bool
229      */
230     ScriptAny assignField(T)(T index, ScriptAny value)
231     {
232         bool ignore; // @suppress(dscanner.suspicious.unmodified)
233         return assignField(index, ignore);
234     }
235 
236     /**
237      * Add a get method to an object
238      */
239     void addGetterProperty(in string name, ScriptFunction func)
240     {
241         if(!isObject)
242             return;
243         _asObject.addGetterProperty(name, func);
244     }
245 
246     /**
247      * Add a set method to an object
248      */
249     void addSetterProperty(in string name, ScriptFunction func)
250     {
251         if(!isObject)
252             return;
253         _asObject.addSetterProperty(name, func);
254     }
255 
256     /**
257      * Defines unary math operations for a ScriptAny.
258      */
259     auto opUnary(string op)()
260     {
261         // plus and minus can work on doubles or longs
262         static if(op == "+" || op == "-")
263         {
264             if(_type == Type.DOUBLE)
265                 mixin("return ScriptAny(" ~ op ~ " toValue!double);");
266             else
267                 mixin("return ScriptAny(" ~ op ~ " toValue!long);");
268         }
269         // bit not only works on integers
270         else static if(op == "~")
271         {
272             return ScriptAny(~toValue!long);
273         }
274         else // increment and decrement have to be handled by the scripting environment
275             static assert(false, "Unary operator " ~ op ~ " is not implemented for this type");
276     }
277 
278     /**
279      * Tests for equality by casting similar types to the same type and comparing values. If the types
280      * are too different to do this, the result is false.
281      */
282     bool opEquals(const ScriptAny other) const
283     {
284         import mildew.types.array : ScriptArray;
285 
286         // if both are undefined then return true
287         if(_type == Type.UNDEFINED && other._type == Type.UNDEFINED)
288             return true;
289         // but if only one is undefined return false
290         else if(_type == Type.UNDEFINED || other._type == Type.UNDEFINED)
291             return false;
292         
293         // if either are strings, convert to string and compare
294         if(_type == Type.STRING || other._type == Type.STRING)
295             return toString() == other.toString();
296 
297         // if either is numeric
298         if(this.isNumber() && other.isNumber())
299         {
300             // if one is double convert both to double and compare
301             if(_type == Type.DOUBLE || other._type == Type.DOUBLE)
302                 return toValue!double == other.toValue!double;
303             // otherwise return the integer comparison
304             return toValue!long == other.toValue!long;
305         }
306 
307         // if both are arrays do an array comparison which should recursively call opEquals on each item
308         if(_type == Type.ARRAY && other._type == Type.ARRAY)
309         {
310             auto arr1 = cast(ScriptArray)_asObject;
311             auto arr2 = cast(ScriptArray)(other._asObject);
312             return arr1.array == arr2.array;
313         }
314 
315         // else compare the objects for now
316         return _asObject == other._asObject;
317     }
318 
319     /**
320      * The comparison operations. Note that this only returns a meaningful, usable value if the values
321      * are similar enough in type to be compared. For the purpose of the scripting language, invalid
322      * comparisons do not throw an exception but they return a meaningless incorrect result.
323      */
324     int opCmp(const ScriptAny other) const
325     {
326         import mildew.types.array: ScriptArray;
327 
328         // undefined is always less than any defined value
329         if(_type == Type.UNDEFINED && !other._type == Type.UNDEFINED)
330             return -1;
331         else if(_type != Type.UNDEFINED && other._type == Type.UNDEFINED)
332             return 1;
333         else if(_type == Type.UNDEFINED && other._type == Type.UNDEFINED)
334             return 0;
335         
336         // if either are strings, convert and compare
337         if(_type == Type.STRING || other._type == Type.STRING)
338         {
339             immutable str1 = toString();
340             immutable str2 = other.toString();
341             if(str1 < str2)
342                 return -1;
343             else if(str1 > str2)
344                 return 1;
345             else
346                 return 0;   
347         }
348 
349         // if both are numeric convert to double or long as needed and compare
350         if(this.isNumber && other.isNumber)
351         {
352             if(_type == Type.DOUBLE || other._type == Type.DOUBLE)
353             {
354                 immutable num1 = toValue!double, num2 = other.toValue!double;
355                 if(num1 < num2)
356                     return -1;
357                 else if(num1 > num2)
358                     return 1;
359                 else
360                     return 0;
361             }
362             else
363             {
364                 immutable num1 = toValue!long, num2 = other.toValue!long;
365                 if(num1 < num2)
366                     return -1;
367                 else if(num1 > num2)
368                     return 1;
369                 else
370                     return 0;                
371             }
372         }
373 
374         // if both are arrays they can be compared
375         if(_type == Type.ARRAY && other._type == Type.ARRAY)
376         {
377             auto arr1 = cast(ScriptArray)_asObject;
378             auto arr2 = cast(ScriptArray)(other._asObject);
379             if(arr1 < arr2)
380                 return -1;
381             else if(arr1 > arr2)
382                 return 1;
383             else
384                 return 0;
385         }
386 
387         if(_asObject == other._asObject)
388             return 0;
389 
390         // TODO handle object and functions
391 
392         // TODO handle native functions and delegates. Not sure how that comparison should work
393         // throw new ScriptAnyException("Unable to compare " ~ this.toString ~ " to " ~ other.toString, other);
394         return -1; // for now
395     }
396 
397     /**
398      * This allows ScriptAny to be used as a key index in a table, however the scripting language currently
399      * only uses strings.
400      */
401     size_t toHash() const nothrow
402     {
403         import mildew.types.array: ScriptArray;
404         final switch(_type)
405         {
406             case Type.UNDEFINED:
407                 return -1; // not sure what else to do
408             case Type.NULL:
409                 return 0; // i don't know what to do for those
410             case Type.BOOLEAN:
411                 return typeid(_asBoolean).getHash(&_asBoolean);
412             case Type.INTEGER:
413                 return typeid(_asInteger).getHash(&_asInteger);
414             case Type.DOUBLE:
415                 return typeid(_asDouble).getHash(&_asDouble);
416             case Type.STRING:
417             {
418                 try
419                 {
420                     auto str = _asObject.toString();
421                     return typeid(str).getHash(&str);
422                 }
423                 catch(Exception ex)
424                 {
425                     return 0; // IDK
426                 }
427             }
428             case Type.ARRAY:
429             {
430                 try 
431                 {
432                     auto arr = (cast(ScriptArray)_asObject).array;
433                     return typeid(arr).getHash(&arr);
434                 }
435                 catch(Exception ex)
436                 {
437                     return 0; // IDK
438                 }
439             }
440             case Type.FUNCTION: 
441             case Type.OBJECT:
442                 return typeid(_asObject).getHash(&_asObject);
443         }
444     }
445 
446     /**
447      * This implements the '===' and '!==' operators. Objects must be exactly the same in type and value.
448      * This operator should not be used on numerical primitives because true === 1 will return false.
449      */
450     bool strictEquals(const ScriptAny other)
451     {
452         if(_type != other._type)
453             return false;
454         
455         final switch(_type)
456         {
457             case Type.UNDEFINED:
458             case Type.NULL:
459                 return true;
460             case Type.BOOLEAN:
461                 return _asBoolean == other._asBoolean;
462             case Type.INTEGER:
463                 return _asInteger == other._asInteger;
464             case Type.DOUBLE:
465                 return _asDouble == other._asDouble;
466             case Type.STRING:
467             case Type.ARRAY:
468             case Type.FUNCTION: 
469             case Type.OBJECT:
470                 return _asObject == other._asObject;
471         }
472     }
473 
474     /**
475      * Returns the read-only type property. This should always be checked before using the
476      * toValue or checkValue template to retrieve the stored D value.
477      */
478     auto type() const nothrow @nogc { return _type; }
479 
480     /**
481      * Returns true if the type is UNDEFINED.
482      */
483     auto isUndefined() const nothrow @nogc { return _type == Type.UNDEFINED; }
484 
485     /**
486      * Returns true if the type is NULL or if it an object or function whose stored value is null
487      */
488     auto isNull() const nothrow @nogc 
489     { 
490         if(_type == Type.NULL)
491             return true;
492         if(isObject)
493             return _asObject is null;
494         return false;
495     }
496 
497     /**
498      * Returns true if the value stored is a numerical type or anything that can be converted into a
499      * valid number such as boolean, or even null, which gets converted to 0.
500      */
501     auto isNumber() const nothrow @nogc
502     {
503         return _type == Type.NULL || _type == Type.BOOLEAN || _type == Type.INTEGER || _type == Type.DOUBLE;
504     }
505 
506     /**
507      * Returns true if the value stored is a valid integer, but not a floating point number.
508      */
509     auto isInteger() const nothrow @nogc
510     {
511         return _type == Type.NULL || _type == Type.BOOLEAN || _type == Type.INTEGER;
512     }
513 
514     /**
515      * This should always be used instead of checking type==OBJECT because ScriptFunction, ScriptArray,
516      * and ScriptString are valid subclasses of ScriptObject.
517      */
518     auto isObject() const nothrow @nogc
519     {
520         return _type == Type.ARRAY || _type == Type.STRING || _type == Type.OBJECT || _type == Type.FUNCTION;
521     }
522 
523     /**
524      * Converts a stored value back into a D value if it is valid, otherwise throws an exception.
525      */
526     T checkValue(T)() const
527     {
528         return convertValue!T(true);
529     }
530 
531     /**
532      * Similar to checkValue except if the type is invalid and doesn't match the template type, a sane
533      * default value such as 0 or null is returned instead of throwing an exception.
534      */
535     T toValue(T)() const
536     {
537         return convertValue!T(false);    
538     }
539 
540     /**
541      * Shorthand for returning nativeObject from casting this to ScriptObject
542      */
543     T toNativeObject(T)() const
544     {
545         if(!isObject)
546             return cast(T)null;
547         return _asObject.nativeObject!T;
548     }
549 
550     /// For use with the scripting language's typeof operator
551     string typeToString() const
552     {
553         final switch(_type)
554         {
555             case Type.NULL: return "null";
556             case Type.UNDEFINED: return "undefined";
557             case Type.BOOLEAN: return "boolean";
558             case Type.INTEGER: return "integer";
559             case Type.DOUBLE: return "double";
560             case Type.STRING: return "string";
561             case Type.ARRAY: return "array";
562             case Type.FUNCTION: return "function";
563             case Type.OBJECT: return "object";
564         }
565     }
566 
567     /**
568      * Shorthand to access fields of the complex object types
569      */
570     ScriptAny opIndex(in string index)
571     {
572         if(!isObject)
573             return UNDEFINED;
574         return _asObject.lookupField(index);
575     }
576 
577     /**
578      * Shorthand to assign fields of the complex object types
579      */
580     ScriptAny opIndexAssign(T)(T value, string index)
581     {
582         if(!isObject)
583             return UNDEFINED;
584         auto any = ScriptAny(value);
585         _asObject.assignField(index, any);
586         return any;
587     }
588 
589     /// Returns a string representation of the stored value
590     auto toString() const
591     {
592         import std.format: format;
593 
594         final switch(_type)
595         {
596             case Type.NULL:
597                 return "null";
598             case Type.UNDEFINED:
599                 return "undefined";
600             case Type.BOOLEAN:
601                 return _asBoolean.to!string;
602             case Type.INTEGER:
603                 return _asInteger.to!string;
604             case Type.DOUBLE:
605                 return format("%.15g", _asDouble);
606             case Type.STRING:
607             case Type.ARRAY:
608             case Type.FUNCTION: 
609             case Type.OBJECT:
610                 if(_asObject !is null)
611                     return _asObject.toString();
612                 return "null";   
613         }
614     }
615 
616     /**
617      * This should always be used to return an undefined value.
618      */
619     static immutable UNDEFINED = ScriptAny();
620 
621 private:
622 
623     void setValue(T)(T value)
624     {
625         import mildew.types.array: ScriptArray;
626         import mildew.types.func: ScriptFunction;
627         import mildew.types.object: ScriptObject;
628         import mildew.types..string: ScriptString;
629 
630         static if(isBoolean!T)
631         {
632             _type = Type.BOOLEAN;
633             _asBoolean = value;
634         }
635         else static if(isIntegral!T)
636         {
637             _type = Type.INTEGER;
638             _asInteger = cast(long)value;
639         }
640         else static if(isFloatingPoint!T)
641         {
642             _type = Type.DOUBLE;
643             _asDouble = cast(double)value;
644         }
645         else static if(isSomeString!T)
646         {
647             _type = Type.STRING;
648             _asObject = new ScriptString(value.to!string);
649         }
650         else static if(is(T == ScriptAny[]))
651         {
652             _type = Type.ARRAY;
653             _asObject = new ScriptArray(value);
654         }
655         else static if(isArray!T)
656         {
657             _type = Type.ARRAY;
658             ScriptAny[] arr;
659             foreach(item; value)
660             {
661                 arr ~= ScriptAny(item);
662             }
663             _asObject = new ScriptArray(arr);
664         }
665         else static if(is(T == ScriptFunction))
666         {
667             _type = Type.FUNCTION;
668             _asObject = value;
669             if(_asObject is null)
670                 _type = Type.NULL;
671         }
672         else static if(is(T == ScriptObject))
673         {
674             _type = Type.OBJECT;
675             _asObject = value;
676             if(_asObject is null)
677                 _type = Type.NULL;
678         }
679         else static if(is(T == ScriptAny) || is(T == immutable(ScriptAny)))
680         {
681             this._type = value._type;
682             final switch(value._type)
683             {
684                 case Type.UNDEFINED:
685                 case Type.NULL:
686                     break;
687                 case Type.BOOLEAN:
688                     this._asBoolean = value._asBoolean;
689                     break;
690                 case Type.INTEGER:
691                     this._asInteger = value._asInteger;
692                     break;
693                 case Type.DOUBLE:
694                     this._asDouble = value._asDouble;
695                     break;
696                 case Type.STRING:
697                 case Type.ARRAY:
698                 case Type.FUNCTION:
699                 case Type.OBJECT:
700                     this._asObject = cast(ScriptObject)(value._asObject);
701                     break;
702             }
703         }
704         else static if(is(T == typeof(null)))
705         {
706             _type = Type.NULL;
707             _asObject = null;
708         }
709         else // can't directly set D objects because ScriptAny must be verified as a ScriptObject first!
710             static assert(false, "This type is not supported: " ~ T.stringof);
711     }
712 
713     T convertValue(T)(bool throwing) const
714     {
715         import mildew.types..string: ScriptString;
716         import mildew.types.array: ScriptArray;
717         import mildew.types.func: ScriptFunction;
718 
719         static if(isBoolean!T)
720         {
721             if(_type == Type.NULL || _type == Type.UNDEFINED)
722                 return false;
723             else if (_type == Type.BOOLEAN)
724                 return _asBoolean;
725             else if (this.isNumber())
726                 return convertValue!double(throwing) != 0.0;
727             else if(_type == Type.STRING)
728             {
729                 auto s = cast(ScriptString)_asObject;
730                 return s.toString() != "";
731             }
732             else if(_type == Type.ARRAY)
733             {
734                 auto arr = cast(ScriptArray)_asObject;
735                 return arr.array.length != 0;
736             }
737             else
738                 return _asObject !is null;
739         }
740         else static if(isIntegral!T || isFloatingPoint!T)
741         {
742             if(!this.isNumber())
743             {
744                 if(throwing)
745                     throw new ScriptAnyException("Unable to convert value " ~ toString ~ " to number", this);
746                 else
747                     return cast(T)0;
748             }
749             else if(_type == Type.BOOLEAN)
750                 return cast(T)_asBoolean;
751             else if(_type == Type.INTEGER)
752                 return cast(T)_asInteger;
753             else if(_type == Type.DOUBLE)
754                 return cast(T)_asDouble;
755             else // if null
756                 return 0;
757         }
758         else static if(isSomeString!T)
759         {
760             return to!T(toString());
761         }
762         else static if(is(T == ScriptAny[]))
763         {
764             if(_type != Type.ARRAY)
765             {
766                 if(throwing)
767                     throw new ScriptAnyException("ScriptAny " ~ toString ~ " is not an array", this);
768                 else
769                     return cast(T)null;
770             }
771             else
772             {
773                 auto arr = cast(ScriptArray)_asObject;
774                 return cast(ScriptAny[])arr.array;
775             }
776         }
777         else static if(is(T : E[], E))
778         {
779             if(_type != Type.ARRAY)
780             {
781                 if(throwing)
782                     throw new ScriptAnyException("ScriptAny " ~ toString ~ " is not an array", this);
783                 else
784                     return cast(T)null;
785             }
786             T arrayToFill = [];
787             auto scriptArray = cast(ScriptArray)_asObject;
788             foreach(item ; scriptArray.array)
789             {
790                 arrayToFill ~= item.convertValue!E(throwing);
791             }
792             return arrayToFill;
793         }
794         else static if(is(T == ScriptArray))
795         {
796             if(_type != Type.ARRAY)
797             {
798                 if(throwing)
799                     throw new ScriptAnyException("ScriptAny " ~ toString ~ " is not a ScriptArray", this);
800                 else
801                     return cast(T)null;
802             }
803             return cast(T)_asObject;
804         }
805         else static if(is(T == ScriptString))
806         {
807             if(_type != Type.STRING)
808             {
809                 if(throwing)
810                     throw new ScriptAnyException("ScriptAny " ~ toString ~ " is not a ScriptString", this);
811                 else
812                     return cast(T)null;
813             }
814             return cast(T)_asObject;
815         }
816         else static if(is(T == ScriptFunction))
817         {
818             if(_type != Type.FUNCTION)
819             {
820                 if(throwing)
821                     throw new ScriptAnyException("ScriptAny " ~ toString ~ " is not a function", this);
822                 else
823                     return cast(T)null;
824             }
825             else
826             {
827                 return cast(T)_asObject;
828             }
829         }
830         else static if(is(T == ScriptObject))
831         {
832             if(!isObject)
833             {
834                 if(throwing)
835                     throw new ScriptAnyException("ScriptAny " ~ toString ~ " is not an object", this);
836                 else
837                     return cast(T)null;
838             }
839             else
840             {
841                 return cast(T)_asObject;
842             }
843         }
844         else static if(is(T == class) || is(T == interface))
845         {
846             if(!isObject)
847             {
848                 if(throwing)
849                     throw new ScriptAnyException("ScriptAny " ~ toString ~ " does not store a D object", this);
850                 else
851                     return cast(T)null;
852             }
853             else
854             {
855                 return _asObject.nativeObject!T;
856             }
857         }
858         else
859             static assert(false, "This type is not supported: " ~ T.stringof);
860     }
861 
862     Type _type = Type.UNDEFINED;
863 
864     union
865     {
866         bool _asBoolean;
867         long _asInteger;
868         double _asDouble;
869         /// this includes array, string, function, or object
870         ScriptObject _asObject;
871     }
872 }
873 
874 /**
875  * This exception is only thrown when using ScriptAny.checkValue. If checkValue is used to check arguments, the host
876  * application running a script should catch this exception in addition to catching ScriptRuntimeException and
877  * ScriptCompileException. Otherwise it makes sense to just use toValue after checking the type field of the ScriptAny
878  * and setting the NativeFunctionError flag appropriately then returning ScriptAny.UNDEFINED.
879  */
880 class ScriptAnyException : Exception
881 {
882     /// ctor
883     this(string msg, ScriptAny val, string file = __FILE__, size_t line = __LINE__)
884     {
885         super(msg, file, line);
886         value = val;
887     }
888     /// the offending value
889     ScriptAny value;
890 }