1 /* 2 FIXME: 3 overloads can be done as an object representing the overload set 4 tat opCall does the dispatch. Then other overloads can actually 5 be added more sanely. 6 7 FIXME: 8 instantiate template members when reflection with certain 9 arguments if marked right... 10 11 12 FIXME: 13 pointer to member functions can give a way to wrap things 14 15 we'll pass it an opaque object as this and it will unpack and call the method 16 17 we can also auto-generate getters and setters for properties with this method 18 19 and constructors, so the script can create class objects too 20 */ 21 22 23 /++ 24 jsvar provides a D type called [var] that works similarly to the same in Javascript. 25 26 It is weakly (even weaker than JS, frequently returning null rather than throwing on 27 an invalid operation) and dynamically typed, but interops pretty easily with D itself: 28 29 --- 30 var a = 10; 31 a ~= "20"; 32 assert(a == "1020"); 33 34 var a = function(int b, int c) { return b+c; }; 35 // note the second set of () is because of broken @property 36 assert(a()(10,20) == 30); 37 38 var a = var.emptyObject; 39 a.foo = 30; 40 assert(a["foo"] == 30); 41 42 var b = json!q{ 43 "foo":12, 44 "bar":{"hey":[1,2,3,"lol"]} 45 }; 46 47 assert(b.bar.hey[1] == 2); 48 --- 49 50 51 You can also use [var.fromJson], a static method, to quickly and easily 52 read json or [var.toJson] to write it. 53 54 Also, if you combine this with my [arsd.script] module, you get pretty 55 easy interop with a little scripting language that resembles a cross between 56 D and Javascript - just like you can write in D itself using this type. 57 58 Please note that function default arguments are NOT likely to work in the script. 59 You'd have to use a helper thing that I haven't written yet. opAssign can never 60 do it because that information is lost when it becomes a pointer. ParamDefault 61 is thus commented out for now. 62 63 64 Properties: 65 $(LIST 66 * note that @property doesn't work right in D, so the opDispatch properties 67 will require double parenthesis to call as functions. 68 69 * Properties inside a var itself are set specially: 70 obj.propName._object = new PropertyPrototype(getter, setter); 71 ) 72 73 D structs can be turned to vars, but it is a copy. 74 75 Wrapping D native objects is coming later, the current ways suck. I really needed 76 properties to do them sanely at all, and now I have it. A native wrapped object will 77 also need to be set with _object prolly. 78 79 Author: Adam D Ruppe 80 81 History: Started in July 2013. 82 +/ 83 module arsd.jsvar; 84 85 import arsd.core; 86 87 version=new_std_json; 88 89 static import std.array; 90 import std.traits; 91 import std.conv; 92 import std.json; 93 94 version(jsvar_throw) 95 /++ 96 Variable to decide if jsvar throws on certain invalid 97 operations or continues on propagating null vars. 98 +/ 99 bool jsvar_throw = true; 100 else 101 /// ditto 102 bool jsvar_throw = false; 103 104 // uda for wrapping classes 105 enum scriptable = "arsd_jsvar_compatible"; 106 107 /* 108 PrototypeObject FIXME: 109 make undefined variables reaction overloadable in PrototypeObject, not just a switch 110 111 script FIXME: 112 113 the Expression should keep scriptFilename and lineNumber around for error messages 114 115 it should consistently throw on missing semicolons 116 117 *) in operator 118 119 *) nesting comments, `` string literals 120 *) opDispatch overloading 121 *) properties???// 122 a.prop on the rhs => a.prop() 123 a.prop on the lhs => a.prop(rhs); 124 if opAssign, it can just do a.prop(a.prop().opBinary!op(rhs)); 125 126 But, how do we mark properties in var? Can we make them work this way in D too? 127 0) add global functions to the object (or at least provide a convenience function to make a pre-populated global object) 128 1) ensure operator precedence is sane 129 2) a++ would prolly be nice, and def -a 130 4) switches? 131 10) __FILE__ and __LINE__ as default function arguments should work like in D 132 16) stack traces on script exceptions 133 17) an exception type that we can create in the script 134 135 14) import???????/ it could just attach a particular object to the local scope, and the module decl just giving the local scope a name 136 there could be a super-global object that is the prototype of the "global" used here 137 then you import, and it pulls moduleGlobal.prototype = superGlobal.modulename... or soemthing. 138 139 to get the vars out in D, you'd have to be aware of this, since you pass the superglobal 140 hmmm maybe not worth it 141 142 though maybe to export vars there could be an explicit export namespace or something. 143 144 145 6) gotos? labels? labeled break/continue? 146 18) what about something like ruby's blocks or macros? parsing foo(arg) { code } is easy enough, but how would we use it? 147 148 var FIXME: 149 150 user defined operator overloading on objects, including opCall, opApply, and more 151 flesh out prototype objects for Array, String, and Function 152 153 looserOpEquals 154 155 it would be nice if delegates on native types could work 156 */ 157 158 static if(__VERSION__ <= 2076) { 159 // compatibility shims with gdc 160 enum JSONType { 161 object = JSON_TYPE.OBJECT, 162 null_ = JSON_TYPE.NULL, 163 false_ = JSON_TYPE.FALSE, 164 true_ = JSON_TYPE.TRUE, 165 integer = JSON_TYPE.INTEGER, 166 float_ = JSON_TYPE.FLOAT, 167 array = JSON_TYPE.ARRAY, 168 string = JSON_TYPE.STRING, 169 uinteger = JSON_TYPE.UINTEGER 170 } 171 } 172 173 174 /* 175 Script notes: 176 177 the one type is var. It works just like the var type in D from arsd.jsvar. 178 (it might be fun to try to add other types, and match D a little better here! We could allow implicit conversion to and from var, but not on the other types, they could get static checking. But for now it is only var. BTW auto is an alias for var right now) 179 180 There is no comma operator, but you can use a scope as an expression: a++, b++; can be written as {a++;b++;} 181 */ 182 183 version(test_script) 184 struct Foop { 185 int a = 12; 186 string n = "hate"; 187 void speak() { writeln(n, " ", a); n = "love"; writeln(n, " is what it is now"); } 188 void speak2() { writeln("speak2 ", n, " ", a); } 189 } 190 version(test_script) 191 void main() { 192 import arsd.script; 193 writeln(interpret("x*x + 3*x;", var(["x":3]))); 194 195 { 196 var a = var.emptyObject; 197 a.qweq = 12; 198 } 199 200 // the WrappedNativeObject is disgusting 201 // but works. sort of. 202 /* 203 Foop foop2; 204 205 var foop; 206 foop._object = new WrappedNativeObject!Foop(foop2); 207 208 foop.speak()(); 209 foop.a = 25; 210 writeln(foop.n); 211 foop.speak2()(); 212 return; 213 */ 214 215 import arsd.script; 216 struct Test { 217 int a = 10; 218 string name = "ten"; 219 } 220 221 auto globals = var.emptyObject; 222 globals.lol = 100; 223 globals.rofl = 23; 224 225 globals.arrtest = var.emptyArray; 226 227 globals.write._function = (var _this, var[] args) { 228 string s; 229 foreach(a; args) 230 s ~= a.get!string; 231 writeln("script said: ", s); 232 return var(null); 233 }; 234 235 // call D defined functions in script 236 globals.func = (var a, var b) { writeln("Hello, world! You are : ", a, " and ", b); }; 237 238 globals.ex = () { throw new ScriptRuntimeException("test", 1); }; 239 240 globals.fun = { return var({ writeln("hello inside!"); }); }; 241 242 import std.file; 243 writeln(interpret(readText("scripttest_code.d"), globals)); 244 245 globals.ten = 10.0; 246 globals.five = 5.0; 247 writeln(interpret(q{ 248 var a = json!q{ }; 249 a.b = json!q{ }; 250 a.b.c = 10; 251 a; 252 }, globals)); 253 254 /* 255 globals.minigui = json!q{}; 256 import arsd.minigui; 257 globals.minigui.createWindow = { 258 var v; 259 auto mw = new MainWindow(); 260 v._object = new OpaqueNativeObject!(MainWindow)(mw); 261 v.loop = { mw.loop(); }; 262 return v; 263 }; 264 */ 265 266 repl(globals); 267 268 writeln("BACK IN D!"); 269 globals.c()(10); // call script defined functions in D (note: this runs the interpreter) 270 271 //writeln(globals._getMember("lol", false)); 272 return; 273 274 var k,l ; 275 276 var j = json!q{ 277 "hello": { 278 "data":[1,2,"giggle",4] 279 }, 280 "world":20 281 }; 282 283 writeln(j.hello.data[2]); 284 285 286 Test t; 287 var rofl = t; 288 writeln(rofl.name); 289 writeln(rofl.a); 290 291 rofl.a = "20"; 292 rofl.name = "twenty"; 293 294 t = rofl.get!Test; 295 writeln(t); 296 297 var a1 = 10; 298 a1 -= "5"; 299 a1 /= 2; 300 301 writeln(a1); 302 303 var a = 10; 304 var b = 20; 305 a = b; 306 307 b = 30; 308 a += 100.2; 309 writeln(a); 310 311 var c = var.emptyObject; 312 c.a = b; 313 314 var d = c; 315 d.b = 50; 316 317 writeln(c.b); 318 319 writeln(d.toJson()); 320 321 var e = a + b; 322 writeln(a, " + ", b, " = ", e); 323 324 e = function(var lol) { 325 writeln("hello with ",lol,"!"); 326 return lol + 10; 327 }; 328 329 writeln(e("15")); 330 331 if(var("ass") > 100) 332 writeln(var("10") / "3"); 333 } 334 335 template json(string s) { 336 // ctfe doesn't support the unions std.json uses :( 337 //enum json = var.fromJsonObject(s); 338 339 // FIXME we should at least validate string s at compile time 340 var json() { 341 return var.fromJson("{" ~ s ~ "}"); 342 } 343 } 344 345 // literals 346 347 // var a = varArray(10, "cool", 2); 348 // assert(a[0] == 10); assert(a[1] == "cool"); assert(a[2] == 2); 349 var varArray(T...)(T t) { 350 var a = var.emptyArray; 351 foreach(arg; t) 352 a ~= var(arg); 353 return a; 354 } 355 356 // var a = varObject("cool", 10, "bar", "baz"); 357 // assert(a.cool == 10 && a.bar == "baz"); 358 var varObject(T...)(T t) { 359 var a = var.emptyObject; 360 361 string lastString; 362 foreach(idx, arg; t) { 363 static if(idx % 2 == 0) { 364 lastString = arg; 365 } else { 366 assert(lastString !is null); 367 a[lastString] = arg; 368 lastString = null; 369 } 370 } 371 return a; 372 } 373 374 375 private double stringToNumber(string s) { 376 double r; 377 try { 378 r = to!double(s); 379 } catch (Exception e) { 380 r = double.nan; 381 } 382 383 return r; 384 } 385 386 private bool doubleIsInteger(double r) { 387 return (r == cast(long) r); 388 } 389 390 // helper template for operator overloading 391 private var _op(alias _this, alias this2, string op, T)(T t) if(op == "~") { 392 static if(is(T == var)) { 393 if(t.payloadType() == var.Type.Array) 394 return _op!(_this, this2, op)(t._payload._array); 395 else if(t.payloadType() == var.Type.String) 396 return _op!(_this, this2, op)(t._payload._string); 397 //else 398 //return _op!(_this, this2, op)(t.get!string); 399 } 400 401 if(this2.payloadType() == var.Type.Array) { 402 auto l = this2._payload._array; 403 static if(isArray!T && !isSomeString!T) 404 foreach(item; t) 405 l ~= var(item); 406 else 407 l ~= var(t); 408 409 _this._type = var.Type.Array; 410 _this._payload._array = l; 411 return _this; 412 } else if(this2.payloadType() == var.Type.String) { 413 auto l = this2._payload._string; 414 l ~= var(t).get!string; // is this right? 415 _this._type = var.Type.String; 416 _this._payload._string = l; 417 return _this; 418 } else { 419 auto l = this2.get!string; 420 l ~= var(t).get!string; 421 _this._type = var.Type.String; 422 _this._payload._string = l; 423 return _this; 424 } 425 426 assert(0); 427 428 } 429 430 // FIXME: maybe the bitops should be moved out to another function like ~ is 431 private var _op(alias _this, alias this2, string op, T)(T t) if(op != "~") { 432 static if(is(T == var)) { 433 if(t.payloadType() == var.Type.Integral) 434 return _op!(_this, this2, op)(t._payload._integral); 435 if(t.payloadType() == var.Type.Floating) 436 return _op!(_this, this2, op)(t._payload._floating); 437 if(t.payloadType() == var.Type.String) 438 return _op!(_this, this2, op)(t._payload._string); 439 throw new Exception("Attempted invalid operator `" ~ op ~ "` on variable of type " ~ to!string(t.payloadType())); 440 } else { 441 if(this2.payloadType() == var.Type.Integral) { 442 auto l = this2._payload._integral; 443 static if(isIntegral!T) { 444 mixin("l "~op~"= t;"); 445 _this._type = var.Type.Integral; 446 _this._payload._integral = l; 447 return _this; 448 } else static if(isFloatingPoint!T) { 449 static if(op == "&" || op == "|" || op == "^" || op == "<<" || op == ">>" || op == ">>>") { 450 this2._type = var.Type.Integral; 451 long f = l; 452 mixin("f "~op~"= cast(long) t;"); 453 _this._type = var.Type.Integral; 454 _this._payload._integral = f; 455 } else { 456 this2._type = var.Type.Floating; 457 double f = l; 458 mixin("f "~op~"= t;"); 459 _this._type = var.Type.Floating; 460 _this._payload._floating = f; 461 } 462 return _this; 463 } else static if(isSomeString!T) { 464 auto rhs = stringToNumber(t); 465 if(doubleIsInteger(rhs)) { 466 mixin("l "~op~"= cast(long) rhs;"); 467 _this._type = var.Type.Integral; 468 _this._payload._integral = l; 469 } else{ 470 static if(op == "&" || op == "|" || op == "^" || op == "<<" || op == ">>" || op == ">>>") { 471 long f = l; 472 mixin("f "~op~"= cast(long) rhs;"); 473 _this._type = var.Type.Integral; 474 _this._payload._integral = f; 475 } else { 476 double f = l; 477 mixin("f "~op~"= rhs;"); 478 _this._type = var.Type.Floating; 479 _this._payload._floating = f; 480 } 481 } 482 return _this; 483 484 } 485 } else if(this2.payloadType() == var.Type.Floating) { 486 auto f = this._payload._floating; 487 488 static if(isIntegral!T || isFloatingPoint!T) { 489 static if(op == "&" || op == "|" || op == "^" || op == "<<" || op == ">>" || op == ">>>") { 490 long argh = cast(long) f; 491 mixin("argh "~op~"= cast(long) t;"); 492 _this._type = var.Type.Integral; 493 _this._payload._integral = argh; 494 } else { 495 mixin("f "~op~"= t;"); 496 _this._type = var.Type.Floating; 497 _this._payload._floating = f; 498 } 499 return _this; 500 } else static if(isSomeString!T) { 501 auto rhs = stringToNumber(t); 502 503 static if(op == "&" || op == "|" || op == "^" || op == "<<" || op == ">>" || op == ">>>") { 504 long pain = cast(long) f; 505 mixin("pain "~op~"= cast(long) rhs;"); 506 _this._type = var.Type.Integral; 507 _this._payload._floating = pain; 508 } else { 509 mixin("f "~op~"= rhs;"); 510 _this._type = var.Type.Floating; 511 _this._payload._floating = f; 512 } 513 return _this; 514 } else static assert(0); 515 } else if(this2.payloadType() == var.Type.String) { 516 static if(op == "&" || op == "|" || op == "^" || op == "<<" || op == ">>" || op == ">>>") { 517 long r = cast(long) stringToNumber(this2._payload._string); 518 long rhs; 519 } else { 520 double r = stringToNumber(this2._payload._string); 521 double rhs; 522 } 523 524 static if(isSomeString!T) { 525 rhs = cast(typeof(rhs)) stringToNumber(t); 526 } else { 527 rhs = to!(typeof(rhs))(t); 528 } 529 530 mixin("r " ~ op ~ "= rhs;"); 531 532 static if(is(typeof(r) == double)) { 533 _this._type = var.Type.Floating; 534 _this._payload._floating = r; 535 } else static if(is(typeof(r) == long)) { 536 _this._type = var.Type.Integral; 537 _this._payload._integral = r; 538 } else static assert(0); 539 return _this; 540 } else { 541 // the operation is nonsensical, we should throw or ignore it 542 var i = 0; 543 return i; 544 } 545 } 546 547 assert(0); 548 } 549 550 // a plain `compiles(T(var))` check can be triggered by an implicit cast 551 // which we don't want due to endless recursion so this more specific check 552 // are better than that anyway 553 private template hasVarConstructor(T) { 554 bool helper() { 555 static if(is(T == struct) || is(T == class)) 556 static if(__traits(hasMember, T, "__ctor")) 557 foreach(overload; __traits(getOverloads, T, "__ctor")) 558 static if(is(typeof(overload) Params == __parameters)) 559 static if(Params.length == 1) 560 static if(is(Params[0] == var)) 561 return true; 562 return false; 563 } 564 565 enum bool hasVarConstructor = helper(); 566 } 567 568 unittest { 569 assert(hasVarConstructor!string == false); 570 571 static struct A { 572 this(var a) {} 573 } 574 575 static struct B { 576 this(int a) {} 577 } 578 579 assert(hasVarConstructor!A == true); 580 assert(hasVarConstructor!B == false); 581 } 582 583 584 /// 585 struct var { 586 587 /++ 588 When calling var.toJson or var.toJsonValue, null members of objects are usually skipped entirely. Setting this to true includes them, false excludes them. 589 590 Note this is global to a thread, not to a json object. 591 592 History: 593 Added December 6, 2025. Before, they were never included, so this being default `true` is a breaking change. 594 +/ 595 static bool includeExplicitNullsWhenConvertingToJson = true; 596 597 598 @implicit 599 public this(T)(T t) { 600 static if(is(T == var)) 601 this = t; 602 else 603 this.opAssign(t); 604 } 605 606 T opImplicitCast(T)() { 607 return this.get!T; 608 } 609 610 // used by the script interpreter... does a .dup on array, new on class if possible, otherwise copies members. 611 public var _copy_new() { 612 if(payloadType() == Type.Object) { 613 var cp; 614 if(this._payload._object !is null) { 615 auto po = this._payload._object.new_(null); 616 cp._object = po; 617 } 618 return cp; 619 } else if(payloadType() == Type.Array) { 620 var cp; 621 cp = this._payload._array.dup; 622 return cp; 623 } else { 624 return this._copy(); 625 } 626 } 627 628 public var _copy() { 629 final switch(payloadType()) { 630 case Type.Integral: 631 case Type.Boolean: 632 case Type.Floating: 633 case Type.Function: 634 case Type.String: 635 // since strings are immutable, we can pretend they are value types too 636 return this; // value types don't need anything special to be copied 637 638 case Type.Array: 639 var cp; 640 cp = this._payload._array[]; 641 return cp; 642 case Type.Object: 643 var cp; 644 if(this._payload._object !is null) 645 cp._object = this._payload._object.copy; 646 return cp; 647 } 648 } 649 650 /// `if(some_var)` will call this and give behavior based on the dynamic type. Shouldn't be too surprising. 651 public bool opCast(T:bool)() { 652 final switch(this._type) { 653 case Type.Object: 654 return this._payload._object !is null; 655 case Type.Array: 656 return this._payload._array.length != 0; 657 case Type.String: 658 return this._payload._string.length != 0; 659 case Type.Integral: 660 return this._payload._integral != 0; 661 case Type.Floating: 662 return this._payload._floating != 0; 663 case Type.Boolean: 664 return this._payload._boolean; 665 case Type.Function: 666 return this._payload._function !is null; 667 } 668 } 669 670 /// You can foreach over a var. 671 public int opApply(scope int delegate(ref var) dg) { 672 foreach(i, item; this) 673 if(auto result = dg(item)) 674 return result; 675 return 0; 676 } 677 678 /// ditto 679 public int opApply(scope int delegate(var, ref var) dg) { 680 if(this.payloadType() == Type.Array) { 681 foreach(i, ref v; this._payload._array) 682 if(auto result = dg(var(i), v)) 683 return result; 684 } else if(this.payloadType() == Type.Object && this._payload._object !is null) { 685 // FIXME: if it offers input range primitives, we should use them 686 // FIXME: user defined opApply on the object 687 foreach(k, ref v; this._payload._object) 688 if(auto result = dg(var(k), v)) 689 return result; 690 } else if(this.payloadType() == Type.String) { 691 // this is to prevent us from allocating a new string on each character, hopefully limiting that massively 692 static immutable string chars = makeAscii!(); 693 694 foreach(i, dchar c; this._payload._string) { 695 var lol = ""; 696 if(c < 128) 697 lol._payload._string = chars[c .. c + 1]; 698 else 699 lol._payload._string = to!string(""d ~ c); // blargh, how slow can we go? 700 if(auto result = dg(var(i), lol)) 701 return result; 702 } 703 } 704 // throw invalid foreach aggregate 705 706 return 0; 707 } 708 709 710 /// Alias for [get]. e.g. `string s = cast(string) v;` 711 public T opCast(T)() { 712 return this.get!T; 713 } 714 715 /// Calls [get] for a type automatically. `int a; var b; b.putInto(a);` will auto-convert to `int`. 716 public auto ref putInto(T)(ref T t) { 717 return t = this.get!T; 718 } 719 720 /++ 721 Assigns a value to the var. It will do necessary implicit conversions 722 and wrapping. 723 724 You can make a method `toArsdJsvar` on your own objects to override this 725 default. It should return a [var]. 726 727 History: 728 On April 20, 2020, I changed the default mode for class assignment 729 to [wrapNativeObject]. Previously it was [wrapOpaquely]. 730 731 With the new [wrapNativeObject] behavior, you can mark methods 732 @[scriptable] to expose them to the script. 733 +/ 734 public var opAssign(T)(T t) if(!is(T == var)) { 735 import std.typecons; 736 static if(__traits(compiles, this = t.toArsdJsvar())) { 737 static if(__traits(compiles, t is null)) { 738 if(t is null) 739 this = null; 740 else 741 this = t.toArsdJsvar(); 742 } else 743 this = t.toArsdJsvar(); 744 } else static if(is(T : PrototypeObject)) { 745 // support direct assignment of pre-made implementation objects 746 // so prewrapped stuff can be easily passed. 747 this._type = Type.Object; 748 this._payload._object = t; 749 } else static if(is(T == Nullable!N, N)) { 750 if(t.isNull()) 751 this = null; 752 else 753 this = t.get(); 754 } else static if(is(T == enum)) { 755 this._type = Type.String; 756 this._payload._string = to!string(t); 757 } else static if(isFloatingPoint!T) { 758 this._type = Type.Floating; 759 this._payload._floating = t; 760 } else static if(isIntegral!T) { 761 this._type = Type.Integral; 762 this._payload._integral = t; 763 } else static if(isCallable!T) { 764 this._type = Type.Function; 765 static if(is(T == typeof(this._payload._function))) { 766 this._payload._function = t; 767 } else 768 this._payload._function = delegate var(var _this, var[] args) { 769 var ret; 770 771 ParameterTypeTuple!T fargs; 772 773 // default args? nope they can't work cuz it is assigning a function pointer by here. alas. 774 enum lol = static_foreach(fargs.length, 1, -1, 775 `t(`,``,` < args.length ? args[`,`].get!(typeof(fargs[`,`])) : typeof(fargs[`,`]).init,`,`)`); 776 //`t(`,``,` < args.length ? args[`,`].get!(typeof(fargs[`,`])) : ParamDefault!(T, `,`)(),`,`)`); 777 /+ 778 foreach(idx, a; fargs) { 779 if(idx == args.length) 780 break; 781 cast(Unqual!(typeof(a))) fargs[idx] = args[idx].get!(typeof(a)); 782 } 783 +/ 784 785 static if(is(ReturnType!t == void)) { 786 //t(fargs); 787 mixin(lol ~ ";"); 788 } else { 789 //ret = t(fargs); 790 ret = mixin(lol); 791 } 792 793 return ret; 794 }; 795 } else static if(isSomeString!T) { 796 this._type = Type.String; 797 this._payload._string = to!string(t); 798 } else static if(is(T == class) || is(T == interface)) { 799 if(t !is null && (cast(Object) t) is null) 800 throw new Exception("Unsupported class or interface"); 801 this._type = Type.Object; 802 this._payload._object = t is null ? null : wrapNativeObject(t); 803 } else static if(.isScriptableOpaque!T) { 804 // auto-wrap other classes with reference semantics 805 this._type = Type.Object; 806 this._payload._object = wrapOpaquely(t); 807 } else static if(is(T == struct) || isAssociativeArray!T) { 808 // copy structs and assoc arrays by value into a var object 809 this._type = Type.Object; 810 auto obj = new PrototypeObject(); 811 this._payload._object = obj; 812 813 static if(is(T == struct)) 814 foreach(member; __traits(allMembers, T)) { 815 static if(__traits(compiles, __traits(getMember, t, member))) { 816 static if(is(typeof(__traits(getMember, t, member)) == function)) { 817 // skipping these because the delegate we get isn't going to work anyway; the object may be dead and certainly won't be updated 818 //this[member] = &__traits(getMember, proxyObject, member); 819 820 // but for simple toString, I'll allow it by recreating the object on demand 821 // and then calling the original function. (I might be able to do that for more but 822 // idk, just doing simple thing first) 823 static if(member == "toString" && is(typeof(&__traits(getMember, t, member)) == string delegate())) { 824 this[member]._function = delegate(var _this, var[] args) { 825 auto val = _this.get!T; 826 return var(val.toString()); 827 }; 828 } 829 } else static if(is(typeof(__traits(getMember, t, member)))) { 830 static if(!is(typeof(__traits(getMember, t, member)) == void)) 831 this[member] = __traits(getMember, t, member); 832 } 833 } 834 } else { 835 // assoc array 836 foreach(l, v; t) { 837 this[var(l)] = var(v); 838 } 839 } 840 } else static if(isArray!T) { 841 this._type = Type.Array; 842 var[] arr; 843 arr.length = t.length; 844 static if(!is(T == void[])) // we can't append a void array but it is nice to support x = []; 845 foreach(i, item; t) 846 arr[i] = var(item); 847 this._payload._array = arr; 848 } else static if(is(T == bool)) { 849 this._type = Type.Boolean; 850 this._payload._boolean = t; 851 } else static if(isSomeChar!T) { 852 this._type = Type.String; 853 this._payload._string = ""; 854 import std.utf; 855 char[4] ugh; 856 auto size = encode(ugh, t); 857 this._payload._string = ugh[0..size].idup; 858 }// else static assert(0, "unsupported type"); 859 860 return this; 861 } 862 863 public size_t opDollar() { 864 return this.length().get!size_t; 865 } 866 867 public var opOpAssign(string op, T)(T t) { 868 if(payloadType() == Type.Object) { 869 if(auto pt = cast(PropertyPrototype) this._payload._object) { 870 auto propValue = pt.get; 871 auto result = propValue.opOpAssign!(op)(t); 872 873 pt.set(result); 874 875 return result; 876 } else if(this._payload._object !is null) { 877 var* operator = this._payload._object._peekMember("opOpAssign", true); 878 if(operator !is null && operator._type == Type.Function) 879 return operator.call(this, op, t); 880 } 881 } 882 883 return _op!(this, this, op, T)(t); 884 } 885 886 public var opUnary(string op : "-")() { 887 static assert(op == "-"); 888 final switch(payloadType()) { 889 case Type.Object: 890 case Type.Array: 891 case Type.Boolean: 892 case Type.String: 893 case Type.Function: 894 assert(0); // FIXME 895 //break; 896 case Type.Integral: 897 return var(-this.get!long); 898 case Type.Floating: 899 return var(-this.get!double); 900 } 901 } 902 903 public var opBinary(string op, T)(T t) { 904 var n; 905 if(payloadType() == Type.Object) { 906 if(this._payload._object is null) 907 return var(null); 908 var* operator = this._payload._object._peekMember("opBinary", true); 909 if(operator !is null && operator._type == Type.Function) { 910 return operator.call(this, op, t); 911 } 912 } 913 return _op!(n, this, op, T)(t); 914 } 915 916 public var opBinaryRight(string op, T)(T s) { 917 return var(s).opBinary!op(this); 918 } 919 920 // this in foo 921 public var* opBinary(string op : "in", T)(T s) { 922 var rhs = var(s); 923 return rhs.opBinaryRight!"in"(this); 924 } 925 926 // foo in this 927 public var* opBinaryRight(string op : "in", T)(T s) { 928 // this needs to be an object 929 if(this._object is null) 930 return null; 931 return var(s).get!string in this._object._properties; 932 } 933 934 public var apply(var _this, var[] args) { 935 if(this.payloadType() == Type.Function) { 936 if(this._payload._function is null) { 937 if(jsvar_throw) 938 throw new DynamicTypeException(this, Type.Function); 939 else 940 return var(null); 941 } 942 return this._payload._function(_this, args); 943 } else if(this.payloadType() == Type.Object) { 944 if(this._payload._object is null) { 945 if(jsvar_throw) 946 throw new DynamicTypeException(this, Type.Function); 947 else 948 return var(null); 949 } 950 var* operator = this._payload._object._peekMember("opCall", true); 951 if(operator !is null && operator._type == Type.Function) 952 return operator.apply(_this, args); 953 } 954 955 if(this.payloadType() == Type.Integral || this.payloadType() == Type.Floating) { 956 if(args.length) 957 return var(this.get!double * args[0].get!double); 958 else 959 return this; 960 } else if(jsvar_throw) { 961 throw new DynamicTypeException(this, Type.Function); 962 } 963 964 //return this; 965 return var(null); 966 } 967 968 public var call(T...)(var _this, T t) { 969 var[] args; 970 foreach(a; t) { 971 args ~= var(a); 972 } 973 return this.apply(_this, args); 974 } 975 976 public var opCall(T...)(T t) { 977 return this.call(this, t); 978 } 979 980 /* 981 public var applyWithMagicLocals(var _this, var[] args, var[string] magicLocals) { 982 983 } 984 */ 985 986 public string toString() { 987 return this.get!string; 988 } 989 990 public T getWno(T)() { 991 if(payloadType == Type.Object) { 992 if(auto wno = cast(WrappedNativeObject) this._payload._object) { 993 auto no = cast(T) wno.getObject(); 994 if(no !is null) 995 return no; 996 } 997 } 998 return null; 999 } 1000 1001 /++ 1002 Gets the var converted to type `T` as best it can. `T` may be constructed 1003 from `T.fromJsVar`, or through type conversions (coercing as needed). If 1004 `T` happens to be a struct, it will automatically introspect to convert 1005 the var object member-by-member. 1006 1007 History: 1008 On April 21, 2020, I changed the behavior of 1009 1010 --- 1011 var a = null; 1012 string b = a.get!string; 1013 --- 1014 1015 Previously, `b == "null"`, which would print the word 1016 when writeln'd. Now, `b is null`, which prints the empty string, 1017 which is a bit less user-friendly, but more consistent with 1018 converting to/from D strings in general. 1019 1020 If you are printing, you can check `a.get!string is null` and print 1021 null at that point if you like. 1022 1023 I also wrote the first draft of this documentation at that time, 1024 even though the function has been public since the beginning. 1025 1026 On January 1, 2021, I changed `get!some_struct` to call properties 1027 on the var, if a member looks like a function or object, to try to 1028 get plain-old-data out. Since the functions are only ever put there 1029 by you or by you allowing script, I don't feel too bad about it, but 1030 it still might not be ideal for all circumstances, so idk if I'll leave 1031 it this way or not. 1032 1033 One thing it helps for though is taking scripted subclasses back into D 1034 structs, since the parent class thing is likely to be virtual properties. 1035 And having that just work in argument lists is really cool... 1036 1037 Search function for the comment "property getter support" to see the impl. 1038 +/ 1039 public T get(T)() if(!is(T == void)) { 1040 import std.typecons; 1041 static if(is(T == var)) { 1042 return this; 1043 } else static if(__traits(compiles, T.fromJsVar(var.init))) { 1044 return T.fromJsVar(this); 1045 } else static if(hasVarConstructor!T && __traits(compiles, T(this))) { 1046 return T(this); 1047 } else static if(hasVarConstructor!T && __traits(compiles, new T(this))) { 1048 return new T(this); 1049 } else static if(is(T == Nullable!N, N)) { 1050 if(payloadType == Type.Object && this._payload._object is null) 1051 return T.init; 1052 else 1053 return nullable(this.get!N); 1054 } else 1055 final switch(payloadType) { 1056 case Type.Boolean: 1057 static if(is(T == bool)) 1058 return this._payload._boolean; 1059 else static if(isFloatingPoint!T || isIntegral!T) 1060 return cast(T) (this._payload._boolean ? 1 : 0); // the cast is for enums, I don't like this so FIXME 1061 else static if(isSomeString!T) 1062 return to!T(this._payload._boolean ? "true" : "false"); 1063 else 1064 return T.init; 1065 case Type.Object: 1066 static if(isAssociativeArray!T) { 1067 T ret; 1068 if(this._payload._object !is null) 1069 foreach(k, v; this._payload._object._properties) 1070 ret[to!(KeyType!T)(k)] = v.get!(ValueType!T); 1071 1072 return ret; 1073 } else static if(is(T : PrototypeObject)) { 1074 // they are requesting an implementation object, just give it to them 1075 return cast(T) this._payload._object; 1076 } else static if(isScriptableOpaque!(Unqual!T)) { 1077 if(auto wno = cast(WrappedOpaque!(Unqual!T)) this._payload._object) { 1078 return wno.wrapping(); 1079 } 1080 static if(is(T == R*, R)) 1081 if(auto wno = cast(WrappedOpaque!(Unqual!(R))) this._payload._object) { 1082 return wno.wrapping(); 1083 } 1084 throw new DynamicTypeException(this, Type.Object); // FIXME: could be better 1085 } else static if(is(T == struct) || is(T == class) || is(T == interface)) { 1086 // first, we'll try to give them back the native object we have, if we have one 1087 static if(is(T : Object) || is(T == interface)) { 1088 auto t = this; 1089 // need to walk up the prototype chain too 1090 while(t != null) { 1091 assert(t.payloadType == Type.Object); 1092 if(auto wno = cast(WrappedNativeObject) t._payload._object) { 1093 auto no = cast(T) wno.getObject(); 1094 1095 if(no !is null) { 1096 auto sc = cast(ScriptableSubclass) no; 1097 if(sc !is null) { 1098 sc.setScriptVar(this); 1099 } 1100 1101 return no; 1102 } 1103 } 1104 t = t.prototype; 1105 } 1106 1107 // FIXME: this is kinda weird. 1108 return null; 1109 } else { 1110 1111 // failing that, generic struct or class getting: try to fill in the fields by name 1112 T t; 1113 bool initialized = true; 1114 static if(is(T == class)) { 1115 static if(__traits(compiles, new T())) { 1116 t = new T(); 1117 } else { 1118 initialized = false; 1119 } 1120 } 1121 1122 1123 if(initialized) 1124 foreach(i, a; t.tupleof) { 1125 var possibility = this[t.tupleof[i].stringof[2..$]]; 1126 // FIXME: so there is the possibility of getting some data getting all caught 1127 // up in a script function doing weird things. If I can prevent that, I'd like to... 1128 // but it is also really useful for this to work for some scenarios... 1129 static if(!is(typeof(a) == return)) // if it is callable, just assign the func ref 1130 if(isCallableJsvarObject(possibility)) 1131 possibility = possibility.apply(this, null); // crude approximation of property getter support 1132 cast(Unqual!(typeof((a)))) t.tupleof[i] = possibility.get!(typeof(a)); 1133 } 1134 1135 return t; 1136 } 1137 } else static if(isSomeString!T) { 1138 if(this._object !is null) 1139 return to!T(this._object.toString()); 1140 return null;// "null"; 1141 } else 1142 return T.init; 1143 case Type.Integral: 1144 static if(isFloatingPoint!T || isIntegral!T) 1145 return to!T(this._payload._integral); 1146 else static if(isSomeString!T) 1147 return to!T(this._payload._integral); 1148 else 1149 return T.init; 1150 case Type.Floating: 1151 static if(isFloatingPoint!T || isIntegral!T) 1152 return to!T(this._payload._floating); 1153 else static if(isSomeString!T) { 1154 // phobos this does insufficient precision, even money amounts truncated 1155 //return to!T(this._payload._floating); 1156 import arsd.conv; 1157 return arsd.conv.to!T(this._payload._floating); 1158 } else 1159 return T.init; 1160 case Type.String: 1161 static if(__traits(compiles, to!T(this._payload._string))) { 1162 try { 1163 return to!T(this._payload._string); 1164 } catch (Exception e) { return T.init; } 1165 } else 1166 return T.init; 1167 case Type.Array: 1168 import std.range; 1169 auto pl = this._payload._array; 1170 static if(isSomeString!T) { 1171 return to!T(pl); 1172 } else static if(is(T == E[N], E, size_t N)) { 1173 T ret; 1174 foreach(i; 0 .. N) { 1175 if(i >= pl.length) 1176 break; 1177 ret[i] = pl[i].get!E; 1178 } 1179 return ret; 1180 } else static if(is(T == E[], E)) { 1181 T ret; 1182 static if(is(ElementType!T == void)) { 1183 static assert(0, "try wrapping the function to get rid of void[] args"); 1184 //alias getType = ubyte; 1185 } else 1186 alias getType = ElementType!T; 1187 foreach(item; pl) 1188 ret ~= item.get!(getType); 1189 return ret; 1190 } else 1191 return T.init; 1192 // is it sane to translate anything else? 1193 case Type.Function: 1194 static if(isSomeString!T) { 1195 return to!T("<function>"); 1196 } else static if(isDelegate!T) { 1197 // making a local copy because otherwise the delegate might refer to a struct on the stack and get corrupted later or something 1198 auto func = this._payload._function; 1199 1200 // the static helper lets me pass specific variables to the closure 1201 static T helper(typeof(func) func) { 1202 return delegate ReturnType!T (ParameterTypeTuple!T args) { 1203 var[] arr; 1204 foreach(arg; args) 1205 arr ~= var(arg); 1206 var ret = func(var(null), arr); 1207 static if(is(ReturnType!T == void)) 1208 return; 1209 else 1210 return ret.get!(ReturnType!T); 1211 }; 1212 } 1213 1214 return helper(func); 1215 1216 } else 1217 return T.init; 1218 // FIXME: we just might be able to do better for both of these 1219 //break; 1220 } 1221 } 1222 1223 public T get(T)() if(is(T == void)) {} 1224 1225 public T nullCoalesce(T)(T t) { 1226 if(_type == Type.Object && _payload._object is null) 1227 return t; 1228 return this.get!T; 1229 } 1230 1231 public double opCmp(T)(T t) { 1232 auto f = this.get!double; 1233 static if(is(T == var)) 1234 auto r = t.get!double; 1235 else 1236 auto r = t; 1237 return f - r; 1238 } 1239 1240 public bool opEquals(T)(T t) { 1241 return this.opEquals(var(t)); 1242 } 1243 1244 public bool opEquals(T:var)(T t) const { 1245 // int and float can implicitly convert 1246 if(this._type == Type.Integral && t._type == Type.Floating) 1247 return _payload._integral == t._payload._floating; 1248 if(t._type == Type.Integral && this._type == Type.Floating) 1249 return t._payload._integral == this._payload._floating; 1250 1251 // but the others are kinda strict 1252 // FIXME: should this be == or === ? 1253 1254 if(this._type != t._type) 1255 return false; 1256 final switch(this._type) { 1257 case Type.Object: 1258 return _payload._object is t._payload._object; 1259 case Type.Integral: 1260 return _payload._integral == t._payload._integral; 1261 case Type.Boolean: 1262 return _payload._boolean == t._payload._boolean; 1263 case Type.Floating: 1264 return _payload._floating == t._payload._floating; // FIXME: approxEquals? 1265 case Type.String: 1266 return _payload._string == t._payload._string; 1267 case Type.Function: 1268 return _payload._function is t._payload._function; 1269 case Type.Array: 1270 return _payload._array == t._payload._array; 1271 } 1272 assert(0); 1273 } 1274 1275 public enum Type { 1276 Object, Array, Integral, Floating, String, Function, Boolean 1277 } 1278 1279 public Type payloadType() { 1280 return _type; 1281 } 1282 1283 private Type _type; 1284 1285 private union Payload { 1286 PrototypeObject _object; 1287 var[] _array; 1288 long _integral; 1289 double _floating; 1290 string _string; 1291 bool _boolean; 1292 var delegate(var _this, var[] args) _function; 1293 } 1294 1295 package VarMetadata _metadata; 1296 1297 public void _function(var delegate(var, var[]) f) { 1298 this._payload._function = f; 1299 this._type = Type.Function; 1300 } 1301 1302 /* 1303 public void _function(var function(var, var[]) f) { 1304 var delegate(var, var[]) dg; 1305 dg.ptr = null; 1306 dg.funcptr = f; 1307 this._function = dg; 1308 } 1309 */ 1310 1311 public void _object(PrototypeObject obj) { 1312 this._type = Type.Object; 1313 this._payload._object = obj; 1314 } 1315 1316 public PrototypeObject _object() { 1317 if(this._type == Type.Object) 1318 return this._payload._object; 1319 return null; 1320 } 1321 1322 package Payload _payload; 1323 1324 private void _requireType(Type t, string file = __FILE__, size_t line = __LINE__){ 1325 if(this.payloadType() != t) 1326 throw new DynamicTypeException(this, t, file, line); 1327 } 1328 1329 public var opSlice(var e1, var e2) { 1330 return this.opSlice(e1.get!ptrdiff_t, e2.get!ptrdiff_t); 1331 } 1332 1333 public var opSlice(ptrdiff_t e1, ptrdiff_t e2) { 1334 if(this.payloadType() == Type.Array) { 1335 if(e1 > _payload._array.length) 1336 e1 = _payload._array.length; 1337 if(e2 > _payload._array.length) 1338 e2 = _payload._array.length; 1339 return var(_payload._array[e1 .. e2]); 1340 } 1341 if(this.payloadType() == Type.String) { 1342 if(e1 > _payload._string.length) 1343 e1 = _payload._string.length; 1344 if(e2 > _payload._string.length) 1345 e2 = _payload._string.length; 1346 return var(_payload._string[e1 .. e2]); 1347 } 1348 if(this.payloadType() == Type.Object) { 1349 var operator = this["opSlice"]; 1350 if(operator._type == Type.Function) { 1351 return operator.call(this, e1, e2); 1352 } 1353 } 1354 1355 // might be worth throwing here too 1356 return var(null); 1357 } 1358 1359 /// Forwards to [opIndex] 1360 public @property ref var opDispatch(string name, string file = __FILE__, size_t line = __LINE__)() { 1361 return this[name]; 1362 } 1363 1364 /// Forwards to [opIndexAssign] 1365 public @property ref var opDispatch(string name, string file = __FILE__, size_t line = __LINE__, T)(T r) { 1366 return this.opIndexAssign!T(r, name); 1367 } 1368 1369 /// Looks up a sub-property of the object 1370 public ref var opIndex(var name, string file = __FILE__, size_t line = __LINE__) { 1371 return opIndex(name.get!string, file, line); 1372 } 1373 1374 /// Sets a sub-property of the object 1375 public ref var opIndexAssign(T)(T t, var name, string file = __FILE__, size_t line = __LINE__) { 1376 return opIndexAssign(t, name.get!string, file, line); 1377 } 1378 1379 public ref var opIndex(string name, string file = __FILE__, size_t line = __LINE__) { 1380 // if name is numeric, we should convert to int for arrays 1381 if(name.length && name[0] >= '0' && name[0] <= '9' && this.payloadType() == Type.Array) 1382 return opIndex(to!size_t(name), file, line); 1383 1384 if(this.payloadType() != Type.Object && name == "prototype") 1385 return prototype(); 1386 1387 if(name == "typeof") { 1388 var* tmp = new var; 1389 *tmp = to!string(this.payloadType()); 1390 return *tmp; 1391 } 1392 1393 if(name == "toJson") { 1394 var* tmp = new var; 1395 *tmp = to!string(this.toJson()); 1396 return *tmp; 1397 } 1398 1399 if(name == "length" && this.payloadType() == Type.String) { 1400 var* tmp = new var; 1401 *tmp = _payload._string.length; 1402 return *tmp; 1403 } 1404 if(name == "length" && this.payloadType() == Type.Array) { 1405 var* tmp = new var; 1406 *tmp = _payload._array.length; 1407 return *tmp; 1408 } 1409 if(name == "__prop" && this.payloadType() == Type.Object) { 1410 var* tmp = new var; 1411 (*tmp)._function = delegate var(var _this, var[] args) { 1412 if(args.length == 0) 1413 return var(null); 1414 if(args.length == 1) { 1415 auto peek = this._payload._object._peekMember(args[0].get!string, false); 1416 if(peek is null) 1417 return var(null); 1418 else 1419 return *peek; 1420 } 1421 if(args.length == 2) { 1422 auto peek = this._payload._object._peekMember(args[0].get!string, false); 1423 if(peek is null) { 1424 this._payload._object._properties[args[0].get!string] = args[1]; 1425 return var(null); 1426 } else { 1427 *peek = args[1]; 1428 return *peek; 1429 } 1430 1431 } 1432 throw new Exception("too many args"); 1433 }; 1434 return *tmp; 1435 } 1436 1437 PrototypeObject from; 1438 if(this.payloadType() == Type.Object) 1439 from = _payload._object; 1440 else { 1441 var pt = this.prototype(); 1442 assert(pt.payloadType() == Type.Object); 1443 from = pt._payload._object; 1444 } 1445 1446 if(from is null) { 1447 if(jsvar_throw) 1448 throw new DynamicTypeException(var(null), Type.Object, file, line); 1449 else 1450 return *(new var); 1451 } 1452 return from._getMember(name, true, false, false, file, line); 1453 } 1454 1455 public ref var opIndexAssign(T)(T t, string name, string file = __FILE__, size_t line = __LINE__) { 1456 if(this.payloadType == Type.Array && name.appearsNumeric()) { 1457 try { 1458 auto i = to!size_t(name); 1459 return opIndexAssign(t, i, file, line); 1460 } catch(Exception) 1461 {} // ignore bad index, use it as a string instead lol 1462 } 1463 _requireType(Type.Object); // FIXME? 1464 if(_payload._object is null) 1465 throw new DynamicTypeException(var(null), Type.Object, file, line); 1466 1467 return this._payload._object._setMember(name, var(t), false, false, false, file, line); 1468 } 1469 1470 public ref var opIndexAssignNoOverload(T)(T t, string name, string file = __FILE__, size_t line = __LINE__) { 1471 if(name.length && name[0] >= '0' && name[0] <= '9') 1472 return opIndexAssign(t, to!size_t(name), file, line); 1473 _requireType(Type.Object); // FIXME? 1474 if(_payload._object is null) 1475 throw new DynamicTypeException(var(null), Type.Object, file, line); 1476 1477 return this._payload._object._setMember(name, var(t), false, false, true, file, line); 1478 } 1479 1480 1481 public ref var opIndex(size_t idx, string file = __FILE__, size_t line = __LINE__) { 1482 if(_type == Type.Array) { 1483 auto arr = this._payload._array; 1484 if(idx < arr.length) 1485 return arr[idx]; 1486 } else if(_type == Type.Object) { 1487 // objects might overload opIndex 1488 var* n = new var(); 1489 if("opIndex" in this) 1490 *n = this["opIndex"](idx); 1491 return *n; 1492 } 1493 if(jsvar_throw) { 1494 throw new DynamicTypeException(this, Type.Array, file, line); 1495 } else { 1496 var* n = new var(); 1497 return *n; 1498 } 1499 } 1500 1501 public ref var opIndexAssign(T)(T t, size_t idx, string file = __FILE__, size_t line = __LINE__) { 1502 if(_type == Type.Array) { 1503 if(idx >= this._payload._array.length) 1504 this._payload._array.length = idx + 1; 1505 this._payload._array[idx] = t; 1506 return this._payload._array[idx]; 1507 } else if(_type == Type.Object) { 1508 return opIndexAssign(t, to!string(idx), file, line); 1509 } 1510 if(jsvar_throw) { 1511 throw new DynamicTypeException(this, Type.Array, file, line); 1512 } else { 1513 var* n = new var(); 1514 return *n; 1515 } 1516 } 1517 1518 ref var _getOwnProperty(string name, string file = __FILE__, size_t line = __LINE__) { 1519 if(_type == Type.Object) { 1520 if(_payload._object !is null) { 1521 auto peek = this._payload._object._peekMember(name, false); 1522 if(peek !is null) 1523 return *peek; 1524 } 1525 } 1526 //if(jsvar_throw) 1527 //throw new DynamicTypeException(this, Type.Object, file, line); 1528 var* n = new var(); 1529 return *n; 1530 } 1531 1532 @property static var emptyObject(PrototypeObject prototype = null) { 1533 var v; 1534 v._type = Type.Object; 1535 v._payload._object = new PrototypeObject(); 1536 v._payload._object.prototype = prototype; 1537 return v; 1538 } 1539 1540 /// 1541 @property static var emptyObject(var prototype) { 1542 if(prototype._type == Type.Object) 1543 return var.emptyObject(prototype._payload._object); 1544 return var.emptyObject(); 1545 } 1546 1547 @property PrototypeObject prototypeObject() { 1548 var v = prototype(); 1549 if(v._type == Type.Object) 1550 return v._payload._object; 1551 return null; 1552 } 1553 1554 // what I call prototype is more like what Mozilla calls __proto__, but tbh I think this is better so meh 1555 @property ref var prototype() { 1556 static var _arrayPrototype; 1557 static var _functionPrototype; 1558 static var _stringPrototype; 1559 1560 final switch(payloadType()) { 1561 case Type.Array: 1562 assert(_arrayPrototype._type == Type.Object); 1563 if(_arrayPrototype._payload._object is null) { 1564 _arrayPrototype._object = new PrototypeObject(); 1565 } 1566 1567 return _arrayPrototype; 1568 case Type.Function: 1569 assert(_functionPrototype._type == Type.Object); 1570 if(_functionPrototype._payload._object is null) { 1571 _functionPrototype._object = new PrototypeObject(); 1572 } 1573 1574 return _functionPrototype; 1575 case Type.String: 1576 assert(_stringPrototype._type == Type.Object); 1577 if(_stringPrototype._payload._object is null) { 1578 auto p = new PrototypeObject(); 1579 _stringPrototype._object = p; 1580 1581 var replaceFunction; 1582 replaceFunction._type = Type.Function; 1583 replaceFunction._function = (var _this, var[] args) { 1584 string s = _this.toString(); 1585 import std.array : replace; 1586 return var(std.array.replace(s, 1587 args[0].toString(), 1588 args[1].toString())); 1589 }; 1590 1591 p._properties["replace"] = replaceFunction; 1592 } 1593 1594 return _stringPrototype; 1595 case Type.Object: 1596 if(_payload._object) 1597 return _payload._object._prototype; 1598 // FIXME: should we do a generic object prototype? 1599 break; 1600 case Type.Integral: 1601 case Type.Floating: 1602 case Type.Boolean: 1603 // these types don't have prototypes 1604 } 1605 1606 1607 var* v = new var(null); 1608 return *v; 1609 } 1610 1611 /// 1612 @property static var emptyArray() { 1613 var v; 1614 v._type = Type.Array; 1615 return v; 1616 } 1617 1618 /// 1619 static var fromJson(const(char)[] json) { 1620 auto decoded = parseJSON(json); 1621 return var.fromJsonValue(decoded); 1622 } 1623 1624 /// 1625 static var fromJsonFile(const(char)[] filename) { 1626 import std.file; 1627 return var.fromJson(readText(filename)); 1628 } 1629 1630 /// 1631 static var fromJsonValue(JSONValue v) { 1632 var ret; 1633 1634 final switch(v.type) { 1635 case JSONType..string: 1636 ret = v.str; 1637 break; 1638 case JSONType.uinteger: 1639 ret = v.uinteger; 1640 break; 1641 case JSONType.integer: 1642 ret = v.integer; 1643 break; 1644 case JSONType.float_: 1645 ret = v.floating; 1646 break; 1647 case JSONType.object: 1648 ret = var.emptyObject; 1649 foreach(k, val; v.object) { 1650 ret[k] = var.fromJsonValue(val); 1651 } 1652 break; 1653 case JSONType.array: 1654 ret = var.emptyArray; 1655 ret._payload._array.length = v.array.length; 1656 foreach(idx, item; v.array) { 1657 ret._payload._array[idx] = var.fromJsonValue(item); 1658 } 1659 break; 1660 case JSONType.true_: 1661 ret = true; 1662 break; 1663 case JSONType.false_: 1664 ret = false; 1665 break; 1666 case JSONType.null_: 1667 ret = null; 1668 break; 1669 } 1670 1671 return ret; 1672 } 1673 1674 /// 1675 string toJson() { 1676 auto v = toJsonValue(); 1677 return toJSON(v); 1678 } 1679 1680 /// 1681 JSONValue toJsonValue() { 1682 JSONValue val; 1683 final switch(payloadType()) { 1684 case Type.Boolean: 1685 version(new_std_json) 1686 val = this._payload._boolean; 1687 else { 1688 if(this._payload._boolean) 1689 val.type = JSONType.true_; 1690 else 1691 val.type = JSONType.false_; 1692 } 1693 break; 1694 case Type.Object: 1695 version(new_std_json) { 1696 if(_payload._object is null) { 1697 val = null; 1698 } else { 1699 val = _payload._object.toJsonValue(); 1700 } 1701 } else { 1702 if(_payload._object is null) { 1703 val.type = JSONType.null_; 1704 } else { 1705 val.type = JSONType.object; 1706 foreach(k, v; _payload._object._properties) { 1707 val.object[k] = v.toJsonValue(); 1708 } 1709 } 1710 } 1711 break; 1712 case Type.String: 1713 version(new_std_json) { } else { 1714 val.type = JSONType..string; 1715 } 1716 val.str = _payload._string; 1717 break; 1718 case Type.Integral: 1719 version(new_std_json) { } else { 1720 val.type = JSONType.integer; 1721 } 1722 val.integer = _payload._integral; 1723 break; 1724 case Type.Floating: 1725 version(new_std_json) { } else { 1726 val.type = JSONType.float_; 1727 } 1728 val.floating = _payload._floating; 1729 break; 1730 case Type.Array: 1731 auto a = _payload._array; 1732 JSONValue[] tmp; 1733 tmp.length = a.length; 1734 foreach(i, v; a) { 1735 tmp[i] = v.toJsonValue(); 1736 } 1737 1738 version(new_std_json) { 1739 val = tmp; 1740 } else { 1741 val.type = JSONType.array; 1742 val.array = tmp; 1743 } 1744 break; 1745 case Type.Function: 1746 version(new_std_json) 1747 val = null; 1748 else 1749 val.type = JSONType.null_; // ideally we would just skip it entirely... 1750 break; 1751 } 1752 return val; 1753 } 1754 } 1755 1756 class PrototypeObject { 1757 string name; 1758 var _prototype; 1759 1760 package PrototypeObject _secondary; // HACK don't use this 1761 1762 PrototypeObject prototype() { 1763 if(_prototype.payloadType() == var.Type.Object) 1764 return _prototype._payload._object; 1765 return null; 1766 } 1767 1768 PrototypeObject prototype(PrototypeObject set) { 1769 this._prototype._object = set; 1770 return set; 1771 } 1772 1773 override string toString() { 1774 1775 var* ts = _peekMember("toString", true); 1776 if(ts) { 1777 var _this; 1778 _this._object = this; 1779 return (*ts).call(_this).get!string; 1780 } 1781 1782 JSONValue val; 1783 version(new_std_json) { 1784 JSONValue[string] tmp; 1785 foreach(k, v; this._properties) 1786 tmp[k] = v.toJsonValue(); 1787 val.object = tmp; 1788 } else { 1789 val.type = JSONType.object; 1790 foreach(k, v; this._properties) 1791 val.object[k] = v.toJsonValue(); 1792 } 1793 1794 return toJSON(val); 1795 } 1796 1797 var[string] _properties; 1798 1799 PrototypeObject copy() { 1800 auto n = new PrototypeObject(); 1801 n.prototype = this.prototype; 1802 n.name = this.name; 1803 foreach(k, v; _properties) { 1804 n._properties[k] = v._copy; 1805 } 1806 return n; 1807 } 1808 1809 bool isSpecial() { return false; } 1810 1811 PrototypeObject new_(PrototypeObject newThis) { 1812 // if any of the prototypes are D objects, we need to try to copy them. 1813 auto p = prototype; 1814 1815 PrototypeObject[32] stack; 1816 PrototypeObject[] fullStack = stack[]; 1817 int stackPos; 1818 1819 while(p !is null) { 1820 1821 if(p.isSpecial()) { 1822 auto n = new PrototypeObject(); 1823 1824 auto proto = p.new_(n); 1825 1826 while(stackPos) { 1827 stackPos--; 1828 auto pr = fullStack[stackPos].copy(); 1829 pr.prototype = proto; 1830 proto = pr; 1831 } 1832 1833 n.prototype = proto; 1834 n.name = this.name; 1835 foreach(k, v; _properties) { 1836 n._properties[k] = v._copy; 1837 } 1838 1839 return n; 1840 } 1841 1842 if(stackPos >= fullStack.length) 1843 fullStack ~= p; 1844 else 1845 fullStack[stackPos] = p; 1846 stackPos++; 1847 1848 p = p.prototype; 1849 } 1850 1851 return copy(); 1852 } 1853 1854 PrototypeObject copyPropertiesFrom(PrototypeObject p) { 1855 foreach(k, v; p._properties) { 1856 this._properties[k] = v._copy; 1857 } 1858 return this; 1859 } 1860 1861 var* _peekMember(string name, bool recurse) { 1862 if(name == "prototype") 1863 return &_prototype; 1864 1865 auto curr = this; 1866 1867 // for the secondary hack 1868 bool triedOne = false; 1869 // for the secondary hack 1870 PrototypeObject possibleSecondary; 1871 1872 tryAgain: 1873 do { 1874 auto prop = name in curr._properties; 1875 if(prop is null) { 1876 // the secondary hack is to do more scoping in the script, it is really hackish 1877 if(possibleSecondary is null) 1878 possibleSecondary = curr._secondary; 1879 1880 if(!recurse) 1881 break; 1882 else 1883 curr = curr.prototype; 1884 } else 1885 return prop; 1886 } while(curr); 1887 1888 if(possibleSecondary !is null) { 1889 curr = possibleSecondary; 1890 if(!triedOne) { 1891 triedOne = true; 1892 goto tryAgain; 1893 } 1894 } 1895 1896 return null; 1897 } 1898 1899 // FIXME: maybe throw something else 1900 /*package*/ ref var _getMember(string name, bool recurse, bool throwOnFailure, bool returnRawProperty = false, string file = __FILE__, size_t line = __LINE__) { 1901 var* mem = _peekMember(name, recurse); 1902 1903 if(mem !is null) { 1904 // If it is a property, we need to call the getter on it 1905 if((*mem).payloadType == var.Type.Object && cast(PropertyPrototype) (*mem)._payload._object) { 1906 if(returnRawProperty) 1907 return *mem; 1908 auto prop = cast(PropertyPrototype) (*mem)._payload._object; 1909 return prop.get; 1910 } 1911 return *mem; 1912 } 1913 1914 mem = _peekMember("opIndex", recurse); 1915 if(mem !is null) { 1916 auto n = new var; 1917 *n = ((*mem)(name)); 1918 return *n; 1919 } 1920 1921 // if we're here, the property was not found, so let's implicitly create it 1922 if(throwOnFailure) 1923 throw new DynamicTypeException("no such property " ~ name, file, line); 1924 var n; 1925 this._properties[name] = n; 1926 return this._properties[name]; 1927 } 1928 1929 // FIXME: maybe throw something else 1930 /*package*/ ref var _setMember(string name, var t, bool recurse, bool throwOnFailure, bool suppressOverloading, string file = __FILE__, size_t line = __LINE__) { 1931 var* mem = _peekMember(name, recurse); 1932 1933 bool onParent = false; 1934 1935 if(mem is null && !recurse) { 1936 mem = _peekMember(name, true); // properties need the check anyway as a setter might be on a prototype 1937 onParent = true; 1938 } 1939 1940 if(mem !is null) { 1941 // Property check - the setter should be proxied over to it 1942 if((*mem).payloadType == var.Type.Object && cast(PropertyPrototype) (*mem)._payload._object) { 1943 auto prop = cast(PropertyPrototype) (*mem)._payload._object; 1944 return prop.set(t); 1945 } 1946 if(!onParent) { 1947 *mem = t; 1948 return *mem; 1949 } else { 1950 mem = null; 1951 } 1952 } 1953 1954 if(!suppressOverloading) { 1955 mem = _peekMember("opIndexAssign", true); 1956 if(mem !is null) { 1957 auto n = new var; 1958 *n = ((*mem)(t, name)); 1959 return *n; 1960 } 1961 } 1962 1963 // if we're here, the property was not found, so let's implicitly create it 1964 if(throwOnFailure) 1965 throw new DynamicTypeException("no such property " ~ name, file, line); 1966 this._properties[name] = t; 1967 return this._properties[name]; 1968 } 1969 1970 JSONValue toJsonValue() { 1971 JSONValue val; 1972 JSONValue[string] tmp; 1973 foreach(k, v; this._properties) { 1974 // if it is an overload set and/or a function, just skip it. 1975 // or really if it is a wrapped native object it should prolly just be skipped anyway 1976 // unless it actually defines a toJson. 1977 if(v.payloadType == var.Type.Function) 1978 continue; 1979 if(v.payloadType == var.Type.Object) { 1980 // I'd love to get the json value out but idk. FIXME 1981 if(v._payload._object is null) { 1982 if(var.includeExplicitNullsWhenConvertingToJson) { 1983 tmp[k] = null; 1984 } 1985 continue; 1986 } 1987 if(auto wno = cast(WrappedNativeObject) v._payload._object) { 1988 auto obj = wno.getObject(); 1989 if(obj is null) 1990 tmp[k] = null; 1991 else 1992 tmp[k] = obj.toString(); 1993 continue; 1994 } else if(typeid(PrototypeObject) !is typeid(v._payload._object)) 1995 continue; 1996 } 1997 1998 tmp[k] = v.toJsonValue(); 1999 } 2000 2001 val = tmp; 2002 return val; 2003 } 2004 2005 public int opApply(scope int delegate(var, ref var) dg) { 2006 foreach(k, v; this._properties) { 2007 if(v.payloadType == var.Type.Object && cast(PropertyPrototype) v._payload._object) 2008 v = (cast(PropertyPrototype) v._payload._object).get; 2009 if(auto result = dg(var(k), v)) 2010 return result; 2011 } 2012 return 0; 2013 } 2014 } 2015 2016 // A property is a special type of object that can only be set by assigning 2017 // one of these instances to foo.child._object. When foo.child is accessed and it 2018 // is an instance of PropertyPrototype, it will return the getter. When foo.child is 2019 // set (excluding direct assignments through _type), it will call the setter. 2020 class PropertyPrototype : PrototypeObject { 2021 var delegate() getter; 2022 void delegate(var) setter; 2023 this(var delegate() getter, void delegate(var) setter) { 2024 this.getter = getter; 2025 this.setter = setter; 2026 } 2027 2028 override string toString() { 2029 return get.toString(); 2030 } 2031 2032 ref var get() { 2033 var* g = new var(); 2034 *g = getter(); 2035 return *g; 2036 } 2037 2038 ref var set(var t) { 2039 setter(t); 2040 return get; 2041 } 2042 2043 override JSONValue toJsonValue() { 2044 return get.toJsonValue(); 2045 } 2046 } 2047 2048 /// 2049 struct ScriptLocation { 2050 string scriptFilename; /// 2051 int lineNumber; /// 2052 } 2053 2054 class DynamicTypeException : Exception { 2055 this(string msg, string file = __FILE__, size_t line = __LINE__) { 2056 super(msg, file, line); 2057 } 2058 this(var v, var.Type required, string file = __FILE__, size_t line = __LINE__) { 2059 import std.string; 2060 if(v.payloadType() == required) 2061 super(format("Tried to use null as a %s", required), file, line); 2062 else { 2063 super(format("Tried to use %s%s as a %s", v == null ? "null ": "", v.payloadType(), required), file, line); 2064 } 2065 } 2066 2067 override void toString(scope void delegate(in char[]) sink) const { 2068 import std.format; 2069 if(varName.length) 2070 sink(varName); 2071 if(callStack.length) { 2072 sink("arsd.jsvar.DynamicTypeException@"); 2073 sink(file); 2074 sink("("); 2075 sink(to!string(line)); 2076 sink("): "); 2077 sink(msg); 2078 foreach(cs; callStack) 2079 sink(format("\n\t%s:%s", cs.scriptFilename, cs.lineNumber)); 2080 2081 bool found; 2082 void hiddenSink(in char[] str) { 2083 // I just want to hide the call stack until the interpret call... 2084 // since the script stack above is more meaningful to users. 2085 // 2086 // but then I will go back to the D functions once on the outside. 2087 import std.string; 2088 if(found) 2089 sink(str); 2090 else if(str.indexOf("arsd.script.interpret(") != -1) 2091 found = true; 2092 } 2093 2094 sink("\n--------"); 2095 2096 super.toString(&hiddenSink); 2097 } else { 2098 super.toString(sink); 2099 } 2100 } 2101 2102 ScriptLocation[] callStack; 2103 string varName; 2104 } 2105 2106 template makeAscii() { 2107 string helper() { 2108 string s; 2109 foreach(i; 0 .. 128) 2110 s ~= cast(char) i; 2111 return s; 2112 } 2113 2114 enum makeAscii = helper(); 2115 } 2116 2117 package interface VarMetadata { } 2118 2119 interface ScriptableSubclass { 2120 void setScriptVar(var); 2121 var getScriptVar(); 2122 final bool methodOverriddenByScript(string name) { 2123 PrototypeObject t = getScriptVar().get!PrototypeObject; 2124 // the top one is the native object from subclassable so we don't want to go all the way there to avoid endless recursion 2125 //import std.stdio; writeln("checking ", name , " ...", "wtf"); 2126 if(t !is null) 2127 while(!t.isSpecial) { 2128 if(t._peekMember(name, false) !is null) 2129 return true; 2130 t = t.prototype; 2131 } 2132 return false; 2133 } 2134 } 2135 2136 /++ 2137 EXPERIMENTAL 2138 2139 Allows you to make a class available to the script rather than just class objects. 2140 You can subclass it in script and then call the methods again through the original 2141 D interface. With caveats... 2142 2143 2144 Assumes ALL $(I virtual) methods and constructors are scriptable, but requires 2145 `@scriptable` to be present on final or static methods. This may change in the future. 2146 2147 Note that it has zero support for `@safe`, `pure`, `nothrow`, and other attributes 2148 at this time and will skip that use those. I may be able to loosen this in the 2149 future as well but I have no concrete plan to at this time. You can still mark 2150 them as `@scriptable` to call them from the script, but they can never be overridden 2151 by script code because it cannot verify those guarantees hold true. 2152 2153 Ditto on `const` and `immutable`. 2154 2155 Its behavior on overloads is currently undefined - it may keep only any random 2156 overload as the only one and do dynamic type conversions to cram data into it. 2157 This is likely to change in the future but for now try not to use this on classes 2158 with overloaded methods. 2159 2160 It also does not wrap member variables unless explicitly marked `@scriptable`; it 2161 is meant to communicate via methods. 2162 2163 History: 2164 Added April 25, 2020 2165 +/ 2166 static if(__traits(compiles, mixin(q{ () { static foreach(i; [1,2]) {} } }) )) 2167 mixin(q{ 2168 var subclassable(T)() if(is(T == class) || is(T == interface)) { 2169 import std.traits; 2170 2171 static final class ScriptableT : T, ScriptableSubclass { 2172 var _this; 2173 void setScriptVar(var v) { _this = v; } 2174 var getScriptVar() { return _this; } 2175 bool _next_devirtualized; 2176 2177 // @scriptable size_t _nativeHandle_() { return cast(size_t) cast(void*) this;} 2178 2179 static if(__traits(compiles, __traits(getOverloads, T, "__ctor"))) 2180 static foreach(ctor; __traits(getOverloads, T, "__ctor")) 2181 @scriptable this(Parameters!ctor p) { super(p); } 2182 2183 static foreach(memberName; __traits(allMembers, T)) { 2184 static if(memberName != "toHash") 2185 static foreach(overload; __traits(getOverloads, T, memberName)) 2186 static if(__traits(isVirtualMethod, overload)) 2187 static if(!__traits(isFinalFunction, overload)) 2188 static if(!__traits(isDeprecated, overload)) 2189 // note: overload behavior undefined 2190 static if(!(functionAttributes!(overload) & (FunctionAttribute.pure_ | FunctionAttribute.safe | FunctionAttribute.trusted | FunctionAttribute.nothrow_ | FunctionAttribute.const_ | FunctionAttribute.inout_))) 2191 static if(!hasRefParam!overload) 2192 static if(__traits(getFunctionVariadicStyle, overload) == "none") 2193 static if(__traits(identifier, overload) == memberName) // to filter out aliases 2194 mixin(q{ 2195 @scriptable 2196 override ReturnType!(overload) 2197 }~memberName~q{ 2198 (Parameters!(overload) p) 2199 { 2200 //import std.stdio; writeln("calling ", T.stringof, ".", memberName, " - ", methodOverriddenByScript(memberName), "/", _next_devirtualized, " on ", cast(size_t) cast(void*) this); 2201 if(_next_devirtualized || !methodOverriddenByScript(memberName)) { 2202 _next_devirtualized = false; 2203 return __traits(getMember, super, memberName)(p); 2204 } 2205 return _this[memberName].call(_this, p).get!(typeof(return)); 2206 } 2207 }); 2208 } 2209 2210 // I don't want to necessarily call a constructor but I need an object t use as the prototype 2211 // hence this faked one. hopefully the new operator will see void[] and assume it can have GC ptrs... 2212 static ScriptableT _allocate_(PrototypeObject newThis) { 2213 void[] store = new void[](__traits(classInstanceSize, ScriptableT)); 2214 store[] = typeid(ScriptableT).initializer[]; 2215 ScriptableT dummy = cast(ScriptableT) store.ptr; 2216 dummy._this = var(newThis); 2217 //import std.stdio; writeln("Allocating new ", cast(ulong) store.ptr); 2218 return dummy; 2219 } 2220 } 2221 2222 ScriptableT dummy = ScriptableT._allocate_(null); 2223 2224 var proto = wrapNativeObject!(ScriptableT, true)(dummy); 2225 2226 var f = var.emptyObject; 2227 f.prototype = proto; 2228 2229 return f; 2230 } 2231 2232 template hasRefParam(alias overload) { 2233 bool helper() { 2234 static if(is(typeof(overload) P == __parameters)) 2235 foreach(idx, p; P) 2236 foreach(thing; __traits(getParameterStorageClasses, overload, idx)) 2237 if(thing == "ref") 2238 return true; 2239 return false; 2240 } 2241 2242 enum hasRefParam = helper(); 2243 } 2244 2245 }); 2246 2247 /// Demonstrates tested capabilities of [subclassable] 2248 version(with_arsd_script) 2249 unittest { 2250 interface IFoo { 2251 string method(); 2252 int method2(); 2253 int args(int, int); 2254 } 2255 // note the static is just here because this 2256 // is written in a unittest; it shouldn't actually 2257 // be necessary under normal circumstances. 2258 static class Foo : IFoo { 2259 ulong handle() { return cast(ulong) cast(void*) this; } 2260 string method() { return "Foo"; } 2261 int method2() { return 10; } 2262 int args(int a, int b) { 2263 //import std.stdio; writeln(a, " + ", b, " + ", member_, " on ", cast(ulong) cast(void*) this); 2264 return member_+a+b; } 2265 2266 int member_; 2267 @property int member(int i) { return member_ = i; } 2268 @property int member() { return member_; } 2269 2270 @scriptable final int fm() { return 56; } 2271 } 2272 static class Bar : Foo { 2273 override string method() { return "Bar"; } 2274 string test1() { return test2(); } 2275 string test2() { return "test2"; } 2276 2277 int acceptFoo(Foo f) { 2278 return f.method2(); 2279 } 2280 } 2281 static class Baz : Bar { 2282 override int method2() { return 20; } 2283 } 2284 2285 static class WithCtor { 2286 // constructors work but are iffy with overloads.... 2287 this(int arg) { this.arg = arg; } 2288 @scriptable int arg; // this is accessible cuz it is @scriptable 2289 int getValue() { return arg; } 2290 } 2291 2292 var globals = var.emptyObject; 2293 globals.Foo = subclassable!Foo; 2294 globals.Bar = subclassable!Bar; 2295 globals.Baz = subclassable!Baz; 2296 globals.WithCtor = subclassable!WithCtor; 2297 2298 import arsd.script; 2299 2300 interpret(q{ 2301 // can instantiate D classes added via subclassable 2302 var foo = new Foo(); 2303 // and call its methods... 2304 assert(foo.method() == "Foo"); 2305 assert(foo.method2() == 10); 2306 2307 foo.member(55); 2308 2309 // proves the new operator actually creates new D 2310 // objects as well to avoid sharing instance state. 2311 var foo2 = new Foo(); 2312 assert(foo2.handle() != foo.handle()); 2313 2314 // passing arguments works 2315 assert(foo.args(2, 4) == 6 + 55); // (and sanity checks operator precedence) 2316 2317 var bar = new Bar(); 2318 assert(bar.method() == "Bar"); 2319 assert(bar.method2() == 10); 2320 2321 assert(bar.acceptFoo(new Foo()) == 10); 2322 assert(bar.acceptFoo(new Baz()) == 20); 2323 2324 // this final member is accessible because it was marked @scriptable 2325 assert(bar.fm() == 56); 2326 2327 assert(bar.test1() == "test2"); 2328 assert(bar.test2() == "test2"); 2329 2330 // the script can even subclass D classes! 2331 class Amazing : Bar { 2332 // and override its methods 2333 var inst = 99; 2334 function method() { 2335 return "Amazing"; 2336 } 2337 2338 // note: to access instance members or virtual call lookup you MUST use the `this` keyword 2339 // otherwise the function will be called with scope limited to this class itself (similar to javascript) 2340 function other() { 2341 // this.inst is needed to get the instance variable (otherwise it would only look for a static var) 2342 // and this.method triggers dynamic lookup there, so it will get children's overridden methods if there is one 2343 return this.inst ~ this.method(); 2344 } 2345 2346 function args(a, b) { 2347 // calling parent class method still possible 2348 return super.args(a*2, b*2); 2349 } 2350 2351 function test2() { 2352 return "script test"; 2353 } 2354 } 2355 2356 var amazing = new Amazing(); 2357 assert(amazing.method() == "Amazing"); 2358 assert(amazing.method2() == 10); // calls back to the parent class 2359 amazing.member(5); 2360 2361 assert(amazing.test1() == "script test"); // even the virtual method call from D goes into the script override 2362 2363 // this line I can paste down to interactively debug the test btw. 2364 //}, globals); repl!true(globals); interpret(q{ 2365 2366 assert(amazing.args(2, 4) == 12+5); 2367 2368 var wc = new WithCtor(5); // argument passed to constructor 2369 assert(wc.getValue() == 5); 2370 2371 // confirm the property read works too 2372 assert(wc.arg == 5); 2373 2374 // but property WRITING is currently not working though. 2375 2376 2377 class DoubleChild : Amazing { 2378 function method() { 2379 return "DoubleChild"; 2380 } 2381 } 2382 2383 // can also do a child of a child class 2384 var dc = new DoubleChild(); 2385 assert(dc.method() == "DoubleChild"); 2386 assert(dc.other() == "99DoubleChild"); // the `this.method` means it uses the replacement now 2387 assert(dc.method2() == 10); // back to the D grandparent 2388 assert(dc.args(2, 4) == 12); // but the args impl from above 2389 }, globals); 2390 2391 Foo foo = globals.foo.get!Foo; // get the native object back out 2392 assert(foo.member == 55); // and see mutation via properties proving object mutability 2393 assert(globals.foo.get!Bar is null); // cannot get the wrong class out of it 2394 assert(globals.foo.get!Object !is null); // but can do parent classes / interfaces 2395 assert(globals.foo.get!IFoo !is null); 2396 assert(globals.bar.get!Foo !is null); // the Bar can also be a Foo 2397 2398 Bar amazing = globals.amazing.get!Bar; // instance of the script's class is still accessible through parent D class or interface 2399 assert(amazing !is null); // object exists 2400 assert(amazing.method() == "Amazing"); // calls the override from the script 2401 assert(amazing.method2() == 10); // non-overridden function works as expected 2402 2403 IFoo iamazing = globals.amazing.get!IFoo; // and through just the interface works the same way 2404 assert(iamazing !is null); 2405 assert(iamazing.method() == "Amazing"); 2406 assert(iamazing.method2() == 10); 2407 2408 // and here ensuring the interface we got out can go back in and back out too 2409 var globals2 = var.emptyObject; 2410 globals2.opDispatch!"iamazing2" = iamazing; 2411 // though inside, it is treated as an opaque reference since nothing is @scriptable 2412 // so nothing to test there... 2413 // however getting it back out should work fine 2414 IFoo gotten = globals2.iamazing2.get!IFoo; 2415 assert(gotten !is null); 2416 assert(gotten is iamazing); 2417 //import std.stdio; writeln("wtf", iamazing.method()); 2418 2419 // now this seems obvious, but adding the interface can actually overwrite the dynamic 2420 // prototype.... i think 2421 //assert(gotten.method() == "Amazing"); 2422 2423 //https://issues.dlang.org/show_bug.cgi?id=22011 necessitated the static! 2424 static class CFoo : IFoo { 2425 @scriptable string method() { return "CFoo"; } 2426 int method2() { return 55; } 2427 int args(int, int) { return 6 + 5; } 2428 } 2429 2430 globals.opDispatch!"iamazing3" = new CFoo; // and also just testing a standard class assign 2431 IFoo input = new CFoo(); 2432 globals.opDispatch!"iamazing4" = input; // and interface 2433 interpret(q{ 2434 var a = iamazing3.method(); 2435 assert(a == "CFoo"); // works cuz the class is scriptable 2436 // but the following line is NOT available because the interface itself is not marked scriptable 2437 //assert(iamazing4.method() == "CFoo"); 2438 }, globals); 2439 2440 // but we can get it right back out 2441 IFoo got2 = globals.iamazing4.get!IFoo; 2442 assert(got2 is input); 2443 assert(got2.method() == "CFoo"); 2444 } 2445 2446 unittest { 2447 static struct Foo { 2448 struct Bar { 2449 int[] x; 2450 } 2451 2452 Bar bar; 2453 } 2454 2455 assert( 2456 var.fromJson(`{ 2457 "bar":{ "x": [1,2,3]} 2458 }`).get!Foo 2459 == 2460 Foo(Foo.Bar([1,2,3])) 2461 ); 2462 } 2463 2464 version(with_arsd_script) 2465 unittest { 2466 import arsd.script; 2467 static class Test { 2468 @scriptable int a; 2469 } 2470 2471 Test test = new Test; 2472 2473 test.a = 15; 2474 2475 var globals = var.emptyObject; 2476 globals.test = test; 2477 globals.Test = subclassable!Test; 2478 2479 interpret(q{ 2480 test.a = 4; 2481 var wtf = test.a += 5; 2482 assert(wtf == 9); 2483 assert(test.a == 9); 2484 var test2 = new Test; 2485 test2.a = 2; 2486 test2.a += 5; 2487 assert(test2.a == 7); 2488 }, globals); 2489 2490 assert(test.a == 9); 2491 assert(globals.test2.get!Test.a == 7); 2492 } 2493 2494 // just a base class we can reference when looking for native objects 2495 class WrappedNativeObject : PrototypeObject { 2496 TypeInfo wrappedType; 2497 abstract Object getObject(); 2498 } 2499 2500 template helper(alias T) { alias helper = T; } 2501 2502 /++ 2503 Wraps a class. If you are manually managing the memory, remember the jsvar may keep a reference to the object; don't free it! 2504 2505 To use this: `var a = wrapNativeObject(your_d_object);` OR `var a = your_d_object`; 2506 2507 By default, it will wrap all methods and members with a public or greater protection level. The second template parameter can filter things differently. FIXME implement this 2508 2509 History: 2510 This became the default after April 24, 2020. Previously, [var.opAssign] would [wrapOpaquely] instead. 2511 +/ 2512 WrappedNativeObject wrapNativeObject(Class, bool special = false)(Class obj) if(is(Class == class) || is(Class == interface)) { 2513 import std.meta; 2514 static class WrappedNativeObjectImpl : WrappedNativeObject { 2515 override Object getObject() { 2516 return cast(Object) obj; 2517 } 2518 2519 override bool isSpecial() { return special; } 2520 2521 static if(special) 2522 override WrappedNativeObject new_(PrototypeObject newThis) { 2523 return new WrappedNativeObjectImpl(obj._allocate_(newThis)); 2524 } 2525 2526 Class obj; 2527 2528 this(Class objIn) { 2529 this.obj = objIn; 2530 wrappedType = typeid(cast(Object) obj); 2531 // wrap the other methods 2532 // and wrap members as scriptable properties 2533 2534 foreach(memberName; __traits(allMembers, Class)) static if(!mixin(deprecationCheck()) && is(typeof(__traits(getMember, obj, memberName)) type)) { 2535 static if(is(type == function)) { 2536 auto os = new OverloadSet(); 2537 foreach(idx, overload; AliasSeq!(__traits(getOverloads, obj, memberName))) static if(.isScriptable!(__traits(getAttributes, overload))()) { 2538 var gen; 2539 gen._function = delegate (var vthis_, var[] vargs) { 2540 Parameters!(__traits(getOverloads, Class, memberName)[idx]) fargs; 2541 2542 2543 enum lol = static_foreach(fargs.length, 1, -1, 2544 `__traits(getOverloads, obj, memberName)[idx](`,``,` < vargs.length ? vargs[`,`].get!(typeof(fargs[`,`])) : ParamDefault!(__traits(getOverloads, Class, memberName)[idx], `,`)(),`,`)`); 2545 /* 2546 enum lol = static_foreach(fargs.length, 1, -1, 2547 `__traits(getOverloads, obj, memberName)[idx](`,``,` < vargs.length ? vargs[`,`].get!(typeof(fargs[`,`])) : 2548 typeof(fargs[`,`]).init,`,`)`); 2549 */ 2550 2551 // FIXME: what if there are multiple @scriptable overloads?! 2552 // FIXME: what about @properties? 2553 2554 static if(special) { 2555 Class obj; 2556 //if(vthis_.payloadType() != var.Type.Object) { import std.stdio; writeln("getwno on ", vthis_); } 2557 // the native object might be a step or two up the prototype 2558 // chain due to script subclasses, need to find it... 2559 while(vthis_ != null) { 2560 obj = vthis_.getWno!Class; 2561 if(obj !is null) 2562 break; 2563 vthis_ = vthis_.prototype; 2564 } 2565 2566 if(obj is null) throw new Exception("null native object"); 2567 } 2568 2569 static if(special) { 2570 obj._next_devirtualized = true; 2571 scope(exit) obj._next_devirtualized = false; 2572 } 2573 2574 var ret; 2575 2576 static if(!is(typeof(__traits(getOverloads, obj, memberName)[idx](fargs)) == void)) 2577 ret = mixin(lol); 2578 else 2579 mixin(lol ~ ";"); 2580 2581 return ret; 2582 }; 2583 2584 Parameters!(overload) fargs; 2585 // FIXME: if an argument type is a class, we need to actually look it up in the script context somehow 2586 var[] definition; 2587 foreach(arg; fargs) { 2588 definition ~= var(arg); 2589 } 2590 2591 //import std.stdio; writeln(Class.stringof, ".", memberName, " ", definition); 2592 os.addOverload(OverloadSet.Overload(definition, gen)); 2593 } 2594 2595 _properties[memberName] = var(os); 2596 } else { 2597 alias fuckThisLanguage = __traits(getOverloads, Class, memberName, true); 2598 static if(fuckThisLanguage.length) 2599 alias fuckYourPointlessDeprecations = fuckThisLanguage[0]; 2600 else 2601 alias fuckYourPointlessDeprecations = __traits(getMember, Class, memberName); 2602 static if(.isScriptable!(__traits(getAttributes, fuckYourPointlessDeprecations))()) 2603 // if it has a type but is not a function, it is prolly a member 2604 _properties[memberName] = new PropertyPrototype( 2605 () => var(__traits(getMember, obj, memberName)), 2606 (var v) { 2607 // read-only property hack 2608 static if(__traits(compiles, __traits(getMember, obj, memberName) = v.get!(type))) 2609 __traits(getMember, obj, memberName) = v.get!(type); 2610 }); 2611 } 2612 } 2613 } 2614 } 2615 2616 return new WrappedNativeObjectImpl(obj); 2617 } 2618 2619 import std.traits; 2620 class WrappedOpaque(T) : PrototypeObject if(isPointer!T || is(T == class) || is(T == interface)) { 2621 T wrapped; 2622 this(T t) { 2623 wrapped = t; 2624 } 2625 T wrapping() { 2626 return wrapped; 2627 } 2628 } 2629 class WrappedOpaque(T) : PrototypeObject if(!isPointer!T && !is(T == class) && !is(T == interface)) { 2630 T* wrapped; 2631 this(T t) { 2632 wrapped = new T; 2633 (cast() *wrapped) = t; 2634 } 2635 this(T* t) { 2636 wrapped = t; 2637 } 2638 T* wrapping() { 2639 return wrapped; 2640 } 2641 } 2642 2643 WrappedOpaque!Obj wrapOpaquely(Obj)(Obj obj) { 2644 static if(is(Obj == class)) { 2645 if(obj is null) 2646 return null; 2647 } 2648 return new WrappedOpaque!Obj(obj); 2649 } 2650 2651 /** 2652 Wraps an opaque struct pointer in a module with ufcs functions 2653 */ 2654 WrappedNativeObject wrapUfcs(alias Module, Type)(Type obj) { 2655 import std.meta; 2656 return new class WrappedNativeObject { 2657 override Object getObject() { 2658 return null; // not actually an object! but close to 2659 } 2660 2661 this() { 2662 wrappedType = typeid(Type); 2663 // wrap the other methods 2664 // and wrap members as scriptable properties 2665 2666 foreach(memberName; __traits(allMembers, Module)) static if(is(typeof(__traits(getMember, Module, memberName)) type)) { 2667 static if(is(type == function)) { 2668 foreach(idx, overload; AliasSeq!(__traits(getOverloads, Module, memberName))) static if(.isScriptable!(__traits(getAttributes, __traits(getOverloads, Module, memberName)[idx]))()) { // lol i have to do the second getOverload to stop the compiler from deprecating 2669 auto helper = &__traits(getOverloads, Module, memberName)[idx]; 2670 static if(Parameters!helper.length >= 1 && is(Parameters!helper[0] == Type)) { 2671 // this staticMap is a bit of a hack so it can handle `in float`... liable to break with others, i'm sure 2672 _properties[memberName] = (staticMap!(Unqual, Parameters!helper[1 .. $]) args) { 2673 return __traits(getOverloads, Module, memberName)[idx](obj, args); 2674 }; 2675 } 2676 } 2677 } 2678 } 2679 } 2680 }; 2681 } 2682 2683 bool isCallableJsvarObject(var possibility) { 2684 if(possibility.payloadType == var.Type.Function) 2685 return true; 2686 if(possibility.payloadType == var.Type.Object) { 2687 if(possibility._payload._object is null) 2688 return false; 2689 2690 if(var* test = possibility._payload._object._peekMember("opCall", true)) { 2691 if(isCallableJsvarObject(*test)) 2692 return true; 2693 } 2694 2695 } 2696 return false; 2697 } 2698 2699 2700 bool isScriptable(attributes...)() { 2701 bool nonConstConditionForWorkingAroundASpuriousDmdWarning = true; 2702 foreach(attribute; attributes) { 2703 static if(is(typeof(attribute) == string)) { 2704 static if(attribute == scriptable) { 2705 if(nonConstConditionForWorkingAroundASpuriousDmdWarning) 2706 return true; 2707 } 2708 } 2709 } 2710 return false; 2711 } 2712 2713 bool isScriptableOpaque(T)() { 2714 static if(is(typeof(T.isOpaqueStruct) == bool)) 2715 return T.isOpaqueStruct == true; 2716 else 2717 return false; 2718 } 2719 2720 int typeCompatibilityScore(var arg, var type) { 2721 int thisScore = 0; 2722 2723 if(type.payloadType == var.Type.Object && type._payload._object is null) { 2724 thisScore += 1; // generic match 2725 return thisScore; 2726 } 2727 if(arg.payloadType == var.Type.Integral && type.payloadType == var.Type.Floating) { 2728 thisScore += 2; // match with implicit conversion 2729 // FIXME: want to support implicit from whatever to bool? 2730 } else if(arg.payloadType != type.payloadType) { 2731 thisScore = 0; 2732 return thisScore; 2733 } else { 2734 // exact type category match 2735 if(type.payloadType == var.Type.Array) { 2736 // arrays not really supported here.... 2737 // so just like if both are arrays i'll take 2738 // it as a bare minimum but i don't love it otherwise 2739 if(arg.payloadType == var.Type.Array) 2740 thisScore = 1; 2741 else 2742 thisScore = 0; 2743 return thisScore; 2744 } else if(type.payloadType == var.Type.Object) { 2745 // objects are the interesting one... 2746 // want to see if it matches by seeing if the 2747 // given type is identical or its prototype is one of the given type's prototype chain 2748 2749 int depth = 0; 2750 PrototypeObject pt = type._payload._object; 2751 while(pt !is null) { 2752 depth++; 2753 pt = pt.prototype; 2754 } 2755 2756 if(type._payload._object is arg._payload._object) 2757 thisScore += 2 + depth; 2758 else { 2759 if(arg._payload._object is null) 2760 return 0; // null sucks. 2761 2762 auto test = type._payload._object.prototype; 2763 // generic object compared against generic object matches 2764 if(test is null && type._payload._object.prototype is null) 2765 thisScore += 1; 2766 else { 2767 pt = arg._payload._object; 2768 while(pt !is null) { 2769 if(pt is test) { 2770 thisScore += 1 + depth; 2771 break; 2772 } 2773 pt = pt.prototype; 2774 } 2775 } 2776 } 2777 } else { 2778 thisScore += 3; // exact match without implicit conversion 2779 } 2780 } 2781 2782 return thisScore; 2783 } 2784 2785 /++ 2786 Does dynamic dispatch to overloads in a jsvar function set. 2787 2788 History: 2789 Added September 1, 2020. 2790 +/ 2791 class OverloadSet : PrototypeObject { 2792 this() { 2793 _properties["opCall"] = &opCall; 2794 _properties["apply"] = &apply; 2795 } 2796 2797 /// 2798 void addIndividualOverload(alias f)() { 2799 var func = &f; 2800 var[] argTypes; 2801 static if(is(typeof(f) Params == __parameters)) 2802 foreach(param; Params) 2803 argTypes ~= var(param.init); 2804 //import std.stdio; writeln("registered ", argTypes); 2805 overloads ~= Overload(argTypes, func); 2806 } 2807 2808 /// 2809 static if(__traits(compiles, mixin(q{ () { static foreach(i; [1,2]) {} } }) )) 2810 mixin(q{ 2811 void addOverloadsOf(alias what)() { 2812 foreach(alias f; __traits(getOverloads, __traits(parent, what), __traits(identifier, what))) 2813 addIndividualOverload!f; 2814 } 2815 }); 2816 2817 static struct Overload { 2818 // I don't even store the arity of a function object 2819 // so argTypes is the nest best thing. 2820 var[] argTypes; 2821 var func; 2822 } 2823 Overload[] overloads; 2824 2825 private bool exactMatch(var[] a, var[] b) { 2826 if(a.length != b.length) 2827 return false; 2828 foreach(i; 0 .. a.length) { 2829 if(a[i] !is b[i]) 2830 return false; 2831 } 2832 return true; 2833 } 2834 2835 void addOverload(Overload o) { 2836 foreach(ref ov; overloads) 2837 if(exactMatch(ov.argTypes, o.argTypes)) { 2838 ov.func = o.func; 2839 return; 2840 } 2841 overloads ~= o; 2842 } 2843 2844 /* 2845 I might have to add Object, Exception, and others to jsvar to represent types. 2846 maybe even int and such. 2847 2848 An object should probably have a secret property that gives its name... 2849 */ 2850 var apply(var this_, var[] arguments) { 2851 return opCall(arguments[0], arguments[1].get!(var[])); 2852 } 2853 2854 var opCall(var this_, var[] arguments) { 2855 // remember script.d supports default args too. 2856 int bestScore = int.min; 2857 Overload bestMatch; 2858 2859 if(overloads.length == 0) { 2860 return var(null); 2861 } 2862 2863 foreach(overload; overloads) { 2864 if(overload.argTypes.length == 0) { 2865 if(arguments.length == 0) { 2866 bestScore = 0; 2867 bestMatch = overload; 2868 break; 2869 } 2870 if(bestScore < 0) { 2871 bestScore = 0; 2872 bestMatch = overload; 2873 continue; 2874 } 2875 } 2876 2877 int thisScore = 0; 2878 foreach(idx, arg; arguments) { 2879 if(idx >= overload.argTypes.length) { 2880 thisScore = 0; 2881 break; 2882 } 2883 2884 // now if it is an object, if we can match, add score based on how derived the specified type is. 2885 // if it is the very same object, that's the best match possible. (prototype chain length + 1. and the one base point for matching at all.) 2886 // then if not, if the arg given can implicitly convert to the arg specified, give points based on how many prototypes the arg specified has. (plus one base point for matching at all) 2887 2888 // otherwise just give one point. 2889 2890 auto s = typeCompatibilityScore(arg, overload.argTypes[idx]); 2891 if(s == 0) { 2892 thisScore = 0; 2893 break; 2894 } 2895 thisScore += s; 2896 } 2897 2898 if(thisScore > 0 && thisScore > bestScore) { 2899 bestScore = thisScore; 2900 bestMatch = overload; 2901 } 2902 } 2903 2904 if(bestScore < 0) 2905 throw new Exception("no matching overload found " ~ to!string(arguments) ~ " " ~ to!string(overloads)); 2906 2907 2908 return bestMatch.func.apply(this_, arguments); 2909 } 2910 } 2911 2912 unittest { 2913 struct A { 2914 static: 2915 string foo(var arg) { return "generic"; } 2916 string foo(string s) { return "string"; } 2917 string foo(int i) { return "int"; } 2918 string foo(float i) { return "float"; } 2919 } 2920 2921 auto os = new OverloadSet(); 2922 os.addOverloadsOf!(A.foo); 2923 var g = var.emptyObject; 2924 g.foo = os; 2925 2926 //g.foo()(); 2927 assert(g.foo()("for me") == "string"); 2928 //g.foo()("for me", "lol"); 2929 assert(g.foo()(1) == "int"); 2930 assert(g.foo()(5.4) == "float"); 2931 assert(g.foo()(new Object) == "generic"); 2932 } 2933 2934 unittest { 2935 import std.typecons; 2936 2937 static struct A { 2938 int a; 2939 Nullable!int b; 2940 } 2941 2942 var a = A(3); 2943 assert(a.a == 3); 2944 assert(a.b == null); 2945 a = A(5, nullable(4)); 2946 assert(a.a == 5); 2947 assert(a.b == 4); 2948 } 2949 2950 bool appearsNumeric(string n) { 2951 if(n.length == 0) 2952 return false; 2953 foreach(c; n) { 2954 if(c < '0' || c > '9') 2955 return false; 2956 } 2957 return true; 2958 } 2959 2960 2961 /// Wraps a struct by reference. The pointer is stored - be sure the struct doesn't get freed or go out of scope! 2962 /// 2963 /// BTW: structs by value can be put in vars with var.opAssign and var.get. It will generate an object with the same fields. The difference is changes to the jsvar won't be reflected in the original struct and native methods won't work if you do it that way. 2964 WrappedNativeObject wrapNativeObject(Struct)(Struct* obj) if(is(Struct == struct)) { 2965 return null; // FIXME 2966 } 2967 2968 /+ 2969 _IDX_ 2970 2971 static_foreach(T.length, q{ 2972 mixin(q{ 2973 void 2974 } ~ __traits(identifier, T[_IDX_]) ~ q{ 2975 2976 } 2977 }); 2978 +/ 2979 2980 private 2981 string static_foreach(size_t length, int t_start_idx, int t_end_idx, string[] t...) pure { 2982 assert(__ctfe); 2983 int slen; 2984 int tlen; 2985 foreach(idx, i; t[0 .. t_start_idx]) 2986 slen += i.length; 2987 foreach(idx, i; t[t_start_idx .. $ + t_end_idx]) { 2988 if(idx) 2989 tlen += 5; 2990 tlen += i.length; 2991 } 2992 foreach(idx, i; t[$ + t_end_idx .. $]) 2993 slen += i.length; 2994 2995 char[] a = new char[](tlen * length + slen); 2996 2997 int loc; 2998 char[5] stringCounter; 2999 stringCounter[] = "00000"[]; 3000 3001 foreach(part; t[0 .. t_start_idx]) { 3002 a[loc .. loc + part.length] = part[]; 3003 loc += part.length; 3004 } 3005 3006 foreach(i; 0 .. length) { 3007 foreach(idx, part; t[t_start_idx .. $ + t_end_idx]) { 3008 if(idx) { 3009 a[loc .. loc + stringCounter.length] = stringCounter[]; 3010 loc += stringCounter.length; 3011 } 3012 a[loc .. loc + part.length] = part[]; 3013 loc += part.length; 3014 } 3015 3016 auto pos = stringCounter.length; 3017 while(pos) { 3018 pos--; 3019 if(stringCounter[pos] == '9') { 3020 stringCounter[pos] = '0'; 3021 } else { 3022 stringCounter[pos] ++; 3023 break; 3024 } 3025 } 3026 while(pos) 3027 stringCounter[--pos] = ' '; 3028 } 3029 3030 foreach(part; t[$ + t_end_idx .. $]) { 3031 a[loc .. loc + part.length] = part[]; 3032 loc += part.length; 3033 } 3034 3035 return a; 3036 } 3037 3038 private string deprecationCheck() { 3039 //https://issues.dlang.org/show_bug.cgi?id=22011 this hack sucks 3040 static if(__VERSION__ >= 2077) 3041 return "__traits(isDeprecated, __traits(getMember, obj, memberName == `this` ? `__ctor` : memberName))"; 3042 else 3043 return "false"; 3044 } 3045 3046 // LOL this can't work because function pointers drop the default :( 3047 private 3048 auto ParamDefault(alias T, size_t idx)() { 3049 static if(is(typeof(T) Params == __parameters)) { 3050 auto fn(Params[idx .. idx + 1] _args__) { // if i used plain `args` and one of the args in the list was also called `args`, this fails to compile and that error will be dropped by opDispatch. LOL. 3051 return _args__[0]; 3052 } 3053 static if(__traits(compiles, fn())) { 3054 return fn(); 3055 } else { 3056 return Params[idx].init; 3057 } 3058 } else static assert(0); 3059 }