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