1 /*
2 	FIXME:
3 		overloads can be done as an object representing the overload set
4 		tat opCall does the dispatch. Then other overloads can actually
5 		be added more sanely.
6 
7 	FIXME:
8 		instantiate template members when reflection with certain
9 		arguments if marked right...
10 
11 
12 	FIXME:
13 		pointer to member functions can give a way to wrap things
14 
15 		we'll pass it an opaque object as this and it will unpack and call the method
16 
17 		we can also auto-generate getters and setters for properties with this method
18 
19 		and constructors, so the script can create class objects too
20 */
21 
22 
23 /++
24 	jsvar provides a D type called [var] that works similarly to the same in Javascript.
25 
26 	It is weakly (even weaker than JS, frequently returning null rather than throwing on
27 	an invalid operation) and dynamically typed, but interops pretty easily with D itself:
28 
29 	---
30 	var a = 10;
31 	a ~= "20";
32 		assert(a == "1020");
33 
34 	var a = function(int b, int c) { return b+c; };
35 	// note the second set of () is because of broken @property
36 	assert(a()(10,20) == 30);
37 
38 	var a = var.emptyObject;
39 	a.foo = 30;
40 	assert(a["foo"] == 30);
41 
42 	var b = json!q{
43 		"foo":12,
44 		"bar":{"hey":[1,2,3,"lol"]}
45 	};
46 
47 	assert(b.bar.hey[1] == 2);
48 	---
49 
50 
51 	You can also use [var.fromJson], a static method, to quickly and easily
52 	read json or [var.toJson] to write it.
53 
54 	Also, if you combine this with my [arsd.script] module, you get pretty
55 	easy interop with a little scripting language that resembles a cross between
56 	D and Javascript - just like you can write in D itself using this type.
57 
58 	Please note that function default arguments are NOT likely to work in the script.
59 	You'd have to use a helper thing that I haven't written yet. opAssign can never
60 	do it because that information is lost when it becomes a pointer. ParamDefault
61 	is thus commented out for now.
62 
63 
64 	Properties:
65 	$(LIST
66 		* note that @property doesn't work right in D, so the opDispatch properties
67 		  will require double parenthesis to call as functions.
68 
69 		* Properties inside a var itself are set specially:
70 			obj.propName._object = new PropertyPrototype(getter, setter);
71 	)
72 
73 	D structs can be turned to vars, but it is a copy.
74 
75 	Wrapping D native objects is coming later, the current ways suck. I really needed
76 	properties to do them sanely at all, and now I have it. A native wrapped object will
77 	also need to be set with _object prolly.
78 
79 	Author: Adam D Ruppe
80 
81 	History: Started in July 2013.
82 +/
83 module arsd.jsvar;
84 
85 version=new_std_json;
86 
87 static import std.array;
88 import std.traits;
89 import std.conv;
90 import std.json;
91 
92 version(jsvar_throw)
93 	/++
94 		Variable to decide if jsvar throws on certain invalid
95 		operations or continues on propagating null vars.
96 	+/
97 	bool jsvar_throw = true;
98 else
99 	/// ditto
100 	bool jsvar_throw = false;
101 
102 // uda for wrapping classes
103 enum scriptable = "arsd_jsvar_compatible";
104 
105 /*
106 	PrototypeObject FIXME:
107 		make undefined variables reaction overloadable in PrototypeObject, not just a switch
108 
109 	script FIXME:
110 
111 	the Expression should keep scriptFilename and lineNumber around for error messages
112 
113 	it should consistently throw on missing semicolons
114 
115 	*) in operator
116 
117 	*) nesting comments, `` string literals
118 	*) opDispatch overloading
119 	*) properties???//
120 		a.prop on the rhs => a.prop()
121 		a.prop on the lhs => a.prop(rhs);
122 		if opAssign, it can just do a.prop(a.prop().opBinary!op(rhs));
123 
124 		But, how do we mark properties in var? Can we make them work this way in D too?
125 	0) add global functions to the object (or at least provide a convenience function to make a pre-populated global object)
126 	1) ensure operator precedence is sane
127 	2) a++ would prolly be nice, and def -a
128 	4) switches?
129 	10) __FILE__ and __LINE__ as default function arguments should work like in D
130 	16) stack traces on script exceptions
131 	17) an exception type that we can create in the script
132 
133 	14) import???????/ it could just attach a particular object to the local scope, and the module decl just giving the local scope a name
134 		there could be a super-global object that is the prototype of the "global" used here
135 		then you import, and it pulls moduleGlobal.prototype = superGlobal.modulename... or soemthing.
136 
137 		to get the vars out in D, you'd have to be aware of this, since you pass the superglobal
138 		hmmm maybe not worth it
139 
140 		though maybe to export vars there could be an explicit export namespace or something.
141 
142 
143 	6) gotos? labels? labeled break/continue?
144 	18) what about something like ruby's blocks or macros? parsing foo(arg) { code } is easy enough, but how would we use it?
145 
146 	var FIXME:
147 
148 	user defined operator overloading on objects, including opCall, opApply, and more
149 	flesh out prototype objects for Array, String, and Function
150 
151 	looserOpEquals
152 
153 	it would be nice if delegates on native types could work
154 */
155 
156 static if(__VERSION__ <= 2076) {
157 	// compatibility shims with gdc
158 	enum JSONType {
159 		object = JSON_TYPE.OBJECT,
160 		null_ = JSON_TYPE.NULL,
161 		false_ = JSON_TYPE.FALSE,
162 		true_ = JSON_TYPE.TRUE,
163 		integer = JSON_TYPE.INTEGER,
164 		float_ = JSON_TYPE.FLOAT,
165 		array = JSON_TYPE.ARRAY,
166 		string = JSON_TYPE.STRING,
167 		uinteger = JSON_TYPE.UINTEGER
168 	}
169 }
170 
171 
172 /*
173 	Script notes:
174 
175 	the one type is var. It works just like the var type in D from arsd.jsvar.
176 	(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)
177 
178 	There is no comma operator, but you can use a scope as an expression: a++, b++; can be written as {a++;b++;}
179 */
180 
181 version(test_script)
182 	struct Foop {
183 		int a = 12;
184 		string n = "hate";
185 		void speak() { writeln(n, " ", a); n = "love"; writeln(n, " is what it is now"); }
186 		void speak2() { writeln("speak2 ", n, " ", a); }
187 	}
188 version(test_script)
189 void main() {
190 import arsd.script;
191 writeln(interpret("x*x + 3*x;", var(["x":3])));
192 
193 	{
194 	var a = var.emptyObject;
195 	a.qweq = 12;
196 	}
197 
198 	// the WrappedNativeObject is disgusting
199 	// but works. sort of.
200 	/*
201 	Foop foop2;
202 
203 	var foop;
204 	foop._object = new WrappedNativeObject!Foop(foop2);
205 
206 	foop.speak()();
207 	foop.a = 25;
208 	writeln(foop.n);
209 	foop.speak2()();
210 	return;
211 	*/
212 
213 	import arsd.script;
214 	struct Test {
215 		int a = 10;
216 		string name = "ten";
217 	}
218 
219 	auto globals = var.emptyObject;
220 	globals.lol = 100;
221 	globals.rofl = 23;
222 
223 	globals.arrtest = var.emptyArray;
224 
225 	globals.write._function = (var _this, var[] args) {
226 		string s;
227 		foreach(a; args)
228 			s ~= a.get!string;
229 		writeln("script said: ", s);
230 		return var(null);
231 	};
232 
233 	// call D defined functions in script
234 	globals.func = (var a, var b) { writeln("Hello, world! You are : ", a, " and ", b); };
235 
236 	globals.ex = () { throw new ScriptRuntimeException("test", 1); };
237 
238 	globals.fun = { return var({ writeln("hello inside!"); }); };
239 
240 	import std.file;
241 	writeln(interpret(readText("scripttest_code.d"), globals));
242 
243 	globals.ten = 10.0;
244 	globals.five = 5.0;
245 	writeln(interpret(q{
246 		var a = json!q{ };
247 		a.b = json!q{ };
248 		a.b.c = 10;
249 		a;
250 	}, globals));
251 
252 	/*
253 	globals.minigui = json!q{};
254 	import arsd.minigui;
255 	globals.minigui.createWindow = {
256 		var v;
257 		auto mw = new MainWindow();
258 		v._object = new OpaqueNativeObject!(MainWindow)(mw);
259 		v.loop = { mw.loop(); };
260 		return v;
261 	};
262 	*/
263 
264 	repl(globals);
265 
266 	writeln("BACK IN D!");
267 	globals.c()(10); // call script defined functions in D (note: this runs the interpreter)
268 
269 	//writeln(globals._getMember("lol", false));
270 	return;
271 
272 	var k,l ;
273 
274 	var j = json!q{
275 		"hello": {
276 			"data":[1,2,"giggle",4]
277 		},
278 		"world":20
279 	};
280 
281 	writeln(j.hello.data[2]);
282 
283 
284 	Test t;
285 	var rofl = t;
286 	writeln(rofl.name);
287 	writeln(rofl.a);
288 
289 	rofl.a = "20";
290 	rofl.name = "twenty";
291 
292 	t = rofl.get!Test;
293 	writeln(t);
294 
295 	var a1 = 10;
296 	a1 -= "5";
297 	a1 /= 2;
298 
299 	writeln(a1);
300 
301 	var a = 10;
302 	var b = 20;
303 	a = b;
304 
305 	b = 30;
306 	a += 100.2;
307 	writeln(a);
308 
309 	var c = var.emptyObject;
310 	c.a = b;
311 
312 	var d = c;
313 	d.b = 50;
314 
315 	writeln(c.b);
316 
317 	writeln(d.toJson());
318 
319 	var e = a + b;
320 	writeln(a, " + ", b, " = ", e);
321 
322 	e = function(var lol) {
323 		writeln("hello with ",lol,"!");
324 		return lol + 10;
325 	};
326 
327 	writeln(e("15"));
328 
329 	if(var("ass") > 100)
330 		writeln(var("10") / "3");
331 }
332 
333 template json(string s) {
334 	// ctfe doesn't support the unions std.json uses :(
335 	//enum json = var.fromJsonObject(s);
336 
337 	// FIXME we should at least validate string s at compile time
338 	var json() {
339 		return var.fromJson("{" ~ s ~ "}");
340 	}
341 }
342 
343 // literals
344 
345 // var a = varArray(10, "cool", 2);
346 // assert(a[0] == 10); assert(a[1] == "cool"); assert(a[2] == 2);
347 var varArray(T...)(T t) {
348 	var a = var.emptyArray;
349 	foreach(arg; t)
350 		a ~= var(arg);
351 	return a;
352 }
353 
354 // var a = varObject("cool", 10, "bar", "baz");
355 // assert(a.cool == 10 && a.bar == "baz");
356 var varObject(T...)(T t) {
357 	var a = var.emptyObject;
358 
359 	string lastString;
360 	foreach(idx, arg; t) {
361 		static if(idx % 2 == 0) {
362 			lastString = arg;
363 		} else {
364 			assert(lastString !is null);
365 			a[lastString] = arg;
366 			lastString = null;
367 		}
368 	}
369 	return a;
370 }
371 
372 
373 private double stringToNumber(string s) {
374 	double r;
375 	try {
376 		r = to!double(s);
377 	} catch (Exception e) {
378 		r = double.nan;
379 	}
380 
381 	return r;
382 }
383 
384 private bool doubleIsInteger(double r) {
385 	return (r == cast(long) r);
386 }
387 
388 // helper template for operator overloading
389 private var _op(alias _this, alias this2, string op, T)(T t) if(op == "~") {
390 	static if(is(T == var)) {
391 		if(t.payloadType() == var.Type.Array)
392 			return _op!(_this, this2, op)(t._payload._array);
393 		else if(t.payloadType() == var.Type.String)
394 			return _op!(_this, this2, op)(t._payload._string);
395 		//else
396 			//return _op!(_this, this2, op)(t.get!string);
397 	}
398 
399 	if(this2.payloadType() == var.Type.Array) {
400 		auto l = this2._payload._array;
401 		static if(isArray!T && !isSomeString!T)
402 			foreach(item; t)
403 				l ~= var(item);
404 		else
405 			l ~= var(t);
406 
407 		_this._type = var.Type.Array;
408 		_this._payload._array = l;
409 		return _this;
410 	} else if(this2.payloadType() == var.Type.String) {
411 		auto l = this2._payload._string;
412 		l ~= var(t).get!string; // is this right?
413 		_this._type = var.Type.String;
414 		_this._payload._string = l;
415 		return _this;
416 	} else {
417 		auto l = this2.get!string;
418 		l ~= var(t).get!string;
419 		_this._type = var.Type.String;
420 		_this._payload._string = l;
421 		return _this;
422 	}
423 
424 	assert(0);
425 
426 }
427 
428 // FIXME: maybe the bitops should be moved out to another function like ~ is
429 private var _op(alias _this, alias this2, string op, T)(T t) if(op != "~") {
430 	static if(is(T == var)) {
431 		if(t.payloadType() == var.Type.Integral)
432 			return _op!(_this, this2, op)(t._payload._integral);
433 		if(t.payloadType() == var.Type.Floating)
434 			return _op!(_this, this2, op)(t._payload._floating);
435 		if(t.payloadType() == var.Type.String)
436 			return _op!(_this, this2, op)(t._payload._string);
437 		throw new Exception("Attempted invalid operator `" ~ op ~ "` on variable of type " ~ to!string(t.payloadType()));
438 	} else {
439 		if(this2.payloadType() == var.Type.Integral) {
440 			auto l = this2._payload._integral;
441 			static if(isIntegral!T) {
442 				mixin("l "~op~"= t;");
443 				_this._type = var.Type.Integral;
444 				_this._payload._integral = l;
445 				return _this;
446 			} else static if(isFloatingPoint!T) {
447 				static if(op == "&" || op == "|" || op == "^" || op == "<<" || op == ">>" || op == ">>>") {
448 					this2._type = var.Type.Integral;
449 					long f = l;
450 					mixin("f "~op~"= cast(long) t;");
451 					_this._type = var.Type.Integral;
452 					_this._payload._integral = f;
453 				} else {
454 					this2._type = var.Type.Floating;
455 					double f = l;
456 					mixin("f "~op~"= t;");
457 					_this._type = var.Type.Floating;
458 					_this._payload._floating = f;
459 				}
460 				return _this;
461 			} else static if(isSomeString!T) {
462 				auto rhs = stringToNumber(t);
463 				if(doubleIsInteger(rhs)) {
464 					mixin("l "~op~"= cast(long) rhs;");
465 					_this._type = var.Type.Integral;
466 					_this._payload._integral = l;
467 				} else{
468 					static if(op == "&" || op == "|" || op == "^" || op == "<<" || op == ">>" || op == ">>>") {
469 						long f = l;
470 						mixin("f "~op~"= cast(long) rhs;");
471 						_this._type = var.Type.Integral;
472 						_this._payload._integral = f;
473 					} else {
474 						double f = l;
475 						mixin("f "~op~"= rhs;");
476 						_this._type = var.Type.Floating;
477 						_this._payload._floating = f;
478 					}
479 				}
480 				return _this;
481 
482 			}
483 		} else if(this2.payloadType() == var.Type.Floating) {
484 			auto f = this._payload._floating;
485 
486 			static if(isIntegral!T || isFloatingPoint!T) {
487 				static if(op == "&" || op == "|" || op == "^" || op == "<<" || op == ">>" || op == ">>>") {
488 					long argh = cast(long) f;
489 					mixin("argh "~op~"= cast(long) t;");
490 					_this._type = var.Type.Integral;
491 					_this._payload._integral = argh;
492 				} else {
493 					mixin("f "~op~"= t;");
494 					_this._type = var.Type.Floating;
495 					_this._payload._floating = f;
496 				}
497 				return _this;
498 			} else static if(isSomeString!T) {
499 				auto rhs = stringToNumber(t);
500 
501 				static if(op == "&" || op == "|" || op == "^" || op == "<<" || op == ">>" || op == ">>>") {
502 					long pain = cast(long) f;
503 					mixin("pain "~op~"= cast(long) rhs;");
504 					_this._type = var.Type.Integral;
505 					_this._payload._floating = pain;
506 				} else {
507 					mixin("f "~op~"= rhs;");
508 					_this._type = var.Type.Floating;
509 					_this._payload._floating = f;
510 				}
511 				return _this;
512 			} else static assert(0);
513 		} else if(this2.payloadType() == var.Type.String) {
514 			static if(op == "&" || op == "|" || op == "^" || op == "<<" || op == ">>" || op == ">>>") {
515 				long r = cast(long) stringToNumber(this2._payload._string);
516 				long rhs;
517 			} else {
518 				double r = stringToNumber(this2._payload._string);
519 				double rhs;
520 			}
521 
522 			static if(isSomeString!T) {
523 				rhs = cast(typeof(rhs)) stringToNumber(t);
524 			} else {
525 				rhs = to!(typeof(rhs))(t);
526 			}
527 
528 			mixin("r " ~ op ~ "= rhs;");
529 
530 			static if(is(typeof(r) == double)) {
531 				_this._type = var.Type.Floating;
532 				_this._payload._floating = r;
533 			} else static if(is(typeof(r) == long)) {
534 				_this._type = var.Type.Integral;
535 				_this._payload._integral = r;
536 			} else static assert(0);
537 			return _this;
538 		} else {
539 			// the operation is nonsensical, we should throw or ignore it
540 			var i = 0;
541 			return i;
542 		}
543 	}
544 
545 	assert(0);
546 }
547 
548 
549 ///
550 struct var {
551 	public this(T)(T t) {
552 		static if(is(T == var))
553 			this = t;
554 		else
555 			this.opAssign(t);
556 	}
557 
558 	// used by the script interpreter... does a .dup on array, new on class if possible, otherwise copies members.
559 	public var _copy_new() {
560 		if(payloadType() == Type.Object) {
561 			var cp;
562 			if(this._payload._object !is null) {
563 				auto po = this._payload._object.new_(null);
564 				cp._object = po;
565 			}
566 			return cp;
567 		} else if(payloadType() == Type.Array) {
568 			var cp;
569 			cp = this._payload._array.dup;
570 			return cp;
571 		} else {
572 			return this._copy();
573 		}
574 	}
575 
576 	public var _copy() {
577 		final switch(payloadType()) {
578 			case Type.Integral:
579 			case Type.Boolean:
580 			case Type.Floating:
581 			case Type.Function:
582 			case Type.String:
583 				// since strings are immutable, we can pretend they are value types too
584 				return this; // value types don't need anything special to be copied
585 
586 			case Type.Array:
587 				var cp;
588 				cp = this._payload._array[];
589 				return cp;
590 			case Type.Object:
591 				var cp;
592 				if(this._payload._object !is null)
593 					cp._object = this._payload._object.copy;
594 				return cp;
595 		}
596 	}
597 
598 	/// `if(some_var)` will call this and give behavior based on the dynamic type. Shouldn't be too surprising.
599 	public bool opCast(T:bool)() {
600 		final switch(this._type) {
601 			case Type.Object:
602 				return this._payload._object !is null;
603 			case Type.Array:
604 				return this._payload._array.length != 0;
605 			case Type.String:
606 				return this._payload._string.length != 0;
607 			case Type.Integral:
608 				return this._payload._integral != 0;
609 			case Type.Floating:
610 				return this._payload._floating != 0;
611 			case Type.Boolean:
612 				return this._payload._boolean;
613 			case Type.Function:
614 				return this._payload._function !is null;
615 		}
616 	}
617 
618 	/// You can foreach over a var.
619 	public int opApply(scope int delegate(ref var) dg) {
620 		foreach(i, item; this)
621 			if(auto result = dg(item))
622 				return result;
623 		return 0;
624 	}
625 
626 	/// ditto
627 	public int opApply(scope int delegate(var, ref var) dg) {
628 		if(this.payloadType() == Type.Array) {
629 			foreach(i, ref v; this._payload._array)
630 				if(auto result = dg(var(i), v))
631 					return result;
632 		} else if(this.payloadType() == Type.Object && this._payload._object !is null) {
633 			// FIXME: if it offers input range primitives, we should use them
634 			// FIXME: user defined opApply on the object
635 			foreach(k, ref v; this._payload._object)
636 				if(auto result = dg(var(k), v))
637 					return result;
638 		} else if(this.payloadType() == Type.String) {
639 			// this is to prevent us from allocating a new string on each character, hopefully limiting that massively
640 			static immutable string chars = makeAscii!();
641 
642 			foreach(i, dchar c; this._payload._string) {
643 				var lol = "";
644 				if(c < 128)
645 					lol._payload._string = chars[c .. c + 1];
646 				else
647 					lol._payload._string = to!string(""d ~ c); // blargh, how slow can we go?
648 				if(auto result = dg(var(i), lol))
649 					return result;
650 			}
651 		}
652 		// throw invalid foreach aggregate
653 
654 		return 0;
655 	}
656 
657 
658 	/// Alias for [get]. e.g. `string s = cast(string) v;`
659 	public T opCast(T)() {
660 		return this.get!T;
661 	}
662 
663 	/// Calls [get] for a type automatically. `int a; var b; b.putInto(a);` will auto-convert to `int`.
664 	public auto ref putInto(T)(ref T t) {
665 		return t = this.get!T;
666 	}
667 
668 	/++
669 		Assigns a value to the var. It will do necessary implicit conversions
670 		and wrapping.
671 
672 		You can make a method `toArsdJsvar` on your own objects to override this
673 		default. It should return a [var].
674 
675 		History:
676 			On April 20, 2020, I changed the default mode for class assignment
677 			to [wrapNativeObject]. Previously it was [wrapOpaquely].
678 
679 			With the new [wrapNativeObject] behavior, you can mark methods
680 			@[scriptable] to expose them to the script.
681 	+/
682 	public var opAssign(T)(T t) if(!is(T == var)) {
683 		import std.typecons;
684 		static if(__traits(compiles, this = t.toArsdJsvar())) {
685 			static if(__traits(compiles, t is null)) {
686 				if(t is null)
687 					this = null;
688 				else
689 					this = t.toArsdJsvar();
690 			} else
691 				this = t.toArsdJsvar();
692 		} else static if(is(T : PrototypeObject)) {
693 			// support direct assignment of pre-made implementation objects
694 			// so prewrapped stuff can be easily passed.
695 			this._type = Type.Object;
696 			this._payload._object = t;
697 		} else static if(is(T == Nullable!N, N)) {
698 			if(t.isNull())
699 				this = null;
700 			else
701 				this = t.get();
702 		} else static if(is(T == enum)) {
703 			this._type = Type.String;
704 			this._payload._string = to!string(t);
705 		} else static if(isFloatingPoint!T) {
706 			this._type = Type.Floating;
707 			this._payload._floating = t;
708 		} else static if(isIntegral!T) {
709 			this._type = Type.Integral;
710 			this._payload._integral = t;
711 		} else static if(isCallable!T) {
712 			this._type = Type.Function;
713 			static if(is(T == typeof(this._payload._function))) {
714 				this._payload._function = t;
715 			} else
716 			this._payload._function = delegate var(var _this, var[] args) {
717 				var ret;
718 
719 				ParameterTypeTuple!T fargs;
720 
721 				// default args? nope they can't work cuz it is assigning a function pointer by here. alas.
722 				enum lol = static_foreach(fargs.length, 1, -1,
723 					`t(`,``,` < args.length ? args[`,`].get!(typeof(fargs[`,`])) : typeof(fargs[`,`]).init,`,`)`);
724 					//`t(`,``,` < args.length ? args[`,`].get!(typeof(fargs[`,`])) : ParamDefault!(T, `,`)(),`,`)`);
725 				/+
726 				foreach(idx, a; fargs) {
727 					if(idx == args.length)
728 						break;
729 					cast(Unqual!(typeof(a))) fargs[idx] = args[idx].get!(typeof(a));
730 				}
731 				+/
732 
733 				static if(is(ReturnType!t == void)) {
734 					//t(fargs);
735 					mixin(lol ~ ";");
736 				} else {
737 					//ret = t(fargs);
738 					ret = mixin(lol);
739 				}
740 
741 				return ret;
742 			};
743 		} else static if(isSomeString!T) {
744 			this._type = Type.String;
745 			this._payload._string = to!string(t);
746 		} else static if(is(T == class) || is(T == interface)) {
747 			if(t !is null && (cast(Object) t) is null)
748 				throw new Exception("Unsupported class or interface");
749 			this._type = Type.Object;
750 			this._payload._object = t is null ? null : wrapNativeObject(t);
751 		} else static if(.isScriptableOpaque!T) {
752 			// auto-wrap other classes with reference semantics
753 			this._type = Type.Object;
754 			this._payload._object = wrapOpaquely(t);
755 		} else static if(is(T == struct) || isAssociativeArray!T) {
756 			// copy structs and assoc arrays by value into a var object
757 			this._type = Type.Object;
758 			auto obj = new PrototypeObject();
759 			this._payload._object = obj;
760 
761 			static if(is(T == struct))
762 			foreach(member; __traits(allMembers, T)) {
763 				static if(__traits(compiles, __traits(getMember, t, member))) {
764 					static if(is(typeof(__traits(getMember, t, member)) == function)) {
765 						// skipping these because the delegate we get isn't going to work anyway; the object may be dead and certainly won't be updated
766 						//this[member] = &__traits(getMember, proxyObject, member);
767 
768 						// but for simple toString, I'll allow it by recreating the object on demand
769 						// and then calling the original function. (I might be able to do that for more but
770 						// idk, just doing simple thing first)
771 						static if(member == "toString" && is(typeof(&__traits(getMember, t, member)) == string delegate())) {
772 							this[member]._function =  delegate(var _this, var[] args) {
773 								auto val = _this.get!T;
774 								return var(val.toString());
775 							};
776 						}
777 					} else static if(is(typeof(__traits(getMember, t, member)))) {
778 						static if(!is(typeof(__traits(getMember, t, member)) == void))
779 							this[member] = __traits(getMember, t, member);
780 					}
781 				}
782 			} else {
783 				// assoc array
784 				foreach(l, v; t) {
785 					this[var(l)] = var(v);
786 				}
787 			}
788 		} else static if(isArray!T) {
789 			this._type = Type.Array;
790 			var[] arr;
791 			arr.length = t.length;
792 			static if(!is(T == void[])) // we can't append a void array but it is nice to support x = [];
793 				foreach(i, item; t)
794 					arr[i] = var(item);
795 			this._payload._array = arr;
796 		} else static if(is(T == bool)) {
797 			this._type = Type.Boolean;
798 			this._payload._boolean = t;
799 		} else static if(isSomeChar!T) {
800 			this._type = Type.String;
801 			this._payload._string = "";
802 			import std.utf;
803 			char[4] ugh;
804 			auto size = encode(ugh, t);
805 			this._payload._string = ugh[0..size].idup;
806 		}// else static assert(0, "unsupported type");
807 
808 		return this;
809 	}
810 
811 	public size_t opDollar() {
812 		return this.length().get!size_t;
813 	}
814 
815 	public var opOpAssign(string op, T)(T t) {
816 		if(payloadType() == Type.Object) {
817 			if(auto pt = cast(PropertyPrototype) this._payload._object) {
818 				auto propValue = pt.get;
819 				auto result = propValue.opOpAssign!(op)(t);
820 
821 				pt.set(result);
822 
823 				return result;
824 			} else if(this._payload._object !is null) {
825 				var* operator = this._payload._object._peekMember("opOpAssign", true);
826 				if(operator !is null && operator._type == Type.Function)
827 					return operator.call(this, op, t);
828 			}
829 		}
830 
831 		return _op!(this, this, op, T)(t);
832 	}
833 
834 	public var opUnary(string op : "-")() {
835 		static assert(op == "-");
836 		final switch(payloadType()) {
837 			case Type.Object:
838 			case Type.Array:
839 			case Type.Boolean:
840 			case Type.String:
841 			case Type.Function:
842 				assert(0); // FIXME
843 			//break;
844 			case Type.Integral:
845 				return var(-this.get!long);
846 			case Type.Floating:
847 				return var(-this.get!double);
848 		}
849 	}
850 
851 	public var opBinary(string op, T)(T t) {
852 		var n;
853 		if(payloadType() == Type.Object) {
854 			if(this._payload._object is null)
855 				return var(null);
856 			var* operator = this._payload._object._peekMember("opBinary", true);
857 			if(operator !is null && operator._type == Type.Function) {
858 				return operator.call(this, op, t);
859 			}
860 		}
861 		return _op!(n, this, op, T)(t);
862 	}
863 
864 	public var opBinaryRight(string op, T)(T s) {
865 		return var(s).opBinary!op(this);
866 	}
867 
868 	// this in foo
869 	public var* opBinary(string op : "in", T)(T s) {
870 		var rhs = var(s);
871 		return rhs.opBinaryRight!"in"(this);
872 	}
873 
874 	// foo in this
875 	public var* opBinaryRight(string op : "in", T)(T s) {
876 		// this needs to be an object
877 		if(this._object is null)
878 			return null;
879 		return var(s).get!string in this._object._properties;
880 	}
881 
882 	public var apply(var _this, var[] args) {
883 		if(this.payloadType() == Type.Function) {
884 			if(this._payload._function is null) {
885 				if(jsvar_throw)
886 					throw new DynamicTypeException(this, Type.Function);
887 				else
888 					return var(null);
889 			}
890 			return this._payload._function(_this, args);
891 		} else if(this.payloadType() == Type.Object) {
892 			if(this._payload._object is null) {
893 				if(jsvar_throw)
894 					throw new DynamicTypeException(this, Type.Function);
895 				else
896 					return var(null);
897 			}
898 			var* operator = this._payload._object._peekMember("opCall", true);
899 			if(operator !is null && operator._type == Type.Function)
900 				return operator.apply(_this, args);
901 		}
902 
903 		if(this.payloadType() == Type.Integral || this.payloadType() == Type.Floating) {
904 			if(args.length)
905 				return var(this.get!double * args[0].get!double);
906 			else
907 				return this;
908 		} else if(jsvar_throw) {
909 			throw new DynamicTypeException(this, Type.Function);
910 		}
911 
912 		//return this;
913 		return var(null);
914 	}
915 
916 	public var call(T...)(var _this, T t) {
917 		var[] args;
918 		foreach(a; t) {
919 			args ~= var(a);
920 		}
921 		return this.apply(_this, args);
922 	}
923 
924 	public var opCall(T...)(T t) {
925 		return this.call(this, t);
926 	}
927 
928 	/*
929 	public var applyWithMagicLocals(var _this, var[] args, var[string] magicLocals) {
930 
931 	}
932 	*/
933 
934 	public string toString() {
935 		return this.get!string;
936 	}
937 
938 	public T getWno(T)() {
939 		if(payloadType == Type.Object) {
940 			if(auto wno = cast(WrappedNativeObject) this._payload._object) {
941 				auto no = cast(T) wno.getObject();
942 				if(no !is null)
943 					return no;
944 			}
945 		}
946 		return null;
947 	}
948 
949 	/++
950 		Gets the var converted to type `T` as best it can. `T` may be constructed
951 		from `T.fromJsVar`, or through type conversions (coercing as needed). If
952 		`T` happens to be a struct, it will automatically introspect to convert
953 		the var object member-by-member.
954 
955 		History:
956 			On April 21, 2020, I changed the behavior of
957 
958 			---
959 			var a = null;
960 			string b = a.get!string;
961 			---
962 
963 			Previously, `b == "null"`, which would print the word
964 			when writeln'd. Now, `b is null`, which prints the empty string,
965 			which is a bit less user-friendly, but more consistent with
966 			converting to/from D strings in general.
967 
968 			If you are printing, you can check `a.get!string is null` and print
969 			null at that point if you like.
970 
971 			I also wrote the first draft of this documentation at that time,
972 			even though the function has been public since the beginning.
973 
974 			On January 1, 2021, I changed `get!some_struct` to call properties
975 			on the var, if a member looks like a function or object, to try to
976 			get plain-old-data out. Since the functions are only ever put there
977 			by you or by you allowing script, I don't feel too bad about it, but
978 			it still might not be ideal for all circumstances, so idk if I'll leave
979 			it this way or not.
980 
981 			One thing it helps for though is taking scripted subclasses back into D
982 			structs, since the parent class thing is likely to be virtual properties.
983 			And having that just work in argument lists is really cool...
984 
985 			Search function for the comment "property getter support" to see the impl.
986 	+/
987 	public T get(T)() if(!is(T == void)) {
988 		import std.typecons;
989 		static if(is(T == var)) {
990 			return this;
991 		} else static if(__traits(compiles, T.fromJsVar(var.init))) {
992 			return T.fromJsVar(this);
993 		} else static if(__traits(compiles, T(this))) {
994 			return T(this);
995 		} else static if(__traits(compiles, new T(this))) {
996 			return new T(this);
997 		} else static if(is(T == Nullable!N, N)) {
998 			if(payloadType == Type.Object && this._payload._object is null)
999 				return T.init;
1000 			else
1001 				return nullable(this.get!N);
1002 		} else
1003 		final switch(payloadType) {
1004 			case Type.Boolean:
1005 				static if(is(T == bool))
1006 					return this._payload._boolean;
1007 				else static if(isFloatingPoint!T || isIntegral!T)
1008 					return cast(T) (this._payload._boolean ? 1 : 0); // the cast is for enums, I don't like this so FIXME
1009 				else static if(isSomeString!T)
1010 					return to!T(this._payload._boolean ? "true" : "false");
1011 				else
1012 				return T.init;
1013 			case Type.Object:
1014 				static if(isAssociativeArray!T) {
1015 					T ret;
1016 					if(this._payload._object !is null)
1017 					foreach(k, v; this._payload._object._properties)
1018 						ret[to!(KeyType!T)(k)] = v.get!(ValueType!T);
1019 
1020 					return ret;
1021 				} else static if(is(T : PrototypeObject)) {
1022 					// they are requesting an implementation object, just give it to them
1023 					return cast(T) this._payload._object;
1024 				} else static if(isScriptableOpaque!(Unqual!T)) {
1025 					if(auto wno = cast(WrappedOpaque!(Unqual!T)) this._payload._object) {
1026 						return wno.wrapping();
1027 					}
1028 					static if(is(T == R*, R))
1029 					if(auto wno = cast(WrappedOpaque!(Unqual!(R))) this._payload._object) {
1030 						return wno.wrapping();
1031 					}
1032 					throw new DynamicTypeException(this, Type.Object); // FIXME: could be better
1033 				} else static if(is(T == struct) || is(T == class) || is(T == interface)) {
1034 					// first, we'll try to give them back the native object we have, if we have one
1035 					static if(is(T : Object) || is(T == interface)) {
1036 						auto t = this;
1037 						// need to walk up the prototype chain too
1038 						while(t != null) {
1039 							assert(t.payloadType == Type.Object);
1040 							if(auto wno = cast(WrappedNativeObject) t._payload._object) {
1041 								auto no = cast(T) wno.getObject();
1042 
1043 								if(no !is null) {
1044 									auto sc = cast(ScriptableSubclass) no;
1045 									if(sc !is null) {
1046 										sc.setScriptVar(this);
1047 									}
1048 
1049 									return no;
1050 								}
1051 							}
1052 							t = t.prototype;
1053 						}
1054 
1055 						// FIXME: this is kinda weird.
1056 						return null;
1057 					} else {
1058 
1059 						// failing that, generic struct or class getting: try to fill in the fields by name
1060 						T t;
1061 						bool initialized = true;
1062 						static if(is(T == class)) {
1063 							static if(__traits(compiles, new T())) {
1064 								t = new T();
1065 							} else {
1066 								initialized = false;
1067 							}
1068 						}
1069 
1070 
1071 						if(initialized)
1072 						foreach(i, a; t.tupleof) {
1073 							var possibility = this[t.tupleof[i].stringof[2..$]];
1074 							// FIXME: so there is the possibility of getting some data getting all caught
1075 							// up in a script function doing weird things. If I can prevent that, I'd like to...
1076 							// but it is also really useful for this to work for some scenarios...
1077 							static if(!is(typeof(a) == return)) // if it is callable, just assign the func ref
1078 							if(isCallableJsvarObject(possibility))
1079 								possibility = possibility.apply(this, null); // crude approximation of property getter support
1080 							cast(Unqual!(typeof((a)))) t.tupleof[i] = possibility.get!(typeof(a));
1081 						}
1082 
1083 						return t;
1084 					}
1085 				} else static if(isSomeString!T) {
1086 					if(this._object !is null)
1087 						return to!T(this._object.toString());
1088 					return null;// "null";
1089 				} else
1090 					return T.init;
1091 			case Type.Integral:
1092 				static if(isFloatingPoint!T || isIntegral!T)
1093 					return to!T(this._payload._integral);
1094 				else static if(isSomeString!T)
1095 					return to!T(this._payload._integral);
1096 				else
1097 					return T.init;
1098 			case Type.Floating:
1099 				static if(isFloatingPoint!T || isIntegral!T)
1100 					return to!T(this._payload._floating);
1101 				else static if(isSomeString!T)
1102 					return to!T(this._payload._floating);
1103 				else
1104 					return T.init;
1105 			case Type.String:
1106 				static if(__traits(compiles, to!T(this._payload._string))) {
1107 					try {
1108 						return to!T(this._payload._string);
1109 					} catch (Exception e) { return T.init; }
1110 				} else
1111 					return T.init;
1112 			case Type.Array:
1113 				import std.range;
1114 				auto pl = this._payload._array;
1115 				static if(isSomeString!T) {
1116 					return to!T(pl);
1117 				} else static if(is(T == E[N], E, size_t N)) {
1118 					T ret;
1119 					foreach(i; 0 .. N) {
1120 						if(i >= pl.length)
1121 							break;
1122 						ret[i] = pl[i].get!E;
1123 					}
1124 					return ret;
1125 				} else static if(is(T == E[], E)) {
1126 					T ret;
1127 					static if(is(ElementType!T == void)) {
1128 						static assert(0, "try wrapping the function to get rid of void[] args");
1129 						//alias getType = ubyte;
1130 					} else
1131 						alias getType = ElementType!T;
1132 					foreach(item; pl)
1133 						ret ~= item.get!(getType);
1134 					return ret;
1135 				} else
1136 					return T.init;
1137 				// is it sane to translate anything else?
1138 			case Type.Function:
1139 				static if(isSomeString!T) {
1140 					return to!T("<function>");
1141 				} else static if(isDelegate!T) {
1142 					// making a local copy because otherwise the delegate might refer to a struct on the stack and get corrupted later or something
1143 					auto func = this._payload._function;
1144 
1145 					// the static helper lets me pass specific variables to the closure
1146 					static T helper(typeof(func) func) {
1147 						return delegate ReturnType!T (ParameterTypeTuple!T args) {
1148 							var[] arr;
1149 							foreach(arg; args)
1150 								arr ~= var(arg);
1151 							var ret = func(var(null), arr);
1152 							static if(is(ReturnType!T == void))
1153 								return;
1154 							else
1155 								return ret.get!(ReturnType!T);
1156 						};
1157 					}
1158 
1159 					return helper(func);
1160 
1161 				} else
1162 					return T.init;
1163 				// FIXME: we just might be able to do better for both of these
1164 			//break;
1165 		}
1166 	}
1167 
1168 	public T get(T)() if(is(T == void)) {}
1169 
1170 	public T nullCoalesce(T)(T t) {
1171 		if(_type == Type.Object && _payload._object is null)
1172 			return t;
1173 		return this.get!T;
1174 	}
1175 
1176 	public double opCmp(T)(T t) {
1177 		auto f = this.get!double;
1178 		static if(is(T == var))
1179 			auto r = t.get!double;
1180 		else
1181 			auto r = t;
1182 		return f - r;
1183 	}
1184 
1185 	public bool opEquals(T)(T t) {
1186 		return this.opEquals(var(t));
1187 	}
1188 
1189 	public bool opEquals(T:var)(T t) const {
1190 		// int and float can implicitly convert
1191 		if(this._type == Type.Integral && t._type == Type.Floating)
1192 			return _payload._integral == t._payload._floating;
1193 		if(t._type == Type.Integral && this._type == Type.Floating)
1194 			return t._payload._integral == this._payload._floating;
1195 
1196 		// but the others are kinda strict
1197 		// FIXME: should this be == or === ?
1198 
1199 		if(this._type != t._type)
1200 			return false;
1201 		final switch(this._type) {
1202 			case Type.Object:
1203 				return _payload._object is t._payload._object;
1204 			case Type.Integral:
1205 				return _payload._integral == t._payload._integral;
1206 			case Type.Boolean:
1207 				return _payload._boolean == t._payload._boolean;
1208 			case Type.Floating:
1209 				return _payload._floating == t._payload._floating; // FIXME: approxEquals?
1210 			case Type.String:
1211 				return _payload._string == t._payload._string;
1212 			case Type.Function:
1213 				return _payload._function is t._payload._function;
1214 			case Type.Array:
1215 				return _payload._array == t._payload._array;
1216 		}
1217 		assert(0);
1218 	}
1219 
1220 	public enum Type {
1221 		Object, Array, Integral, Floating, String, Function, Boolean
1222 	}
1223 
1224 	public Type payloadType() {
1225 		return _type;
1226 	}
1227 
1228 	private Type _type;
1229 
1230 	private union Payload {
1231 		PrototypeObject _object;
1232 		var[] _array;
1233 		long _integral;
1234 		double _floating;
1235 		string _string;
1236 		bool _boolean;
1237 		var delegate(var _this, var[] args) _function;
1238 	}
1239 
1240 	package VarMetadata _metadata;
1241 
1242 	public void _function(var delegate(var, var[]) f) {
1243 		this._payload._function = f;
1244 		this._type = Type.Function;
1245 	}
1246 
1247 	/*
1248 	public void _function(var function(var, var[]) f) {
1249 		var delegate(var, var[]) dg;
1250 		dg.ptr = null;
1251 		dg.funcptr = f;
1252 		this._function = dg;
1253 	}
1254 	*/
1255 
1256 	public void _object(PrototypeObject obj) {
1257 		this._type = Type.Object;
1258 		this._payload._object = obj;
1259 	}
1260 
1261 	public PrototypeObject _object() {
1262 		if(this._type == Type.Object)
1263 			return this._payload._object;
1264 		return null;
1265 	}
1266 
1267 	package Payload _payload;
1268 
1269 	private void _requireType(Type t, string file = __FILE__, size_t line = __LINE__){
1270 		if(this.payloadType() != t)
1271 			throw new DynamicTypeException(this, t, file, line);
1272 	}
1273 
1274 	public var opSlice(var e1, var e2) {
1275 		return this.opSlice(e1.get!ptrdiff_t, e2.get!ptrdiff_t);
1276 	}
1277 
1278 	public var opSlice(ptrdiff_t e1, ptrdiff_t e2) {
1279 		if(this.payloadType() == Type.Array) {
1280 			if(e1 > _payload._array.length)
1281 				e1 = _payload._array.length;
1282 			if(e2 > _payload._array.length)
1283 				e2 = _payload._array.length;
1284 			return var(_payload._array[e1 .. e2]);
1285 		}
1286 		if(this.payloadType() == Type.String) {
1287 			if(e1 > _payload._string.length)
1288 				e1 = _payload._string.length;
1289 			if(e2 > _payload._string.length)
1290 				e2 = _payload._string.length;
1291 			return var(_payload._string[e1 .. e2]);
1292 		}
1293 		if(this.payloadType() == Type.Object) {
1294 			var operator = this["opSlice"];
1295 			if(operator._type == Type.Function) {
1296 				return operator.call(this, e1, e2);
1297 			}
1298 		}
1299 
1300 		// might be worth throwing here too
1301 		return var(null);
1302 	}
1303 
1304 	/// Forwards to [opIndex]
1305 	public @property ref var opDispatch(string name, string file = __FILE__, size_t line = __LINE__)() {
1306 		return this[name];
1307 	}
1308 
1309 	/// Forwards to [opIndexAssign]
1310 	public @property ref var opDispatch(string name, string file = __FILE__, size_t line = __LINE__, T)(T r) {
1311 		return this.opIndexAssign!T(r, name);
1312 	}
1313 
1314 	/// Looks up a sub-property of the object
1315 	public ref var opIndex(var name, string file = __FILE__, size_t line = __LINE__) {
1316 		return opIndex(name.get!string, file, line);
1317 	}
1318 
1319 	/// Sets a sub-property of the object
1320 	public ref var opIndexAssign(T)(T t, var name, string file = __FILE__, size_t line = __LINE__) {
1321 		return opIndexAssign(t, name.get!string, file, line);
1322 	}
1323 
1324 	public ref var opIndex(string name, string file = __FILE__, size_t line = __LINE__) {
1325 		// if name is numeric, we should convert to int for arrays
1326 		if(name.length && name[0] >= '0' && name[0] <= '9' && this.payloadType() == Type.Array)
1327 			return opIndex(to!size_t(name), file, line);
1328 
1329 		if(this.payloadType() != Type.Object && name == "prototype")
1330 			return prototype();
1331 
1332 		if(name == "typeof") {
1333 			var* tmp = new var;
1334 			*tmp = to!string(this.payloadType());
1335 			return *tmp;
1336 		}
1337 
1338 		if(name == "toJson") {
1339 			var* tmp = new var;
1340 			*tmp = to!string(this.toJson());
1341 			return *tmp;
1342 		}
1343 
1344 		if(name == "length" && this.payloadType() == Type.String) {
1345 			var* tmp = new var;
1346 			*tmp = _payload._string.length;
1347 			return *tmp;
1348 		}
1349 		if(name == "length" && this.payloadType() == Type.Array) {
1350 			var* tmp = new var;
1351 			*tmp = _payload._array.length;
1352 			return *tmp;
1353 		}
1354 		if(name == "__prop" && this.payloadType() == Type.Object) {
1355 			var* tmp = new var;
1356 			(*tmp)._function = delegate var(var _this, var[] args) {
1357 				if(args.length == 0)
1358 					return var(null);
1359 				if(args.length == 1) {
1360 					auto peek = this._payload._object._peekMember(args[0].get!string, false);
1361 					if(peek is null)
1362 						return var(null);
1363 					else
1364 						return *peek;
1365 				}
1366 				if(args.length == 2) {
1367 					auto peek = this._payload._object._peekMember(args[0].get!string, false);
1368 					if(peek is null) {
1369 						this._payload._object._properties[args[0].get!string] = args[1];
1370 						return var(null);
1371 					} else {
1372 						*peek = args[1];
1373 						return *peek;
1374 					}
1375 
1376 				}
1377 				throw new Exception("too many args");
1378 			};
1379 			return *tmp;
1380 		}
1381 
1382 		PrototypeObject from;
1383 		if(this.payloadType() == Type.Object)
1384 			from = _payload._object;
1385 		else {
1386 			var pt = this.prototype();
1387 			assert(pt.payloadType() == Type.Object);
1388 			from = pt._payload._object;
1389 		}
1390 
1391 		if(from is null) {
1392 			if(jsvar_throw)
1393 				throw new DynamicTypeException(var(null), Type.Object, file, line);
1394 			else
1395 				return *(new var);
1396 		}
1397 		return from._getMember(name, true, false, false, file, line);
1398 	}
1399 
1400 	public ref var opIndexAssign(T)(T t, string name, string file = __FILE__, size_t line = __LINE__) {
1401 		if(this.payloadType == Type.Array && name.appearsNumeric()) {
1402 			try {
1403 				auto i = to!size_t(name);
1404 				return opIndexAssign(t, i, file, line);
1405 			} catch(Exception)
1406 				{} // ignore bad index, use it as a string instead lol
1407 		}
1408 		_requireType(Type.Object); // FIXME?
1409 		if(_payload._object is null)
1410 			throw new DynamicTypeException(var(null), Type.Object, file, line);
1411 
1412 		return this._payload._object._setMember(name, var(t), false, false, false, file, line);
1413 	}
1414 
1415 	public ref var opIndexAssignNoOverload(T)(T t, string name, string file = __FILE__, size_t line = __LINE__) {
1416 		if(name.length && name[0] >= '0' && name[0] <= '9')
1417 			return opIndexAssign(t, to!size_t(name), file, line);
1418 		_requireType(Type.Object); // FIXME?
1419 		if(_payload._object is null)
1420 			throw new DynamicTypeException(var(null), Type.Object, file, line);
1421 
1422 		return this._payload._object._setMember(name, var(t), false, false, true, file, line);
1423 	}
1424 
1425 
1426 	public ref var opIndex(size_t idx, string file = __FILE__, size_t line = __LINE__) {
1427 		if(_type == Type.Array) {
1428 			auto arr = this._payload._array;
1429 			if(idx < arr.length)
1430 				return arr[idx];
1431 		} else if(_type == Type.Object) {
1432 			// objects might overload opIndex
1433 			var* n = new var();
1434 			if("opIndex" in this)
1435 				*n = this["opIndex"](idx);
1436 			return *n;
1437 		}
1438 		if(jsvar_throw) {
1439 			throw new DynamicTypeException(this, Type.Array, file, line);
1440 		} else {
1441 			var* n = new var();
1442 			return *n;
1443 		}
1444 	}
1445 
1446 	public ref var opIndexAssign(T)(T t, size_t idx, string file = __FILE__, size_t line = __LINE__) {
1447 		if(_type == Type.Array) {
1448 			if(idx >= this._payload._array.length)
1449 				this._payload._array.length = idx + 1;
1450 			this._payload._array[idx] = t;
1451 			return this._payload._array[idx];
1452 		} else if(_type == Type.Object) {
1453 			return opIndexAssign(t, to!string(idx), file, line);
1454 		}
1455 		if(jsvar_throw) {
1456 			throw new DynamicTypeException(this, Type.Array, file, line);
1457 		} else {
1458 			var* n = new var();
1459 			return *n;
1460 		}
1461 	}
1462 
1463 	ref var _getOwnProperty(string name, string file = __FILE__, size_t line = __LINE__) {
1464 		if(_type == Type.Object) {
1465 			if(_payload._object !is null) {
1466 				auto peek = this._payload._object._peekMember(name, false);
1467 				if(peek !is null)
1468 					return *peek;
1469 			}
1470 		}
1471 		//if(jsvar_throw)
1472 			//throw new DynamicTypeException(this, Type.Object, file, line);
1473 		var* n = new var();
1474 		return *n;
1475 	}
1476 
1477 	@property static var emptyObject(PrototypeObject prototype = null) {
1478 		var v;
1479 		v._type = Type.Object;
1480 		v._payload._object = new PrototypeObject();
1481 		v._payload._object.prototype = prototype;
1482 		return v;
1483 	}
1484 
1485 	///
1486 	@property static var emptyObject(var prototype) {
1487 		if(prototype._type == Type.Object)
1488 			return var.emptyObject(prototype._payload._object);
1489 		return var.emptyObject();
1490 	}
1491 
1492 	@property PrototypeObject prototypeObject() {
1493 		var v = prototype();
1494 		if(v._type == Type.Object)
1495 			return v._payload._object;
1496 		return null;
1497 	}
1498 
1499 	// what I call prototype is more like what Mozilla calls __proto__, but tbh I think this is better so meh
1500 	@property ref var prototype() {
1501 		static var _arrayPrototype;
1502 		static var _functionPrototype;
1503 		static var _stringPrototype;
1504 
1505 		final switch(payloadType()) {
1506 			case Type.Array:
1507 				assert(_arrayPrototype._type == Type.Object);
1508 				if(_arrayPrototype._payload._object is null) {
1509 					_arrayPrototype._object = new PrototypeObject();
1510 				}
1511 
1512 				return _arrayPrototype;
1513 			case Type.Function:
1514 				assert(_functionPrototype._type == Type.Object);
1515 				if(_functionPrototype._payload._object is null) {
1516 					_functionPrototype._object = new PrototypeObject();
1517 				}
1518 
1519 				return _functionPrototype;
1520 			case Type.String:
1521 				assert(_stringPrototype._type == Type.Object);
1522 				if(_stringPrototype._payload._object is null) {
1523 					auto p = new PrototypeObject();
1524 					_stringPrototype._object = p;
1525 
1526 					var replaceFunction;
1527 					replaceFunction._type = Type.Function;
1528 					replaceFunction._function = (var _this, var[] args) {
1529 						string s = _this.toString();
1530 						import std.array : replace;
1531 						return var(std.array.replace(s,
1532 							args[0].toString(),
1533 							args[1].toString()));
1534 					};
1535 
1536 					p._properties["replace"] = replaceFunction;
1537 				}
1538 
1539 				return _stringPrototype;
1540 			case Type.Object:
1541 				if(_payload._object)
1542 					return _payload._object._prototype;
1543 				// FIXME: should we do a generic object prototype?
1544 			break;
1545 			case Type.Integral:
1546 			case Type.Floating:
1547 			case Type.Boolean:
1548 				// these types don't have prototypes
1549 		}
1550 
1551 
1552 		var* v = new var(null);
1553 		return *v;
1554 	}
1555 
1556 	///
1557 	@property static var emptyArray() {
1558 		var v;
1559 		v._type = Type.Array;
1560 		return v;
1561 	}
1562 
1563 	///
1564 	static var fromJson(const(char)[] json) {
1565 		auto decoded = parseJSON(json);
1566 		return var.fromJsonValue(decoded);
1567 	}
1568 
1569 	///
1570 	static var fromJsonFile(const(char)[] filename) {
1571 		import std.file;
1572 		return var.fromJson(readText(filename));
1573 	}
1574 
1575 	///
1576 	static var fromJsonValue(JSONValue v) {
1577 		var ret;
1578 
1579 		final switch(v.type) {
1580 			case JSONType..string:
1581 				ret = v.str;
1582 			break;
1583 			case JSONType.uinteger:
1584 				ret = v.uinteger;
1585 			break;
1586 			case JSONType.integer:
1587 				ret = v.integer;
1588 			break;
1589 			case JSONType.float_:
1590 				ret = v.floating;
1591 			break;
1592 			case JSONType.object:
1593 				ret = var.emptyObject;
1594 				foreach(k, val; v.object) {
1595 					ret[k] = var.fromJsonValue(val);
1596 				}
1597 			break;
1598 			case JSONType.array:
1599 				ret = var.emptyArray;
1600 				ret._payload._array.length = v.array.length;
1601 				foreach(idx, item; v.array) {
1602 					ret._payload._array[idx] = var.fromJsonValue(item);
1603 				}
1604 			break;
1605 			case JSONType.true_:
1606 				ret = true;
1607 			break;
1608 			case JSONType.false_:
1609 				ret = false;
1610 			break;
1611 			case JSONType.null_:
1612 				ret = null;
1613 			break;
1614 		}
1615 
1616 		return ret;
1617 	}
1618 
1619 	///
1620 	string toJson() {
1621 		auto v = toJsonValue();
1622 		return toJSON(v);
1623 	}
1624 
1625 	///
1626 	JSONValue toJsonValue() {
1627 		JSONValue val;
1628 		final switch(payloadType()) {
1629 			case Type.Boolean:
1630 				version(new_std_json)
1631 					val = this._payload._boolean;
1632 				else {
1633 					if(this._payload._boolean)
1634 						val.type = JSONType.true_;
1635 					else
1636 						val.type = JSONType.false_;
1637 				}
1638 			break;
1639 			case Type.Object:
1640 				version(new_std_json) {
1641 					if(_payload._object is null) {
1642 						val = null;
1643 					} else {
1644 						val = _payload._object.toJsonValue();
1645 					}
1646 				} else {
1647 					if(_payload._object is null) {
1648 						val.type = JSONType.null_;
1649 					} else {
1650 						val.type = JSONType.object;
1651 						foreach(k, v; _payload._object._properties) {
1652 							val.object[k] = v.toJsonValue();
1653 						}
1654 					}
1655 				}
1656 			break;
1657 			case Type.String:
1658 				version(new_std_json) { } else {
1659 					val.type = JSONType..string;
1660 				}
1661 				val.str = _payload._string;
1662 			break;
1663 			case Type.Integral:
1664 				version(new_std_json) { } else {
1665 					val.type = JSONType.integer;
1666 				}
1667 				val.integer = _payload._integral;
1668 			break;
1669 			case Type.Floating:
1670 				version(new_std_json) { } else {
1671 					val.type = JSONType.float_;
1672 				}
1673 				val.floating = _payload._floating;
1674 			break;
1675 			case Type.Array:
1676 				auto a = _payload._array;
1677 				JSONValue[] tmp;
1678 				tmp.length = a.length;
1679 				foreach(i, v; a) {
1680 					tmp[i] = v.toJsonValue();
1681 				}
1682 
1683 				version(new_std_json) {
1684 					val = tmp;
1685 				} else {
1686 					val.type = JSONType.array;
1687 					val.array = tmp;
1688 				}
1689 			break;
1690 			case Type.Function:
1691 				version(new_std_json)
1692 					val = null;
1693 				else
1694 					val.type = JSONType.null_; // ideally we would just skip it entirely...
1695 			break;
1696 		}
1697 		return val;
1698 	}
1699 }
1700 
1701 class PrototypeObject {
1702 	string name;
1703 	var _prototype;
1704 
1705 	package PrototypeObject _secondary; // HACK don't use this
1706 
1707 	PrototypeObject prototype() {
1708 		if(_prototype.payloadType() == var.Type.Object)
1709 			return _prototype._payload._object;
1710 		return null;
1711 	}
1712 
1713 	PrototypeObject prototype(PrototypeObject set) {
1714 		this._prototype._object = set;
1715 		return set;
1716 	}
1717 
1718 	override string toString() {
1719 
1720 		var* ts = _peekMember("toString", true);
1721 		if(ts) {
1722 			var _this;
1723 			_this._object = this;
1724 			return (*ts).call(_this).get!string;
1725 		}
1726 
1727 		JSONValue val;
1728 		version(new_std_json) {
1729 			JSONValue[string] tmp;
1730 			foreach(k, v; this._properties)
1731 				tmp[k] = v.toJsonValue();
1732 			val.object = tmp;
1733 		} else {
1734 			val.type = JSONType.object;
1735 			foreach(k, v; this._properties)
1736 				val.object[k] = v.toJsonValue();
1737 		}
1738 
1739 		return toJSON(val);
1740 	}
1741 
1742 	var[string] _properties;
1743 
1744 	PrototypeObject copy() {
1745 		auto n = new PrototypeObject();
1746 		n.prototype = this.prototype;
1747 		n.name = this.name;
1748 		foreach(k, v; _properties) {
1749 			n._properties[k] = v._copy;
1750 		}
1751 		return n;
1752 	}
1753 
1754 	bool isSpecial() { return false; }
1755 
1756 	PrototypeObject new_(PrototypeObject newThis) {
1757 		// if any of the prototypes are D objects, we need to try to copy them.
1758 		auto p = prototype;
1759 
1760 		PrototypeObject[32] stack;
1761 		PrototypeObject[] fullStack = stack[];
1762 		int stackPos;
1763 
1764 		while(p !is null) {
1765 
1766 			if(p.isSpecial()) {
1767 				auto n = new PrototypeObject();
1768 
1769 				auto proto = p.new_(n);
1770 
1771 				while(stackPos) {
1772 					stackPos--;
1773 					auto pr = fullStack[stackPos].copy();
1774 					pr.prototype = proto;
1775 					proto = pr;
1776 				}
1777 
1778 				n.prototype = proto;
1779 				n.name = this.name;
1780 				foreach(k, v; _properties) {
1781 					n._properties[k] = v._copy;
1782 				}
1783 
1784 				return n;
1785 			}
1786 
1787 			if(stackPos >= fullStack.length)
1788 				fullStack ~= p;
1789 			else
1790 				fullStack[stackPos] = p;
1791 			stackPos++;
1792 
1793 			p = p.prototype;
1794 		}
1795 
1796 		return copy();
1797 	}
1798 
1799 	PrototypeObject copyPropertiesFrom(PrototypeObject p) {
1800 		foreach(k, v; p._properties) {
1801 			this._properties[k] = v._copy;
1802 		}
1803 		return this;
1804 	}
1805 
1806 	var* _peekMember(string name, bool recurse) {
1807 		if(name == "prototype")
1808 			return &_prototype;
1809 
1810 		auto curr = this;
1811 
1812 		// for the secondary hack
1813 		bool triedOne = false;
1814 		// for the secondary hack
1815 		PrototypeObject possibleSecondary;
1816 
1817 		tryAgain:
1818 		do {
1819 			auto prop = name in curr._properties;
1820 			if(prop is null) {
1821 				// the secondary hack is to do more scoping in the script, it is really hackish
1822 				if(possibleSecondary is null)
1823 					possibleSecondary = curr._secondary;
1824 
1825 				if(!recurse)
1826 					break;
1827 				else
1828 					curr = curr.prototype;
1829 			} else
1830 				return prop;
1831 		} while(curr);
1832 
1833 		if(possibleSecondary !is null) {
1834 			curr = possibleSecondary;
1835 			if(!triedOne) {
1836 				triedOne = true;
1837 				goto tryAgain;
1838 			}
1839 		}
1840 
1841 		return null;
1842 	}
1843 
1844 	// FIXME: maybe throw something else
1845 	/*package*/ ref var _getMember(string name, bool recurse, bool throwOnFailure, bool returnRawProperty = false, string file = __FILE__, size_t line = __LINE__) {
1846 		var* mem = _peekMember(name, recurse);
1847 
1848 		if(mem !is null) {
1849 			// If it is a property, we need to call the getter on it
1850 			if((*mem).payloadType == var.Type.Object && cast(PropertyPrototype) (*mem)._payload._object) {
1851 				if(returnRawProperty)
1852 					return *mem;
1853 				auto prop = cast(PropertyPrototype) (*mem)._payload._object;
1854 				return prop.get;
1855 			}
1856 			return *mem;
1857 		}
1858 
1859 		mem = _peekMember("opIndex", recurse);
1860 		if(mem !is null) {
1861 			auto n = new var;
1862 			*n = ((*mem)(name));
1863 			return *n;
1864 		}
1865 
1866 		// if we're here, the property was not found, so let's implicitly create it
1867 		if(throwOnFailure)
1868 			throw new DynamicTypeException("no such property " ~ name, file, line);
1869 		var n;
1870 		this._properties[name] = n;
1871 		return this._properties[name];
1872 	}
1873 
1874 	// FIXME: maybe throw something else
1875 	/*package*/ ref var _setMember(string name, var t, bool recurse, bool throwOnFailure, bool suppressOverloading, string file = __FILE__, size_t line = __LINE__) {
1876 		var* mem = _peekMember(name, recurse);
1877 
1878 		bool onParent = false;
1879 
1880 		if(mem is null && !recurse) {
1881 			mem = _peekMember(name, true); // properties need the check anyway as a setter might be on a prototype
1882 			onParent = true;
1883 		}
1884 
1885 		if(mem !is null) {
1886 			// Property check - the setter should be proxied over to it
1887 			if((*mem).payloadType == var.Type.Object && cast(PropertyPrototype) (*mem)._payload._object) {
1888 				auto prop = cast(PropertyPrototype) (*mem)._payload._object;
1889 				return prop.set(t);
1890 			}
1891 			if(!onParent) {
1892 				*mem = t;
1893 				return *mem;
1894 			} else {
1895 				mem = null;
1896 			}
1897 		}
1898 
1899 		if(!suppressOverloading) {
1900 			mem = _peekMember("opIndexAssign", true);
1901 			if(mem !is null) {
1902 				auto n = new var;
1903 				*n = ((*mem)(t, name));
1904 				return *n;
1905 			}
1906 		}
1907 
1908 		// if we're here, the property was not found, so let's implicitly create it
1909 		if(throwOnFailure)
1910 			throw new DynamicTypeException("no such property " ~ name, file, line);
1911 		this._properties[name] = t;
1912 		return this._properties[name];
1913 	}
1914 
1915 	JSONValue toJsonValue() {
1916 		JSONValue val;
1917 		JSONValue[string] tmp;
1918 		foreach(k, v; this._properties) {
1919 			// if it is an overload set and/or a function, just skip it.
1920 			// or really if it is a wrapped native object it should prolly just be skipped anyway
1921 			// unless it actually defines a toJson.
1922 			if(v.payloadType == var.Type.Function)
1923 				continue;
1924 			if(v.payloadType == var.Type.Object) {
1925 				// I'd love to get the json value out but idk. FIXME
1926 				if(v._payload._object is null) continue;
1927 				if(auto wno = cast(WrappedNativeObject) v._payload._object) {
1928 					auto obj = wno.getObject();
1929 					if(obj is null)
1930 						tmp[k] = null;
1931 					else
1932 						tmp[k] = obj.toString();
1933 					continue;
1934 				} else if(typeid(PrototypeObject) !is typeid(v._payload._object))
1935 					continue;
1936 			}
1937 
1938 			tmp[k] = v.toJsonValue();
1939 		}
1940 
1941 		val = tmp;
1942 		return val;
1943 	}
1944 
1945 	public int opApply(scope int delegate(var, ref var) dg) {
1946 		foreach(k, v; this._properties) {
1947 			if(v.payloadType == var.Type.Object && cast(PropertyPrototype) v._payload._object)
1948 				v = (cast(PropertyPrototype) v._payload._object).get;
1949 			if(auto result = dg(var(k), v))
1950 				return result;
1951 		}
1952 		return 0;
1953 	}
1954 }
1955 
1956 // A property is a special type of object that can only be set by assigning
1957 // one of these instances to foo.child._object. When foo.child is accessed and it
1958 // is an instance of PropertyPrototype, it will return the getter. When foo.child is
1959 // set (excluding direct assignments through _type), it will call the setter.
1960 class PropertyPrototype : PrototypeObject {
1961 	var delegate() getter;
1962 	void delegate(var) setter;
1963 	this(var delegate() getter, void delegate(var) setter) {
1964 		this.getter = getter;
1965 		this.setter = setter;
1966 	}
1967 
1968 	override string toString() {
1969 		return get.toString();
1970 	}
1971 
1972 	ref var get() {
1973 		var* g = new var();
1974 		*g = getter();
1975 		return *g;
1976 	}
1977 
1978 	ref var set(var t) {
1979 		setter(t);
1980 		return get;
1981 	}
1982 
1983 	override JSONValue toJsonValue() {
1984 		return get.toJsonValue();
1985 	}
1986 }
1987 
1988 ///
1989 struct ScriptLocation {
1990 	string scriptFilename; ///
1991 	int lineNumber; ///
1992 }
1993 
1994 class DynamicTypeException : Exception {
1995 	this(string msg, string file = __FILE__, size_t line = __LINE__) {
1996 		super(msg, file, line);
1997 	}
1998 	this(var v, var.Type required, string file = __FILE__, size_t line = __LINE__) {
1999 		import std.string;
2000 		if(v.payloadType() == required)
2001 			super(format("Tried to use null as a %s", required), file, line);
2002 		else {
2003 			super(format("Tried to use %s%s as a %s", v == null ? "null ": "", v.payloadType(), required), file, line);
2004 		}
2005 	}
2006 
2007 	override void toString(scope void delegate(in char[]) sink) const {
2008 		import std.format;
2009 		if(varName.length)
2010 			sink(varName);
2011 		if(callStack.length) {
2012 			sink("arsd.jsvar.DynamicTypeException@");
2013 			sink(file);
2014 			sink("(");
2015 			sink(to!string(line));
2016 			sink("): ");
2017 			sink(msg);
2018 			foreach(cs; callStack)
2019 				sink(format("\n\t%s:%s", cs.scriptFilename, cs.lineNumber));
2020 
2021 			bool found;
2022 			void hiddenSink(in char[] str) {
2023 				// I just want to hide the call stack until the interpret call...
2024 				// since the script stack above is more meaningful to users.
2025 				//
2026 				// but then I will go back to the D functions once on the outside.
2027 				import std.string;
2028 				if(found)
2029 					sink(str);
2030 				else if(str.indexOf("arsd.script.interpret(") != -1)
2031 					found = true;
2032 			}
2033 
2034 			sink("\n--------");
2035 
2036 			super.toString(&hiddenSink);
2037 		} else {
2038 			super.toString(sink);
2039 		}
2040 	}
2041 
2042 	ScriptLocation[] callStack;
2043 	string varName;
2044 }
2045 
2046 template makeAscii() {
2047 	string helper() {
2048 		string s;
2049 		foreach(i; 0 .. 128)
2050 			s ~= cast(char) i;
2051 		return s;
2052 	}
2053 
2054 	enum makeAscii = helper();
2055 }
2056 
2057 package interface VarMetadata { }
2058 
2059 interface ScriptableSubclass {
2060 	void setScriptVar(var);
2061 	var  getScriptVar();
2062 	final bool methodOverriddenByScript(string name) {
2063 		PrototypeObject t = getScriptVar().get!PrototypeObject;
2064 		// the top one is the native object from subclassable so we don't want to go all the way there to avoid endless recursion
2065 		//import std.stdio; writeln("checking ", name , " ...", "wtf");
2066 		if(t !is null)
2067 		while(!t.isSpecial) {
2068 			if(t._peekMember(name, false) !is null)
2069 				return true;
2070 			t = t.prototype;
2071 		}
2072 		return false;
2073 	}
2074 }
2075 
2076 /++
2077 	EXPERIMENTAL
2078 
2079 	Allows you to make a class available to the script rather than just class objects.
2080 	You can subclass it in script and then call the methods again through the original
2081 	D interface. With caveats...
2082 
2083 
2084 	Assumes ALL $(I virtual) methods and constructors are scriptable, but requires
2085 	`@scriptable` to be present on final or static methods. This may change in the future.
2086 
2087 	Note that it has zero support for `@safe`, `pure`, `nothrow`, and other attributes
2088 	at this time and will skip that use those. I may be able to loosen this in the
2089 	future as well but I have no concrete plan to at this time. You can still mark
2090 	them as `@scriptable` to call them from the script, but they can never be overridden
2091 	by script code because it cannot verify those guarantees hold true.
2092 
2093 	Ditto on `const` and `immutable`.
2094 
2095 	Its behavior on overloads is currently undefined - it may keep only any random
2096 	overload as the only one and do dynamic type conversions to cram data into it.
2097 	This is likely to change in the future but for now try not to use this on classes
2098 	with overloaded methods.
2099 
2100 	It also does not wrap member variables unless explicitly marked `@scriptable`; it
2101 	is meant to communicate via methods.
2102 
2103 	History:
2104 	Added April 25, 2020
2105 +/
2106 static if(__traits(compiles, mixin(q{  () { static foreach(i; [1,2]) {} } }) ))
2107 mixin(q{
2108 var subclassable(T)() if(is(T == class) || is(T == interface)) {
2109 	import std.traits;
2110 
2111 	static final class ScriptableT : T, ScriptableSubclass {
2112 		var _this;
2113 		void setScriptVar(var v) { _this = v; }
2114 		var getScriptVar() { return _this; }
2115 		bool _next_devirtualized;
2116 
2117 		// @scriptable size_t _nativeHandle_() { return cast(size_t) cast(void*) this;}
2118 
2119 		static if(__traits(compiles,  __traits(getOverloads, T, "__ctor")))
2120 		static foreach(ctor; __traits(getOverloads, T, "__ctor"))
2121 			@scriptable this(Parameters!ctor p) { super(p); }
2122 
2123 		static foreach(memberName; __traits(allMembers, T)) {
2124 		static if(memberName != "toHash")
2125 		static foreach(overload; __traits(getOverloads, T, memberName))
2126 		static if(__traits(isVirtualMethod, overload))
2127 		static if(!__traits(isFinalFunction, overload))
2128 		static if(!__traits(isDeprecated, overload))
2129 		// note: overload behavior undefined
2130 		static if(!(functionAttributes!(overload) & (FunctionAttribute.pure_ | FunctionAttribute.safe | FunctionAttribute.trusted | FunctionAttribute.nothrow_ | FunctionAttribute.const_ | FunctionAttribute.inout_)))
2131 		static if(!hasRefParam!overload)
2132 		static if(__traits(getFunctionVariadicStyle, overload) == "none")
2133 		static if(__traits(identifier, overload) == memberName) // to filter out aliases
2134 		mixin(q{
2135 			@scriptable
2136 			override ReturnType!(overload)
2137 			}~memberName~q{
2138 			(Parameters!(overload) p)
2139 			{
2140 			//import std.stdio; writeln("calling ", T.stringof, ".", memberName, " - ", methodOverriddenByScript(memberName), "/", _next_devirtualized, " on ", cast(size_t) cast(void*) this);
2141 				if(_next_devirtualized || !methodOverriddenByScript(memberName)) {
2142 					_next_devirtualized = false;
2143 					return __traits(getMember, super, memberName)(p);
2144 				}
2145 				return _this[memberName].call(_this, p).get!(typeof(return));
2146 			}
2147 		});
2148 		}
2149 
2150 		// I don't want to necessarily call a constructor but I need an object t use as the prototype
2151 		// hence this faked one. hopefully the new operator will see void[] and assume it can have GC ptrs...
2152 		static ScriptableT _allocate_(PrototypeObject newThis) {
2153 			void[] store = new void[](__traits(classInstanceSize, ScriptableT));
2154 			store[] = typeid(ScriptableT).initializer[];
2155 			ScriptableT dummy = cast(ScriptableT) store.ptr;
2156 			dummy._this = var(newThis);
2157 			//import std.stdio; writeln("Allocating new ", cast(ulong) store.ptr);
2158 			return dummy;
2159 		}
2160 	}
2161 
2162 	ScriptableT dummy = ScriptableT._allocate_(null);
2163 
2164 	var proto = wrapNativeObject!(ScriptableT, true)(dummy);
2165 
2166 	var f = var.emptyObject;
2167 	f.prototype = proto;
2168 
2169 	return f;
2170 }
2171 
2172 template hasRefParam(alias overload) {
2173 	bool helper() {
2174 		static if(is(typeof(overload) P == __parameters))
2175 		foreach(idx, p; P)
2176 		foreach(thing; __traits(getParameterStorageClasses, overload, idx))
2177 			if(thing == "ref")
2178 				return true;
2179 		return false;
2180 	}
2181 
2182 	enum hasRefParam = helper();
2183 }
2184 
2185 });
2186 
2187 /// Demonstrates tested capabilities of [subclassable]
2188 version(with_arsd_script)
2189 unittest {
2190 	interface IFoo {
2191 		string method();
2192 		int method2();
2193 		int args(int, int);
2194 	}
2195 	// note the static is just here because this
2196 	// is written in a unittest; it shouldn't actually
2197 	// be necessary under normal circumstances.
2198 	static class Foo : IFoo {
2199 		ulong handle() { return cast(ulong) cast(void*) this; }
2200 		string method() { return "Foo"; }
2201 		int method2() { return 10; }
2202 		int args(int a, int b) {
2203 			//import std.stdio; writeln(a, " + ", b, " + ", member_, " on ", cast(ulong) cast(void*) this);
2204 			return member_+a+b; }
2205 
2206 		int member_;
2207 		@property int member(int i) { return member_ = i; }
2208 		@property int member() { return member_; }
2209 
2210 		@scriptable final int fm() { return 56; }
2211 	}
2212 	static class Bar : Foo {
2213 		override string method() { return "Bar"; }
2214 		string test1() { return test2(); }
2215 		string test2() { return "test2"; }
2216 
2217 		int acceptFoo(Foo f) {
2218 			return f.method2();
2219 		}
2220 	}
2221 	static class Baz : Bar {
2222 		override int method2() { return 20; }
2223 	}
2224 
2225 	static class WithCtor {
2226 		// constructors work but are iffy with overloads....
2227 		this(int arg) { this.arg = arg; }
2228 		@scriptable int arg; // this is accessible cuz it is @scriptable
2229 		int getValue() { return arg; }
2230 	}
2231 
2232 	var globals = var.emptyObject;
2233 	globals.Foo = subclassable!Foo;
2234 	globals.Bar = subclassable!Bar;
2235 	globals.Baz = subclassable!Baz;
2236 	globals.WithCtor = subclassable!WithCtor;
2237 
2238 	import arsd.script;
2239 
2240 	interpret(q{
2241 		// can instantiate D classes added via subclassable
2242 		var foo = new Foo();
2243 		// and call its methods...
2244 		assert(foo.method() == "Foo");
2245 		assert(foo.method2() == 10);
2246 
2247 		foo.member(55);
2248 
2249 		// proves the new operator actually creates new D
2250 		// objects as well to avoid sharing instance state.
2251 		var foo2 = new Foo();
2252 		assert(foo2.handle() != foo.handle());
2253 
2254 		// passing arguments works
2255 		assert(foo.args(2, 4) == 6 + 55); // (and sanity checks operator precedence)
2256 
2257 		var bar = new Bar();
2258 		assert(bar.method() == "Bar");
2259 		assert(bar.method2() == 10);
2260 
2261 		assert(bar.acceptFoo(new Foo()) == 10);
2262 		assert(bar.acceptFoo(new Baz()) == 20);
2263 
2264 		// this final member is accessible because it was marked @scriptable
2265 		assert(bar.fm() == 56);
2266 
2267 		assert(bar.test1() == "test2");
2268 		assert(bar.test2() == "test2");
2269 
2270 		// the script can even subclass D classes!
2271 		class Amazing : Bar {
2272 			// and override its methods
2273 			var inst = 99;
2274 			function method() {
2275 				return "Amazing";
2276 			}
2277 
2278 			// note: to access instance members or virtual call lookup you MUST use the `this` keyword
2279 			// otherwise the function will be called with scope limited to this class itself (similar to javascript)
2280 			function other() {
2281 				// this.inst is needed to get the instance variable (otherwise it would only look for a static var)
2282 				// and this.method triggers dynamic lookup there, so it will get children's overridden methods if there is one
2283 				return this.inst ~ this.method();
2284 			}
2285 
2286 			function args(a, b) {
2287 				// calling parent class method still possible
2288 				return super.args(a*2, b*2);
2289 			}
2290 
2291 			function test2() {
2292 				return "script test";
2293 			}
2294 		}
2295 
2296 		var amazing = new Amazing();
2297 		assert(amazing.method() == "Amazing");
2298 		assert(amazing.method2() == 10); // calls back to the parent class
2299 		amazing.member(5);
2300 
2301 		assert(amazing.test1() == "script test"); // even the virtual method call from D goes into the script override
2302 
2303 		// this line I can paste down to interactively debug the test btw.
2304 		//}, globals); repl!true(globals); interpret(q{
2305 
2306 		assert(amazing.args(2, 4) == 12+5);
2307 
2308 		var wc = new WithCtor(5); // argument passed to constructor
2309 		assert(wc.getValue() == 5);
2310 
2311 		// confirm the property read works too
2312 		assert(wc.arg == 5);
2313 
2314 		// but property WRITING is currently not working though.
2315 
2316 
2317 		class DoubleChild : Amazing {
2318 			function method() {
2319 				return "DoubleChild";
2320 			}
2321 		}
2322 
2323 		// can also do a child of a child class
2324 		var dc = new DoubleChild();
2325 		assert(dc.method() == "DoubleChild");
2326 		assert(dc.other() == "99DoubleChild"); // the `this.method` means it uses the replacement now
2327 		assert(dc.method2() == 10); // back to the D grandparent
2328 		assert(dc.args(2, 4) == 12); // but the args impl from above
2329 	}, globals);
2330 
2331 	Foo foo = globals.foo.get!Foo; // get the native object back out
2332 	assert(foo.member == 55); // and see mutation via properties proving object mutability
2333 	assert(globals.foo.get!Bar is null); // cannot get the wrong class out of it
2334 	assert(globals.foo.get!Object !is null); // but can do parent classes / interfaces
2335 	assert(globals.foo.get!IFoo !is null);
2336 	assert(globals.bar.get!Foo !is null); // the Bar can also be a Foo
2337 
2338 	Bar amazing = globals.amazing.get!Bar; // instance of the script's class is still accessible through parent D class or interface
2339 	assert(amazing !is null); // object exists
2340 	assert(amazing.method() == "Amazing"); // calls the override from the script
2341 	assert(amazing.method2() == 10); // non-overridden function works as expected
2342 
2343 	IFoo iamazing = globals.amazing.get!IFoo; // and through just the interface works the same way
2344 	assert(iamazing !is null);
2345 	assert(iamazing.method() == "Amazing");
2346 	assert(iamazing.method2() == 10);
2347 
2348 	// and here ensuring the interface we got out can go back in and back out too
2349 	var globals2 = var.emptyObject;
2350 	globals2.opDispatch!"iamazing2" = iamazing;
2351 	// though inside, it is treated as an opaque reference since nothing is @scriptable
2352 	// so nothing to test there...
2353 	// however getting it back out should work fine
2354 	IFoo gotten = globals2.iamazing2.get!IFoo;
2355 	assert(gotten !is null);
2356 	assert(gotten is iamazing);
2357 	//import std.stdio; writeln("wtf", iamazing.method());
2358 
2359 	// now this seems obvious, but adding the interface can actually overwrite the dynamic
2360 	// prototype.... i think
2361 	//assert(gotten.method() == "Amazing");
2362 
2363 	//https://issues.dlang.org/show_bug.cgi?id=22011 necessitated the static!
2364 	static class CFoo : IFoo {
2365 		@scriptable string method() { return "CFoo"; }
2366 		int method2() { return 55; }
2367 		int args(int, int) { return 6 + 5; }
2368 	}
2369 
2370 	globals.opDispatch!"iamazing3" = new CFoo; // and also just testing a standard class assign
2371 	IFoo input = new CFoo();
2372 	globals.opDispatch!"iamazing4" = input; // and interface
2373 	interpret(q{
2374 		var a = iamazing3.method();
2375 		assert(a == "CFoo"); // works cuz the class is scriptable
2376 		// but the following line is NOT available because the interface itself is not marked scriptable
2377 		//assert(iamazing4.method() == "CFoo");
2378 	}, globals);
2379 
2380 	// but we can get it right back out
2381 	IFoo got2 = globals.iamazing4.get!IFoo;
2382 	assert(got2 is input);
2383 	assert(got2.method() == "CFoo");
2384 }
2385 
2386 unittest {
2387 	static struct Foo {
2388 		struct Bar {
2389 			int[] x;
2390 		}
2391 
2392 		Bar bar;
2393 	}
2394 
2395 	assert(
2396 		var.fromJson(`{
2397 			"bar":{ "x": [1,2,3]}
2398 		}`).get!Foo
2399 		==
2400 		Foo(Foo.Bar([1,2,3]))
2401 	);
2402 }
2403 
2404 version(with_arsd_script)
2405 unittest {
2406 	import arsd.script;
2407 	static class Test {
2408 		@scriptable int a;
2409 	}
2410 
2411 	Test test = new Test;
2412 
2413 	test.a = 15;
2414 
2415 	var globals = var.emptyObject;
2416 	globals.test = test;
2417 	globals.Test = subclassable!Test;
2418 
2419 	interpret(q{
2420 		  test.a = 4;
2421 		  var wtf = test.a += 5;
2422 		  assert(wtf == 9);
2423 		  assert(test.a == 9);
2424 		  var test2 = new Test;
2425 		  test2.a = 2;
2426 		  test2.a += 5;
2427 		  assert(test2.a == 7);
2428 	}, globals);
2429 
2430 	assert(test.a == 9);
2431 	assert(globals.test2.get!Test.a == 7);
2432 }
2433 
2434 // just a base class we can reference when looking for native objects
2435 class WrappedNativeObject : PrototypeObject {
2436 	TypeInfo wrappedType;
2437 	abstract Object getObject();
2438 }
2439 
2440 template helper(alias T) { alias helper = T; }
2441 
2442 /++
2443 	Wraps a class. If you are manually managing the memory, remember the jsvar may keep a reference to the object; don't free it!
2444 
2445 	To use this: `var a = wrapNativeObject(your_d_object);` OR `var a = your_d_object`;
2446 
2447 	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
2448 
2449 	History:
2450 		This became the default after April 24, 2020. Previously, [var.opAssign] would [wrapOpaquely] instead.
2451 +/
2452 WrappedNativeObject wrapNativeObject(Class, bool special = false)(Class obj) if(is(Class == class) || is(Class == interface)) {
2453 	import std.meta;
2454 	static class WrappedNativeObjectImpl : WrappedNativeObject {
2455 		override Object getObject() {
2456 			return cast(Object) obj;
2457 		}
2458 
2459 		override bool isSpecial() { return special; }
2460 
2461 		static if(special)
2462 		override WrappedNativeObject new_(PrototypeObject newThis) {
2463 			return new WrappedNativeObjectImpl(obj._allocate_(newThis));
2464 		}
2465 
2466 		Class obj;
2467 
2468 		this(Class objIn) {
2469 			this.obj = objIn;
2470 			wrappedType = typeid(cast(Object) obj);
2471 			// wrap the other methods
2472 			// and wrap members as scriptable properties
2473 
2474 			foreach(memberName; __traits(allMembers, Class)) static if(!mixin(deprecationCheck()) && is(typeof(__traits(getMember, obj, memberName)) type)) {
2475 				static if(is(type == function)) {
2476 					auto os = new OverloadSet();
2477 					foreach(idx, overload; AliasSeq!(__traits(getOverloads, obj, memberName))) static if(.isScriptable!(__traits(getAttributes, overload))()) {
2478 						var gen;
2479 						gen._function = delegate (var vthis_, var[] vargs) {
2480 							Parameters!(__traits(getOverloads, Class, memberName)[idx]) fargs;
2481 
2482 
2483 							enum lol = static_foreach(fargs.length, 1, -1,
2484 								`__traits(getOverloads, obj, memberName)[idx](`,``,` < vargs.length ? vargs[`,`].get!(typeof(fargs[`,`])) : ParamDefault!(__traits(getOverloads, Class, memberName)[idx], `,`)(),`,`)`);
2485 							/*
2486 							enum lol = static_foreach(fargs.length, 1, -1,
2487 								`__traits(getOverloads, obj, memberName)[idx](`,``,` < vargs.length ? vargs[`,`].get!(typeof(fargs[`,`])) :
2488 								typeof(fargs[`,`]).init,`,`)`);
2489 							*/
2490 
2491 							// FIXME: what if there are multiple @scriptable overloads?!
2492 							// FIXME: what about @properties?
2493 
2494 							static if(special) {
2495 								Class obj;
2496 								//if(vthis_.payloadType() != var.Type.Object) { import std.stdio; writeln("getwno on ", vthis_); }
2497 								// the native object might be a step or two up the prototype
2498 								// chain due to script subclasses, need to find it...
2499 								while(vthis_ != null) {
2500 									obj = vthis_.getWno!Class;
2501 									if(obj !is null)
2502 										break;
2503 									vthis_ = vthis_.prototype;
2504 								}
2505 
2506 								if(obj is null) throw new Exception("null native object");
2507 							}
2508 
2509 							static if(special) {
2510 								obj._next_devirtualized = true;
2511 								scope(exit) obj._next_devirtualized = false;
2512 							}
2513 
2514 							var ret;
2515 
2516 							static if(!is(typeof(__traits(getOverloads, obj, memberName)[idx](fargs)) == void))
2517 								ret = mixin(lol);
2518 							else
2519 								mixin(lol ~ ";");
2520 
2521 							return ret;
2522 						};
2523 
2524 						Parameters!(overload) fargs;
2525 						// FIXME: if an argument type is a class, we need to actually look it up in the script context somehow
2526 						var[] definition;
2527 						foreach(arg; fargs) {
2528 							definition ~= var(arg);
2529 						}
2530 
2531 						//import std.stdio; writeln(Class.stringof, ".", memberName, " ", definition);
2532 						os.addOverload(OverloadSet.Overload(definition, gen));
2533 					}
2534 
2535 					_properties[memberName] = var(os);
2536 				} else {
2537 					alias fuckThisLanguage = __traits(getOverloads, Class, memberName, true);
2538 					static if(fuckThisLanguage.length)
2539 						alias fuckYourPointlessDeprecations = fuckThisLanguage[0];
2540 					else
2541 						alias fuckYourPointlessDeprecations = __traits(getMember, Class, memberName);
2542 					static if(.isScriptable!(__traits(getAttributes, fuckYourPointlessDeprecations))())
2543 					// if it has a type but is not a function, it is prolly a member
2544 					_properties[memberName] = new PropertyPrototype(
2545 						() => var(__traits(getMember, obj, memberName)),
2546 						(var v) {
2547 							// read-only property hack
2548 							static if(__traits(compiles, __traits(getMember, obj, memberName) = v.get!(type)))
2549 							__traits(getMember, obj, memberName) = v.get!(type);
2550 						});
2551 				}
2552 			}
2553 		}
2554 	}
2555 
2556 	return new WrappedNativeObjectImpl(obj);
2557 }
2558 
2559 import std.traits;
2560 class WrappedOpaque(T) : PrototypeObject if(isPointer!T || is(T == class) || is(T == interface)) {
2561 	T wrapped;
2562 	this(T t) {
2563 		wrapped = t;
2564 	}
2565 	T wrapping() {
2566 		return wrapped;
2567 	}
2568 }
2569 class WrappedOpaque(T) : PrototypeObject if(!isPointer!T && !is(T == class) && !is(T == interface)) {
2570 	T* wrapped;
2571 	this(T t) {
2572 		wrapped = new T;
2573 		(cast() *wrapped) = t;
2574 	}
2575 	this(T* t) {
2576 		wrapped = t;
2577 	}
2578 	T* wrapping() {
2579 		return wrapped;
2580 	}
2581 }
2582 
2583 WrappedOpaque!Obj wrapOpaquely(Obj)(Obj obj) {
2584 	static if(is(Obj == class)) {
2585 		if(obj is null)
2586 			return null;
2587 	}
2588 	return new WrappedOpaque!Obj(obj);
2589 }
2590 
2591 /**
2592 	Wraps an opaque struct pointer in a module with ufcs functions
2593 */
2594 WrappedNativeObject wrapUfcs(alias Module, Type)(Type obj) {
2595 	import std.meta;
2596 	return new class WrappedNativeObject {
2597 		override Object getObject() {
2598 			return null; // not actually an object! but close to
2599 		}
2600 
2601 		this() {
2602 			wrappedType = typeid(Type);
2603 			// wrap the other methods
2604 			// and wrap members as scriptable properties
2605 
2606 			foreach(memberName; __traits(allMembers, Module)) static if(is(typeof(__traits(getMember, Module, memberName)) type)) {
2607 				static if(is(type == function)) {
2608 					foreach(idx, overload; AliasSeq!(__traits(getOverloads, Module, memberName))) static if(.isScriptable!(__traits(getAttributes, __traits(getOverloads, Module, memberName)[idx]))()) { // lol  i have to do the second getOverload to stop the compiler from deprecating
2609 						auto helper = &__traits(getOverloads, Module, memberName)[idx];
2610 						static if(Parameters!helper.length >= 1 && is(Parameters!helper[0] == Type)) {
2611 							// this staticMap is a bit of a hack so it can handle `in float`... liable to break with others, i'm sure
2612 							_properties[memberName] = (staticMap!(Unqual, Parameters!helper[1 .. $]) args) {
2613 								return __traits(getOverloads, Module, memberName)[idx](obj, args);
2614 							};
2615 						}
2616 					}
2617 				}
2618 			}
2619 		}
2620 	};
2621 }
2622 
2623 bool isCallableJsvarObject(var possibility) {
2624 	if(possibility.payloadType == var.Type.Function)
2625 		return true;
2626 	if(possibility.payloadType == var.Type.Object) {
2627 		if(possibility._payload._object is null)
2628 			return false;
2629 
2630 		if(var* test = possibility._payload._object._peekMember("opCall", true)) {
2631 			if(isCallableJsvarObject(*test))
2632 				return true;
2633 		}
2634 
2635 	}
2636 	return false;
2637 }
2638 
2639 
2640 bool isScriptable(attributes...)() {
2641 	bool nonConstConditionForWorkingAroundASpuriousDmdWarning = true;
2642 	foreach(attribute; attributes) {
2643 		static if(is(typeof(attribute) == string)) {
2644 			static if(attribute == scriptable) {
2645 				if(nonConstConditionForWorkingAroundASpuriousDmdWarning)
2646 				return true;
2647 			}
2648 		}
2649 	}
2650 	return false;
2651 }
2652 
2653 bool isScriptableOpaque(T)() {
2654 	static if(is(typeof(T.isOpaqueStruct) == bool))
2655 		return T.isOpaqueStruct == true;
2656 	else
2657 		return false;
2658 }
2659 
2660 int typeCompatibilityScore(var arg, var type) {
2661 	int thisScore = 0;
2662 
2663 	if(type.payloadType == var.Type.Object && type._payload._object is null) {
2664 		thisScore += 1; // generic match
2665 		return thisScore;
2666 	}
2667 	if(arg.payloadType == var.Type.Integral && type.payloadType == var.Type.Floating) {
2668 		thisScore += 2; // match with implicit conversion
2669 	// FIXME: want to support implicit from whatever to bool?
2670 	} else if(arg.payloadType != type.payloadType) {
2671 		thisScore = 0;
2672 		return thisScore;
2673 	} else {
2674 		// exact type category match
2675 		if(type.payloadType == var.Type.Array) {
2676 			// arrays not really supported here....
2677 			// so just like if both are arrays i'll take
2678 			// it as a bare minimum but i don't love it otherwise
2679 			if(arg.payloadType == var.Type.Array)
2680 				thisScore = 1;
2681 			else
2682 				thisScore = 0;
2683 			return thisScore;
2684 		} else if(type.payloadType == var.Type.Object) {
2685 			// objects are the interesting one...
2686 			// want to see if it matches by seeing if the
2687 			// given type is identical or its prototype is one of the given type's prototype chain
2688 
2689 			int depth = 0;
2690 			PrototypeObject pt = type._payload._object;
2691 			while(pt !is null) {
2692 				depth++;
2693 				pt = pt.prototype;
2694 			}
2695 
2696 			if(type._payload._object is arg._payload._object)
2697 				thisScore += 2 + depth;
2698 			else {
2699 				if(arg._payload._object is null)
2700 					return 0; // null sucks.
2701 
2702 				auto test = type._payload._object.prototype;
2703 				// generic object compared against generic object matches
2704 				if(test is null && type._payload._object.prototype is null)
2705 					thisScore += 1;
2706 				else {
2707 					pt = arg._payload._object;
2708 					while(pt !is null) {
2709 						if(pt is test) {
2710 							thisScore += 1 + depth;
2711 							break;
2712 						}
2713 						pt = pt.prototype;
2714 					}
2715 				}
2716 			}
2717 		} else {
2718 			thisScore += 3; // exact match without implicit conversion
2719 		}
2720 	}
2721 
2722 	return thisScore;
2723 }
2724 
2725 /++
2726 	Does dynamic dispatch to overloads in a jsvar function set.
2727 
2728 	History:
2729 		Added September 1, 2020.
2730 +/
2731 class OverloadSet : PrototypeObject {
2732 	this() {
2733 		_properties["opCall"] = &opCall;
2734 		_properties["apply"] = &apply;
2735 	}
2736 
2737 	///
2738 	void addIndividualOverload(alias f)() {
2739 		var func = &f;
2740 		var[] argTypes;
2741 		static if(is(typeof(f) Params == __parameters))
2742 		foreach(param; Params)
2743 			argTypes ~= var(param.init);
2744 		//import std.stdio; writeln("registered ", argTypes);
2745 		overloads ~= Overload(argTypes, func);
2746 	}
2747 
2748 	///
2749 	static if(__traits(compiles, mixin(q{  () { static foreach(i; [1,2]) {} } }) ))
2750 	mixin(q{
2751 	void addOverloadsOf(alias what)() {
2752 		foreach(alias f; __traits(getOverloads, __traits(parent, what), __traits(identifier, what)))
2753 			addIndividualOverload!f;
2754 	}
2755 	});
2756 
2757 	static struct Overload {
2758 		// I don't even store the arity of a function object
2759 		// so argTypes is the nest best thing.
2760 		var[] argTypes;
2761 		var func;
2762 	}
2763 	Overload[] overloads;
2764 
2765 	private bool exactMatch(var[] a, var[] b) {
2766 		if(a.length != b.length)
2767 			return false;
2768 		foreach(i; 0 .. a.length) {
2769 			if(a[i] !is b[i])
2770 				return false;
2771 		}
2772 		return true;
2773 	}
2774 
2775 	void addOverload(Overload o) {
2776 		foreach(ref ov; overloads)
2777 			if(exactMatch(ov.argTypes, o.argTypes)) {
2778 				ov.func = o.func;
2779 				return;
2780 			}
2781 		overloads ~= o;
2782 	}
2783 
2784 	/*
2785 		I might have to add Object, Exception, and others to jsvar to represent types.
2786 		maybe even int and such.
2787 
2788 		An object should probably have a secret property that gives its name...
2789 	*/
2790 	var apply(var this_, var[] arguments) {
2791 		return opCall(arguments[0], arguments[1].get!(var[]));
2792 	}
2793 
2794 	var opCall(var this_, var[] arguments) {
2795 		// remember script.d supports default args too.
2796 		int bestScore = int.min;
2797 		Overload bestMatch;
2798 
2799 		if(overloads.length == 0) {
2800 			return var(null);
2801 		}
2802 
2803 		foreach(overload; overloads) {
2804 			if(overload.argTypes.length == 0) {
2805 				if(arguments.length == 0) {
2806 					bestScore = 0;
2807 					bestMatch = overload;
2808 					break;
2809 				}
2810 				if(bestScore < 0) {
2811 					bestScore = 0;
2812 					bestMatch = overload;
2813 					continue;
2814 				}
2815 			}
2816 
2817 			int thisScore = 0;
2818 			foreach(idx, arg; arguments) {
2819 				if(idx >= overload.argTypes.length) {
2820 					thisScore = 0;
2821 					break;
2822 				}
2823 
2824 				// now if it is an object, if we can match, add score based on how derived the specified type is.
2825 				// if it is the very same object, that's the best match possible. (prototype chain length + 1. and the one base point for matching at all.)
2826 				// then if not, if the arg given can implicitly convert to the arg specified, give points based on how many prototypes the arg specified has. (plus one base point for matching at all)
2827 
2828 				// otherwise just give one point.
2829 
2830 				auto s = typeCompatibilityScore(arg, overload.argTypes[idx]);
2831 				if(s == 0) {
2832 					thisScore = 0;
2833 					break;
2834 				}
2835 				thisScore += s;
2836 			}
2837 
2838 			if(thisScore > 0 && thisScore > bestScore) {
2839 				bestScore = thisScore;
2840 				bestMatch = overload;
2841 			}
2842 		}
2843 
2844 		if(bestScore < 0)
2845 			throw new Exception("no matching overload found " ~ to!string(arguments) ~ " " ~ to!string(overloads));
2846 
2847 
2848 		return bestMatch.func.apply(this_, arguments);
2849 	}
2850 }
2851 
2852 unittest {
2853 	struct A {
2854 		static:
2855 		string foo(var arg) { return "generic"; }
2856 		string foo(string s) { return "string"; }
2857 		string foo(int i) { return "int"; }
2858 		string foo(float i) { return "float"; }
2859 	}
2860 
2861         auto os = new OverloadSet();
2862         os.addOverloadsOf!(A.foo);
2863         var g = var.emptyObject;
2864         g.foo = os;
2865 
2866         //g.foo()();
2867         assert(g.foo()("for me") == "string");
2868         //g.foo()("for me", "lol");
2869         assert(g.foo()(1) == "int");
2870         assert(g.foo()(5.4) == "float");
2871         assert(g.foo()(new Object) == "generic");
2872 }
2873 
2874 unittest {
2875 	import std.typecons;
2876 
2877 	static struct A {
2878 		int a;
2879 		Nullable!int b;
2880 	}
2881 
2882 	var a = A(3);
2883 	assert(a.a == 3);
2884 	assert(a.b == null);
2885 	a = A(5, nullable(4));
2886 	assert(a.a == 5);
2887 	assert(a.b == 4);
2888 }
2889 
2890 bool appearsNumeric(string n) {
2891 	if(n.length == 0)
2892 		return false;
2893 	foreach(c; n) {
2894 		if(c < '0' || c > '9')
2895 			return false;
2896 	}
2897 	return true;
2898 }
2899 
2900 
2901 /// Wraps a struct by reference. The pointer is stored - be sure the struct doesn't get freed or go out of scope!
2902 ///
2903 /// 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.
2904 WrappedNativeObject wrapNativeObject(Struct)(Struct* obj) if(is(Struct == struct)) {
2905 	return null; // FIXME
2906 }
2907 
2908 /+
2909 	_IDX_
2910 
2911 	static_foreach(T.length, q{
2912 		mixin(q{
2913 		void
2914 		} ~ __traits(identifier, T[_IDX_]) ~ q{
2915 
2916 		}
2917 	});
2918 +/
2919 
2920 private
2921 string static_foreach(size_t length, int t_start_idx, int t_end_idx, string[] t...) pure {
2922 	assert(__ctfe);
2923 	int slen;
2924 	int tlen;
2925 	foreach(idx, i; t[0 .. t_start_idx])
2926 		slen += i.length;
2927 	foreach(idx, i; t[t_start_idx .. $ + t_end_idx]) {
2928 		if(idx)
2929 			tlen += 5;
2930 		tlen += i.length;
2931 	}
2932 	foreach(idx, i; t[$ + t_end_idx .. $])
2933 		slen += i.length;
2934 
2935 	char[] a = new char[](tlen * length + slen);
2936 
2937 	int loc;
2938 	char[5] stringCounter;
2939 	stringCounter[] = "00000"[];
2940 
2941 	foreach(part; t[0 .. t_start_idx]) {
2942 		a[loc .. loc + part.length] = part[];
2943 		loc += part.length;
2944 	}
2945 
2946 	foreach(i; 0 .. length) {
2947 		foreach(idx, part; t[t_start_idx .. $ + t_end_idx]) {
2948 			if(idx) {
2949 				a[loc .. loc + stringCounter.length] = stringCounter[];
2950 				loc += stringCounter.length;
2951 			}
2952 			a[loc .. loc + part.length] = part[];
2953 			loc += part.length;
2954 		}
2955 
2956 		auto pos = stringCounter.length;
2957 		while(pos) {
2958 			pos--;
2959 			if(stringCounter[pos] == '9') {
2960 				stringCounter[pos] = '0';
2961 			} else {
2962 				stringCounter[pos] ++;
2963 				break;
2964 			}
2965 		}
2966 		while(pos)
2967 			stringCounter[--pos] = ' ';
2968 	}
2969 
2970 	foreach(part; t[$ + t_end_idx .. $]) {
2971 		a[loc .. loc + part.length] = part[];
2972 		loc += part.length;
2973 	}
2974 
2975 	return a;
2976 }
2977 
2978 private string deprecationCheck() {
2979 	//https://issues.dlang.org/show_bug.cgi?id=22011 this hack sucks
2980 	static if(__VERSION__ >= 2077)
2981 		return "__traits(isDeprecated, __traits(getMember, obj, memberName == `this` ? `__ctor` : memberName))";
2982 	else
2983 		return "false";
2984 }
2985 
2986 // LOL this can't work because function pointers drop the default :(
2987 private
2988 auto ParamDefault(alias T, size_t idx)() {
2989 	static if(is(typeof(T) Params == __parameters)) {
2990 		auto fn(Params[idx .. idx + 1] _args__) { // if i used plain `args` and one of the args in the list was also called `args`, this fails to compile and that error will be dropped by opDispatch.  LOL.
2991 			return _args__[0];
2992 		}
2993 		static if(__traits(compiles, fn())) {
2994 			return fn();
2995 		} else {
2996 			return Params[idx].init;
2997 		}
2998 	} else static assert(0);
2999 }