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