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