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 }