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