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