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(__traits(compiles, this = t.toArsdJsvar())) {
605 			this = t.toArsdJsvar();
606 		} else static if(isFloatingPoint!T) {
607 			this._type = Type.Floating;
608 			this._payload._floating = t;
609 		} else static if(isIntegral!T) {
610 			this._type = Type.Integral;
611 			this._payload._integral = t;
612 		} else static if(isCallable!T) {
613 			this._type = Type.Function;
614 			static if(is(T == typeof(this._payload._function))) {
615 				this._payload._function = t;
616 			} else
617 			this._payload._function = delegate var(var _this, var[] args) {
618 				var ret;
619 
620 				ParameterTypeTuple!T fargs;
621 				foreach(idx, a; fargs) {
622 					if(idx == args.length)
623 						break;
624 					cast(Unqual!(typeof(a))) fargs[idx] = args[idx].get!(typeof(a));
625 				}
626 
627 				static if(is(ReturnType!t == void)) {
628 					t(fargs);
629 				} else {
630 					ret = t(fargs);
631 				}
632 
633 				return ret;
634 			};
635 		} else static if(isSomeString!T) {
636 			this._type = Type.String;
637 			this._payload._string = to!string(t);
638 		} else static if(is(T : PrototypeObject)) {
639 			// support direct assignment of pre-made implementation objects
640 			// so prewrapped stuff can be easily passed.
641 			this._type = Type.Object;
642 			this._payload._object = t;
643 		} else static if(is(T == class) || .isScriptableOpaque!T) {
644 			// auto-wrap other classes with reference semantics
645 			this._type = Type.Object;
646 			this._payload._object = wrapOpaquely(t);
647 		} else static if(is(T == struct) || isAssociativeArray!T) {
648 			// copy structs and assoc arrays by value into a var object
649 			this._type = Type.Object;
650 			auto obj = new PrototypeObject();
651 			this._payload._object = obj;
652 
653 			static if(is(T == struct))
654 			foreach(member; __traits(allMembers, T)) {
655 				static if(__traits(compiles, __traits(getMember, t, member))) {
656 					static if(is(typeof(__traits(getMember, t, member)) == function)) {
657 						// skipping these because the delegate we get isn't going to work anyway; the object may be dead and certainly won't be updated
658 						//this[member] = &__traits(getMember, proxyObject, member);
659 
660 						// but for simple toString, I'll allow it by recreating the object on demand
661 						// and then calling the original function. (I might be able to do that for more but
662 						// idk, just doing simple thing first)
663 						static if(member == "toString" && is(typeof(&__traits(getMember, t, member)) == string delegate())) {
664 							this[member]._function =  delegate(var _this, var[] args) {
665 								auto val = _this.get!T;
666 								return var(val.toString());
667 							};
668 						}
669 					} else static if(is(typeof(__traits(getMember, t, member)))) {
670 						this[member] = __traits(getMember, t, member);
671 					}
672 				}
673 			} else {
674 				// assoc array
675 				foreach(l, v; t) {
676 					this[var(l)] = var(v);
677 				}
678 			}
679 		} else static if(isArray!T) {
680 			this._type = Type.Array;
681 			var[] arr;
682 			arr.length = t.length;
683 			static if(!is(T == void[])) // we can't append a void array but it is nice to support x = [];
684 				foreach(i, item; t)
685 					arr[i] = var(item);
686 			this._payload._array = arr;
687 		} else static if(is(T == bool)) {
688 			this._type = Type.Boolean;
689 			this._payload._boolean = t;
690 		} else static if(isSomeChar!T) {
691 			this._type = Type.String;
692 			this._payload._string = "";
693 			import std.utf;
694 			char[4] ugh;
695 			auto size = encode(ugh, t);
696 			this._payload._string = ugh[0..size].idup;
697 		}// else static assert(0, "unsupported type");
698 
699 		return this;
700 	}
701 
702 	public size_t opDollar() {
703 		return this.length().get!size_t;
704 	}
705 
706 	public var opOpAssign(string op, T)(T t) {
707 		if(payloadType() == Type.Object) {
708 			if(this._payload._object !is null) {
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 
715 		return _op!(this, this, op, T)(t);
716 	}
717 
718 	public var opUnary(string op : "-")() {
719 		static assert(op == "-");
720 		final switch(payloadType()) {
721 			case Type.Object:
722 			case Type.Array:
723 			case Type.Boolean:
724 			case Type.String:
725 			case Type.Function:
726 				assert(0); // FIXME
727 			//break;
728 			case Type.Integral:
729 				return var(-this.get!long);
730 			case Type.Floating:
731 				return var(-this.get!double);
732 		}
733 	}
734 
735 	public var opBinary(string op, T)(T t) {
736 		var n;
737 		if(payloadType() == Type.Object) {
738 			var* operator = this._payload._object._peekMember("opBinary", true);
739 			if(operator !is null && operator._type == Type.Function) {
740 				return operator.call(this, op, t);
741 			}
742 		}
743 		return _op!(n, this, op, T)(t);
744 	}
745 
746 	public var opBinaryRight(string op, T)(T s) {
747 		return var(s).opBinary!op(this);
748 	}
749 
750 	// this in foo
751 	public var* opBinary(string op : "in", T)(T s) {
752 		var rhs = var(s);
753 		return rhs.opBinaryRight!"in"(this);
754 	}
755 
756 	// foo in this
757 	public var* opBinaryRight(string op : "in", T)(T s) {
758 		// this needs to be an object
759 		return var(s).get!string in this._object._properties;
760 	}
761 
762 	public var apply(var _this, var[] args) {
763 		if(this.payloadType() == Type.Function) {
764 			if(this._payload._function is null) {
765 				version(jsvar_throw)
766 					throw new DynamicTypeException(this, Type.Function);
767 				else
768 					return var(null);
769 			}
770 			return this._payload._function(_this, args);
771 		} else if(this.payloadType() == Type.Object) {
772 			if(this._payload._object is null) {
773 				version(jsvar_throw)
774 					throw new DynamicTypeException(this, Type.Function);
775 				else
776 					return var(null);
777 			}
778 			var* operator = this._payload._object._peekMember("opCall", true);
779 			if(operator !is null && operator._type == Type.Function)
780 				return operator.apply(_this, args);
781 		}
782 
783 		version(jsvar_throw)
784 			throw new DynamicTypeException(this, Type.Function);
785 
786 		if(this.payloadType() == Type.Integral || this.payloadType() == Type.Floating) {
787 			if(args.length)
788 				return var(this.get!double * args[0].get!double);
789 		}
790 
791 		//return this;
792 		return var(null);
793 	}
794 
795 	public var call(T...)(var _this, T t) {
796 		var[] args;
797 		foreach(a; t) {
798 			args ~= var(a);
799 		}
800 		return this.apply(_this, args);
801 	}
802 
803 	public var opCall(T...)(T t) {
804 		return this.call(this, t);
805 	}
806 
807 	/*
808 	public var applyWithMagicLocals(var _this, var[] args, var[string] magicLocals) {
809 
810 	}
811 	*/
812 
813 	public string toString() {
814 		return this.get!string;
815 	}
816 
817 	public T getWno(T)() {
818 		if(payloadType == Type.Object) {
819 			if(auto wno = cast(WrappedNativeObject) this._payload._object) {
820 				auto no = cast(T) wno.getObject();
821 					if(no !is null)
822 						return no;
823 			}
824 		}
825 		return null;
826 	}
827 
828 	public T get(T)() if(!is(T == void)) {
829 		static if(is(T == var)) {
830 			return this;
831 		} else static if(__traits(compiles, T.fromJsVar(var.init))) {
832 			return T.fromJsVar(this);
833 		} else static if(__traits(compiles, T(this))) {
834 			return T(this);
835 		} else static if(__traits(compiles, new T(this))) {
836 			return new T(this);
837 		} else
838 		final switch(payloadType) {
839 			case Type.Boolean:
840 				static if(is(T == bool))
841 					return this._payload._boolean;
842 				else static if(isFloatingPoint!T || isIntegral!T)
843 					return cast(T) (this._payload._boolean ? 1 : 0); // the cast is for enums, I don't like this so FIXME
844 				else static if(isSomeString!T)
845 					return this._payload._boolean ? "true" : "false";
846 				else
847 				return T.init;
848 			case Type.Object:
849 				static if(isAssociativeArray!T) {
850 					T ret;
851 					if(this._payload._object !is null)
852 					foreach(k, v; this._payload._object._properties)
853 						ret[to!(KeyType!T)(k)] = v.get!(ValueType!T);
854 
855 					return ret;
856 				} else static if(is(T : PrototypeObject)) {
857 					// they are requesting an implementation object, just give it to them
858 					return cast(T) this._payload._object;
859 				} else static if(isScriptableOpaque!(Unqual!T)) {
860 					if(auto wno = cast(WrappedOpaque!(Unqual!T)) this._payload._object) {
861 						return wno.wrapping();
862 					}
863 					static if(is(T == R*, R))
864 					if(auto wno = cast(WrappedOpaque!(Unqual!(R))) this._payload._object) {
865 						return wno.wrapping();
866 					}
867 					throw new DynamicTypeException(this, Type.Object); // FIXME: could be better
868 				} else static if(is(T == struct) || is(T == class)) {
869 					// first, we'll try to give them back the native object we have, if we have one
870 					static if(is(T : Object)) {
871 						if(auto wno = cast(WrappedNativeObject) this._payload._object) {
872 							auto no = cast(T) wno.getObject();
873 							if(no !is null)
874 								return no;
875 						}
876 
877 						// FIXME: this is kinda weird.
878 						return null;
879 					} else {
880 
881 						// failing that, generic struct or class getting: try to fill in the fields by name
882 						T t;
883 						bool initialized = true;
884 						static if(is(T == class)) {
885 							static if(__traits(compiles, new T()))
886 								t = new T();
887 							else
888 								initialized = false;
889 						}
890 
891 
892 						if(initialized)
893 						foreach(i, a; t.tupleof) {
894 							cast(Unqual!(typeof((a)))) t.tupleof[i] = this[t.tupleof[i].stringof[2..$]].get!(typeof(a));
895 						}
896 
897 						return t;
898 					}
899 				} else static if(isSomeString!T) {
900 					if(this._object !is null)
901 						return this._object.toString();
902 					return "null";
903 				} else
904 					return T.init;
905 			case Type.Integral:
906 				static if(isFloatingPoint!T || isIntegral!T)
907 					return to!T(this._payload._integral);
908 				else static if(isSomeString!T)
909 					return to!string(this._payload._integral);
910 				else
911 					return T.init;
912 			case Type.Floating:
913 				static if(isFloatingPoint!T || isIntegral!T)
914 					return to!T(this._payload._floating);
915 				else static if(isSomeString!T)
916 					return to!string(this._payload._floating);
917 				else
918 					return T.init;
919 			case Type.String:
920 				static if(__traits(compiles, to!T(this._payload._string))) {
921 					try {
922 						return to!T(this._payload._string);
923 					} catch (Exception e) { return T.init; }
924 				} else
925 					return T.init;
926 			case Type.Array:
927 				import std.range;
928 				auto pl = this._payload._array;
929 				static if(isSomeString!T) {
930 					return to!string(pl);
931 				} else static if(isArray!T) {
932 					T ret;
933 					static if(is(ElementType!T == void)) {
934 						static assert(0, "try wrapping the function to get rid of void[] args");
935 						//alias getType = ubyte;
936 					} else
937 						alias getType = ElementType!T;
938 					foreach(item; pl)
939 						ret ~= item.get!(getType);
940 					return ret;
941 				} else
942 					return T.init;
943 				// is it sane to translate anything else?
944 			case Type.Function:
945 				static if(isSomeString!T)
946 					return "<function>";
947 				else static if(isDelegate!T) {
948 					// making a local copy because otherwise the delegate might refer to a struct on the stack and get corrupted later or something
949 					auto func = this._payload._function;
950 
951 					// the static helper lets me pass specific variables to the closure
952 					static T helper(typeof(func) func) {
953 						return delegate ReturnType!T (ParameterTypeTuple!T args) {
954 							var[] arr;
955 							foreach(arg; args)
956 								arr ~= var(arg);
957 							var ret = func(var(null), arr);
958 							static if(is(ReturnType!T == void))
959 								return;
960 							else
961 								return ret.get!(ReturnType!T);
962 						};
963 					}
964 
965 					return helper(func);
966 
967 				} else
968 					return T.init;
969 				// FIXME: we just might be able to do better for both of these
970 			//break;
971 		}
972 	}
973 
974 	public T nullCoalesce(T)(T t) {
975 		if(_type == Type.Object && _payload._object is null)
976 			return t;
977 		return this.get!T;
978 	}
979 
980 	public int opCmp(T)(T t) {
981 		auto f = this.get!real;
982 		static if(is(T == var))
983 			auto r = t.get!real;
984 		else
985 			auto r = t;
986 		return cast(int)(f - r);
987 	}
988 
989 	public bool opEquals(T)(T t) {
990 		return this.opEquals(var(t));
991 	}
992 
993 	public bool opEquals(T:var)(T t) const {
994 		// FIXME: should this be == or === ?
995 		if(this._type != t._type)
996 			return false;
997 		final switch(this._type) {
998 			case Type.Object:
999 				return _payload._object is t._payload._object;
1000 			case Type.Integral:
1001 				return _payload._integral == t._payload._integral;
1002 			case Type.Boolean:
1003 				return _payload._boolean == t._payload._boolean;
1004 			case Type.Floating:
1005 				return _payload._floating == t._payload._floating; // FIXME: approxEquals?
1006 			case Type.String:
1007 				return _payload._string == t._payload._string;
1008 			case Type.Function:
1009 				return _payload._function is t._payload._function;
1010 			case Type.Array:
1011 				return _payload._array == t._payload._array;
1012 		}
1013 		assert(0);
1014 	}
1015 
1016 	public enum Type {
1017 		Object, Array, Integral, Floating, String, Function, Boolean
1018 	}
1019 
1020 	public Type payloadType() {
1021 		return _type;
1022 	}
1023 
1024 	private Type _type;
1025 
1026 	private union Payload {
1027 		PrototypeObject _object;
1028 		var[] _array;
1029 		long _integral;
1030 		real _floating;
1031 		string _string;
1032 		bool _boolean;
1033 		var delegate(var _this, var[] args) _function;
1034 	}
1035 
1036 	public void _function(var delegate(var, var[]) f) {
1037 		this._payload._function = f;
1038 		this._type = Type.Function;
1039 	}
1040 
1041 	/*
1042 	public void _function(var function(var, var[]) f) {
1043 		var delegate(var, var[]) dg;
1044 		dg.ptr = null;
1045 		dg.funcptr = f;
1046 		this._function = dg;
1047 	}
1048 	*/
1049 
1050 	public void _object(PrototypeObject obj) {
1051 		this._type = Type.Object;
1052 		this._payload._object = obj;
1053 	}
1054 
1055 	public PrototypeObject _object() {
1056 		if(this._type == Type.Object)
1057 			return this._payload._object;
1058 		return null;
1059 	}
1060 
1061 	package Payload _payload;
1062 
1063 	private void _requireType(Type t, string file = __FILE__, size_t line = __LINE__){
1064 		if(this.payloadType() != t)
1065 			throw new DynamicTypeException(this, t, file, line);
1066 	}
1067 
1068 	public var opSlice(var e1, var e2) {
1069 		return this.opSlice(e1.get!ptrdiff_t, e2.get!ptrdiff_t);
1070 	}
1071 
1072 	public var opSlice(ptrdiff_t e1, ptrdiff_t e2) {
1073 		if(this.payloadType() == Type.Array) {
1074 			if(e1 > _payload._array.length)
1075 				e1 = _payload._array.length;
1076 			if(e2 > _payload._array.length)
1077 				e2 = _payload._array.length;
1078 			return var(_payload._array[e1 .. e2]);
1079 		}
1080 		if(this.payloadType() == Type.String) {
1081 			if(e1 > _payload._string.length)
1082 				e1 = _payload._string.length;
1083 			if(e2 > _payload._string.length)
1084 				e2 = _payload._string.length;
1085 			return var(_payload._string[e1 .. e2]);
1086 		}
1087 		if(this.payloadType() == Type.Object) {
1088 			var operator = this["opSlice"];
1089 			if(operator._type == Type.Function) {
1090 				return operator.call(this, e1, e2);
1091 			}
1092 		}
1093 
1094 		// might be worth throwing here too
1095 		return var(null);
1096 	}
1097 
1098 	public @property ref var opDispatch(string name, string file = __FILE__, size_t line = __LINE__)() {
1099 		return this[name];
1100 	}
1101 
1102 	public @property ref var opDispatch(string name, string file = __FILE__, size_t line = __LINE__, T)(T r) {
1103 		return this.opIndexAssign!T(r, name);
1104 	}
1105 
1106 	public ref var opIndex(var name, string file = __FILE__, size_t line = __LINE__) {
1107 		return opIndex(name.get!string, file, line);
1108 	}
1109 
1110 	public ref var opIndexAssign(T)(T t, var name, string file = __FILE__, size_t line = __LINE__) {
1111 		return opIndexAssign(t, name.get!string, file, line);
1112 	}
1113 
1114 	public ref var opIndex(string name, string file = __FILE__, size_t line = __LINE__) {
1115 		// if name is numeric, we should convert to int for arrays
1116 		if(name.length && name[0] >= '0' && name[0] <= '9' && this.payloadType() == Type.Array)
1117 			return opIndex(to!size_t(name), file, line);
1118 
1119 		if(this.payloadType() != Type.Object && name == "prototype")
1120 			return prototype();
1121 
1122 		if(name == "typeof") {
1123 			var* tmp = new var;
1124 			*tmp = to!string(this.payloadType());
1125 			return *tmp;
1126 		}
1127 
1128 		if(name == "toJson") {
1129 			var* tmp = new var;
1130 			*tmp = to!string(this.toJson());
1131 			return *tmp;
1132 		}
1133 
1134 		if(name == "length" && this.payloadType() == Type.String) {
1135 			var* tmp = new var;
1136 			*tmp = _payload._string.length;
1137 			return *tmp;
1138 		}
1139 		if(name == "length" && this.payloadType() == Type.Array) {
1140 			var* tmp = new var;
1141 			*tmp = _payload._array.length;
1142 			return *tmp;
1143 		}
1144 		if(name == "__prop" && this.payloadType() == Type.Object) {
1145 			var* tmp = new var;
1146 			(*tmp)._function = delegate var(var _this, var[] args) {
1147 				if(args.length == 0)
1148 					return var(null);
1149 				if(args.length == 1) {
1150 					auto peek = this._payload._object._peekMember(args[0].get!string, false);
1151 					if(peek is null)
1152 						return var(null);
1153 					else
1154 						return *peek;
1155 				}
1156 				if(args.length == 2) {
1157 					auto peek = this._payload._object._peekMember(args[0].get!string, false);
1158 					if(peek is null) {
1159 						this._payload._object._properties[args[0].get!string] = args[1];
1160 						return var(null);
1161 					} else {
1162 						*peek = args[1];
1163 						return *peek;
1164 					}
1165 
1166 				}
1167 				throw new Exception("too many args");
1168 			};
1169 			return *tmp;
1170 		}
1171 
1172 		PrototypeObject from;
1173 		if(this.payloadType() == Type.Object)
1174 			from = _payload._object;
1175 		else {
1176 			var pt = this.prototype();
1177 			assert(pt.payloadType() == Type.Object);
1178 			from = pt._payload._object;
1179 		}
1180 
1181 		if(from is null) {
1182 			version(jsvar_throw)
1183 				throw new DynamicTypeException(var(null), Type.Object, file, line);
1184 			else
1185 				return *(new var);
1186 		}
1187 		return from._getMember(name, true, false, file, line);
1188 	}
1189 
1190 	public ref var opIndexAssign(T)(T t, string name, string file = __FILE__, size_t line = __LINE__) {
1191 		if(this.payloadType == Type.Array && name.appearsNumeric()) {
1192 			try {
1193 				auto i = to!size_t(name);
1194 				return opIndexAssign(t, i, file, line);
1195 			} catch(Exception)
1196 				{} // ignore bad index, use it as a string instead lol
1197 		}
1198 		_requireType(Type.Object); // FIXME?
1199 		if(_payload._object is null)
1200 			throw new DynamicTypeException(var(null), Type.Object, file, line);
1201 
1202 		return this._payload._object._setMember(name, var(t), false, false, false, file, line);
1203 	}
1204 
1205 	public ref var opIndexAssignNoOverload(T)(T t, string name, string file = __FILE__, size_t line = __LINE__) {
1206 		if(name.length && name[0] >= '0' && name[0] <= '9')
1207 			return opIndexAssign(t, to!size_t(name), file, line);
1208 		_requireType(Type.Object); // FIXME?
1209 		if(_payload._object is null)
1210 			throw new DynamicTypeException(var(null), Type.Object, file, line);
1211 
1212 		return this._payload._object._setMember(name, var(t), false, false, true, file, line);
1213 	}
1214 
1215 
1216 	public ref var opIndex(size_t idx, string file = __FILE__, size_t line = __LINE__) {
1217 		if(_type == Type.Array) {
1218 			auto arr = this._payload._array;
1219 			if(idx < arr.length)
1220 				return arr[idx];
1221 		} else if(_type == Type.Object) {
1222 			// objects might overload opIndex
1223 			var* n = new var();
1224 			if("opIndex" in this)
1225 				*n = this["opIndex"](idx);
1226 			return *n;
1227 		}
1228 		version(jsvar_throw)
1229 			throw new DynamicTypeException(this, Type.Array, file, line);
1230 		var* n = new var();
1231 		return *n;
1232 	}
1233 
1234 	public ref var opIndexAssign(T)(T t, size_t idx, string file = __FILE__, size_t line = __LINE__) {
1235 		if(_type == Type.Array) {
1236 			if(idx >= this._payload._array.length)
1237 				this._payload._array.length = idx + 1;
1238 			this._payload._array[idx] = t;
1239 			return this._payload._array[idx];
1240 		} else if(_type == Type.Object) {
1241 			return opIndexAssign(t, to!string(idx), file, line);
1242 		}
1243 		version(jsvar_throw)
1244 			throw new DynamicTypeException(this, Type.Array, file, line);
1245 		var* n = new var();
1246 		return *n;
1247 	}
1248 
1249 	ref var _getOwnProperty(string name, string file = __FILE__, size_t line = __LINE__) {
1250 		if(_type == Type.Object) {
1251 			if(_payload._object !is null) {
1252 				auto peek = this._payload._object._peekMember(name, false);
1253 				if(peek !is null)
1254 					return *peek;
1255 			}
1256 		}
1257 		version(jsvar_throw)
1258 			throw new DynamicTypeException(this, Type.Object, file, line);
1259 		var* n = new var();
1260 		return *n;
1261 	}
1262 
1263 	@property static var emptyObject(PrototypeObject prototype = null) {
1264 		var v;
1265 		v._type = Type.Object;
1266 		v._payload._object = new PrototypeObject();
1267 		v._payload._object.prototype = prototype;
1268 		return v;
1269 	}
1270 
1271 	@property static var emptyObject(var prototype) {
1272 		if(prototype._type == Type.Object)
1273 			return var.emptyObject(prototype._payload._object);
1274 		return var.emptyObject();
1275 	}
1276 
1277 	@property PrototypeObject prototypeObject() {
1278 		var v = prototype();
1279 		if(v._type == Type.Object)
1280 			return v._payload._object;
1281 		return null;
1282 	}
1283 
1284 	// what I call prototype is more like what Mozilla calls __proto__, but tbh I think this is better so meh
1285 	@property ref var prototype() {
1286 		static var _arrayPrototype;
1287 		static var _functionPrototype;
1288 		static var _stringPrototype;
1289 
1290 		final switch(payloadType()) {
1291 			case Type.Array:
1292 				assert(_arrayPrototype._type == Type.Object);
1293 				if(_arrayPrototype._payload._object is null) {
1294 					_arrayPrototype._object = new PrototypeObject();
1295 				}
1296 
1297 				return _arrayPrototype;
1298 			case Type.Function:
1299 				assert(_functionPrototype._type == Type.Object);
1300 				if(_functionPrototype._payload._object is null) {
1301 					_functionPrototype._object = new PrototypeObject();
1302 				}
1303 
1304 				return _functionPrototype;
1305 			case Type.String:
1306 				assert(_stringPrototype._type == Type.Object);
1307 				if(_stringPrototype._payload._object is null) {
1308 					auto p = new PrototypeObject();
1309 					_stringPrototype._object = p;
1310 
1311 					var replaceFunction;
1312 					replaceFunction._type = Type.Function;
1313 					replaceFunction._function = (var _this, var[] args) {
1314 						string s = _this.toString();
1315 						import std.array : replace;
1316 						return var(std.array.replace(s,
1317 							args[0].toString(),
1318 							args[1].toString()));
1319 					};
1320 
1321 					p._properties["replace"] = replaceFunction;
1322 				}
1323 
1324 				return _stringPrototype;
1325 			case Type.Object:
1326 				if(_payload._object)
1327 					return _payload._object._prototype;
1328 				// FIXME: should we do a generic object prototype?
1329 			break;
1330 			case Type.Integral:
1331 			case Type.Floating:
1332 			case Type.Boolean:
1333 				// these types don't have prototypes
1334 		}
1335 
1336 
1337 		var* v = new var(null);
1338 		return *v;
1339 	}
1340 
1341 	@property static var emptyArray() {
1342 		var v;
1343 		v._type = Type.Array;
1344 		return v;
1345 	}
1346 
1347 	static var fromJson(string json) {
1348 		auto decoded = parseJSON(json);
1349 		return var.fromJsonValue(decoded);
1350 	}
1351 
1352 	static var fromJsonValue(JSONValue v) {
1353 		var ret;
1354 
1355 		final switch(v.type) {
1356 			case JSONType..string:
1357 				ret = v.str;
1358 			break;
1359 			case JSONType.uinteger:
1360 				ret = v.uinteger;
1361 			break;
1362 			case JSONType.integer:
1363 				ret = v.integer;
1364 			break;
1365 			case JSONType.float_:
1366 				ret = v.floating;
1367 			break;
1368 			case JSONType.object:
1369 				ret = var.emptyObject;
1370 				foreach(k, val; v.object) {
1371 					ret[k] = var.fromJsonValue(val);
1372 				}
1373 			break;
1374 			case JSONType.array:
1375 				ret = var.emptyArray;
1376 				ret._payload._array.length = v.array.length;
1377 				foreach(idx, item; v.array) {
1378 					ret._payload._array[idx] = var.fromJsonValue(item);
1379 				}
1380 			break;
1381 			case JSONType.true_:
1382 				ret = true;
1383 			break;
1384 			case JSONType.false_:
1385 				ret = false;
1386 			break;
1387 			case JSONType.null_:
1388 				ret = null;
1389 			break;
1390 		}
1391 
1392 		return ret;
1393 	}
1394 
1395 	string toJson() {
1396 		auto v = toJsonValue();
1397 		return toJSON(v);
1398 	}
1399 
1400 	JSONValue toJsonValue() {
1401 		JSONValue val;
1402 		final switch(payloadType()) {
1403 			case Type.Boolean:
1404 				version(new_std_json)
1405 					val = this._payload._boolean;
1406 				else {
1407 					if(this._payload._boolean)
1408 						val.type = JSONType.true_;
1409 					else
1410 						val.type = JSONType.false_;
1411 				}
1412 			break;
1413 			case Type.Object:
1414 				version(new_std_json) {
1415 					if(_payload._object is null) {
1416 						val = null;
1417 					} else {
1418 						val = _payload._object.toJsonValue();
1419 					}
1420 				} else {
1421 					if(_payload._object is null) {
1422 						val.type = JSONType.null_;
1423 					} else {
1424 						val.type = JSONType.object;
1425 						foreach(k, v; _payload._object._properties)
1426 							val.object[k] = v.toJsonValue();
1427 					}
1428 				}
1429 			break;
1430 			case Type.String:
1431 				version(new_std_json) { } else {
1432 					val.type = JSONType..string;
1433 				}
1434 				val.str = _payload._string;
1435 			break;
1436 			case Type.Integral:
1437 				version(new_std_json) { } else {
1438 					val.type = JSONType.integer;
1439 				}
1440 				val.integer = _payload._integral;
1441 			break;
1442 			case Type.Floating:
1443 				version(new_std_json) { } else {
1444 					val.type = JSONType.float_;
1445 				}
1446 				val.floating = _payload._floating;
1447 			break;
1448 			case Type.Array:
1449 				auto a = _payload._array;
1450 				JSONValue[] tmp;
1451 				tmp.length = a.length;
1452 				foreach(i, v; a) {
1453 					tmp[i] = v.toJsonValue();
1454 				}
1455 
1456 				version(new_std_json) {
1457 					val = tmp;
1458 				} else {
1459 					val.type = JSONType.array;
1460 					val.array = tmp;
1461 				}
1462 			break;
1463 			case Type.Function:
1464 				version(new_std_json)
1465 					val = null;
1466 				else
1467 					val.type = JSONType.null_; // ideally we would just skip it entirely...
1468 			break;
1469 		}
1470 		return val;
1471 	}
1472 }
1473 
1474 class PrototypeObject {
1475 	string name;
1476 	var _prototype;
1477 
1478 	package PrototypeObject _secondary; // HACK don't use this
1479 
1480 	PrototypeObject prototype() {
1481 		if(_prototype.payloadType() == var.Type.Object)
1482 			return _prototype._payload._object;
1483 		return null;
1484 	}
1485 
1486 	PrototypeObject prototype(PrototypeObject set) {
1487 		this._prototype._object = set;
1488 		return set;
1489 	}
1490 
1491 	override string toString() {
1492 
1493 		var* ts = _peekMember("toString", true);
1494 		if(ts) {
1495 			var _this;
1496 			_this._object = this;
1497 			return (*ts).call(_this).get!string;
1498 		}
1499 
1500 		JSONValue val;
1501 		version(new_std_json) {
1502 			JSONValue[string] tmp;
1503 			foreach(k, v; this._properties)
1504 				tmp[k] = v.toJsonValue();
1505 			val.object = tmp;
1506 		} else {
1507 			val.type = JSONType.object;
1508 			foreach(k, v; this._properties)
1509 				val.object[k] = v.toJsonValue();
1510 		}
1511 
1512 		return toJSON(val);
1513 	}
1514 
1515 	var[string] _properties;
1516 
1517 	PrototypeObject copy() {
1518 		auto n = new PrototypeObject();
1519 		n.prototype = this.prototype;
1520 		n.name = this.name;
1521 		foreach(k, v; _properties) {
1522 			n._properties[k] = v._copy;
1523 		}
1524 		return n;
1525 	}
1526 
1527 	PrototypeObject copyPropertiesFrom(PrototypeObject p) {
1528 		foreach(k, v; p._properties) {
1529 			this._properties[k] = v._copy;
1530 		}
1531 		return this;
1532 	}
1533 
1534 	var* _peekMember(string name, bool recurse) {
1535 		if(name == "prototype")
1536 			return &_prototype;
1537 
1538 		auto curr = this;
1539 
1540 		// for the secondary hack
1541 		bool triedOne = false;
1542 		// for the secondary hack
1543 		PrototypeObject possibleSecondary;
1544 
1545 		tryAgain:
1546 		do {
1547 			auto prop = name in curr._properties;
1548 			if(prop is null) {
1549 				// the secondary hack is to do more scoping in the script, it is really hackish
1550 				if(possibleSecondary is null)
1551 					possibleSecondary = curr._secondary;
1552 
1553 				if(!recurse)
1554 					break;
1555 				else
1556 					curr = curr.prototype;
1557 			} else
1558 				return prop;
1559 		} while(curr);
1560 
1561 		if(possibleSecondary !is null) {
1562 			curr = possibleSecondary;
1563 			if(!triedOne) {
1564 				triedOne = true;
1565 				goto tryAgain;
1566 			}
1567 		}
1568 
1569 		return null;
1570 	}
1571 
1572 	// FIXME: maybe throw something else
1573 	/*package*/ ref var _getMember(string name, bool recurse, bool throwOnFailure, string file = __FILE__, size_t line = __LINE__) {
1574 		var* mem = _peekMember(name, recurse);
1575 
1576 		if(mem !is null) {
1577 			// If it is a property, we need to call the getter on it
1578 			if((*mem).payloadType == var.Type.Object && cast(PropertyPrototype) (*mem)._payload._object) {
1579 				auto prop = cast(PropertyPrototype) (*mem)._payload._object;
1580 				return prop.get;
1581 			}
1582 			return *mem;
1583 		}
1584 
1585 		mem = _peekMember("opIndex", recurse);
1586 		if(mem !is null) {
1587 			auto n = new var;
1588 			*n = ((*mem)(name));
1589 			return *n;
1590 		}
1591 
1592 		// if we're here, the property was not found, so let's implicitly create it
1593 		if(throwOnFailure)
1594 			throw new Exception("no such property " ~ name, file, line);
1595 		var n;
1596 		this._properties[name] = n;
1597 		return this._properties[name];
1598 	}
1599 
1600 	// FIXME: maybe throw something else
1601 	/*package*/ ref var _setMember(string name, var t, bool recurse, bool throwOnFailure, bool suppressOverloading, string file = __FILE__, size_t line = __LINE__) {
1602 		var* mem = _peekMember(name, recurse);
1603 
1604 		if(mem !is null) {
1605 			// Property check - the setter should be proxied over to it
1606 			if((*mem).payloadType == var.Type.Object && cast(PropertyPrototype) (*mem)._payload._object) {
1607 				auto prop = cast(PropertyPrototype) (*mem)._payload._object;
1608 				return prop.set(t);
1609 			}
1610 			*mem = t;
1611 			return *mem;
1612 		}
1613 
1614 		if(!suppressOverloading) {
1615 			mem = _peekMember("opIndexAssign", true);
1616 			if(mem !is null) {
1617 				auto n = new var;
1618 				*n = ((*mem)(t, name));
1619 				return *n;
1620 			}
1621 		}
1622 
1623 		// if we're here, the property was not found, so let's implicitly create it
1624 		if(throwOnFailure)
1625 			throw new Exception("no such property " ~ name, file, line);
1626 		this._properties[name] = t;
1627 		return this._properties[name];
1628 	}
1629 
1630 	JSONValue toJsonValue() {
1631 		JSONValue val;
1632 		JSONValue[string] tmp;
1633 		foreach(k, v; this._properties)
1634 			tmp[k] = v.toJsonValue();
1635 		val = tmp;
1636 		return val;
1637 	}
1638 
1639 	public int opApply(scope int delegate(var, ref var) dg) {
1640 		foreach(k, v; this._properties) {
1641 			if(v.payloadType == var.Type.Object && cast(PropertyPrototype) v._payload._object)
1642 				v = (cast(PropertyPrototype) v._payload._object).get;
1643 			if(auto result = dg(var(k), v))
1644 				return result;
1645 		}
1646 		return 0;
1647 	}
1648 }
1649 
1650 // A property is a special type of object that can only be set by assigning
1651 // one of these instances to foo.child._object. When foo.child is accessed and it
1652 // is an instance of PropertyPrototype, it will return the getter. When foo.child is
1653 // set (excluding direct assignments through _type), it will call the setter.
1654 class PropertyPrototype : PrototypeObject {
1655 	var delegate() getter;
1656 	void delegate(var) setter;
1657 	this(var delegate() getter, void delegate(var) setter) {
1658 		this.getter = getter;
1659 		this.setter = setter;
1660 	}
1661 
1662 	override string toString() {
1663 		return get.toString();
1664 	}
1665 
1666 	ref var get() {
1667 		var* g = new var();
1668 		*g = getter();
1669 		return *g;
1670 	}
1671 
1672 	ref var set(var t) {
1673 		setter(t);
1674 		return get;
1675 	}
1676 
1677 	override JSONValue toJsonValue() {
1678 		return get.toJsonValue();
1679 	}
1680 }
1681 
1682 
1683 class DynamicTypeException : Exception {
1684 	this(var v, var.Type required, string file = __FILE__, size_t line = __LINE__) {
1685 		import std..string;
1686 		if(v.payloadType() == required)
1687 			super(format("Tried to use null as a %s", required), file, line);
1688 		else
1689 			super(format("Tried to use %s as a %s", v.payloadType(), required), file, line);
1690 	}
1691 }
1692 
1693 template makeAscii() {
1694 	string helper() {
1695 		string s;
1696 		foreach(i; 0 .. 128)
1697 			s ~= cast(char) i;
1698 		return s;
1699 	}
1700 
1701 	enum makeAscii = helper();
1702 }
1703 
1704 // just a base class we can reference when looking for native objects
1705 class WrappedNativeObject : PrototypeObject {
1706 	TypeInfo wrappedType;
1707 	abstract Object getObject();
1708 }
1709 
1710 template helper(alias T) { alias helper = T; }
1711 
1712 /++
1713 	Wraps a class. If you are manually managing the memory, remember the jsvar may keep a reference to the object; don't free it!
1714 
1715 	To use this: `var a = wrapNativeObject(your_d_object);` OR `var a = your_d_object`;
1716 
1717 	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
1718 
1719 	That may be done automatically with `opAssign` in the future.
1720 +/
1721 WrappedNativeObject wrapNativeObject(Class)(Class obj) if(is(Class == class)) {
1722 	import std.meta;
1723 	return new class WrappedNativeObject {
1724 		override Object getObject() {
1725 			return obj;
1726 		}
1727 
1728 		this() {
1729 			wrappedType = typeid(obj);
1730 			// wrap the other methods
1731 			// and wrap members as scriptable properties
1732 
1733 			foreach(memberName; __traits(allMembers, Class)) static if(is(typeof(__traits(getMember, obj, memberName)) type)) {
1734 				static if(is(type == function)) {
1735 					foreach(idx, overload; AliasSeq!(__traits(getOverloads, obj, memberName))) static if(.isScriptable!(__traits(getAttributes, overload))()) {
1736 						auto helper = &__traits(getOverloads, obj, memberName)[idx];
1737 						_properties[memberName] = (Parameters!helper args) {
1738 							return __traits(getOverloads, obj, memberName)[idx](args);
1739 						};
1740 					}
1741 				} else {
1742 					static if(.isScriptable!(__traits(getAttributes, __traits(getMember, Class, memberName)))())
1743 					// if it has a type but is not a function, it is prolly a member
1744 					_properties[memberName] = new PropertyPrototype(
1745 						() => var(__traits(getMember, obj, memberName)),
1746 						(var v) {
1747 							// read-only property hack
1748 							static if(__traits(compiles, __traits(getMember, obj, memberName) = v.get!(type)))
1749 							__traits(getMember, obj, memberName) = v.get!(type);
1750 						});
1751 				}
1752 			}
1753 		}
1754 	};
1755 }
1756 
1757 import std.traits;
1758 class WrappedOpaque(T) : PrototypeObject if(isPointer!T || is(T == class)) {
1759 	T wrapped;
1760 	this(T t) {
1761 		wrapped = t;
1762 	}
1763 	T wrapping() {
1764 		return wrapped;
1765 	}
1766 }
1767 class WrappedOpaque(T) : PrototypeObject if(!isPointer!T && !is(T == class)) {
1768 	T* wrapped;
1769 	this(T t) {
1770 		wrapped = new T;
1771 		(cast() *wrapped) = t;
1772 	}
1773 	this(T* t) {
1774 		wrapped = t;
1775 	}
1776 	T* wrapping() {
1777 		return wrapped;
1778 	}
1779 }
1780 
1781 WrappedOpaque!Obj wrapOpaquely(Obj)(Obj obj) {
1782 	static if(is(Obj == class)) {
1783 		if(obj is null)
1784 			return null;
1785 	}
1786 	return new WrappedOpaque!Obj(obj);
1787 }
1788 
1789 /**
1790 	Wraps an opaque struct pointer in a module with ufcs functions
1791 */
1792 WrappedNativeObject wrapUfcs(alias Module, Type)(Type obj) {
1793 	import std.meta;
1794 	return new class WrappedNativeObject {
1795 		override Object getObject() {
1796 			return null; // not actually an object! but close to
1797 		}
1798 
1799 		this() {
1800 			wrappedType = typeid(Type);
1801 			// wrap the other methods
1802 			// and wrap members as scriptable properties
1803 
1804 			foreach(memberName; __traits(allMembers, Module)) static if(is(typeof(__traits(getMember, Module, memberName)) type)) {
1805 				static if(is(type == function)) {
1806 					foreach(idx, overload; AliasSeq!(__traits(getOverloads, Module, memberName))) static if(.isScriptable!(__traits(getAttributes, overload))()) {
1807 						auto helper = &__traits(getOverloads, Module, memberName)[idx];
1808 						static if(Parameters!helper.length >= 1 && is(Parameters!helper[0] == Type)) {
1809 							// this staticMap is a bit of a hack so it can handle `in float`... liable to break with others, i'm sure
1810 							_properties[memberName] = (staticMap!(Unqual, Parameters!helper[1 .. $]) args) {
1811 								return __traits(getOverloads, Module, memberName)[idx](obj, args);
1812 							};
1813 						}
1814 					}
1815 				}
1816 			}
1817 		}
1818 	};
1819 }
1820 
1821 bool isScriptable(attributes...)() {
1822 	foreach(attribute; attributes) {
1823 		static if(is(typeof(attribute) == string)) {
1824 			static if(attribute == scriptable) {
1825 				return true;
1826 			}
1827 		}
1828 	}
1829 	return false;
1830 }
1831 
1832 bool isScriptableOpaque(T)() {
1833 	static if(is(typeof(T.isOpaqueStruct) == bool))
1834 		return T.isOpaqueStruct == true;
1835 	return false;
1836 }
1837 
1838 bool appearsNumeric(string n) {
1839 	if(n.length == 0)
1840 		return false;
1841 	foreach(c; n) {
1842 		if(c < '0' || c > '9')
1843 			return false;
1844 	}
1845 	return true;
1846 }
1847 
1848 
1849 /// Wraps a struct by reference. The pointer is stored - be sure the struct doesn't get freed or go out of scope!
1850 ///
1851 /// 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.
1852 WrappedNativeObject wrapNativeObject(Struct)(Struct* obj) if(is(Struct == struct)) {
1853 	return null; // FIXME
1854 }
Suggestion Box / Bug Report