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