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