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 == "^" || 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 == "^" || 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 == "^" || 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 == "^" || 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 == "^" || 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 import std.typecons; 684 static if(__traits(compiles, this = t.toArsdJsvar())) { 685 static if(__traits(compiles, t is null)) { 686 if(t is null) 687 this = null; 688 else 689 this = t.toArsdJsvar(); 690 } else 691 this = t.toArsdJsvar(); 692 } else static if(is(T : PrototypeObject)) { 693 // support direct assignment of pre-made implementation objects 694 // so prewrapped stuff can be easily passed. 695 this._type = Type.Object; 696 this._payload._object = t; 697 } else static if(is(T == Nullable!N, N)) { 698 if(t.isNull()) 699 this = null; 700 else 701 this = t.get(); 702 } else static if(is(T == enum)) { 703 this._type = Type.String; 704 this._payload._string = to!string(t); 705 } else static if(isFloatingPoint!T) { 706 this._type = Type.Floating; 707 this._payload._floating = t; 708 } else static if(isIntegral!T) { 709 this._type = Type.Integral; 710 this._payload._integral = t; 711 } else static if(isCallable!T) { 712 this._type = Type.Function; 713 static if(is(T == typeof(this._payload._function))) { 714 this._payload._function = t; 715 } else 716 this._payload._function = delegate var(var _this, var[] args) { 717 var ret; 718 719 ParameterTypeTuple!T fargs; 720 721 // default args? nope they can't work cuz it is assigning a function pointer by here. alas. 722 enum lol = static_foreach(fargs.length, 1, -1, 723 `t(`,``,` < args.length ? args[`,`].get!(typeof(fargs[`,`])) : typeof(fargs[`,`]).init,`,`)`); 724 //`t(`,``,` < args.length ? args[`,`].get!(typeof(fargs[`,`])) : ParamDefault!(T, `,`)(),`,`)`); 725 /+ 726 foreach(idx, a; fargs) { 727 if(idx == args.length) 728 break; 729 cast(Unqual!(typeof(a))) fargs[idx] = args[idx].get!(typeof(a)); 730 } 731 +/ 732 733 static if(is(ReturnType!t == void)) { 734 //t(fargs); 735 mixin(lol ~ ";"); 736 } else { 737 //ret = t(fargs); 738 ret = mixin(lol); 739 } 740 741 return ret; 742 }; 743 } else static if(isSomeString!T) { 744 this._type = Type.String; 745 this._payload._string = to!string(t); 746 } else static if(is(T == class) || is(T == interface)) { 747 if(t !is null && (cast(Object) t) is null) 748 throw new Exception("Unsupported class or interface"); 749 this._type = Type.Object; 750 this._payload._object = t is null ? null : wrapNativeObject(t); 751 } else static if(.isScriptableOpaque!T) { 752 // auto-wrap other classes with reference semantics 753 this._type = Type.Object; 754 this._payload._object = wrapOpaquely(t); 755 } else static if(is(T == struct) || isAssociativeArray!T) { 756 // copy structs and assoc arrays by value into a var object 757 this._type = Type.Object; 758 auto obj = new PrototypeObject(); 759 this._payload._object = obj; 760 761 static if(is(T == struct)) 762 foreach(member; __traits(allMembers, T)) { 763 static if(__traits(compiles, __traits(getMember, t, member))) { 764 static if(is(typeof(__traits(getMember, t, member)) == function)) { 765 // skipping these because the delegate we get isn't going to work anyway; the object may be dead and certainly won't be updated 766 //this[member] = &__traits(getMember, proxyObject, member); 767 768 // but for simple toString, I'll allow it by recreating the object on demand 769 // and then calling the original function. (I might be able to do that for more but 770 // idk, just doing simple thing first) 771 static if(member == "toString" && is(typeof(&__traits(getMember, t, member)) == string delegate())) { 772 this[member]._function = delegate(var _this, var[] args) { 773 auto val = _this.get!T; 774 return var(val.toString()); 775 }; 776 } 777 } else static if(is(typeof(__traits(getMember, t, member)))) { 778 static if(!is(typeof(__traits(getMember, t, member)) == void)) 779 this[member] = __traits(getMember, t, member); 780 } 781 } 782 } else { 783 // assoc array 784 foreach(l, v; t) { 785 this[var(l)] = var(v); 786 } 787 } 788 } else static if(isArray!T) { 789 this._type = Type.Array; 790 var[] arr; 791 arr.length = t.length; 792 static if(!is(T == void[])) // we can't append a void array but it is nice to support x = []; 793 foreach(i, item; t) 794 arr[i] = var(item); 795 this._payload._array = arr; 796 } else static if(is(T == bool)) { 797 this._type = Type.Boolean; 798 this._payload._boolean = t; 799 } else static if(isSomeChar!T) { 800 this._type = Type.String; 801 this._payload._string = ""; 802 import std.utf; 803 char[4] ugh; 804 auto size = encode(ugh, t); 805 this._payload._string = ugh[0..size].idup; 806 }// else static assert(0, "unsupported type"); 807 808 return this; 809 } 810 811 public size_t opDollar() { 812 return this.length().get!size_t; 813 } 814 815 public var opOpAssign(string op, T)(T t) { 816 if(payloadType() == Type.Object) { 817 if(auto pt = cast(PropertyPrototype) this._payload._object) { 818 auto propValue = pt.get; 819 auto result = propValue.opOpAssign!(op)(t); 820 821 pt.set(result); 822 823 return result; 824 } else if(this._payload._object !is null) { 825 var* operator = this._payload._object._peekMember("opOpAssign", true); 826 if(operator !is null && operator._type == Type.Function) 827 return operator.call(this, op, t); 828 } 829 } 830 831 return _op!(this, this, op, T)(t); 832 } 833 834 public var opUnary(string op : "-")() { 835 static assert(op == "-"); 836 final switch(payloadType()) { 837 case Type.Object: 838 case Type.Array: 839 case Type.Boolean: 840 case Type.String: 841 case Type.Function: 842 assert(0); // FIXME 843 //break; 844 case Type.Integral: 845 return var(-this.get!long); 846 case Type.Floating: 847 return var(-this.get!double); 848 } 849 } 850 851 public var opBinary(string op, T)(T t) { 852 var n; 853 if(payloadType() == Type.Object) { 854 if(this._payload._object is null) 855 return var(null); 856 var* operator = this._payload._object._peekMember("opBinary", true); 857 if(operator !is null && operator._type == Type.Function) { 858 return operator.call(this, op, t); 859 } 860 } 861 return _op!(n, this, op, T)(t); 862 } 863 864 public var opBinaryRight(string op, T)(T s) { 865 return var(s).opBinary!op(this); 866 } 867 868 // this in foo 869 public var* opBinary(string op : "in", T)(T s) { 870 var rhs = var(s); 871 return rhs.opBinaryRight!"in"(this); 872 } 873 874 // foo in this 875 public var* opBinaryRight(string op : "in", T)(T s) { 876 // this needs to be an object 877 if(this._object is null) 878 return null; 879 return var(s).get!string in this._object._properties; 880 } 881 882 public var apply(var _this, var[] args) { 883 if(this.payloadType() == Type.Function) { 884 if(this._payload._function is null) { 885 if(jsvar_throw) 886 throw new DynamicTypeException(this, Type.Function); 887 else 888 return var(null); 889 } 890 return this._payload._function(_this, args); 891 } else if(this.payloadType() == Type.Object) { 892 if(this._payload._object is null) { 893 if(jsvar_throw) 894 throw new DynamicTypeException(this, Type.Function); 895 else 896 return var(null); 897 } 898 var* operator = this._payload._object._peekMember("opCall", true); 899 if(operator !is null && operator._type == Type.Function) 900 return operator.apply(_this, args); 901 } 902 903 if(this.payloadType() == Type.Integral || this.payloadType() == Type.Floating) { 904 if(args.length) 905 return var(this.get!double * args[0].get!double); 906 else 907 return this; 908 } else if(jsvar_throw) { 909 throw new DynamicTypeException(this, Type.Function); 910 } 911 912 //return this; 913 return var(null); 914 } 915 916 public var call(T...)(var _this, T t) { 917 var[] args; 918 foreach(a; t) { 919 args ~= var(a); 920 } 921 return this.apply(_this, args); 922 } 923 924 public var opCall(T...)(T t) { 925 return this.call(this, t); 926 } 927 928 /* 929 public var applyWithMagicLocals(var _this, var[] args, var[string] magicLocals) { 930 931 } 932 */ 933 934 public string toString() { 935 return this.get!string; 936 } 937 938 public T getWno(T)() { 939 if(payloadType == Type.Object) { 940 if(auto wno = cast(WrappedNativeObject) this._payload._object) { 941 auto no = cast(T) wno.getObject(); 942 if(no !is null) 943 return no; 944 } 945 } 946 return null; 947 } 948 949 /++ 950 Gets the var converted to type `T` as best it can. `T` may be constructed 951 from `T.fromJsVar`, or through type conversions (coercing as needed). If 952 `T` happens to be a struct, it will automatically introspect to convert 953 the var object member-by-member. 954 955 History: 956 On April 21, 2020, I changed the behavior of 957 958 --- 959 var a = null; 960 string b = a.get!string; 961 --- 962 963 Previously, `b == "null"`, which would print the word 964 when writeln'd. Now, `b is null`, which prints the empty string, 965 which is a bit less user-friendly, but more consistent with 966 converting to/from D strings in general. 967 968 If you are printing, you can check `a.get!string is null` and print 969 null at that point if you like. 970 971 I also wrote the first draft of this documentation at that time, 972 even though the function has been public since the beginning. 973 974 On January 1, 2021, I changed `get!some_struct` to call properties 975 on the var, if a member looks like a function or object, to try to 976 get plain-old-data out. Since the functions are only ever put there 977 by you or by you allowing script, I don't feel too bad about it, but 978 it still might not be ideal for all circumstances, so idk if I'll leave 979 it this way or not. 980 981 One thing it helps for though is taking scripted subclasses back into D 982 structs, since the parent class thing is likely to be virtual properties. 983 And having that just work in argument lists is really cool... 984 985 Search function for the comment "property getter support" to see the impl. 986 +/ 987 public T get(T)() if(!is(T == void)) { 988 import std.typecons; 989 static if(is(T == var)) { 990 return this; 991 } else static if(__traits(compiles, T.fromJsVar(var.init))) { 992 return T.fromJsVar(this); 993 } else static if(__traits(compiles, T(this))) { 994 return T(this); 995 } else static if(__traits(compiles, new T(this))) { 996 return new T(this); 997 } else static if(is(T == Nullable!N, N)) { 998 if(payloadType == Type.Object && this._payload._object is null) 999 return T.init; 1000 else 1001 return nullable(this.get!N); 1002 } else 1003 final switch(payloadType) { 1004 case Type.Boolean: 1005 static if(is(T == bool)) 1006 return this._payload._boolean; 1007 else static if(isFloatingPoint!T || isIntegral!T) 1008 return cast(T) (this._payload._boolean ? 1 : 0); // the cast is for enums, I don't like this so FIXME 1009 else static if(isSomeString!T) 1010 return to!T(this._payload._boolean ? "true" : "false"); 1011 else 1012 return T.init; 1013 case Type.Object: 1014 static if(isAssociativeArray!T) { 1015 T ret; 1016 if(this._payload._object !is null) 1017 foreach(k, v; this._payload._object._properties) 1018 ret[to!(KeyType!T)(k)] = v.get!(ValueType!T); 1019 1020 return ret; 1021 } else static if(is(T : PrototypeObject)) { 1022 // they are requesting an implementation object, just give it to them 1023 return cast(T) this._payload._object; 1024 } else static if(isScriptableOpaque!(Unqual!T)) { 1025 if(auto wno = cast(WrappedOpaque!(Unqual!T)) this._payload._object) { 1026 return wno.wrapping(); 1027 } 1028 static if(is(T == R*, R)) 1029 if(auto wno = cast(WrappedOpaque!(Unqual!(R))) this._payload._object) { 1030 return wno.wrapping(); 1031 } 1032 throw new DynamicTypeException(this, Type.Object); // FIXME: could be better 1033 } else static if(is(T == struct) || is(T == class) || is(T == interface)) { 1034 // first, we'll try to give them back the native object we have, if we have one 1035 static if(is(T : Object) || is(T == interface)) { 1036 auto t = this; 1037 // need to walk up the prototype chain too 1038 while(t != null) { 1039 assert(t.payloadType == Type.Object); 1040 if(auto wno = cast(WrappedNativeObject) t._payload._object) { 1041 auto no = cast(T) wno.getObject(); 1042 1043 if(no !is null) { 1044 auto sc = cast(ScriptableSubclass) no; 1045 if(sc !is null) { 1046 sc.setScriptVar(this); 1047 } 1048 1049 return no; 1050 } 1051 } 1052 t = t.prototype; 1053 } 1054 1055 // FIXME: this is kinda weird. 1056 return null; 1057 } else { 1058 1059 // failing that, generic struct or class getting: try to fill in the fields by name 1060 T t; 1061 bool initialized = true; 1062 static if(is(T == class)) { 1063 static if(__traits(compiles, new T())) { 1064 t = new T(); 1065 } else { 1066 initialized = false; 1067 } 1068 } 1069 1070 1071 if(initialized) 1072 foreach(i, a; t.tupleof) { 1073 var possibility = this[t.tupleof[i].stringof[2..$]]; 1074 // FIXME: so there is the possibility of getting some data getting all caught 1075 // up in a script function doing weird things. If I can prevent that, I'd like to... 1076 // but it is also really useful for this to work for some scenarios... 1077 static if(!is(typeof(a) == return)) // if it is callable, just assign the func ref 1078 if(isCallableJsvarObject(possibility)) 1079 possibility = possibility.apply(this, null); // crude approximation of property getter support 1080 cast(Unqual!(typeof((a)))) t.tupleof[i] = possibility.get!(typeof(a)); 1081 } 1082 1083 return t; 1084 } 1085 } else static if(isSomeString!T) { 1086 if(this._object !is null) 1087 return to!T(this._object.toString()); 1088 return null;// "null"; 1089 } else 1090 return T.init; 1091 case Type.Integral: 1092 static if(isFloatingPoint!T || isIntegral!T) 1093 return to!T(this._payload._integral); 1094 else static if(isSomeString!T) 1095 return to!T(this._payload._integral); 1096 else 1097 return T.init; 1098 case Type.Floating: 1099 static if(isFloatingPoint!T || isIntegral!T) 1100 return to!T(this._payload._floating); 1101 else static if(isSomeString!T) 1102 return to!T(this._payload._floating); 1103 else 1104 return T.init; 1105 case Type.String: 1106 static if(__traits(compiles, to!T(this._payload._string))) { 1107 try { 1108 return to!T(this._payload._string); 1109 } catch (Exception e) { return T.init; } 1110 } else 1111 return T.init; 1112 case Type.Array: 1113 import std.range; 1114 auto pl = this._payload._array; 1115 static if(isSomeString!T) { 1116 return to!T(pl); 1117 } else static if(is(T == E[N], E, size_t N)) { 1118 T ret; 1119 foreach(i; 0 .. N) { 1120 if(i >= pl.length) 1121 break; 1122 ret[i] = pl[i].get!E; 1123 } 1124 return ret; 1125 } else static if(is(T == E[], E)) { 1126 T ret; 1127 static if(is(ElementType!T == void)) { 1128 static assert(0, "try wrapping the function to get rid of void[] args"); 1129 //alias getType = ubyte; 1130 } else 1131 alias getType = ElementType!T; 1132 foreach(item; pl) 1133 ret ~= item.get!(getType); 1134 return ret; 1135 } else 1136 return T.init; 1137 // is it sane to translate anything else? 1138 case Type.Function: 1139 static if(isSomeString!T) { 1140 return to!T("<function>"); 1141 } else static if(isDelegate!T) { 1142 // making a local copy because otherwise the delegate might refer to a struct on the stack and get corrupted later or something 1143 auto func = this._payload._function; 1144 1145 // the static helper lets me pass specific variables to the closure 1146 static T helper(typeof(func) func) { 1147 return delegate ReturnType!T (ParameterTypeTuple!T args) { 1148 var[] arr; 1149 foreach(arg; args) 1150 arr ~= var(arg); 1151 var ret = func(var(null), arr); 1152 static if(is(ReturnType!T == void)) 1153 return; 1154 else 1155 return ret.get!(ReturnType!T); 1156 }; 1157 } 1158 1159 return helper(func); 1160 1161 } else 1162 return T.init; 1163 // FIXME: we just might be able to do better for both of these 1164 //break; 1165 } 1166 } 1167 1168 public T get(T)() if(is(T == void)) {} 1169 1170 public T nullCoalesce(T)(T t) { 1171 if(_type == Type.Object && _payload._object is null) 1172 return t; 1173 return this.get!T; 1174 } 1175 1176 public double opCmp(T)(T t) { 1177 auto f = this.get!double; 1178 static if(is(T == var)) 1179 auto r = t.get!double; 1180 else 1181 auto r = t; 1182 return f - r; 1183 } 1184 1185 public bool opEquals(T)(T t) { 1186 return this.opEquals(var(t)); 1187 } 1188 1189 public bool opEquals(T:var)(T t) const { 1190 // int and float can implicitly convert 1191 if(this._type == Type.Integral && t._type == Type.Floating) 1192 return _payload._integral == t._payload._floating; 1193 if(t._type == Type.Integral && this._type == Type.Floating) 1194 return t._payload._integral == this._payload._floating; 1195 1196 // but the others are kinda strict 1197 // FIXME: should this be == or === ? 1198 1199 if(this._type != t._type) 1200 return false; 1201 final switch(this._type) { 1202 case Type.Object: 1203 return _payload._object is t._payload._object; 1204 case Type.Integral: 1205 return _payload._integral == t._payload._integral; 1206 case Type.Boolean: 1207 return _payload._boolean == t._payload._boolean; 1208 case Type.Floating: 1209 return _payload._floating == t._payload._floating; // FIXME: approxEquals? 1210 case Type.String: 1211 return _payload._string == t._payload._string; 1212 case Type.Function: 1213 return _payload._function is t._payload._function; 1214 case Type.Array: 1215 return _payload._array == t._payload._array; 1216 } 1217 assert(0); 1218 } 1219 1220 public enum Type { 1221 Object, Array, Integral, Floating, String, Function, Boolean 1222 } 1223 1224 public Type payloadType() { 1225 return _type; 1226 } 1227 1228 private Type _type; 1229 1230 private union Payload { 1231 PrototypeObject _object; 1232 var[] _array; 1233 long _integral; 1234 double _floating; 1235 string _string; 1236 bool _boolean; 1237 var delegate(var _this, var[] args) _function; 1238 } 1239 1240 package VarMetadata _metadata; 1241 1242 public void _function(var delegate(var, var[]) f) { 1243 this._payload._function = f; 1244 this._type = Type.Function; 1245 } 1246 1247 /* 1248 public void _function(var function(var, var[]) f) { 1249 var delegate(var, var[]) dg; 1250 dg.ptr = null; 1251 dg.funcptr = f; 1252 this._function = dg; 1253 } 1254 */ 1255 1256 public void _object(PrototypeObject obj) { 1257 this._type = Type.Object; 1258 this._payload._object = obj; 1259 } 1260 1261 public PrototypeObject _object() { 1262 if(this._type == Type.Object) 1263 return this._payload._object; 1264 return null; 1265 } 1266 1267 package Payload _payload; 1268 1269 private void _requireType(Type t, string file = __FILE__, size_t line = __LINE__){ 1270 if(this.payloadType() != t) 1271 throw new DynamicTypeException(this, t, file, line); 1272 } 1273 1274 public var opSlice(var e1, var e2) { 1275 return this.opSlice(e1.get!ptrdiff_t, e2.get!ptrdiff_t); 1276 } 1277 1278 public var opSlice(ptrdiff_t e1, ptrdiff_t e2) { 1279 if(this.payloadType() == Type.Array) { 1280 if(e1 > _payload._array.length) 1281 e1 = _payload._array.length; 1282 if(e2 > _payload._array.length) 1283 e2 = _payload._array.length; 1284 return var(_payload._array[e1 .. e2]); 1285 } 1286 if(this.payloadType() == Type.String) { 1287 if(e1 > _payload._string.length) 1288 e1 = _payload._string.length; 1289 if(e2 > _payload._string.length) 1290 e2 = _payload._string.length; 1291 return var(_payload._string[e1 .. e2]); 1292 } 1293 if(this.payloadType() == Type.Object) { 1294 var operator = this["opSlice"]; 1295 if(operator._type == Type.Function) { 1296 return operator.call(this, e1, e2); 1297 } 1298 } 1299 1300 // might be worth throwing here too 1301 return var(null); 1302 } 1303 1304 /// Forwards to [opIndex] 1305 public @property ref var opDispatch(string name, string file = __FILE__, size_t line = __LINE__)() { 1306 return this[name]; 1307 } 1308 1309 /// Forwards to [opIndexAssign] 1310 public @property ref var opDispatch(string name, string file = __FILE__, size_t line = __LINE__, T)(T r) { 1311 return this.opIndexAssign!T(r, name); 1312 } 1313 1314 /// Looks up a sub-property of the object 1315 public ref var opIndex(var name, string file = __FILE__, size_t line = __LINE__) { 1316 return opIndex(name.get!string, file, line); 1317 } 1318 1319 /// Sets a sub-property of the object 1320 public ref var opIndexAssign(T)(T t, var name, string file = __FILE__, size_t line = __LINE__) { 1321 return opIndexAssign(t, name.get!string, file, line); 1322 } 1323 1324 public ref var opIndex(string name, string file = __FILE__, size_t line = __LINE__) { 1325 // if name is numeric, we should convert to int for arrays 1326 if(name.length && name[0] >= '0' && name[0] <= '9' && this.payloadType() == Type.Array) 1327 return opIndex(to!size_t(name), file, line); 1328 1329 if(this.payloadType() != Type.Object && name == "prototype") 1330 return prototype(); 1331 1332 if(name == "typeof") { 1333 var* tmp = new var; 1334 *tmp = to!string(this.payloadType()); 1335 return *tmp; 1336 } 1337 1338 if(name == "toJson") { 1339 var* tmp = new var; 1340 *tmp = to!string(this.toJson()); 1341 return *tmp; 1342 } 1343 1344 if(name == "length" && this.payloadType() == Type.String) { 1345 var* tmp = new var; 1346 *tmp = _payload._string.length; 1347 return *tmp; 1348 } 1349 if(name == "length" && this.payloadType() == Type.Array) { 1350 var* tmp = new var; 1351 *tmp = _payload._array.length; 1352 return *tmp; 1353 } 1354 if(name == "__prop" && this.payloadType() == Type.Object) { 1355 var* tmp = new var; 1356 (*tmp)._function = delegate var(var _this, var[] args) { 1357 if(args.length == 0) 1358 return var(null); 1359 if(args.length == 1) { 1360 auto peek = this._payload._object._peekMember(args[0].get!string, false); 1361 if(peek is null) 1362 return var(null); 1363 else 1364 return *peek; 1365 } 1366 if(args.length == 2) { 1367 auto peek = this._payload._object._peekMember(args[0].get!string, false); 1368 if(peek is null) { 1369 this._payload._object._properties[args[0].get!string] = args[1]; 1370 return var(null); 1371 } else { 1372 *peek = args[1]; 1373 return *peek; 1374 } 1375 1376 } 1377 throw new Exception("too many args"); 1378 }; 1379 return *tmp; 1380 } 1381 1382 PrototypeObject from; 1383 if(this.payloadType() == Type.Object) 1384 from = _payload._object; 1385 else { 1386 var pt = this.prototype(); 1387 assert(pt.payloadType() == Type.Object); 1388 from = pt._payload._object; 1389 } 1390 1391 if(from is null) { 1392 if(jsvar_throw) 1393 throw new DynamicTypeException(var(null), Type.Object, file, line); 1394 else 1395 return *(new var); 1396 } 1397 return from._getMember(name, true, false, false, file, line); 1398 } 1399 1400 public ref var opIndexAssign(T)(T t, string name, string file = __FILE__, size_t line = __LINE__) { 1401 if(this.payloadType == Type.Array && name.appearsNumeric()) { 1402 try { 1403 auto i = to!size_t(name); 1404 return opIndexAssign(t, i, file, line); 1405 } catch(Exception) 1406 {} // ignore bad index, use it as a string instead lol 1407 } 1408 _requireType(Type.Object); // FIXME? 1409 if(_payload._object is null) 1410 throw new DynamicTypeException(var(null), Type.Object, file, line); 1411 1412 return this._payload._object._setMember(name, var(t), false, false, false, file, line); 1413 } 1414 1415 public ref var opIndexAssignNoOverload(T)(T t, string name, string file = __FILE__, size_t line = __LINE__) { 1416 if(name.length && name[0] >= '0' && name[0] <= '9') 1417 return opIndexAssign(t, to!size_t(name), file, line); 1418 _requireType(Type.Object); // FIXME? 1419 if(_payload._object is null) 1420 throw new DynamicTypeException(var(null), Type.Object, file, line); 1421 1422 return this._payload._object._setMember(name, var(t), false, false, true, file, line); 1423 } 1424 1425 1426 public ref var opIndex(size_t idx, string file = __FILE__, size_t line = __LINE__) { 1427 if(_type == Type.Array) { 1428 auto arr = this._payload._array; 1429 if(idx < arr.length) 1430 return arr[idx]; 1431 } else if(_type == Type.Object) { 1432 // objects might overload opIndex 1433 var* n = new var(); 1434 if("opIndex" in this) 1435 *n = this["opIndex"](idx); 1436 return *n; 1437 } 1438 if(jsvar_throw) { 1439 throw new DynamicTypeException(this, Type.Array, file, line); 1440 } else { 1441 var* n = new var(); 1442 return *n; 1443 } 1444 } 1445 1446 public ref var opIndexAssign(T)(T t, size_t idx, string file = __FILE__, size_t line = __LINE__) { 1447 if(_type == Type.Array) { 1448 if(idx >= this._payload._array.length) 1449 this._payload._array.length = idx + 1; 1450 this._payload._array[idx] = t; 1451 return this._payload._array[idx]; 1452 } else if(_type == Type.Object) { 1453 return opIndexAssign(t, to!string(idx), file, line); 1454 } 1455 if(jsvar_throw) { 1456 throw new DynamicTypeException(this, Type.Array, file, line); 1457 } else { 1458 var* n = new var(); 1459 return *n; 1460 } 1461 } 1462 1463 ref var _getOwnProperty(string name, string file = __FILE__, size_t line = __LINE__) { 1464 if(_type == Type.Object) { 1465 if(_payload._object !is null) { 1466 auto peek = this._payload._object._peekMember(name, false); 1467 if(peek !is null) 1468 return *peek; 1469 } 1470 } 1471 //if(jsvar_throw) 1472 //throw new DynamicTypeException(this, Type.Object, file, line); 1473 var* n = new var(); 1474 return *n; 1475 } 1476 1477 @property static var emptyObject(PrototypeObject prototype = null) { 1478 var v; 1479 v._type = Type.Object; 1480 v._payload._object = new PrototypeObject(); 1481 v._payload._object.prototype = prototype; 1482 return v; 1483 } 1484 1485 /// 1486 @property static var emptyObject(var prototype) { 1487 if(prototype._type == Type.Object) 1488 return var.emptyObject(prototype._payload._object); 1489 return var.emptyObject(); 1490 } 1491 1492 @property PrototypeObject prototypeObject() { 1493 var v = prototype(); 1494 if(v._type == Type.Object) 1495 return v._payload._object; 1496 return null; 1497 } 1498 1499 // what I call prototype is more like what Mozilla calls __proto__, but tbh I think this is better so meh 1500 @property ref var prototype() { 1501 static var _arrayPrototype; 1502 static var _functionPrototype; 1503 static var _stringPrototype; 1504 1505 final switch(payloadType()) { 1506 case Type.Array: 1507 assert(_arrayPrototype._type == Type.Object); 1508 if(_arrayPrototype._payload._object is null) { 1509 _arrayPrototype._object = new PrototypeObject(); 1510 } 1511 1512 return _arrayPrototype; 1513 case Type.Function: 1514 assert(_functionPrototype._type == Type.Object); 1515 if(_functionPrototype._payload._object is null) { 1516 _functionPrototype._object = new PrototypeObject(); 1517 } 1518 1519 return _functionPrototype; 1520 case Type.String: 1521 assert(_stringPrototype._type == Type.Object); 1522 if(_stringPrototype._payload._object is null) { 1523 auto p = new PrototypeObject(); 1524 _stringPrototype._object = p; 1525 1526 var replaceFunction; 1527 replaceFunction._type = Type.Function; 1528 replaceFunction._function = (var _this, var[] args) { 1529 string s = _this.toString(); 1530 import std.array : replace; 1531 return var(std.array.replace(s, 1532 args[0].toString(), 1533 args[1].toString())); 1534 }; 1535 1536 p._properties["replace"] = replaceFunction; 1537 } 1538 1539 return _stringPrototype; 1540 case Type.Object: 1541 if(_payload._object) 1542 return _payload._object._prototype; 1543 // FIXME: should we do a generic object prototype? 1544 break; 1545 case Type.Integral: 1546 case Type.Floating: 1547 case Type.Boolean: 1548 // these types don't have prototypes 1549 } 1550 1551 1552 var* v = new var(null); 1553 return *v; 1554 } 1555 1556 /// 1557 @property static var emptyArray() { 1558 var v; 1559 v._type = Type.Array; 1560 return v; 1561 } 1562 1563 /// 1564 static var fromJson(const(char)[] json) { 1565 auto decoded = parseJSON(json); 1566 return var.fromJsonValue(decoded); 1567 } 1568 1569 /// 1570 static var fromJsonFile(const(char)[] filename) { 1571 import std.file; 1572 return var.fromJson(readText(filename)); 1573 } 1574 1575 /// 1576 static var fromJsonValue(JSONValue v) { 1577 var ret; 1578 1579 final switch(v.type) { 1580 case JSONType..string: 1581 ret = v.str; 1582 break; 1583 case JSONType.uinteger: 1584 ret = v.uinteger; 1585 break; 1586 case JSONType.integer: 1587 ret = v.integer; 1588 break; 1589 case JSONType.float_: 1590 ret = v.floating; 1591 break; 1592 case JSONType.object: 1593 ret = var.emptyObject; 1594 foreach(k, val; v.object) { 1595 ret[k] = var.fromJsonValue(val); 1596 } 1597 break; 1598 case JSONType.array: 1599 ret = var.emptyArray; 1600 ret._payload._array.length = v.array.length; 1601 foreach(idx, item; v.array) { 1602 ret._payload._array[idx] = var.fromJsonValue(item); 1603 } 1604 break; 1605 case JSONType.true_: 1606 ret = true; 1607 break; 1608 case JSONType.false_: 1609 ret = false; 1610 break; 1611 case JSONType.null_: 1612 ret = null; 1613 break; 1614 } 1615 1616 return ret; 1617 } 1618 1619 /// 1620 string toJson() { 1621 auto v = toJsonValue(); 1622 return toJSON(v); 1623 } 1624 1625 /// 1626 JSONValue toJsonValue() { 1627 JSONValue val; 1628 final switch(payloadType()) { 1629 case Type.Boolean: 1630 version(new_std_json) 1631 val = this._payload._boolean; 1632 else { 1633 if(this._payload._boolean) 1634 val.type = JSONType.true_; 1635 else 1636 val.type = JSONType.false_; 1637 } 1638 break; 1639 case Type.Object: 1640 version(new_std_json) { 1641 if(_payload._object is null) { 1642 val = null; 1643 } else { 1644 val = _payload._object.toJsonValue(); 1645 } 1646 } else { 1647 if(_payload._object is null) { 1648 val.type = JSONType.null_; 1649 } else { 1650 val.type = JSONType.object; 1651 foreach(k, v; _payload._object._properties) { 1652 val.object[k] = v.toJsonValue(); 1653 } 1654 } 1655 } 1656 break; 1657 case Type.String: 1658 version(new_std_json) { } else { 1659 val.type = JSONType..string; 1660 } 1661 val.str = _payload._string; 1662 break; 1663 case Type.Integral: 1664 version(new_std_json) { } else { 1665 val.type = JSONType.integer; 1666 } 1667 val.integer = _payload._integral; 1668 break; 1669 case Type.Floating: 1670 version(new_std_json) { } else { 1671 val.type = JSONType.float_; 1672 } 1673 val.floating = _payload._floating; 1674 break; 1675 case Type.Array: 1676 auto a = _payload._array; 1677 JSONValue[] tmp; 1678 tmp.length = a.length; 1679 foreach(i, v; a) { 1680 tmp[i] = v.toJsonValue(); 1681 } 1682 1683 version(new_std_json) { 1684 val = tmp; 1685 } else { 1686 val.type = JSONType.array; 1687 val.array = tmp; 1688 } 1689 break; 1690 case Type.Function: 1691 version(new_std_json) 1692 val = null; 1693 else 1694 val.type = JSONType.null_; // ideally we would just skip it entirely... 1695 break; 1696 } 1697 return val; 1698 } 1699 } 1700 1701 class PrototypeObject { 1702 string name; 1703 var _prototype; 1704 1705 package PrototypeObject _secondary; // HACK don't use this 1706 1707 PrototypeObject prototype() { 1708 if(_prototype.payloadType() == var.Type.Object) 1709 return _prototype._payload._object; 1710 return null; 1711 } 1712 1713 PrototypeObject prototype(PrototypeObject set) { 1714 this._prototype._object = set; 1715 return set; 1716 } 1717 1718 override string toString() { 1719 1720 var* ts = _peekMember("toString", true); 1721 if(ts) { 1722 var _this; 1723 _this._object = this; 1724 return (*ts).call(_this).get!string; 1725 } 1726 1727 JSONValue val; 1728 version(new_std_json) { 1729 JSONValue[string] tmp; 1730 foreach(k, v; this._properties) 1731 tmp[k] = v.toJsonValue(); 1732 val.object = tmp; 1733 } else { 1734 val.type = JSONType.object; 1735 foreach(k, v; this._properties) 1736 val.object[k] = v.toJsonValue(); 1737 } 1738 1739 return toJSON(val); 1740 } 1741 1742 var[string] _properties; 1743 1744 PrototypeObject copy() { 1745 auto n = new PrototypeObject(); 1746 n.prototype = this.prototype; 1747 n.name = this.name; 1748 foreach(k, v; _properties) { 1749 n._properties[k] = v._copy; 1750 } 1751 return n; 1752 } 1753 1754 bool isSpecial() { return false; } 1755 1756 PrototypeObject new_(PrototypeObject newThis) { 1757 // if any of the prototypes are D objects, we need to try to copy them. 1758 auto p = prototype; 1759 1760 PrototypeObject[32] stack; 1761 PrototypeObject[] fullStack = stack[]; 1762 int stackPos; 1763 1764 while(p !is null) { 1765 1766 if(p.isSpecial()) { 1767 auto n = new PrototypeObject(); 1768 1769 auto proto = p.new_(n); 1770 1771 while(stackPos) { 1772 stackPos--; 1773 auto pr = fullStack[stackPos].copy(); 1774 pr.prototype = proto; 1775 proto = pr; 1776 } 1777 1778 n.prototype = proto; 1779 n.name = this.name; 1780 foreach(k, v; _properties) { 1781 n._properties[k] = v._copy; 1782 } 1783 1784 return n; 1785 } 1786 1787 if(stackPos >= fullStack.length) 1788 fullStack ~= p; 1789 else 1790 fullStack[stackPos] = p; 1791 stackPos++; 1792 1793 p = p.prototype; 1794 } 1795 1796 return copy(); 1797 } 1798 1799 PrototypeObject copyPropertiesFrom(PrototypeObject p) { 1800 foreach(k, v; p._properties) { 1801 this._properties[k] = v._copy; 1802 } 1803 return this; 1804 } 1805 1806 var* _peekMember(string name, bool recurse) { 1807 if(name == "prototype") 1808 return &_prototype; 1809 1810 auto curr = this; 1811 1812 // for the secondary hack 1813 bool triedOne = false; 1814 // for the secondary hack 1815 PrototypeObject possibleSecondary; 1816 1817 tryAgain: 1818 do { 1819 auto prop = name in curr._properties; 1820 if(prop is null) { 1821 // the secondary hack is to do more scoping in the script, it is really hackish 1822 if(possibleSecondary is null) 1823 possibleSecondary = curr._secondary; 1824 1825 if(!recurse) 1826 break; 1827 else 1828 curr = curr.prototype; 1829 } else 1830 return prop; 1831 } while(curr); 1832 1833 if(possibleSecondary !is null) { 1834 curr = possibleSecondary; 1835 if(!triedOne) { 1836 triedOne = true; 1837 goto tryAgain; 1838 } 1839 } 1840 1841 return null; 1842 } 1843 1844 // FIXME: maybe throw something else 1845 /*package*/ ref var _getMember(string name, bool recurse, bool throwOnFailure, bool returnRawProperty = false, string file = __FILE__, size_t line = __LINE__) { 1846 var* mem = _peekMember(name, recurse); 1847 1848 if(mem !is null) { 1849 // If it is a property, we need to call the getter on it 1850 if((*mem).payloadType == var.Type.Object && cast(PropertyPrototype) (*mem)._payload._object) { 1851 if(returnRawProperty) 1852 return *mem; 1853 auto prop = cast(PropertyPrototype) (*mem)._payload._object; 1854 return prop.get; 1855 } 1856 return *mem; 1857 } 1858 1859 mem = _peekMember("opIndex", recurse); 1860 if(mem !is null) { 1861 auto n = new var; 1862 *n = ((*mem)(name)); 1863 return *n; 1864 } 1865 1866 // if we're here, the property was not found, so let's implicitly create it 1867 if(throwOnFailure) 1868 throw new DynamicTypeException("no such property " ~ name, file, line); 1869 var n; 1870 this._properties[name] = n; 1871 return this._properties[name]; 1872 } 1873 1874 // FIXME: maybe throw something else 1875 /*package*/ ref var _setMember(string name, var t, bool recurse, bool throwOnFailure, bool suppressOverloading, string file = __FILE__, size_t line = __LINE__) { 1876 var* mem = _peekMember(name, recurse); 1877 1878 bool onParent = false; 1879 1880 if(mem is null && !recurse) { 1881 mem = _peekMember(name, true); // properties need the check anyway as a setter might be on a prototype 1882 onParent = true; 1883 } 1884 1885 if(mem !is null) { 1886 // Property check - the setter should be proxied over to it 1887 if((*mem).payloadType == var.Type.Object && cast(PropertyPrototype) (*mem)._payload._object) { 1888 auto prop = cast(PropertyPrototype) (*mem)._payload._object; 1889 return prop.set(t); 1890 } 1891 if(!onParent) { 1892 *mem = t; 1893 return *mem; 1894 } else { 1895 mem = null; 1896 } 1897 } 1898 1899 if(!suppressOverloading) { 1900 mem = _peekMember("opIndexAssign", true); 1901 if(mem !is null) { 1902 auto n = new var; 1903 *n = ((*mem)(t, name)); 1904 return *n; 1905 } 1906 } 1907 1908 // if we're here, the property was not found, so let's implicitly create it 1909 if(throwOnFailure) 1910 throw new DynamicTypeException("no such property " ~ name, file, line); 1911 this._properties[name] = t; 1912 return this._properties[name]; 1913 } 1914 1915 JSONValue toJsonValue() { 1916 JSONValue val; 1917 JSONValue[string] tmp; 1918 foreach(k, v; this._properties) { 1919 // if it is an overload set and/or a function, just skip it. 1920 // or really if it is a wrapped native object it should prolly just be skipped anyway 1921 // unless it actually defines a toJson. 1922 if(v.payloadType == var.Type.Function) 1923 continue; 1924 if(v.payloadType == var.Type.Object) { 1925 // I'd love to get the json value out but idk. FIXME 1926 if(v._payload._object is null) continue; 1927 if(auto wno = cast(WrappedNativeObject) v._payload._object) { 1928 auto obj = wno.getObject(); 1929 if(obj is null) 1930 tmp[k] = null; 1931 else 1932 tmp[k] = obj.toString(); 1933 continue; 1934 } else if(typeid(PrototypeObject) !is typeid(v._payload._object)) 1935 continue; 1936 } 1937 1938 tmp[k] = v.toJsonValue(); 1939 } 1940 1941 val = tmp; 1942 return val; 1943 } 1944 1945 public int opApply(scope int delegate(var, ref var) dg) { 1946 foreach(k, v; this._properties) { 1947 if(v.payloadType == var.Type.Object && cast(PropertyPrototype) v._payload._object) 1948 v = (cast(PropertyPrototype) v._payload._object).get; 1949 if(auto result = dg(var(k), v)) 1950 return result; 1951 } 1952 return 0; 1953 } 1954 } 1955 1956 // A property is a special type of object that can only be set by assigning 1957 // one of these instances to foo.child._object. When foo.child is accessed and it 1958 // is an instance of PropertyPrototype, it will return the getter. When foo.child is 1959 // set (excluding direct assignments through _type), it will call the setter. 1960 class PropertyPrototype : PrototypeObject { 1961 var delegate() getter; 1962 void delegate(var) setter; 1963 this(var delegate() getter, void delegate(var) setter) { 1964 this.getter = getter; 1965 this.setter = setter; 1966 } 1967 1968 override string toString() { 1969 return get.toString(); 1970 } 1971 1972 ref var get() { 1973 var* g = new var(); 1974 *g = getter(); 1975 return *g; 1976 } 1977 1978 ref var set(var t) { 1979 setter(t); 1980 return get; 1981 } 1982 1983 override JSONValue toJsonValue() { 1984 return get.toJsonValue(); 1985 } 1986 } 1987 1988 /// 1989 struct ScriptLocation { 1990 string scriptFilename; /// 1991 int lineNumber; /// 1992 } 1993 1994 class DynamicTypeException : Exception { 1995 this(string msg, string file = __FILE__, size_t line = __LINE__) { 1996 super(msg, file, line); 1997 } 1998 this(var v, var.Type required, string file = __FILE__, size_t line = __LINE__) { 1999 import std.string; 2000 if(v.payloadType() == required) 2001 super(format("Tried to use null as a %s", required), file, line); 2002 else { 2003 super(format("Tried to use %s%s as a %s", v == null ? "null ": "", v.payloadType(), required), file, line); 2004 } 2005 } 2006 2007 override void toString(scope void delegate(in char[]) sink) const { 2008 import std.format; 2009 if(varName.length) 2010 sink(varName); 2011 if(callStack.length) { 2012 sink("arsd.jsvar.DynamicTypeException@"); 2013 sink(file); 2014 sink("("); 2015 sink(to!string(line)); 2016 sink("): "); 2017 sink(msg); 2018 foreach(cs; callStack) 2019 sink(format("\n\t%s:%s", cs.scriptFilename, cs.lineNumber)); 2020 2021 bool found; 2022 void hiddenSink(in char[] str) { 2023 // I just want to hide the call stack until the interpret call... 2024 // since the script stack above is more meaningful to users. 2025 // 2026 // but then I will go back to the D functions once on the outside. 2027 import std.string; 2028 if(found) 2029 sink(str); 2030 else if(str.indexOf("arsd.script.interpret(") != -1) 2031 found = true; 2032 } 2033 2034 sink("\n--------"); 2035 2036 super.toString(&hiddenSink); 2037 } else { 2038 super.toString(sink); 2039 } 2040 } 2041 2042 ScriptLocation[] callStack; 2043 string varName; 2044 } 2045 2046 template makeAscii() { 2047 string helper() { 2048 string s; 2049 foreach(i; 0 .. 128) 2050 s ~= cast(char) i; 2051 return s; 2052 } 2053 2054 enum makeAscii = helper(); 2055 } 2056 2057 package interface VarMetadata { } 2058 2059 interface ScriptableSubclass { 2060 void setScriptVar(var); 2061 var getScriptVar(); 2062 final bool methodOverriddenByScript(string name) { 2063 PrototypeObject t = getScriptVar().get!PrototypeObject; 2064 // the top one is the native object from subclassable so we don't want to go all the way there to avoid endless recursion 2065 //import std.stdio; writeln("checking ", name , " ...", "wtf"); 2066 if(t !is null) 2067 while(!t.isSpecial) { 2068 if(t._peekMember(name, false) !is null) 2069 return true; 2070 t = t.prototype; 2071 } 2072 return false; 2073 } 2074 } 2075 2076 /++ 2077 EXPERIMENTAL 2078 2079 Allows you to make a class available to the script rather than just class objects. 2080 You can subclass it in script and then call the methods again through the original 2081 D interface. With caveats... 2082 2083 2084 Assumes ALL $(I virtual) methods and constructors are scriptable, but requires 2085 `@scriptable` to be present on final or static methods. This may change in the future. 2086 2087 Note that it has zero support for `@safe`, `pure`, `nothrow`, and other attributes 2088 at this time and will skip that use those. I may be able to loosen this in the 2089 future as well but I have no concrete plan to at this time. You can still mark 2090 them as `@scriptable` to call them from the script, but they can never be overridden 2091 by script code because it cannot verify those guarantees hold true. 2092 2093 Ditto on `const` and `immutable`. 2094 2095 Its behavior on overloads is currently undefined - it may keep only any random 2096 overload as the only one and do dynamic type conversions to cram data into it. 2097 This is likely to change in the future but for now try not to use this on classes 2098 with overloaded methods. 2099 2100 It also does not wrap member variables unless explicitly marked `@scriptable`; it 2101 is meant to communicate via methods. 2102 2103 History: 2104 Added April 25, 2020 2105 +/ 2106 static if(__traits(compiles, mixin(q{ () { static foreach(i; [1,2]) {} } }) )) 2107 mixin(q{ 2108 var subclassable(T)() if(is(T == class) || is(T == interface)) { 2109 import std.traits; 2110 2111 static final class ScriptableT : T, ScriptableSubclass { 2112 var _this; 2113 void setScriptVar(var v) { _this = v; } 2114 var getScriptVar() { return _this; } 2115 bool _next_devirtualized; 2116 2117 // @scriptable size_t _nativeHandle_() { return cast(size_t) cast(void*) this;} 2118 2119 static if(__traits(compiles, __traits(getOverloads, T, "__ctor"))) 2120 static foreach(ctor; __traits(getOverloads, T, "__ctor")) 2121 @scriptable this(Parameters!ctor p) { super(p); } 2122 2123 static foreach(memberName; __traits(allMembers, T)) { 2124 static if(memberName != "toHash") 2125 static foreach(overload; __traits(getOverloads, T, memberName)) 2126 static if(__traits(isVirtualMethod, overload)) 2127 static if(!__traits(isFinalFunction, overload)) 2128 static if(!__traits(isDeprecated, overload)) 2129 // note: overload behavior undefined 2130 static if(!(functionAttributes!(overload) & (FunctionAttribute.pure_ | FunctionAttribute.safe | FunctionAttribute.trusted | FunctionAttribute.nothrow_ | FunctionAttribute.const_ | FunctionAttribute.inout_))) 2131 static if(!hasRefParam!overload) 2132 static if(__traits(getFunctionVariadicStyle, overload) == "none") 2133 static if(__traits(identifier, overload) == memberName) // to filter out aliases 2134 mixin(q{ 2135 @scriptable 2136 override ReturnType!(overload) 2137 }~memberName~q{ 2138 (Parameters!(overload) p) 2139 { 2140 //import std.stdio; writeln("calling ", T.stringof, ".", memberName, " - ", methodOverriddenByScript(memberName), "/", _next_devirtualized, " on ", cast(size_t) cast(void*) this); 2141 if(_next_devirtualized || !methodOverriddenByScript(memberName)) { 2142 _next_devirtualized = false; 2143 return __traits(getMember, super, memberName)(p); 2144 } 2145 return _this[memberName].call(_this, p).get!(typeof(return)); 2146 } 2147 }); 2148 } 2149 2150 // I don't want to necessarily call a constructor but I need an object t use as the prototype 2151 // hence this faked one. hopefully the new operator will see void[] and assume it can have GC ptrs... 2152 static ScriptableT _allocate_(PrototypeObject newThis) { 2153 void[] store = new void[](__traits(classInstanceSize, ScriptableT)); 2154 store[] = typeid(ScriptableT).initializer[]; 2155 ScriptableT dummy = cast(ScriptableT) store.ptr; 2156 dummy._this = var(newThis); 2157 //import std.stdio; writeln("Allocating new ", cast(ulong) store.ptr); 2158 return dummy; 2159 } 2160 } 2161 2162 ScriptableT dummy = ScriptableT._allocate_(null); 2163 2164 var proto = wrapNativeObject!(ScriptableT, true)(dummy); 2165 2166 var f = var.emptyObject; 2167 f.prototype = proto; 2168 2169 return f; 2170 } 2171 2172 template hasRefParam(alias overload) { 2173 bool helper() { 2174 static if(is(typeof(overload) P == __parameters)) 2175 foreach(idx, p; P) 2176 foreach(thing; __traits(getParameterStorageClasses, overload, idx)) 2177 if(thing == "ref") 2178 return true; 2179 return false; 2180 } 2181 2182 enum hasRefParam = helper(); 2183 } 2184 2185 }); 2186 2187 /// Demonstrates tested capabilities of [subclassable] 2188 version(with_arsd_script) 2189 unittest { 2190 interface IFoo { 2191 string method(); 2192 int method2(); 2193 int args(int, int); 2194 } 2195 // note the static is just here because this 2196 // is written in a unittest; it shouldn't actually 2197 // be necessary under normal circumstances. 2198 static class Foo : IFoo { 2199 ulong handle() { return cast(ulong) cast(void*) this; } 2200 string method() { return "Foo"; } 2201 int method2() { return 10; } 2202 int args(int a, int b) { 2203 //import std.stdio; writeln(a, " + ", b, " + ", member_, " on ", cast(ulong) cast(void*) this); 2204 return member_+a+b; } 2205 2206 int member_; 2207 @property int member(int i) { return member_ = i; } 2208 @property int member() { return member_; } 2209 2210 @scriptable final int fm() { return 56; } 2211 } 2212 static class Bar : Foo { 2213 override string method() { return "Bar"; } 2214 string test1() { return test2(); } 2215 string test2() { return "test2"; } 2216 2217 int acceptFoo(Foo f) { 2218 return f.method2(); 2219 } 2220 } 2221 static class Baz : Bar { 2222 override int method2() { return 20; } 2223 } 2224 2225 static class WithCtor { 2226 // constructors work but are iffy with overloads.... 2227 this(int arg) { this.arg = arg; } 2228 @scriptable int arg; // this is accessible cuz it is @scriptable 2229 int getValue() { return arg; } 2230 } 2231 2232 var globals = var.emptyObject; 2233 globals.Foo = subclassable!Foo; 2234 globals.Bar = subclassable!Bar; 2235 globals.Baz = subclassable!Baz; 2236 globals.WithCtor = subclassable!WithCtor; 2237 2238 import arsd.script; 2239 2240 interpret(q{ 2241 // can instantiate D classes added via subclassable 2242 var foo = new Foo(); 2243 // and call its methods... 2244 assert(foo.method() == "Foo"); 2245 assert(foo.method2() == 10); 2246 2247 foo.member(55); 2248 2249 // proves the new operator actually creates new D 2250 // objects as well to avoid sharing instance state. 2251 var foo2 = new Foo(); 2252 assert(foo2.handle() != foo.handle()); 2253 2254 // passing arguments works 2255 assert(foo.args(2, 4) == 6 + 55); // (and sanity checks operator precedence) 2256 2257 var bar = new Bar(); 2258 assert(bar.method() == "Bar"); 2259 assert(bar.method2() == 10); 2260 2261 assert(bar.acceptFoo(new Foo()) == 10); 2262 assert(bar.acceptFoo(new Baz()) == 20); 2263 2264 // this final member is accessible because it was marked @scriptable 2265 assert(bar.fm() == 56); 2266 2267 assert(bar.test1() == "test2"); 2268 assert(bar.test2() == "test2"); 2269 2270 // the script can even subclass D classes! 2271 class Amazing : Bar { 2272 // and override its methods 2273 var inst = 99; 2274 function method() { 2275 return "Amazing"; 2276 } 2277 2278 // note: to access instance members or virtual call lookup you MUST use the `this` keyword 2279 // otherwise the function will be called with scope limited to this class itself (similar to javascript) 2280 function other() { 2281 // this.inst is needed to get the instance variable (otherwise it would only look for a static var) 2282 // and this.method triggers dynamic lookup there, so it will get children's overridden methods if there is one 2283 return this.inst ~ this.method(); 2284 } 2285 2286 function args(a, b) { 2287 // calling parent class method still possible 2288 return super.args(a*2, b*2); 2289 } 2290 2291 function test2() { 2292 return "script test"; 2293 } 2294 } 2295 2296 var amazing = new Amazing(); 2297 assert(amazing.method() == "Amazing"); 2298 assert(amazing.method2() == 10); // calls back to the parent class 2299 amazing.member(5); 2300 2301 assert(amazing.test1() == "script test"); // even the virtual method call from D goes into the script override 2302 2303 // this line I can paste down to interactively debug the test btw. 2304 //}, globals); repl!true(globals); interpret(q{ 2305 2306 assert(amazing.args(2, 4) == 12+5); 2307 2308 var wc = new WithCtor(5); // argument passed to constructor 2309 assert(wc.getValue() == 5); 2310 2311 // confirm the property read works too 2312 assert(wc.arg == 5); 2313 2314 // but property WRITING is currently not working though. 2315 2316 2317 class DoubleChild : Amazing { 2318 function method() { 2319 return "DoubleChild"; 2320 } 2321 } 2322 2323 // can also do a child of a child class 2324 var dc = new DoubleChild(); 2325 assert(dc.method() == "DoubleChild"); 2326 assert(dc.other() == "99DoubleChild"); // the `this.method` means it uses the replacement now 2327 assert(dc.method2() == 10); // back to the D grandparent 2328 assert(dc.args(2, 4) == 12); // but the args impl from above 2329 }, globals); 2330 2331 Foo foo = globals.foo.get!Foo; // get the native object back out 2332 assert(foo.member == 55); // and see mutation via properties proving object mutability 2333 assert(globals.foo.get!Bar is null); // cannot get the wrong class out of it 2334 assert(globals.foo.get!Object !is null); // but can do parent classes / interfaces 2335 assert(globals.foo.get!IFoo !is null); 2336 assert(globals.bar.get!Foo !is null); // the Bar can also be a Foo 2337 2338 Bar amazing = globals.amazing.get!Bar; // instance of the script's class is still accessible through parent D class or interface 2339 assert(amazing !is null); // object exists 2340 assert(amazing.method() == "Amazing"); // calls the override from the script 2341 assert(amazing.method2() == 10); // non-overridden function works as expected 2342 2343 IFoo iamazing = globals.amazing.get!IFoo; // and through just the interface works the same way 2344 assert(iamazing !is null); 2345 assert(iamazing.method() == "Amazing"); 2346 assert(iamazing.method2() == 10); 2347 2348 // and here ensuring the interface we got out can go back in and back out too 2349 var globals2 = var.emptyObject; 2350 globals2.opDispatch!"iamazing2" = iamazing; 2351 // though inside, it is treated as an opaque reference since nothing is @scriptable 2352 // so nothing to test there... 2353 // however getting it back out should work fine 2354 IFoo gotten = globals2.iamazing2.get!IFoo; 2355 assert(gotten !is null); 2356 assert(gotten is iamazing); 2357 //import std.stdio; writeln("wtf", iamazing.method()); 2358 2359 // now this seems obvious, but adding the interface can actually overwrite the dynamic 2360 // prototype.... i think 2361 //assert(gotten.method() == "Amazing"); 2362 2363 //https://issues.dlang.org/show_bug.cgi?id=22011 necessitated the static! 2364 static class CFoo : IFoo { 2365 @scriptable string method() { return "CFoo"; } 2366 int method2() { return 55; } 2367 int args(int, int) { return 6 + 5; } 2368 } 2369 2370 globals.opDispatch!"iamazing3" = new CFoo; // and also just testing a standard class assign 2371 IFoo input = new CFoo(); 2372 globals.opDispatch!"iamazing4" = input; // and interface 2373 interpret(q{ 2374 var a = iamazing3.method(); 2375 assert(a == "CFoo"); // works cuz the class is scriptable 2376 // but the following line is NOT available because the interface itself is not marked scriptable 2377 //assert(iamazing4.method() == "CFoo"); 2378 }, globals); 2379 2380 // but we can get it right back out 2381 IFoo got2 = globals.iamazing4.get!IFoo; 2382 assert(got2 is input); 2383 assert(got2.method() == "CFoo"); 2384 } 2385 2386 unittest { 2387 static struct Foo { 2388 struct Bar { 2389 int[] x; 2390 } 2391 2392 Bar bar; 2393 } 2394 2395 assert( 2396 var.fromJson(`{ 2397 "bar":{ "x": [1,2,3]} 2398 }`).get!Foo 2399 == 2400 Foo(Foo.Bar([1,2,3])) 2401 ); 2402 } 2403 2404 version(with_arsd_script) 2405 unittest { 2406 import arsd.script; 2407 static class Test { 2408 @scriptable int a; 2409 } 2410 2411 Test test = new Test; 2412 2413 test.a = 15; 2414 2415 var globals = var.emptyObject; 2416 globals.test = test; 2417 globals.Test = subclassable!Test; 2418 2419 interpret(q{ 2420 test.a = 4; 2421 var wtf = test.a += 5; 2422 assert(wtf == 9); 2423 assert(test.a == 9); 2424 var test2 = new Test; 2425 test2.a = 2; 2426 test2.a += 5; 2427 assert(test2.a == 7); 2428 }, globals); 2429 2430 assert(test.a == 9); 2431 assert(globals.test2.get!Test.a == 7); 2432 } 2433 2434 // just a base class we can reference when looking for native objects 2435 class WrappedNativeObject : PrototypeObject { 2436 TypeInfo wrappedType; 2437 abstract Object getObject(); 2438 } 2439 2440 template helper(alias T) { alias helper = T; } 2441 2442 /++ 2443 Wraps a class. If you are manually managing the memory, remember the jsvar may keep a reference to the object; don't free it! 2444 2445 To use this: `var a = wrapNativeObject(your_d_object);` OR `var a = your_d_object`; 2446 2447 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 2448 2449 History: 2450 This became the default after April 24, 2020. Previously, [var.opAssign] would [wrapOpaquely] instead. 2451 +/ 2452 WrappedNativeObject wrapNativeObject(Class, bool special = false)(Class obj) if(is(Class == class) || is(Class == interface)) { 2453 import std.meta; 2454 static class WrappedNativeObjectImpl : WrappedNativeObject { 2455 override Object getObject() { 2456 return cast(Object) obj; 2457 } 2458 2459 override bool isSpecial() { return special; } 2460 2461 static if(special) 2462 override WrappedNativeObject new_(PrototypeObject newThis) { 2463 return new WrappedNativeObjectImpl(obj._allocate_(newThis)); 2464 } 2465 2466 Class obj; 2467 2468 this(Class objIn) { 2469 this.obj = objIn; 2470 wrappedType = typeid(cast(Object) obj); 2471 // wrap the other methods 2472 // and wrap members as scriptable properties 2473 2474 foreach(memberName; __traits(allMembers, Class)) static if(!mixin(deprecationCheck()) && is(typeof(__traits(getMember, obj, memberName)) type)) { 2475 static if(is(type == function)) { 2476 auto os = new OverloadSet(); 2477 foreach(idx, overload; AliasSeq!(__traits(getOverloads, obj, memberName))) static if(.isScriptable!(__traits(getAttributes, overload))()) { 2478 var gen; 2479 gen._function = delegate (var vthis_, var[] vargs) { 2480 Parameters!(__traits(getOverloads, Class, memberName)[idx]) fargs; 2481 2482 2483 enum lol = static_foreach(fargs.length, 1, -1, 2484 `__traits(getOverloads, obj, memberName)[idx](`,``,` < vargs.length ? vargs[`,`].get!(typeof(fargs[`,`])) : ParamDefault!(__traits(getOverloads, Class, memberName)[idx], `,`)(),`,`)`); 2485 /* 2486 enum lol = static_foreach(fargs.length, 1, -1, 2487 `__traits(getOverloads, obj, memberName)[idx](`,``,` < vargs.length ? vargs[`,`].get!(typeof(fargs[`,`])) : 2488 typeof(fargs[`,`]).init,`,`)`); 2489 */ 2490 2491 // FIXME: what if there are multiple @scriptable overloads?! 2492 // FIXME: what about @properties? 2493 2494 static if(special) { 2495 Class obj; 2496 //if(vthis_.payloadType() != var.Type.Object) { import std.stdio; writeln("getwno on ", vthis_); } 2497 // the native object might be a step or two up the prototype 2498 // chain due to script subclasses, need to find it... 2499 while(vthis_ != null) { 2500 obj = vthis_.getWno!Class; 2501 if(obj !is null) 2502 break; 2503 vthis_ = vthis_.prototype; 2504 } 2505 2506 if(obj is null) throw new Exception("null native object"); 2507 } 2508 2509 static if(special) { 2510 obj._next_devirtualized = true; 2511 scope(exit) obj._next_devirtualized = false; 2512 } 2513 2514 var ret; 2515 2516 static if(!is(typeof(__traits(getOverloads, obj, memberName)[idx](fargs)) == void)) 2517 ret = mixin(lol); 2518 else 2519 mixin(lol ~ ";"); 2520 2521 return ret; 2522 }; 2523 2524 Parameters!(overload) fargs; 2525 // FIXME: if an argument type is a class, we need to actually look it up in the script context somehow 2526 var[] definition; 2527 foreach(arg; fargs) { 2528 definition ~= var(arg); 2529 } 2530 2531 //import std.stdio; writeln(Class.stringof, ".", memberName, " ", definition); 2532 os.addOverload(OverloadSet.Overload(definition, gen)); 2533 } 2534 2535 _properties[memberName] = var(os); 2536 } else { 2537 alias fuckThisLanguage = __traits(getOverloads, Class, memberName, true); 2538 static if(fuckThisLanguage.length) 2539 alias fuckYourPointlessDeprecations = fuckThisLanguage[0]; 2540 else 2541 alias fuckYourPointlessDeprecations = __traits(getMember, Class, memberName); 2542 static if(.isScriptable!(__traits(getAttributes, fuckYourPointlessDeprecations))()) 2543 // if it has a type but is not a function, it is prolly a member 2544 _properties[memberName] = new PropertyPrototype( 2545 () => var(__traits(getMember, obj, memberName)), 2546 (var v) { 2547 // read-only property hack 2548 static if(__traits(compiles, __traits(getMember, obj, memberName) = v.get!(type))) 2549 __traits(getMember, obj, memberName) = v.get!(type); 2550 }); 2551 } 2552 } 2553 } 2554 } 2555 2556 return new WrappedNativeObjectImpl(obj); 2557 } 2558 2559 import std.traits; 2560 class WrappedOpaque(T) : PrototypeObject if(isPointer!T || is(T == class) || is(T == interface)) { 2561 T wrapped; 2562 this(T t) { 2563 wrapped = t; 2564 } 2565 T wrapping() { 2566 return wrapped; 2567 } 2568 } 2569 class WrappedOpaque(T) : PrototypeObject if(!isPointer!T && !is(T == class) && !is(T == interface)) { 2570 T* wrapped; 2571 this(T t) { 2572 wrapped = new T; 2573 (cast() *wrapped) = t; 2574 } 2575 this(T* t) { 2576 wrapped = t; 2577 } 2578 T* wrapping() { 2579 return wrapped; 2580 } 2581 } 2582 2583 WrappedOpaque!Obj wrapOpaquely(Obj)(Obj obj) { 2584 static if(is(Obj == class)) { 2585 if(obj is null) 2586 return null; 2587 } 2588 return new WrappedOpaque!Obj(obj); 2589 } 2590 2591 /** 2592 Wraps an opaque struct pointer in a module with ufcs functions 2593 */ 2594 WrappedNativeObject wrapUfcs(alias Module, Type)(Type obj) { 2595 import std.meta; 2596 return new class WrappedNativeObject { 2597 override Object getObject() { 2598 return null; // not actually an object! but close to 2599 } 2600 2601 this() { 2602 wrappedType = typeid(Type); 2603 // wrap the other methods 2604 // and wrap members as scriptable properties 2605 2606 foreach(memberName; __traits(allMembers, Module)) static if(is(typeof(__traits(getMember, Module, memberName)) type)) { 2607 static if(is(type == function)) { 2608 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 2609 auto helper = &__traits(getOverloads, Module, memberName)[idx]; 2610 static if(Parameters!helper.length >= 1 && is(Parameters!helper[0] == Type)) { 2611 // this staticMap is a bit of a hack so it can handle `in float`... liable to break with others, i'm sure 2612 _properties[memberName] = (staticMap!(Unqual, Parameters!helper[1 .. $]) args) { 2613 return __traits(getOverloads, Module, memberName)[idx](obj, args); 2614 }; 2615 } 2616 } 2617 } 2618 } 2619 } 2620 }; 2621 } 2622 2623 bool isCallableJsvarObject(var possibility) { 2624 if(possibility.payloadType == var.Type.Function) 2625 return true; 2626 if(possibility.payloadType == var.Type.Object) { 2627 if(possibility._payload._object is null) 2628 return false; 2629 2630 if(var* test = possibility._payload._object._peekMember("opCall", true)) { 2631 if(isCallableJsvarObject(*test)) 2632 return true; 2633 } 2634 2635 } 2636 return false; 2637 } 2638 2639 2640 bool isScriptable(attributes...)() { 2641 bool nonConstConditionForWorkingAroundASpuriousDmdWarning = true; 2642 foreach(attribute; attributes) { 2643 static if(is(typeof(attribute) == string)) { 2644 static if(attribute == scriptable) { 2645 if(nonConstConditionForWorkingAroundASpuriousDmdWarning) 2646 return true; 2647 } 2648 } 2649 } 2650 return false; 2651 } 2652 2653 bool isScriptableOpaque(T)() { 2654 static if(is(typeof(T.isOpaqueStruct) == bool)) 2655 return T.isOpaqueStruct == true; 2656 else 2657 return false; 2658 } 2659 2660 int typeCompatibilityScore(var arg, var type) { 2661 int thisScore = 0; 2662 2663 if(type.payloadType == var.Type.Object && type._payload._object is null) { 2664 thisScore += 1; // generic match 2665 return thisScore; 2666 } 2667 if(arg.payloadType == var.Type.Integral && type.payloadType == var.Type.Floating) { 2668 thisScore += 2; // match with implicit conversion 2669 // FIXME: want to support implicit from whatever to bool? 2670 } else if(arg.payloadType != type.payloadType) { 2671 thisScore = 0; 2672 return thisScore; 2673 } else { 2674 // exact type category match 2675 if(type.payloadType == var.Type.Array) { 2676 // arrays not really supported here.... 2677 // so just like if both are arrays i'll take 2678 // it as a bare minimum but i don't love it otherwise 2679 if(arg.payloadType == var.Type.Array) 2680 thisScore = 1; 2681 else 2682 thisScore = 0; 2683 return thisScore; 2684 } else if(type.payloadType == var.Type.Object) { 2685 // objects are the interesting one... 2686 // want to see if it matches by seeing if the 2687 // given type is identical or its prototype is one of the given type's prototype chain 2688 2689 int depth = 0; 2690 PrototypeObject pt = type._payload._object; 2691 while(pt !is null) { 2692 depth++; 2693 pt = pt.prototype; 2694 } 2695 2696 if(type._payload._object is arg._payload._object) 2697 thisScore += 2 + depth; 2698 else { 2699 if(arg._payload._object is null) 2700 return 0; // null sucks. 2701 2702 auto test = type._payload._object.prototype; 2703 // generic object compared against generic object matches 2704 if(test is null && type._payload._object.prototype is null) 2705 thisScore += 1; 2706 else { 2707 pt = arg._payload._object; 2708 while(pt !is null) { 2709 if(pt is test) { 2710 thisScore += 1 + depth; 2711 break; 2712 } 2713 pt = pt.prototype; 2714 } 2715 } 2716 } 2717 } else { 2718 thisScore += 3; // exact match without implicit conversion 2719 } 2720 } 2721 2722 return thisScore; 2723 } 2724 2725 /++ 2726 Does dynamic dispatch to overloads in a jsvar function set. 2727 2728 History: 2729 Added September 1, 2020. 2730 +/ 2731 class OverloadSet : PrototypeObject { 2732 this() { 2733 _properties["opCall"] = &opCall; 2734 _properties["apply"] = &apply; 2735 } 2736 2737 /// 2738 void addIndividualOverload(alias f)() { 2739 var func = &f; 2740 var[] argTypes; 2741 static if(is(typeof(f) Params == __parameters)) 2742 foreach(param; Params) 2743 argTypes ~= var(param.init); 2744 //import std.stdio; writeln("registered ", argTypes); 2745 overloads ~= Overload(argTypes, func); 2746 } 2747 2748 /// 2749 static if(__traits(compiles, mixin(q{ () { static foreach(i; [1,2]) {} } }) )) 2750 mixin(q{ 2751 void addOverloadsOf(alias what)() { 2752 foreach(alias f; __traits(getOverloads, __traits(parent, what), __traits(identifier, what))) 2753 addIndividualOverload!f; 2754 } 2755 }); 2756 2757 static struct Overload { 2758 // I don't even store the arity of a function object 2759 // so argTypes is the nest best thing. 2760 var[] argTypes; 2761 var func; 2762 } 2763 Overload[] overloads; 2764 2765 private bool exactMatch(var[] a, var[] b) { 2766 if(a.length != b.length) 2767 return false; 2768 foreach(i; 0 .. a.length) { 2769 if(a[i] !is b[i]) 2770 return false; 2771 } 2772 return true; 2773 } 2774 2775 void addOverload(Overload o) { 2776 foreach(ref ov; overloads) 2777 if(exactMatch(ov.argTypes, o.argTypes)) { 2778 ov.func = o.func; 2779 return; 2780 } 2781 overloads ~= o; 2782 } 2783 2784 /* 2785 I might have to add Object, Exception, and others to jsvar to represent types. 2786 maybe even int and such. 2787 2788 An object should probably have a secret property that gives its name... 2789 */ 2790 var apply(var this_, var[] arguments) { 2791 return opCall(arguments[0], arguments[1].get!(var[])); 2792 } 2793 2794 var opCall(var this_, var[] arguments) { 2795 // remember script.d supports default args too. 2796 int bestScore = int.min; 2797 Overload bestMatch; 2798 2799 if(overloads.length == 0) { 2800 return var(null); 2801 } 2802 2803 foreach(overload; overloads) { 2804 if(overload.argTypes.length == 0) { 2805 if(arguments.length == 0) { 2806 bestScore = 0; 2807 bestMatch = overload; 2808 break; 2809 } 2810 if(bestScore < 0) { 2811 bestScore = 0; 2812 bestMatch = overload; 2813 continue; 2814 } 2815 } 2816 2817 int thisScore = 0; 2818 foreach(idx, arg; arguments) { 2819 if(idx >= overload.argTypes.length) { 2820 thisScore = 0; 2821 break; 2822 } 2823 2824 // now if it is an object, if we can match, add score based on how derived the specified type is. 2825 // 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.) 2826 // 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) 2827 2828 // otherwise just give one point. 2829 2830 auto s = typeCompatibilityScore(arg, overload.argTypes[idx]); 2831 if(s == 0) { 2832 thisScore = 0; 2833 break; 2834 } 2835 thisScore += s; 2836 } 2837 2838 if(thisScore > 0 && thisScore > bestScore) { 2839 bestScore = thisScore; 2840 bestMatch = overload; 2841 } 2842 } 2843 2844 if(bestScore < 0) 2845 throw new Exception("no matching overload found " ~ to!string(arguments) ~ " " ~ to!string(overloads)); 2846 2847 2848 return bestMatch.func.apply(this_, arguments); 2849 } 2850 } 2851 2852 unittest { 2853 struct A { 2854 static: 2855 string foo(var arg) { return "generic"; } 2856 string foo(string s) { return "string"; } 2857 string foo(int i) { return "int"; } 2858 string foo(float i) { return "float"; } 2859 } 2860 2861 auto os = new OverloadSet(); 2862 os.addOverloadsOf!(A.foo); 2863 var g = var.emptyObject; 2864 g.foo = os; 2865 2866 //g.foo()(); 2867 assert(g.foo()("for me") == "string"); 2868 //g.foo()("for me", "lol"); 2869 assert(g.foo()(1) == "int"); 2870 assert(g.foo()(5.4) == "float"); 2871 assert(g.foo()(new Object) == "generic"); 2872 } 2873 2874 unittest { 2875 import std.typecons; 2876 2877 static struct A { 2878 int a; 2879 Nullable!int b; 2880 } 2881 2882 var a = A(3); 2883 assert(a.a == 3); 2884 assert(a.b == null); 2885 a = A(5, nullable(4)); 2886 assert(a.a == 5); 2887 assert(a.b == 4); 2888 } 2889 2890 bool appearsNumeric(string n) { 2891 if(n.length == 0) 2892 return false; 2893 foreach(c; n) { 2894 if(c < '0' || c > '9') 2895 return false; 2896 } 2897 return true; 2898 } 2899 2900 2901 /// Wraps a struct by reference. The pointer is stored - be sure the struct doesn't get freed or go out of scope! 2902 /// 2903 /// 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. 2904 WrappedNativeObject wrapNativeObject(Struct)(Struct* obj) if(is(Struct == struct)) { 2905 return null; // FIXME 2906 } 2907 2908 /+ 2909 _IDX_ 2910 2911 static_foreach(T.length, q{ 2912 mixin(q{ 2913 void 2914 } ~ __traits(identifier, T[_IDX_]) ~ q{ 2915 2916 } 2917 }); 2918 +/ 2919 2920 private 2921 string static_foreach(size_t length, int t_start_idx, int t_end_idx, string[] t...) pure { 2922 assert(__ctfe); 2923 int slen; 2924 int tlen; 2925 foreach(idx, i; t[0 .. t_start_idx]) 2926 slen += i.length; 2927 foreach(idx, i; t[t_start_idx .. $ + t_end_idx]) { 2928 if(idx) 2929 tlen += 5; 2930 tlen += i.length; 2931 } 2932 foreach(idx, i; t[$ + t_end_idx .. $]) 2933 slen += i.length; 2934 2935 char[] a = new char[](tlen * length + slen); 2936 2937 int loc; 2938 char[5] stringCounter; 2939 stringCounter[] = "00000"[]; 2940 2941 foreach(part; t[0 .. t_start_idx]) { 2942 a[loc .. loc + part.length] = part[]; 2943 loc += part.length; 2944 } 2945 2946 foreach(i; 0 .. length) { 2947 foreach(idx, part; t[t_start_idx .. $ + t_end_idx]) { 2948 if(idx) { 2949 a[loc .. loc + stringCounter.length] = stringCounter[]; 2950 loc += stringCounter.length; 2951 } 2952 a[loc .. loc + part.length] = part[]; 2953 loc += part.length; 2954 } 2955 2956 auto pos = stringCounter.length; 2957 while(pos) { 2958 pos--; 2959 if(stringCounter[pos] == '9') { 2960 stringCounter[pos] = '0'; 2961 } else { 2962 stringCounter[pos] ++; 2963 break; 2964 } 2965 } 2966 while(pos) 2967 stringCounter[--pos] = ' '; 2968 } 2969 2970 foreach(part; t[$ + t_end_idx .. $]) { 2971 a[loc .. loc + part.length] = part[]; 2972 loc += part.length; 2973 } 2974 2975 return a; 2976 } 2977 2978 private string deprecationCheck() { 2979 //https://issues.dlang.org/show_bug.cgi?id=22011 this hack sucks 2980 static if(__VERSION__ >= 2077) 2981 return "__traits(isDeprecated, __traits(getMember, obj, memberName == `this` ? `__ctor` : memberName))"; 2982 else 2983 return "false"; 2984 } 2985 2986 // LOL this can't work because function pointers drop the default :( 2987 private 2988 auto ParamDefault(alias T, size_t idx)() { 2989 static if(is(typeof(T) Params == __parameters)) { 2990 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. 2991 return _args__[0]; 2992 } 2993 static if(__traits(compiles, fn())) { 2994 return fn(); 2995 } else { 2996 return Params[idx].init; 2997 } 2998 } else static assert(0); 2999 }