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