1 /* 2 FIXME: 3 pointer to member functions can give a way to wrap things 4 5 we'll pass it an opaque object as this and it will unpack and call the method 6 7 we can also auto-generate getters and setters for properties with this method 8 9 and constructors, so the script can create class objects too 10 */ 11 12 13 /++ 14 jsvar provides a D type called [var] that works similarly to the same in Javascript. 15 16 It is weakly (even weaker than JS, frequently returning null rather than throwing on 17 an invalid operation) and dynamically typed, but interops pretty easily with D itself: 18 19 --- 20 var a = 10; 21 a ~= "20"; 22 assert(a == "1020"); 23 24 var a = function(int b, int c) { return b+c; }; 25 // note the second set of () is because of broken @property 26 assert(a()(10,20) == 30); 27 28 var a = var.emptyObject; 29 a.foo = 30; 30 assert(a["foo"] == 30); 31 32 var b = json!q{ 33 "foo":12, 34 "bar":{"hey":[1,2,3,"lol"]} 35 }; 36 37 assert(b.bar.hey[1] == 2); 38 --- 39 40 41 You can also use [var.fromJson], a static method, to quickly and easily 42 read json or [var.toJson] to write it. 43 44 Also, if you combine this with my [arsd.script] module, you get pretty 45 easy interop with a little scripting language that resembles a cross between 46 D and Javascript - just like you can write in D itself using this type. 47 48 49 Properties: 50 $(LIST 51 * note that @property doesn't work right in D, so the opDispatch properties 52 will require double parenthesis to call as functions. 53 54 * Properties inside a var itself are set specially: 55 obj.propName._object = new PropertyPrototype(getter, setter); 56 ) 57 58 D structs can be turned to vars, but it is a copy. 59 60 Wrapping D native objects is coming later, the current ways suck. I really needed 61 properties to do them sanely at all, and now I have it. A native wrapped object will 62 also need to be set with _object prolly. 63 +/ 64 module arsd.jsvar; 65 66 version=new_std_json; 67 68 import std.stdio; 69 static import std.array; 70 import std.traits; 71 import std.conv; 72 import std.json; 73 74 // uda for wrapping classes 75 enum scriptable = "arsd_jsvar_compatible"; 76 77 /* 78 PrototypeObject FIXME: 79 make undefined variables reaction overloadable in PrototypeObject, not just a switch 80 81 script FIXME: 82 83 the Expression should keep scriptFilename and lineNumber around for error messages 84 85 it should consistently throw on missing semicolons 86 87 *) in operator 88 89 *) nesting comments, `` string literals 90 *) opDispatch overloading 91 *) properties???// 92 a.prop on the rhs => a.prop() 93 a.prop on the lhs => a.prop(rhs); 94 if opAssign, it can just do a.prop(a.prop().opBinary!op(rhs)); 95 96 But, how do we mark properties in var? Can we make them work this way in D too? 97 0) add global functions to the object (or at least provide a convenience function to make a pre-populated global object) 98 1) ensure operator precedence is sane 99 2) a++ would prolly be nice, and def -a 100 4) switches? 101 10) __FILE__ and __LINE__ as default function arguments should work like in D 102 16) stack traces on script exceptions 103 17) an exception type that we can create in the script 104 105 14) import???????/ it could just attach a particular object to the local scope, and the module decl just giving the local scope a name 106 there could be a super-global object that is the prototype of the "global" used here 107 then you import, and it pulls moduleGlobal.prototype = superGlobal.modulename... or soemthing. 108 109 to get the vars out in D, you'd have to be aware of this, since you pass the superglobal 110 hmmm maybe not worth it 111 112 though maybe to export vars there could be an explicit export namespace or something. 113 114 115 6) gotos? labels? labeled break/continue? 116 18) what about something like ruby's blocks or macros? parsing foo(arg) { code } is easy enough, but how would we use it? 117 118 var FIXME: 119 120 user defined operator overloading on objects, including opCall, opApply, and more 121 flesh out prototype objects for Array, String, and Function 122 123 looserOpEquals 124 125 it would be nice if delegates on native types could work 126 */ 127 128 static if(__VERSION__ <= 2076) { 129 // compatibility shims with gdc 130 enum JSONType { 131 object = JSON_TYPE.OBJECT, 132 null_ = JSON_TYPE.NULL, 133 false_ = JSON_TYPE.FALSE, 134 true_ = JSON_TYPE.TRUE, 135 integer = JSON_TYPE.INTEGER, 136 float_ = JSON_TYPE.FLOAT, 137 array = JSON_TYPE.ARRAY, 138 string = JSON_TYPE.STRING, 139 uinteger = JSON_TYPE.UINTEGER 140 } 141 } 142 143 144 /* 145 Script notes: 146 147 the one type is var. It works just like the var type in D from arsd.jsvar. 148 (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) 149 150 There is no comma operator, but you can use a scope as an expression: a++, b++; can be written as {a++;b++;} 151 */ 152 153 version(test_script) 154 struct Foop { 155 int a = 12; 156 string n = "hate"; 157 void speak() { writeln(n, " ", a); n = "love"; writeln(n, " is what it is now"); } 158 void speak2() { writeln("speak2 ", n, " ", a); } 159 } 160 version(test_script) 161 void main() { 162 import arsd.script; 163 writeln(interpret("x*x + 3*x;", var(["x":3]))); 164 165 { 166 var a = var.emptyObject; 167 a.qweq = 12; 168 } 169 170 // the WrappedNativeObject is disgusting 171 // but works. sort of. 172 /* 173 Foop foop2; 174 175 var foop; 176 foop._object = new WrappedNativeObject!Foop(foop2); 177 178 foop.speak()(); 179 foop.a = 25; 180 writeln(foop.n); 181 foop.speak2()(); 182 return; 183 */ 184 185 import arsd.script; 186 struct Test { 187 int a = 10; 188 string name = "ten"; 189 } 190 191 auto globals = var.emptyObject; 192 globals.lol = 100; 193 globals.rofl = 23; 194 195 globals.arrtest = var.emptyArray; 196 197 globals.write._function = (var _this, var[] args) { 198 string s; 199 foreach(a; args) 200 s ~= a.get!string; 201 writeln("script said: ", s); 202 return var(null); 203 }; 204 205 // call D defined functions in script 206 globals.func = (var a, var b) { writeln("Hello, world! You are : ", a, " and ", b); }; 207 208 globals.ex = () { throw new ScriptRuntimeException("test", 1); }; 209 210 globals.fun = { return var({ writeln("hello inside!"); }); }; 211 212 import std.file; 213 writeln(interpret(readText("scripttest_code.d"), globals)); 214 215 globals.ten = 10.0; 216 globals.five = 5.0; 217 writeln(interpret(q{ 218 var a = json!q{ }; 219 a.b = json!q{ }; 220 a.b.c = 10; 221 a; 222 }, globals)); 223 224 /* 225 globals.minigui = json!q{}; 226 import arsd.minigui; 227 globals.minigui.createWindow = { 228 var v; 229 auto mw = new MainWindow(); 230 v._object = new OpaqueNativeObject!(MainWindow)(mw); 231 v.loop = { mw.loop(); }; 232 return v; 233 }; 234 */ 235 236 repl(globals); 237 238 writeln("BACK IN D!"); 239 globals.c()(10); // call script defined functions in D (note: this runs the interpreter) 240 241 //writeln(globals._getMember("lol", false)); 242 return; 243 244 var k,l ; 245 246 var j = json!q{ 247 "hello": { 248 "data":[1,2,"giggle",4] 249 }, 250 "world":20 251 }; 252 253 writeln(j.hello.data[2]); 254 255 256 Test t; 257 var rofl = t; 258 writeln(rofl.name); 259 writeln(rofl.a); 260 261 rofl.a = "20"; 262 rofl.name = "twenty"; 263 264 t = rofl.get!Test; 265 writeln(t); 266 267 var a1 = 10; 268 a1 -= "5"; 269 a1 /= 2; 270 271 writeln(a1); 272 273 var a = 10; 274 var b = 20; 275 a = b; 276 277 b = 30; 278 a += 100.2; 279 writeln(a); 280 281 var c = var.emptyObject; 282 c.a = b; 283 284 var d = c; 285 d.b = 50; 286 287 writeln(c.b); 288 289 writeln(d.toJson()); 290 291 var e = a + b; 292 writeln(a, " + ", b, " = ", e); 293 294 e = function(var lol) { 295 writeln("hello with ",lol,"!"); 296 return lol + 10; 297 }; 298 299 writeln(e("15")); 300 301 if(var("ass") > 100) 302 writeln(var("10") / "3"); 303 } 304 305 template json(string s) { 306 // ctfe doesn't support the unions std.json uses :( 307 //enum json = var.fromJsonObject(s); 308 309 // FIXME we should at least validate string s at compile time 310 var json() { 311 return var.fromJson("{" ~ s ~ "}"); 312 } 313 } 314 315 // literals 316 317 // var a = varArray(10, "cool", 2); 318 // assert(a[0] == 10); assert(a[1] == "cool"); assert(a[2] == 2); 319 var varArray(T...)(T t) { 320 var a = var.emptyArray; 321 foreach(arg; t) 322 a ~= var(arg); 323 return a; 324 } 325 326 // var a = varObject("cool", 10, "bar", "baz"); 327 // assert(a.cool == 10 && a.bar == "baz"); 328 var varObject(T...)(T t) { 329 var a = var.emptyObject; 330 331 string lastString; 332 foreach(idx, arg; t) { 333 static if(idx % 2 == 0) { 334 lastString = arg; 335 } else { 336 assert(lastString !is null); 337 a[lastString] = arg; 338 lastString = null; 339 } 340 } 341 return a; 342 } 343 344 345 private double stringToNumber(string s) { 346 double r; 347 try { 348 r = to!double(s); 349 } catch (Exception e) { 350 r = double.nan; 351 } 352 353 return r; 354 } 355 356 private bool doubleIsInteger(double r) { 357 return (r == cast(long) r); 358 } 359 360 // helper template for operator overloading 361 private var _op(alias _this, alias this2, string op, T)(T t) if(op == "~") { 362 static if(is(T == var)) { 363 if(t.payloadType() == var.Type.Array) 364 return _op!(_this, this2, op)(t._payload._array); 365 else if(t.payloadType() == var.Type.String) 366 return _op!(_this, this2, op)(t._payload._string); 367 //else 368 //return _op!(_this, this2, op)(t.get!string); 369 } 370 371 if(this2.payloadType() == var.Type.Array) { 372 auto l = this2._payload._array; 373 static if(isArray!T && !isSomeString!T) 374 foreach(item; t) 375 l ~= var(item); 376 else 377 l ~= var(t); 378 379 _this._type = var.Type.Array; 380 _this._payload._array = l; 381 return _this; 382 } else if(this2.payloadType() == var.Type.String) { 383 auto l = this2._payload._string; 384 l ~= var(t).get!string; // is this right? 385 _this._type = var.Type.String; 386 _this._payload._string = l; 387 return _this; 388 } else { 389 auto l = this2.get!string; 390 l ~= var(t).get!string; 391 _this._type = var.Type.String; 392 _this._payload._string = l; 393 return _this; 394 } 395 396 assert(0); 397 398 } 399 400 // FIXME: maybe the bitops should be moved out to another function like ~ is 401 private var _op(alias _this, alias this2, string op, T)(T t) if(op != "~") { 402 static if(is(T == var)) { 403 if(t.payloadType() == var.Type.Integral) 404 return _op!(_this, this2, op)(t._payload._integral); 405 if(t.payloadType() == var.Type.Floating) 406 return _op!(_this, this2, op)(t._payload._floating); 407 if(t.payloadType() == var.Type.String) 408 return _op!(_this, this2, op)(t._payload._string); 409 throw new Exception("Attempted invalid operator `" ~ op ~ "` on variable of type " ~ to!string(t.payloadType())); 410 } else { 411 if(this2.payloadType() == var.Type.Integral) { 412 auto l = this2._payload._integral; 413 static if(isIntegral!T) { 414 mixin("l "~op~"= t;"); 415 _this._type = var.Type.Integral; 416 _this._payload._integral = l; 417 return _this; 418 } else static if(isFloatingPoint!T) { 419 static if(op == "&" || op == "|" || op == "^") { 420 this2._type = var.Type.Integral; 421 long f = l; 422 mixin("f "~op~"= cast(long) t;"); 423 _this._type = var.Type.Integral; 424 _this._payload._integral = f; 425 } else { 426 this2._type = var.Type.Floating; 427 double f = l; 428 mixin("f "~op~"= t;"); 429 _this._type = var.Type.Floating; 430 _this._payload._floating = f; 431 } 432 return _this; 433 } else static if(isSomeString!T) { 434 auto rhs = stringToNumber(t); 435 if(doubleIsInteger(rhs)) { 436 mixin("l "~op~"= cast(long) rhs;"); 437 _this._type = var.Type.Integral; 438 _this._payload._integral = l; 439 } else{ 440 static if(op == "&" || op == "|" || op == "^") { 441 long f = l; 442 mixin("f "~op~"= cast(long) rhs;"); 443 _this._type = var.Type.Integral; 444 _this._payload._integral = f; 445 } else { 446 double f = l; 447 mixin("f "~op~"= rhs;"); 448 _this._type = var.Type.Floating; 449 _this._payload._floating = f; 450 } 451 } 452 return _this; 453 454 } 455 } else if(this2.payloadType() == var.Type.Floating) { 456 auto f = this._payload._floating; 457 458 static if(isIntegral!T || isFloatingPoint!T) { 459 static if(op == "&" || op == "|" || op == "^") { 460 long argh = cast(long) f; 461 mixin("argh "~op~"= cast(long) t;"); 462 _this._type = var.Type.Integral; 463 _this._payload._integral = argh; 464 } else { 465 mixin("f "~op~"= t;"); 466 _this._type = var.Type.Floating; 467 _this._payload._floating = f; 468 } 469 return _this; 470 } else static if(isSomeString!T) { 471 auto rhs = stringToNumber(t); 472 473 static if(op == "&" || op == "|" || op == "^") { 474 long pain = cast(long) f; 475 mixin("pain "~op~"= cast(long) rhs;"); 476 _this._type = var.Type.Integral; 477 _this._payload._floating = pain; 478 } else { 479 mixin("f "~op~"= rhs;"); 480 _this._type = var.Type.Floating; 481 _this._payload._floating = f; 482 } 483 return _this; 484 } else static assert(0); 485 } else if(this2.payloadType() == var.Type.String) { 486 static if(op == "&" || op == "|" || op == "^") { 487 long r = cast(long) stringToNumber(this2._payload._string); 488 long rhs; 489 } else { 490 double r = stringToNumber(this2._payload._string); 491 double rhs; 492 } 493 494 static if(isSomeString!T) { 495 rhs = cast(typeof(rhs)) stringToNumber(t); 496 } else { 497 rhs = to!(typeof(rhs))(t); 498 } 499 500 mixin("r " ~ op ~ "= rhs;"); 501 502 static if(is(typeof(r) == double)) { 503 _this._type = var.Type.Floating; 504 _this._payload._floating = r; 505 } else static if(is(typeof(r) == long)) { 506 _this._type = var.Type.Integral; 507 _this._payload._integral = r; 508 } else static assert(0); 509 return _this; 510 } else { 511 // the operation is nonsensical, we should throw or ignore it 512 var i = 0; 513 return i; 514 } 515 } 516 517 assert(0); 518 } 519 520 521 /// 522 struct var { 523 public this(T)(T t) { 524 static if(is(T == var)) 525 this = t; 526 else 527 this.opAssign(t); 528 } 529 530 public var _copy() { 531 final switch(payloadType()) { 532 case Type.Integral: 533 case Type.Boolean: 534 case Type.Floating: 535 case Type.Function: 536 case Type.String: 537 // since strings are immutable, we can pretend they are value types too 538 return this; // value types don't need anything special to be copied 539 540 case Type.Array: 541 var cp; 542 cp = this._payload._array[]; 543 return cp; 544 case Type.Object: 545 var cp; 546 if(this._payload._object !is null) 547 cp._object = this._payload._object.copy; 548 return cp; 549 } 550 } 551 552 public bool opCast(T:bool)() { 553 final switch(this._type) { 554 case Type.Object: 555 return this._payload._object !is null; 556 case Type.Array: 557 return this._payload._array.length != 0; 558 case Type.String: 559 return this._payload._string.length != 0; 560 case Type.Integral: 561 return this._payload._integral != 0; 562 case Type.Floating: 563 return this._payload._floating != 0; 564 case Type.Boolean: 565 return this._payload._boolean; 566 case Type.Function: 567 return this._payload._function !is null; 568 } 569 } 570 571 public int opApply(scope int delegate(ref var) dg) { 572 foreach(i, item; this) 573 if(auto result = dg(item)) 574 return result; 575 return 0; 576 } 577 578 public int opApply(scope int delegate(var, ref var) dg) { 579 if(this.payloadType() == Type.Array) { 580 foreach(i, ref v; this._payload._array) 581 if(auto result = dg(var(i), v)) 582 return result; 583 } else if(this.payloadType() == Type.Object && this._payload._object !is null) { 584 // FIXME: if it offers input range primitives, we should use them 585 // FIXME: user defined opApply on the object 586 foreach(k, ref v; this._payload._object) 587 if(auto result = dg(var(k), v)) 588 return result; 589 } else if(this.payloadType() == Type.String) { 590 // this is to prevent us from allocating a new string on each character, hopefully limiting that massively 591 static immutable string chars = makeAscii!(); 592 593 foreach(i, dchar c; this._payload._string) { 594 var lol = ""; 595 if(c < 128) 596 lol._payload._string = chars[c .. c + 1]; 597 else 598 lol._payload._string = to!string(""d ~ c); // blargh, how slow can we go? 599 if(auto result = dg(var(i), lol)) 600 return result; 601 } 602 } 603 // throw invalid foreach aggregate 604 605 return 0; 606 } 607 608 609 public T opCast(T)() { 610 return this.get!T; 611 } 612 613 public auto ref putInto(T)(ref T t) { 614 return t = this.get!T; 615 } 616 617 // if it is var, we'll just blit it over 618 public var opAssign(T)(T t) if(!is(T == var)) { 619 static if(__traits(compiles, this = t.toArsdJsvar())) { 620 this = t.toArsdJsvar(); 621 } else static if(isFloatingPoint!T) { 622 this._type = Type.Floating; 623 this._payload._floating = t; 624 } else static if(isIntegral!T) { 625 this._type = Type.Integral; 626 this._payload._integral = t; 627 } else static if(isCallable!T) { 628 this._type = Type.Function; 629 static if(is(T == typeof(this._payload._function))) { 630 this._payload._function = t; 631 } else 632 this._payload._function = delegate var(var _this, var[] args) { 633 var ret; 634 635 ParameterTypeTuple!T fargs; 636 foreach(idx, a; fargs) { 637 if(idx == args.length) 638 break; 639 cast(Unqual!(typeof(a))) fargs[idx] = args[idx].get!(typeof(a)); 640 } 641 642 static if(is(ReturnType!t == void)) { 643 t(fargs); 644 } else { 645 ret = t(fargs); 646 } 647 648 return ret; 649 }; 650 } else static if(isSomeString!T) { 651 this._type = Type.String; 652 this._payload._string = to!string(t); 653 } else static if(is(T : PrototypeObject)) { 654 // support direct assignment of pre-made implementation objects 655 // so prewrapped stuff can be easily passed. 656 this._type = Type.Object; 657 this._payload._object = t; 658 } else static if(is(T == class) || .isScriptableOpaque!T) { 659 // auto-wrap other classes with reference semantics 660 this._type = Type.Object; 661 this._payload._object = wrapOpaquely(t); 662 } else static if(is(T == struct) || isAssociativeArray!T) { 663 // copy structs and assoc arrays by value into a var object 664 this._type = Type.Object; 665 auto obj = new PrototypeObject(); 666 this._payload._object = obj; 667 668 static if(is(T == struct)) 669 foreach(member; __traits(allMembers, T)) { 670 static if(__traits(compiles, __traits(getMember, t, member))) { 671 static if(is(typeof(__traits(getMember, t, member)) == function)) { 672 // skipping these because the delegate we get isn't going to work anyway; the object may be dead and certainly won't be updated 673 //this[member] = &__traits(getMember, proxyObject, member); 674 675 // but for simple toString, I'll allow it by recreating the object on demand 676 // and then calling the original function. (I might be able to do that for more but 677 // idk, just doing simple thing first) 678 static if(member == "toString" && is(typeof(&__traits(getMember, t, member)) == string delegate())) { 679 this[member]._function = delegate(var _this, var[] args) { 680 auto val = _this.get!T; 681 return var(val.toString()); 682 }; 683 } 684 } else static if(is(typeof(__traits(getMember, t, member)))) { 685 this[member] = __traits(getMember, t, member); 686 } 687 } 688 } else { 689 // assoc array 690 foreach(l, v; t) { 691 this[var(l)] = var(v); 692 } 693 } 694 } else static if(isArray!T) { 695 this._type = Type.Array; 696 var[] arr; 697 arr.length = t.length; 698 static if(!is(T == void[])) // we can't append a void array but it is nice to support x = []; 699 foreach(i, item; t) 700 arr[i] = var(item); 701 this._payload._array = arr; 702 } else static if(is(T == bool)) { 703 this._type = Type.Boolean; 704 this._payload._boolean = t; 705 } else static if(isSomeChar!T) { 706 this._type = Type.String; 707 this._payload._string = ""; 708 import std.utf; 709 char[4] ugh; 710 auto size = encode(ugh, t); 711 this._payload._string = ugh[0..size].idup; 712 }// else static assert(0, "unsupported type"); 713 714 return this; 715 } 716 717 public size_t opDollar() { 718 return this.length().get!size_t; 719 } 720 721 public var opOpAssign(string op, T)(T t) { 722 if(payloadType() == Type.Object) { 723 if(this._payload._object !is null) { 724 var* operator = this._payload._object._peekMember("opOpAssign", true); 725 if(operator !is null && operator._type == Type.Function) 726 return operator.call(this, op, t); 727 } 728 } 729 730 return _op!(this, this, op, T)(t); 731 } 732 733 public var opUnary(string op : "-")() { 734 static assert(op == "-"); 735 final switch(payloadType()) { 736 case Type.Object: 737 case Type.Array: 738 case Type.Boolean: 739 case Type.String: 740 case Type.Function: 741 assert(0); // FIXME 742 //break; 743 case Type.Integral: 744 return var(-this.get!long); 745 case Type.Floating: 746 return var(-this.get!double); 747 } 748 } 749 750 public var opBinary(string op, T)(T t) { 751 var n; 752 if(payloadType() == Type.Object) { 753 var* operator = this._payload._object._peekMember("opBinary", true); 754 if(operator !is null && operator._type == Type.Function) { 755 return operator.call(this, op, t); 756 } 757 } 758 return _op!(n, this, op, T)(t); 759 } 760 761 public var opBinaryRight(string op, T)(T s) { 762 return var(s).opBinary!op(this); 763 } 764 765 // this in foo 766 public var* opBinary(string op : "in", T)(T s) { 767 var rhs = var(s); 768 return rhs.opBinaryRight!"in"(this); 769 } 770 771 // foo in this 772 public var* opBinaryRight(string op : "in", T)(T s) { 773 // this needs to be an object 774 return var(s).get!string in this._object._properties; 775 } 776 777 public var apply(var _this, var[] args) { 778 if(this.payloadType() == Type.Function) { 779 if(this._payload._function is null) { 780 version(jsvar_throw) 781 throw new DynamicTypeException(this, Type.Function); 782 else 783 return var(null); 784 } 785 return this._payload._function(_this, args); 786 } else if(this.payloadType() == Type.Object) { 787 if(this._payload._object is null) { 788 version(jsvar_throw) 789 throw new DynamicTypeException(this, Type.Function); 790 else 791 return var(null); 792 } 793 var* operator = this._payload._object._peekMember("opCall", true); 794 if(operator !is null && operator._type == Type.Function) 795 return operator.apply(_this, args); 796 } 797 798 version(jsvar_throw) 799 throw new DynamicTypeException(this, Type.Function); 800 801 if(this.payloadType() == Type.Integral || this.payloadType() == Type.Floating) { 802 if(args.length) 803 return var(this.get!double * args[0].get!double); 804 } 805 806 //return this; 807 return var(null); 808 } 809 810 public var call(T...)(var _this, T t) { 811 var[] args; 812 foreach(a; t) { 813 args ~= var(a); 814 } 815 return this.apply(_this, args); 816 } 817 818 public var opCall(T...)(T t) { 819 return this.call(this, t); 820 } 821 822 /* 823 public var applyWithMagicLocals(var _this, var[] args, var[string] magicLocals) { 824 825 } 826 */ 827 828 public string toString() { 829 return this.get!string; 830 } 831 832 public T getWno(T)() { 833 if(payloadType == Type.Object) { 834 if(auto wno = cast(WrappedNativeObject) this._payload._object) { 835 auto no = cast(T) wno.getObject(); 836 if(no !is null) 837 return no; 838 } 839 } 840 return null; 841 } 842 843 public T get(T)() if(!is(T == void)) { 844 static if(is(T == var)) { 845 return this; 846 } else static if(__traits(compiles, T.fromJsVar(var.init))) { 847 return T.fromJsVar(this); 848 } else static if(__traits(compiles, T(this))) { 849 return T(this); 850 } else static if(__traits(compiles, new T(this))) { 851 return new T(this); 852 } else 853 final switch(payloadType) { 854 case Type.Boolean: 855 static if(is(T == bool)) 856 return this._payload._boolean; 857 else static if(isFloatingPoint!T || isIntegral!T) 858 return cast(T) (this._payload._boolean ? 1 : 0); // the cast is for enums, I don't like this so FIXME 859 else static if(isSomeString!T) 860 return this._payload._boolean ? "true" : "false"; 861 else 862 return T.init; 863 case Type.Object: 864 static if(isAssociativeArray!T) { 865 T ret; 866 if(this._payload._object !is null) 867 foreach(k, v; this._payload._object._properties) 868 ret[to!(KeyType!T)(k)] = v.get!(ValueType!T); 869 870 return ret; 871 } else static if(is(T : PrototypeObject)) { 872 // they are requesting an implementation object, just give it to them 873 return cast(T) this._payload._object; 874 } else static if(isScriptableOpaque!(Unqual!T)) { 875 if(auto wno = cast(WrappedOpaque!(Unqual!T)) this._payload._object) { 876 return wno.wrapping(); 877 } 878 static if(is(T == R*, R)) 879 if(auto wno = cast(WrappedOpaque!(Unqual!(R))) this._payload._object) { 880 return wno.wrapping(); 881 } 882 throw new DynamicTypeException(this, Type.Object); // FIXME: could be better 883 } else static if(is(T == struct) || is(T == class)) { 884 // first, we'll try to give them back the native object we have, if we have one 885 static if(is(T : Object)) { 886 if(auto wno = cast(WrappedNativeObject) this._payload._object) { 887 auto no = cast(T) wno.getObject(); 888 if(no !is null) 889 return no; 890 } 891 892 // FIXME: this is kinda weird. 893 return null; 894 } else { 895 896 // failing that, generic struct or class getting: try to fill in the fields by name 897 T t; 898 bool initialized = true; 899 static if(is(T == class)) { 900 static if(__traits(compiles, new T())) 901 t = new T(); 902 else 903 initialized = false; 904 } 905 906 907 if(initialized) 908 foreach(i, a; t.tupleof) { 909 cast(Unqual!(typeof((a)))) t.tupleof[i] = this[t.tupleof[i].stringof[2..$]].get!(typeof(a)); 910 } 911 912 return t; 913 } 914 } else static if(isSomeString!T) { 915 if(this._object !is null) 916 return this._object.toString(); 917 return "null"; 918 } else 919 return T.init; 920 case Type.Integral: 921 static if(isFloatingPoint!T || isIntegral!T) 922 return to!T(this._payload._integral); 923 else static if(isSomeString!T) 924 return to!string(this._payload._integral); 925 else 926 return T.init; 927 case Type.Floating: 928 static if(isFloatingPoint!T || isIntegral!T) 929 return to!T(this._payload._floating); 930 else static if(isSomeString!T) 931 return to!string(this._payload._floating); 932 else 933 return T.init; 934 case Type.String: 935 static if(__traits(compiles, to!T(this._payload._string))) { 936 try { 937 return to!T(this._payload._string); 938 } catch (Exception e) { return T.init; } 939 } else 940 return T.init; 941 case Type.Array: 942 import std.range; 943 auto pl = this._payload._array; 944 static if(isSomeString!T) { 945 return to!string(pl); 946 } else static if(isArray!T) { 947 T ret; 948 static if(is(ElementType!T == void)) { 949 static assert(0, "try wrapping the function to get rid of void[] args"); 950 //alias getType = ubyte; 951 } else 952 alias getType = ElementType!T; 953 foreach(item; pl) 954 ret ~= item.get!(getType); 955 return ret; 956 } else 957 return T.init; 958 // is it sane to translate anything else? 959 case Type.Function: 960 static if(isSomeString!T) { 961 return "<function>"; 962 } else static if(isDelegate!T) { 963 // making a local copy because otherwise the delegate might refer to a struct on the stack and get corrupted later or something 964 auto func = this._payload._function; 965 966 // the static helper lets me pass specific variables to the closure 967 static T helper(typeof(func) func) { 968 return delegate ReturnType!T (ParameterTypeTuple!T args) { 969 var[] arr; 970 foreach(arg; args) 971 arr ~= var(arg); 972 var ret = func(var(null), arr); 973 static if(is(ReturnType!T == void)) 974 return; 975 else 976 return ret.get!(ReturnType!T); 977 }; 978 } 979 980 return helper(func); 981 982 } else 983 return T.init; 984 // FIXME: we just might be able to do better for both of these 985 //break; 986 } 987 } 988 989 public T nullCoalesce(T)(T t) { 990 if(_type == Type.Object && _payload._object is null) 991 return t; 992 return this.get!T; 993 } 994 995 public int opCmp(T)(T t) { 996 auto f = this.get!double; 997 static if(is(T == var)) 998 auto r = t.get!double; 999 else 1000 auto r = t; 1001 return cast(int)(f - r); 1002 } 1003 1004 public bool opEquals(T)(T t) { 1005 return this.opEquals(var(t)); 1006 } 1007 1008 public bool opEquals(T:var)(T t) const { 1009 // FIXME: should this be == or === ? 1010 if(this._type != t._type) 1011 return false; 1012 final switch(this._type) { 1013 case Type.Object: 1014 return _payload._object is t._payload._object; 1015 case Type.Integral: 1016 return _payload._integral == t._payload._integral; 1017 case Type.Boolean: 1018 return _payload._boolean == t._payload._boolean; 1019 case Type.Floating: 1020 return _payload._floating == t._payload._floating; // FIXME: approxEquals? 1021 case Type.String: 1022 return _payload._string == t._payload._string; 1023 case Type.Function: 1024 return _payload._function is t._payload._function; 1025 case Type.Array: 1026 return _payload._array == t._payload._array; 1027 } 1028 assert(0); 1029 } 1030 1031 public enum Type { 1032 Object, Array, Integral, Floating, String, Function, Boolean 1033 } 1034 1035 public Type payloadType() { 1036 return _type; 1037 } 1038 1039 private Type _type; 1040 1041 private union Payload { 1042 PrototypeObject _object; 1043 var[] _array; 1044 long _integral; 1045 double _floating; 1046 string _string; 1047 bool _boolean; 1048 var delegate(var _this, var[] args) _function; 1049 } 1050 1051 package VarMetadata _metadata; 1052 1053 public void _function(var delegate(var, var[]) f) { 1054 this._payload._function = f; 1055 this._type = Type.Function; 1056 } 1057 1058 /* 1059 public void _function(var function(var, var[]) f) { 1060 var delegate(var, var[]) dg; 1061 dg.ptr = null; 1062 dg.funcptr = f; 1063 this._function = dg; 1064 } 1065 */ 1066 1067 public void _object(PrototypeObject obj) { 1068 this._type = Type.Object; 1069 this._payload._object = obj; 1070 } 1071 1072 public PrototypeObject _object() { 1073 if(this._type == Type.Object) 1074 return this._payload._object; 1075 return null; 1076 } 1077 1078 package Payload _payload; 1079 1080 private void _requireType(Type t, string file = __FILE__, size_t line = __LINE__){ 1081 if(this.payloadType() != t) 1082 throw new DynamicTypeException(this, t, file, line); 1083 } 1084 1085 public var opSlice(var e1, var e2) { 1086 return this.opSlice(e1.get!ptrdiff_t, e2.get!ptrdiff_t); 1087 } 1088 1089 public var opSlice(ptrdiff_t e1, ptrdiff_t e2) { 1090 if(this.payloadType() == Type.Array) { 1091 if(e1 > _payload._array.length) 1092 e1 = _payload._array.length; 1093 if(e2 > _payload._array.length) 1094 e2 = _payload._array.length; 1095 return var(_payload._array[e1 .. e2]); 1096 } 1097 if(this.payloadType() == Type.String) { 1098 if(e1 > _payload._string.length) 1099 e1 = _payload._string.length; 1100 if(e2 > _payload._string.length) 1101 e2 = _payload._string.length; 1102 return var(_payload._string[e1 .. e2]); 1103 } 1104 if(this.payloadType() == Type.Object) { 1105 var operator = this["opSlice"]; 1106 if(operator._type == Type.Function) { 1107 return operator.call(this, e1, e2); 1108 } 1109 } 1110 1111 // might be worth throwing here too 1112 return var(null); 1113 } 1114 1115 public @property ref var opDispatch(string name, string file = __FILE__, size_t line = __LINE__)() { 1116 return this[name]; 1117 } 1118 1119 public @property ref var opDispatch(string name, string file = __FILE__, size_t line = __LINE__, T)(T r) { 1120 return this.opIndexAssign!T(r, name); 1121 } 1122 1123 public ref var opIndex(var name, string file = __FILE__, size_t line = __LINE__) { 1124 return opIndex(name.get!string, file, line); 1125 } 1126 1127 public ref var opIndexAssign(T)(T t, var name, string file = __FILE__, size_t line = __LINE__) { 1128 return opIndexAssign(t, name.get!string, file, line); 1129 } 1130 1131 public ref var opIndex(string name, string file = __FILE__, size_t line = __LINE__) { 1132 // if name is numeric, we should convert to int for arrays 1133 if(name.length && name[0] >= '0' && name[0] <= '9' && this.payloadType() == Type.Array) 1134 return opIndex(to!size_t(name), file, line); 1135 1136 if(this.payloadType() != Type.Object && name == "prototype") 1137 return prototype(); 1138 1139 if(name == "typeof") { 1140 var* tmp = new var; 1141 *tmp = to!string(this.payloadType()); 1142 return *tmp; 1143 } 1144 1145 if(name == "toJson") { 1146 var* tmp = new var; 1147 *tmp = to!string(this.toJson()); 1148 return *tmp; 1149 } 1150 1151 if(name == "length" && this.payloadType() == Type.String) { 1152 var* tmp = new var; 1153 *tmp = _payload._string.length; 1154 return *tmp; 1155 } 1156 if(name == "length" && this.payloadType() == Type.Array) { 1157 var* tmp = new var; 1158 *tmp = _payload._array.length; 1159 return *tmp; 1160 } 1161 if(name == "__prop" && this.payloadType() == Type.Object) { 1162 var* tmp = new var; 1163 (*tmp)._function = delegate var(var _this, var[] args) { 1164 if(args.length == 0) 1165 return var(null); 1166 if(args.length == 1) { 1167 auto peek = this._payload._object._peekMember(args[0].get!string, false); 1168 if(peek is null) 1169 return var(null); 1170 else 1171 return *peek; 1172 } 1173 if(args.length == 2) { 1174 auto peek = this._payload._object._peekMember(args[0].get!string, false); 1175 if(peek is null) { 1176 this._payload._object._properties[args[0].get!string] = args[1]; 1177 return var(null); 1178 } else { 1179 *peek = args[1]; 1180 return *peek; 1181 } 1182 1183 } 1184 throw new Exception("too many args"); 1185 }; 1186 return *tmp; 1187 } 1188 1189 PrototypeObject from; 1190 if(this.payloadType() == Type.Object) 1191 from = _payload._object; 1192 else { 1193 var pt = this.prototype(); 1194 assert(pt.payloadType() == Type.Object); 1195 from = pt._payload._object; 1196 } 1197 1198 if(from is null) { 1199 version(jsvar_throw) 1200 throw new DynamicTypeException(var(null), Type.Object, file, line); 1201 else 1202 return *(new var); 1203 } 1204 return from._getMember(name, true, false, file, line); 1205 } 1206 1207 public ref var opIndexAssign(T)(T t, string name, string file = __FILE__, size_t line = __LINE__) { 1208 if(this.payloadType == Type.Array && name.appearsNumeric()) { 1209 try { 1210 auto i = to!size_t(name); 1211 return opIndexAssign(t, i, file, line); 1212 } catch(Exception) 1213 {} // ignore bad index, use it as a string instead lol 1214 } 1215 _requireType(Type.Object); // FIXME? 1216 if(_payload._object is null) 1217 throw new DynamicTypeException(var(null), Type.Object, file, line); 1218 1219 return this._payload._object._setMember(name, var(t), false, false, false, file, line); 1220 } 1221 1222 public ref var opIndexAssignNoOverload(T)(T t, string name, string file = __FILE__, size_t line = __LINE__) { 1223 if(name.length && name[0] >= '0' && name[0] <= '9') 1224 return opIndexAssign(t, to!size_t(name), file, line); 1225 _requireType(Type.Object); // FIXME? 1226 if(_payload._object is null) 1227 throw new DynamicTypeException(var(null), Type.Object, file, line); 1228 1229 return this._payload._object._setMember(name, var(t), false, false, true, file, line); 1230 } 1231 1232 1233 public ref var opIndex(size_t idx, string file = __FILE__, size_t line = __LINE__) { 1234 if(_type == Type.Array) { 1235 auto arr = this._payload._array; 1236 if(idx < arr.length) 1237 return arr[idx]; 1238 } else if(_type == Type.Object) { 1239 // objects might overload opIndex 1240 var* n = new var(); 1241 if("opIndex" in this) 1242 *n = this["opIndex"](idx); 1243 return *n; 1244 } 1245 version(jsvar_throw) 1246 throw new DynamicTypeException(this, Type.Array, file, line); 1247 var* n = new var(); 1248 return *n; 1249 } 1250 1251 public ref var opIndexAssign(T)(T t, size_t idx, string file = __FILE__, size_t line = __LINE__) { 1252 if(_type == Type.Array) { 1253 if(idx >= this._payload._array.length) 1254 this._payload._array.length = idx + 1; 1255 this._payload._array[idx] = t; 1256 return this._payload._array[idx]; 1257 } else if(_type == Type.Object) { 1258 return opIndexAssign(t, to!string(idx), file, line); 1259 } 1260 version(jsvar_throw) 1261 throw new DynamicTypeException(this, Type.Array, file, line); 1262 var* n = new var(); 1263 return *n; 1264 } 1265 1266 ref var _getOwnProperty(string name, string file = __FILE__, size_t line = __LINE__) { 1267 if(_type == Type.Object) { 1268 if(_payload._object !is null) { 1269 auto peek = this._payload._object._peekMember(name, false); 1270 if(peek !is null) 1271 return *peek; 1272 } 1273 } 1274 version(jsvar_throw) 1275 throw new DynamicTypeException(this, Type.Object, file, line); 1276 var* n = new var(); 1277 return *n; 1278 } 1279 1280 @property static var emptyObject(PrototypeObject prototype = null) { 1281 var v; 1282 v._type = Type.Object; 1283 v._payload._object = new PrototypeObject(); 1284 v._payload._object.prototype = prototype; 1285 return v; 1286 } 1287 1288 @property static var emptyObject(var prototype) { 1289 if(prototype._type == Type.Object) 1290 return var.emptyObject(prototype._payload._object); 1291 return var.emptyObject(); 1292 } 1293 1294 @property PrototypeObject prototypeObject() { 1295 var v = prototype(); 1296 if(v._type == Type.Object) 1297 return v._payload._object; 1298 return null; 1299 } 1300 1301 // what I call prototype is more like what Mozilla calls __proto__, but tbh I think this is better so meh 1302 @property ref var prototype() { 1303 static var _arrayPrototype; 1304 static var _functionPrototype; 1305 static var _stringPrototype; 1306 1307 final switch(payloadType()) { 1308 case Type.Array: 1309 assert(_arrayPrototype._type == Type.Object); 1310 if(_arrayPrototype._payload._object is null) { 1311 _arrayPrototype._object = new PrototypeObject(); 1312 } 1313 1314 return _arrayPrototype; 1315 case Type.Function: 1316 assert(_functionPrototype._type == Type.Object); 1317 if(_functionPrototype._payload._object is null) { 1318 _functionPrototype._object = new PrototypeObject(); 1319 } 1320 1321 return _functionPrototype; 1322 case Type.String: 1323 assert(_stringPrototype._type == Type.Object); 1324 if(_stringPrototype._payload._object is null) { 1325 auto p = new PrototypeObject(); 1326 _stringPrototype._object = p; 1327 1328 var replaceFunction; 1329 replaceFunction._type = Type.Function; 1330 replaceFunction._function = (var _this, var[] args) { 1331 string s = _this.toString(); 1332 import std.array : replace; 1333 return var(std.array.replace(s, 1334 args[0].toString(), 1335 args[1].toString())); 1336 }; 1337 1338 p._properties["replace"] = replaceFunction; 1339 } 1340 1341 return _stringPrototype; 1342 case Type.Object: 1343 if(_payload._object) 1344 return _payload._object._prototype; 1345 // FIXME: should we do a generic object prototype? 1346 break; 1347 case Type.Integral: 1348 case Type.Floating: 1349 case Type.Boolean: 1350 // these types don't have prototypes 1351 } 1352 1353 1354 var* v = new var(null); 1355 return *v; 1356 } 1357 1358 @property static var emptyArray() { 1359 var v; 1360 v._type = Type.Array; 1361 return v; 1362 } 1363 1364 static var fromJson(string json) { 1365 auto decoded = parseJSON(json); 1366 return var.fromJsonValue(decoded); 1367 } 1368 1369 static var fromJsonValue(JSONValue v) { 1370 var ret; 1371 1372 final switch(v.type) { 1373 case JSONType..string: 1374 ret = v.str; 1375 break; 1376 case JSONType.uinteger: 1377 ret = v.uinteger; 1378 break; 1379 case JSONType.integer: 1380 ret = v.integer; 1381 break; 1382 case JSONType.float_: 1383 ret = v.floating; 1384 break; 1385 case JSONType.object: 1386 ret = var.emptyObject; 1387 foreach(k, val; v.object) { 1388 ret[k] = var.fromJsonValue(val); 1389 } 1390 break; 1391 case JSONType.array: 1392 ret = var.emptyArray; 1393 ret._payload._array.length = v.array.length; 1394 foreach(idx, item; v.array) { 1395 ret._payload._array[idx] = var.fromJsonValue(item); 1396 } 1397 break; 1398 case JSONType.true_: 1399 ret = true; 1400 break; 1401 case JSONType.false_: 1402 ret = false; 1403 break; 1404 case JSONType.null_: 1405 ret = null; 1406 break; 1407 } 1408 1409 return ret; 1410 } 1411 1412 string toJson() { 1413 auto v = toJsonValue(); 1414 return toJSON(v); 1415 } 1416 1417 JSONValue toJsonValue() { 1418 JSONValue val; 1419 final switch(payloadType()) { 1420 case Type.Boolean: 1421 version(new_std_json) 1422 val = this._payload._boolean; 1423 else { 1424 if(this._payload._boolean) 1425 val.type = JSONType.true_; 1426 else 1427 val.type = JSONType.false_; 1428 } 1429 break; 1430 case Type.Object: 1431 version(new_std_json) { 1432 if(_payload._object is null) { 1433 val = null; 1434 } else { 1435 val = _payload._object.toJsonValue(); 1436 } 1437 } else { 1438 if(_payload._object is null) { 1439 val.type = JSONType.null_; 1440 } else { 1441 val.type = JSONType.object; 1442 foreach(k, v; _payload._object._properties) 1443 val.object[k] = v.toJsonValue(); 1444 } 1445 } 1446 break; 1447 case Type.String: 1448 version(new_std_json) { } else { 1449 val.type = JSONType..string; 1450 } 1451 val.str = _payload._string; 1452 break; 1453 case Type.Integral: 1454 version(new_std_json) { } else { 1455 val.type = JSONType.integer; 1456 } 1457 val.integer = _payload._integral; 1458 break; 1459 case Type.Floating: 1460 version(new_std_json) { } else { 1461 val.type = JSONType.float_; 1462 } 1463 val.floating = _payload._floating; 1464 break; 1465 case Type.Array: 1466 auto a = _payload._array; 1467 JSONValue[] tmp; 1468 tmp.length = a.length; 1469 foreach(i, v; a) { 1470 tmp[i] = v.toJsonValue(); 1471 } 1472 1473 version(new_std_json) { 1474 val = tmp; 1475 } else { 1476 val.type = JSONType.array; 1477 val.array = tmp; 1478 } 1479 break; 1480 case Type.Function: 1481 version(new_std_json) 1482 val = null; 1483 else 1484 val.type = JSONType.null_; // ideally we would just skip it entirely... 1485 break; 1486 } 1487 return val; 1488 } 1489 } 1490 1491 class PrototypeObject { 1492 string name; 1493 var _prototype; 1494 1495 package PrototypeObject _secondary; // HACK don't use this 1496 1497 PrototypeObject prototype() { 1498 if(_prototype.payloadType() == var.Type.Object) 1499 return _prototype._payload._object; 1500 return null; 1501 } 1502 1503 PrototypeObject prototype(PrototypeObject set) { 1504 this._prototype._object = set; 1505 return set; 1506 } 1507 1508 override string toString() { 1509 1510 var* ts = _peekMember("toString", true); 1511 if(ts) { 1512 var _this; 1513 _this._object = this; 1514 return (*ts).call(_this).get!string; 1515 } 1516 1517 JSONValue val; 1518 version(new_std_json) { 1519 JSONValue[string] tmp; 1520 foreach(k, v; this._properties) 1521 tmp[k] = v.toJsonValue(); 1522 val.object = tmp; 1523 } else { 1524 val.type = JSONType.object; 1525 foreach(k, v; this._properties) 1526 val.object[k] = v.toJsonValue(); 1527 } 1528 1529 return toJSON(val); 1530 } 1531 1532 var[string] _properties; 1533 1534 PrototypeObject copy() { 1535 auto n = new PrototypeObject(); 1536 n.prototype = this.prototype; 1537 n.name = this.name; 1538 foreach(k, v; _properties) { 1539 n._properties[k] = v._copy; 1540 } 1541 return n; 1542 } 1543 1544 PrototypeObject copyPropertiesFrom(PrototypeObject p) { 1545 foreach(k, v; p._properties) { 1546 this._properties[k] = v._copy; 1547 } 1548 return this; 1549 } 1550 1551 var* _peekMember(string name, bool recurse) { 1552 if(name == "prototype") 1553 return &_prototype; 1554 1555 auto curr = this; 1556 1557 // for the secondary hack 1558 bool triedOne = false; 1559 // for the secondary hack 1560 PrototypeObject possibleSecondary; 1561 1562 tryAgain: 1563 do { 1564 auto prop = name in curr._properties; 1565 if(prop is null) { 1566 // the secondary hack is to do more scoping in the script, it is really hackish 1567 if(possibleSecondary is null) 1568 possibleSecondary = curr._secondary; 1569 1570 if(!recurse) 1571 break; 1572 else 1573 curr = curr.prototype; 1574 } else 1575 return prop; 1576 } while(curr); 1577 1578 if(possibleSecondary !is null) { 1579 curr = possibleSecondary; 1580 if(!triedOne) { 1581 triedOne = true; 1582 goto tryAgain; 1583 } 1584 } 1585 1586 return null; 1587 } 1588 1589 // FIXME: maybe throw something else 1590 /*package*/ ref var _getMember(string name, bool recurse, bool throwOnFailure, string file = __FILE__, size_t line = __LINE__) { 1591 var* mem = _peekMember(name, recurse); 1592 1593 if(mem !is null) { 1594 // If it is a property, we need to call the getter on it 1595 if((*mem).payloadType == var.Type.Object && cast(PropertyPrototype) (*mem)._payload._object) { 1596 auto prop = cast(PropertyPrototype) (*mem)._payload._object; 1597 return prop.get; 1598 } 1599 return *mem; 1600 } 1601 1602 mem = _peekMember("opIndex", recurse); 1603 if(mem !is null) { 1604 auto n = new var; 1605 *n = ((*mem)(name)); 1606 return *n; 1607 } 1608 1609 // if we're here, the property was not found, so let's implicitly create it 1610 if(throwOnFailure) 1611 throw new Exception("no such property " ~ name, file, line); 1612 var n; 1613 this._properties[name] = n; 1614 return this._properties[name]; 1615 } 1616 1617 // FIXME: maybe throw something else 1618 /*package*/ ref var _setMember(string name, var t, bool recurse, bool throwOnFailure, bool suppressOverloading, string file = __FILE__, size_t line = __LINE__) { 1619 var* mem = _peekMember(name, recurse); 1620 1621 if(mem !is null) { 1622 // Property check - the setter should be proxied over to it 1623 if((*mem).payloadType == var.Type.Object && cast(PropertyPrototype) (*mem)._payload._object) { 1624 auto prop = cast(PropertyPrototype) (*mem)._payload._object; 1625 return prop.set(t); 1626 } 1627 *mem = t; 1628 return *mem; 1629 } 1630 1631 if(!suppressOverloading) { 1632 mem = _peekMember("opIndexAssign", true); 1633 if(mem !is null) { 1634 auto n = new var; 1635 *n = ((*mem)(t, name)); 1636 return *n; 1637 } 1638 } 1639 1640 // if we're here, the property was not found, so let's implicitly create it 1641 if(throwOnFailure) 1642 throw new Exception("no such property " ~ name, file, line); 1643 this._properties[name] = t; 1644 return this._properties[name]; 1645 } 1646 1647 JSONValue toJsonValue() { 1648 JSONValue val; 1649 JSONValue[string] tmp; 1650 foreach(k, v; this._properties) 1651 tmp[k] = v.toJsonValue(); 1652 val = tmp; 1653 return val; 1654 } 1655 1656 public int opApply(scope int delegate(var, ref var) dg) { 1657 foreach(k, v; this._properties) { 1658 if(v.payloadType == var.Type.Object && cast(PropertyPrototype) v._payload._object) 1659 v = (cast(PropertyPrototype) v._payload._object).get; 1660 if(auto result = dg(var(k), v)) 1661 return result; 1662 } 1663 return 0; 1664 } 1665 } 1666 1667 // A property is a special type of object that can only be set by assigning 1668 // one of these instances to foo.child._object. When foo.child is accessed and it 1669 // is an instance of PropertyPrototype, it will return the getter. When foo.child is 1670 // set (excluding direct assignments through _type), it will call the setter. 1671 class PropertyPrototype : PrototypeObject { 1672 var delegate() getter; 1673 void delegate(var) setter; 1674 this(var delegate() getter, void delegate(var) setter) { 1675 this.getter = getter; 1676 this.setter = setter; 1677 } 1678 1679 override string toString() { 1680 return get.toString(); 1681 } 1682 1683 ref var get() { 1684 var* g = new var(); 1685 *g = getter(); 1686 return *g; 1687 } 1688 1689 ref var set(var t) { 1690 setter(t); 1691 return get; 1692 } 1693 1694 override JSONValue toJsonValue() { 1695 return get.toJsonValue(); 1696 } 1697 } 1698 1699 1700 class DynamicTypeException : Exception { 1701 this(var v, var.Type required, string file = __FILE__, size_t line = __LINE__) { 1702 import std.string; 1703 if(v.payloadType() == required) 1704 super(format("Tried to use null as a %s", required), file, line); 1705 else 1706 super(format("Tried to use %s as a %s", v.payloadType(), required), file, line); 1707 } 1708 } 1709 1710 template makeAscii() { 1711 string helper() { 1712 string s; 1713 foreach(i; 0 .. 128) 1714 s ~= cast(char) i; 1715 return s; 1716 } 1717 1718 enum makeAscii = helper(); 1719 } 1720 1721 package interface VarMetadata { } 1722 1723 // just a base class we can reference when looking for native objects 1724 class WrappedNativeObject : PrototypeObject { 1725 TypeInfo wrappedType; 1726 abstract Object getObject(); 1727 } 1728 1729 template helper(alias T) { alias helper = T; } 1730 1731 /++ 1732 Wraps a class. If you are manually managing the memory, remember the jsvar may keep a reference to the object; don't free it! 1733 1734 To use this: `var a = wrapNativeObject(your_d_object);` OR `var a = your_d_object`; 1735 1736 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 1737 1738 That may be done automatically with `opAssign` in the future. 1739 +/ 1740 WrappedNativeObject wrapNativeObject(Class)(Class obj) if(is(Class == class)) { 1741 import std.meta; 1742 return new class WrappedNativeObject { 1743 override Object getObject() { 1744 return obj; 1745 } 1746 1747 this() { 1748 wrappedType = typeid(obj); 1749 // wrap the other methods 1750 // and wrap members as scriptable properties 1751 1752 foreach(memberName; __traits(allMembers, Class)) static if(is(typeof(__traits(getMember, obj, memberName)) type)) { 1753 static if(is(type == function)) { 1754 foreach(idx, overload; AliasSeq!(__traits(getOverloads, obj, memberName))) static if(.isScriptable!(__traits(getAttributes, overload))()) { 1755 auto helper = &__traits(getOverloads, obj, memberName)[idx]; 1756 _properties[memberName] = (Parameters!helper args) { 1757 return __traits(getOverloads, obj, memberName)[idx](args); 1758 }; 1759 } 1760 } else { 1761 static if(.isScriptable!(__traits(getAttributes, __traits(getMember, Class, memberName)))()) 1762 // if it has a type but is not a function, it is prolly a member 1763 _properties[memberName] = new PropertyPrototype( 1764 () => var(__traits(getMember, obj, memberName)), 1765 (var v) { 1766 // read-only property hack 1767 static if(__traits(compiles, __traits(getMember, obj, memberName) = v.get!(type))) 1768 __traits(getMember, obj, memberName) = v.get!(type); 1769 }); 1770 } 1771 } 1772 } 1773 }; 1774 } 1775 1776 import std.traits; 1777 class WrappedOpaque(T) : PrototypeObject if(isPointer!T || is(T == class)) { 1778 T wrapped; 1779 this(T t) { 1780 wrapped = t; 1781 } 1782 T wrapping() { 1783 return wrapped; 1784 } 1785 } 1786 class WrappedOpaque(T) : PrototypeObject if(!isPointer!T && !is(T == class)) { 1787 T* wrapped; 1788 this(T t) { 1789 wrapped = new T; 1790 (cast() *wrapped) = t; 1791 } 1792 this(T* t) { 1793 wrapped = t; 1794 } 1795 T* wrapping() { 1796 return wrapped; 1797 } 1798 } 1799 1800 WrappedOpaque!Obj wrapOpaquely(Obj)(Obj obj) { 1801 static if(is(Obj == class)) { 1802 if(obj is null) 1803 return null; 1804 } 1805 return new WrappedOpaque!Obj(obj); 1806 } 1807 1808 /** 1809 Wraps an opaque struct pointer in a module with ufcs functions 1810 */ 1811 WrappedNativeObject wrapUfcs(alias Module, Type)(Type obj) { 1812 import std.meta; 1813 return new class WrappedNativeObject { 1814 override Object getObject() { 1815 return null; // not actually an object! but close to 1816 } 1817 1818 this() { 1819 wrappedType = typeid(Type); 1820 // wrap the other methods 1821 // and wrap members as scriptable properties 1822 1823 foreach(memberName; __traits(allMembers, Module)) static if(is(typeof(__traits(getMember, Module, memberName)) type)) { 1824 static if(is(type == function)) { 1825 foreach(idx, overload; AliasSeq!(__traits(getOverloads, Module, memberName))) static if(.isScriptable!(__traits(getAttributes, overload))()) { 1826 auto helper = &__traits(getOverloads, Module, memberName)[idx]; 1827 static if(Parameters!helper.length >= 1 && is(Parameters!helper[0] == Type)) { 1828 // this staticMap is a bit of a hack so it can handle `in float`... liable to break with others, i'm sure 1829 _properties[memberName] = (staticMap!(Unqual, Parameters!helper[1 .. $]) args) { 1830 return __traits(getOverloads, Module, memberName)[idx](obj, args); 1831 }; 1832 } 1833 } 1834 } 1835 } 1836 } 1837 }; 1838 } 1839 1840 bool isScriptable(attributes...)() { 1841 bool nonConstConditionForWorkingAroundASpuriousDmdWarning = true; 1842 foreach(attribute; attributes) { 1843 static if(is(typeof(attribute) == string)) { 1844 static if(attribute == scriptable) { 1845 if(nonConstConditionForWorkingAroundASpuriousDmdWarning) 1846 return true; 1847 } 1848 } 1849 } 1850 return false; 1851 } 1852 1853 bool isScriptableOpaque(T)() { 1854 static if(is(typeof(T.isOpaqueStruct) == bool)) 1855 return T.isOpaqueStruct == true; 1856 return false; 1857 } 1858 1859 bool appearsNumeric(string n) { 1860 if(n.length == 0) 1861 return false; 1862 foreach(c; n) { 1863 if(c < '0' || c > '9') 1864 return false; 1865 } 1866 return true; 1867 } 1868 1869 1870 /// Wraps a struct by reference. The pointer is stored - be sure the struct doesn't get freed or go out of scope! 1871 /// 1872 /// 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. 1873 WrappedNativeObject wrapNativeObject(Struct)(Struct* obj) if(is(Struct == struct)) { 1874 return null; // FIXME 1875 }