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