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