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