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