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