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