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