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