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