1 /*
2 
3 	FIXME: i kinda do want a catch type filter e.g. catch(Exception f)
4 
5 	FIXME: I also kinda want implicit construction of structs at times.
6 
7 	REPL plan:
8 		easy movement to/from a real editor
9 		can edit a specific function
10 		repl is a different set of globals
11 		maybe ctrl+enter to execute vs insert another line
12 
13 
14 		write state to file
15 		read state from file
16 			state consists of all variables and source to functions.
17 			maybe need @retained for a variable that is meant to keep
18 			its value between loads?
19 
20 		ddoc????
21 
22 	Steal Ruby's [regex, capture] maybe
23 
24 	and the => operator too
25 
26 	I kinda like the javascript foo`blargh` template literals too.
27 
28 	++ and -- are not implemented.
29 
30 */
31 
32 /++
33 	A small script interpreter that builds on [arsd.jsvar] to be easily embedded inside and to have has easy
34 	two-way interop with the host D program.  The script language it implements is based on a hybrid of D and Javascript.
35 	The type the language uses is based directly on [var] from [arsd.jsvar].
36 
37 	The interpreter is slightly buggy and poorly documented, but the basic functionality works well and much of
38 	your existing knowledge from Javascript will carry over, making it hopefully easy to use right out of the box.
39 	See the [#examples] to quickly get the feel of the script language as well as the interop.
40 
41 
42 	$(TIP
43 		A goal of this language is to blur the line between D and script, but
44 		in the examples below, which are generated from D unit tests,
45 		the non-italics code is D, and the italics is the script. Notice
46 		how it is a string passed to the [interpret] function.
47 
48 		In some smaller, stand-alone code samples, there will be a tag "adrscript"
49 		in the upper right of the box to indicate it is script. Otherwise, it
50 		is D.
51 	)
52 
53 	Installation_instructions:
54 	This script interpreter is contained entirely in two files: jsvar.d and script.d. Download both of them
55 	and add them to your project. Then, `import arsd.script;`, declare and populate a `var globals = var.emptyObject;`,
56 	and `interpret("some code", globals);` in D.
57 
58 	There's nothing else to it, no complicated build, no external dependencies.
59 
60 	$(CONSOLE
61 		$ wget https://raw.githubusercontent.com/adamdruppe/arsd/master/script.d
62 		$ wget https://raw.githubusercontent.com/adamdruppe/arsd/master/jsvar.d
63 
64 		$ dmd yourfile.d script.d jsvar.d
65 	)
66 
67 	Script_features:
68 
69 	OVERVIEW
70 	$(LIST
71 	* easy interop with D thanks to arsd.jsvar. When interpreting, pass a var object to use as globals.
72 		This object also contains the global state when interpretation is done.
73 	* mostly familiar syntax, hybrid of D and Javascript
74 	* simple implementation is moderately small and fairly easy to hack on (though it gets messier by the day), but it isn't made for speed.
75 	)
76 
77 	SPECIFICS
78 	$(LIST
79 	// * Allows identifiers-with-dashes. To do subtraction, put spaces around the minus sign.
80 	* Allows identifiers starting with a dollar sign.
81 	* string literals come in "foo" or 'foo', like Javascript, or `raw string` like D. Also come as “nested “double quotes” are an option!”
82 	* double quoted string literals can do Ruby-style interpolation: "Hello, #{name}".
83 	* mixin aka eval (does it at runtime, so more like eval than mixin, but I want it to look like D)
84 	* scope guards, like in D
85 	* Built-in assert() which prints its source and its arguments
86 	* try/catch/finally/throw
87 		You can use try as an expression without any following catch to return the exception:
88 
89 		```adrscript
90 		var a = try throw "exception";; // the double ; is because one closes the try, the second closes the var
91 		// a is now the thrown exception
92 		```
93 	* for/while/foreach
94 	* D style operators: +-/* on all numeric types, ~ on strings and arrays, |&^ on integers.
95 		Operators can coerce types as needed: 10 ~ "hey" == "10hey". 10 + "3" == 13.
96 		Any math, except bitwise math, with a floating point component returns a floating point component, but pure int math is done as ints (unlike Javascript btw).
97 		Any bitwise math coerces to int.
98 
99 		So you can do some type coercion like this:
100 
101 		```adrscript
102 		a = a|0; // forces to int
103 		a = "" ~ a; // forces to string
104 		a = a+0.0; // coerces to float
105 		```
106 
107 		Though casting is probably better.
108 	* Type coercion via cast, similarly to D.
109 		```adrscript
110 		var a = "12";
111 		a.typeof == "String";
112 		a = cast(int) a;
113 		a.typeof == "Integral";
114 		a == 12;
115 		```
116 
117 		Supported types for casting to: int/long (both actually an alias for long, because of how var works), float/double/real, string, char/dchar (these return *integral* types), and arrays, int[], string[], and float[].
118 
119 		This forwards directly to the D function var.opCast.
120 
121 	* some operator overloading on objects, passing opBinary(op, rhs), length, and perhaps others through like they would be in D.
122 		opIndex(name)
123 		opIndexAssign(value, name) // same order as D, might some day support [n1, n2] => (value, n1, n2)
124 
125 		obj.__prop("name", value); // bypasses operator overloading, useful for use inside the opIndexAssign especially
126 
127 		Note: if opIndex is not overloaded, getting a non-existent member will actually add it to the member. This might be a bug but is needed right now in the D impl for nice chaining. Or is it? FIXME
128 
129 		FIXME: it doesn't do opIndex with multiple args.
130 	* if/else
131 	* array slicing, but note that slices are rvalues currently
132 	* variables must start with A-Z, a-z, _, or $, then must be [A-Za-z0-9_]*.
133 		(The $ can also stand alone, and this is a special thing when slicing, so you probably shouldn't use it at all.).
134 		Variable names that start with __ are reserved and you shouldn't use them.
135 	* int, float, string, array, bool, and `#{}` (previously known as `json!q{}` aka object) literals
136 	* var.prototype, var.typeof. prototype works more like Mozilla's __proto__ than standard javascript prototype.
137 	* the `|>` pipeline operator
138 	* classes:
139 		```adrscript
140 		// inheritance works
141 		class Foo : bar {
142 			// constructors, D style
143 			this(var a) { ctor.... }
144 
145 			// static vars go on the auto created prototype
146 			static var b = 10;
147 
148 			// instance vars go on this instance itself
149 			var instancevar = 20;
150 
151 			// "virtual" functions can be overridden kinda like you expect in D, though there is no override keyword
152 			function virt() {
153 				b = 30; // lexical scoping is supported for static variables and functions
154 
155 				// but be sure to use this. as a prefix for any class defined instance variables in here
156 				this.instancevar = 10;
157 			}
158 		}
159 
160 		var foo = new Foo(12);
161 
162 		foo.newFunc = function() { this.derived = 0; }; // this is ok too, and scoping, including 'this', works like in Javascript
163 		```
164 
165 		You can also use 'new' on another object to get a copy of it.
166 	* return, break, continue, but currently cannot do labeled breaks and continues
167 	* __FILE__, __LINE__, but currently not as default arguments for D behavior (they always evaluate at the definition point)
168 	* most everything are expressions, though note this is pretty buggy! But as a consequence:
169 		for(var a = 0, b = 0; a < 10; a+=1, b+=1) {}
170 		won't work but this will:
171 		for(var a = 0, b = 0; a < 10; {a+=1; b+=1}) {}
172 
173 		You can encase things in {} anywhere instead of a comma operator, and it works kinda similarly.
174 
175 		{} creates a new scope inside it and returns the last value evaluated.
176 	* functions:
177 		var fn = function(args...) expr;
178 		or
179 		function fn(args....) expr;
180 
181 		Special function local variables:
182 			_arguments = var[] of the arguments passed
183 			_thisfunc = reference to the function itself
184 			this = reference to the object on which it is being called - note this is like Javascript, not D.
185 
186 		args can say var if you want, but don't have to
187 		default arguments supported in any position
188 		when calling, you can use the default keyword to use the default value in any position
189 	* macros:
190 		A macro is defined just like a function, except with the
191 		macro keyword instead of the function keyword. The difference
192 		is a macro must interpret its own arguments - it is passed
193 		AST objects instead of values. Still a WIP.
194 	)
195 
196 
197 	Todo_list:
198 
199 	I also have a wishlist here that I may do in the future, but don't expect them any time soon.
200 
201 FIXME: maybe some kind of splat operator too. choose([1,2,3]...) expands to choose(1,2,3)
202 
203 make sure superclass ctors are called
204 
205    FIXME: prettier stack trace when sent to D
206 
207    FIXME: support more escape things in strings like \n, \t etc.
208 
209    FIXME: add easy to use premade packages for the global object.
210 
211    FIXME: the debugger statement from javascript might be cool to throw in too.
212 
213    FIXME: add continuations or something too - actually doing it with fibers works pretty well
214 
215    FIXME: Also ability to get source code for function something so you can mixin.
216 
217    FIXME: add COM support on Windows ????
218 
219 
220 	Might be nice:
221 		varargs
222 		lambdas - maybe without function keyword and the x => foo syntax from D.
223 
224 
225 	History:
226 		April 28, 2020: added `#{}` as an alternative to the `json!q{}` syntax for object literals. Also fixed unary `!` operator.
227 
228 		April 26, 2020: added `switch`, fixed precedence bug, fixed doc issues and added some unittests
229 
230 		Started writing it in July 2013. Yes, a basic precedence issue was there for almost SEVEN YEARS. You can use this as a toy but please don't use it for anything too serious, it really is very poorly written and not intelligently designed at all.
231 +/
232 module arsd.script;
233 
234 /++
235 	This example shows the basics of how to interact with the script.
236 	The string enclosed in `q{ .. }` is the script language source.
237 
238 	The [var] type comes from [arsd.jsvar] and provides a dynamic type
239 	to D. It is the same type used in the script language and is weakly
240 	typed, providing operator overloads to work with many D types seamlessly.
241 
242 	However, if you do need to convert it to a static type, such as if passing
243 	to a function, you can use `get!T` to get a static type out of it.
244 +/
245 unittest {
246 	var globals = var.emptyObject;
247 	globals.x = 25; // we can set variables on the global object
248 	globals.name = "script.d"; // of various types
249 	// and we can make native functions available to the script
250 	globals.sum = (int a, int b) {
251 		return a + b;
252 	};
253 
254 	// This is the source code of the script. It is similar
255 	// to javascript with pieces borrowed from D, so should
256 	// be pretty familiar.
257 	string scriptSource = q{
258 		function foo() {
259 			return 13;
260 		}
261 
262 		var a = foo() + 12;
263 		assert(a == 25);
264 
265 		// you can also access the D globals from the script
266 		assert(x == 25);
267 		assert(name == "script.d");
268 
269 		// as well as call D functions set via globals:
270 		assert(sum(5, 6) == 11);
271 
272 		// I will also set a function to call from D
273 		function bar(str) {
274 			// unlike Javascript though, we use the D style
275 			// concatenation operator.
276 			return str ~ " concatenation";
277 		}
278 	};
279 	
280 	// once you have the globals set up, you call the interpreter
281 	// with one simple function.
282 	interpret(scriptSource, globals);
283 
284 	// finally, globals defined from the script are accessible here too:
285 	// however, notice the two sets of parenthesis: the first is because
286 	// @property is broken in D. The second set calls the function and you
287 	// can pass values to it.
288 	assert(globals.foo()() == 13);
289 
290 	assert(globals.bar()("test") == "test concatenation");
291 
292 	// this shows how to convert the var back to a D static type.
293 	int x = globals.x.get!int;
294 }
295 
296 /++
297 	$(H3 Macros)
298 
299 	Macros are like functions, but instead of evaluating their arguments at
300 	the call site and passing value, the AST nodes are passed right in. Calling
301 	the node evaluates the argument and yields the result (this is similar to
302 	to `lazy` parameters in D), and they also have methods like `toSourceCode`,
303 	`type`, and `interpolate`, which forwards to the given string.
304 
305 	The language also supports macros and custom interpolation functions. This
306 	example shows an interpolation string being passed to a macro and used
307 	with a custom interpolation string.
308 
309 	You might use this to encode interpolated things or something like that.
310 +/
311 unittest {
312 	var globals = var.emptyObject;
313 	interpret(q{
314 		macro test(x) {
315 			return x.interpolate(function(str) {
316 				return str ~ "test";
317 			});
318 		}
319 
320 		var a = "cool";
321 		assert(test("hey #{a}") == "hey cooltest");
322 	}, globals);
323 }
324 
325 /++
326 	$(H3 Classes demo)
327 
328 	See also: [arsd.jsvar.subclassable] for more interop with D classes.
329 +/
330 unittest {
331 	var globals = var.emptyObject;
332 	interpret(q{
333 		class Base {
334 			function foo() { return "Base"; }
335 			function set() { this.a = 10; }
336 			function get() { return this.a; } // this MUST be used for instance variables though as they do not exist in static lookup
337 			function test() { return foo(); } // I did NOT use `this` here which means it does STATIC lookup!
338 							// kinda like mixin templates in D lol.
339 			var a = 5;
340 			static var b = 10; // static vars are attached to the class specifically
341 		}
342 		class Child : Base {
343 			function foo() {
344 				assert(super.foo() == "Base");
345 				return "Child";
346 			};
347 			function set() { this.a = 7; }
348 			function get2() { return this.a; }
349 			var a = 9;
350 		}
351 
352 		var c = new Child();
353 		assert(c.foo() == "Child");
354 
355 		assert(c.test() == "Base"); // static lookup of methods if you don't use `this`
356 
357 		/*
358 		// these would pass in D, but do NOT pass here because of dynamic variable lookup in script.
359 		assert(c.get() == 5);
360 		assert(c.get2() == 9);
361 		c.set();
362 		assert(c.get() == 5); // parent instance is separate
363 		assert(c.get2() == 7);
364 		*/
365 
366 		// showing the shared vars now.... I personally prefer the D way but meh, this lang
367 		// is an unholy cross of D and Javascript so that means it sucks sometimes.
368 		assert(c.get() == c.get2());
369 		c.set();
370 		assert(c.get2() == 7);
371 		assert(c.get() == c.get2());
372 
373 		// super, on the other hand, must always be looked up statically, or else this
374 		// next example with infinite recurse and smash the stack.
375 		class Third : Child { }
376 		var t = new Third();
377 		assert(t.foo() == "Child");
378 	}, globals);
379 }
380 
381 /++
382 	$(H3 Properties from D)
383 
384 	Note that it is not possible yet to define a property function from the script language.
385 +/
386 unittest {
387 	static class Test {
388 		// the @scriptable is required to make it accessible
389 		@scriptable int a;
390 
391 		@scriptable @property int ro() { return 30; }
392 
393 		int _b = 20;
394 		@scriptable @property int b() { return _b; }
395 		@scriptable @property int b(int val) { return _b = val; }
396 	}
397 
398 	Test test = new Test;
399 
400 	test.a = 15;
401 
402 	var globals = var.emptyObject;
403 	globals.test = test;
404 	// but once it is @scriptable, both read and write works from here:
405 	interpret(q{
406 		assert(test.a == 15);
407 		test.a = 10;
408 		assert(test.a == 10);
409 
410 		assert(test.ro == 30); // @property functions from D wrapped too
411 		test.ro = 40;
412 		assert(test.ro == 30); // setting it does nothing though
413 
414 		assert(test.b == 20); // reader still works if read/write available too
415 		test.b = 25;
416 		assert(test.b == 25); // writer action reflected
417 
418 		// however other opAssign operators are not implemented correctly on properties at this time so this fails!
419 		//test.b *= 2;
420 		//assert(test.b == 50);
421 	}, globals);
422 
423 	// and update seen back in D
424 	assert(test.a == 10); // on the original native object
425 	assert(test.b == 25);
426 
427 	assert(globals.test.a == 10); // and via the var accessor for member var
428 	assert(globals.test.b == 25); // as well as @property func
429 }
430 
431 
432 public import arsd.jsvar;
433 
434 import std.stdio;
435 import std.traits;
436 import std.conv;
437 import std.json;
438 
439 import std.array;
440 import std.range;
441 
442 /* **************************************
443   script to follow
444 ****************************************/
445 
446 /++
447 	A base class for exceptions that can never be caught by scripts;
448 	throwing it from a function called from a script is guaranteed to
449 	bubble all the way up to your [interpret] call..
450 	(scripts can also never catch Error btw)
451 
452 	History:
453 		Added on April 24, 2020 (v7.3.0)
454 +/
455 class NonScriptCatchableException : Exception {
456 	import std.exception;
457 	///
458 	mixin basicExceptionCtors;
459 }
460 
461 //class TEST : Throwable {this() { super("lol"); }}
462 
463 /// Thrown on script syntax errors and the sort.
464 class ScriptCompileException : Exception {
465 	string s;
466 	int lineNumber;
467 	this(string msg, string s, int lineNumber, string file = __FILE__, size_t line = __LINE__) {
468 		this.s = s;
469 		this.lineNumber = lineNumber;
470 		super(to!string(lineNumber) ~ ": " ~ msg, file, line);
471 	}
472 }
473 
474 /// Thrown on things like interpretation failures.
475 class ScriptRuntimeException : Exception {
476 	string s;
477 	int lineNumber;
478 	this(string msg, string s, int lineNumber, string file = __FILE__, size_t line = __LINE__) {
479 		this.s = s;
480 		this.lineNumber = lineNumber;
481 		super(to!string(lineNumber) ~ ": " ~ msg, file, line);
482 	}
483 }
484 
485 /// This represents an exception thrown by `throw x;` inside the script as it is interpreted.
486 class ScriptException : Exception {
487 	///
488 	var payload;
489 	///
490 	ScriptLocation loc;
491 	///
492 	ScriptLocation[] callStack;
493 	this(var payload, ScriptLocation loc, string file = __FILE__, size_t line = __LINE__) {
494 		this.payload = payload;
495 		if(loc.scriptFilename.length == 0)
496 			loc.scriptFilename = "user_script";
497 		this.loc = loc;
498 		super(loc.scriptFilename ~ "@" ~ to!string(loc.lineNumber) ~ ": " ~ to!string(payload), file, line);
499 	}
500 
501 	/*
502 	override string toString() {
503 		return loc.scriptFilename ~ "@" ~ to!string(loc.lineNumber) ~ ": " ~ payload.get!string ~ to!string(callStack);
504 	}
505 	*/
506 
507 	// might be nice to take a D exception and put a script stack trace in there too......
508 	// also need toString to show the callStack
509 }
510 
511 struct ScriptToken {
512 	enum Type { identifier, keyword, symbol, string, int_number, float_number }
513 	Type type;
514 	string str;
515 	string scriptFilename;
516 	int lineNumber;
517 
518 	string wasSpecial;
519 }
520 
521 	// these need to be ordered from longest to shortest
522 	// some of these aren't actually used, like struct and goto right now, but I want them reserved for later
523 private enum string[] keywords = [
524 	"function", "continue",
525 	"__FILE__", "__LINE__", // these two are special to the lexer
526 	"foreach", "json!q{", "default", "finally",
527 	"return", "static", "struct", "import", "module", "assert", "switch",
528 	"while", "catch", "throw", "scope", "break", "class", "false", "mixin", "macro", "super",
529 	// "this" is just treated as just a magic identifier.....
530 	"auto", // provided as an alias for var right now, may change later
531 	"null", "else", "true", "eval", "goto", "enum", "case", "cast",
532 	"var", "for", "try", "new",
533 	"if", "do",
534 ];
535 private enum string[] symbols = [
536 	">>>", // FIXME
537 	"//", "/*", "/+",
538 	"&&", "||",
539 	"+=", "-=", "*=", "/=", "~=",  "==", "<=", ">=","!=", "%=",
540 	"&=", "|=", "^=",
541 	"#{",
542 	"..",
543 	"<<", ">>", // FIXME
544 	"|>",
545 	"=>", // FIXME
546 	"?", ".",",",";",":",
547 	"[", "]", "{", "}", "(", ")",
548 	"&", "|", "^",
549 	"+", "-", "*", "/", "=", "<", ">","~","!","%"
550 ];
551 
552 // we need reference semantics on this all the time
553 class TokenStream(TextStream) {
554 	TextStream textStream;
555 	string text;
556 	int lineNumber = 1;
557 	string scriptFilename;
558 
559 	void advance(ptrdiff_t size) {
560 		foreach(i; 0 .. size) {
561 			if(text.empty)
562 				break;
563 			if(text[0] == '\n')
564 				lineNumber ++;
565 			text = text[1 .. $];
566 			// text.popFront(); // don't want this because it pops too much trying to do its own UTF-8, which we already handled!
567 		}
568 	}
569 
570 	this(TextStream ts, string fn) {
571 		textStream = ts;
572 		scriptFilename = fn;
573 		text = textStream.front;
574 		popFront;
575 	}
576 
577 	ScriptToken next;
578 
579 	// FIXME: might be worth changing this so i can peek far enough ahead to do () => expr lambdas.
580 	ScriptToken peek;
581 	bool peeked;
582 	void pushFront(ScriptToken f) {
583 		peek = f;
584 		peeked = true;
585 	}
586 
587 	ScriptToken front() {
588 		if(peeked)
589 			return peek;
590 		else
591 			return next;
592 	}
593 
594 	bool empty() {
595 		advanceSkips();
596 		return text.length == 0 && textStream.empty && !peeked;
597 	}
598 
599 	int skipNext;
600 	void advanceSkips() {
601 		if(skipNext) {
602 			skipNext--;
603 			popFront();
604 		}
605 	}
606 
607 	void popFront() {
608 		if(peeked) {
609 			peeked = false;
610 			return;
611 		}
612 
613 		assert(!empty);
614 		mainLoop:
615 		while(text.length) {
616 			ScriptToken token;
617 			token.lineNumber = lineNumber;
618 			token.scriptFilename = scriptFilename;
619 
620 			if(text[0] == ' ' || text[0] == '\t' || text[0] == '\n' || text[0] == '\r') {
621 				advance(1);
622 				continue;
623 			} else if(text[0] >= '0' && text[0] <= '9') {
624 				int pos;
625 				bool sawDot;
626 				while(pos < text.length && ((text[pos] >= '0' && text[pos] <= '9') || text[pos] == '.')) {
627 					if(text[pos] == '.') {
628 						if(sawDot)
629 							break;
630 						else
631 							sawDot = true;
632 					}
633 					pos++;
634 				}
635 
636 				if(text[pos - 1] == '.') {
637 					// This is something like "1.x", which is *not* a floating literal; it is UFCS on an int
638 					sawDot = false;
639 					pos --;
640 				}
641 
642 				token.type = sawDot ? ScriptToken.Type.float_number : ScriptToken.Type.int_number;
643 				token.str = text[0 .. pos];
644 				advance(pos);
645 			} else if((text[0] >= 'a' && text[0] <= 'z') || (text[0] == '_') || (text[0] >= 'A' && text[0] <= 'Z') || text[0] == '$') {
646 				bool found = false;
647 				foreach(keyword; keywords)
648 					if(text.length >= keyword.length && text[0 .. keyword.length] == keyword && 
649 						// making sure this isn't an identifier that starts with a keyword
650 						(text.length == keyword.length || !(
651 							(
652 								(text[keyword.length] >= '0' && text[keyword.length] <= '9') ||
653 								(text[keyword.length] >= 'a' && text[keyword.length] <= 'z') ||
654 								(text[keyword.length] == '_') ||
655 								(text[keyword.length] >= 'A' && text[keyword.length] <= 'Z')
656 							)
657 						)))
658 					{
659 						found = true;
660 						if(keyword == "__FILE__") {
661 							token.type = ScriptToken.Type..string;
662 							token.str = to!string(token.scriptFilename);
663 							token.wasSpecial = keyword;
664 						} else if(keyword == "__LINE__") {
665 							token.type = ScriptToken.Type.int_number;
666 							token.str = to!string(token.lineNumber);
667 							token.wasSpecial = keyword;
668 						} else {
669 							token.type = ScriptToken.Type.keyword;
670 							// auto is done as an alias to var in the lexer just so D habits work there too
671 							if(keyword == "auto") {
672 								token.str = "var";
673 								token.wasSpecial = keyword;
674 							} else
675 								token.str = keyword;
676 						}
677 						advance(keyword.length);
678 						break;
679 					}
680 
681 				if(!found) {
682 					token.type = ScriptToken.Type.identifier;
683 					int pos;
684 					if(text[0] == '$')
685 						pos++;
686 
687 					while(pos < text.length
688 						&& ((text[pos] >= 'a' && text[pos] <= 'z') ||
689 							(text[pos] == '_') ||
690 							//(pos != 0 && text[pos] == '-') || // allow mid-identifier dashes for this-kind-of-name. For subtraction, add a space.
691 							(text[pos] >= 'A' && text[pos] <= 'Z') ||
692 							(text[pos] >= '0' && text[pos] <= '9')))
693 					{
694 						pos++;
695 					}
696 
697 					token.str = text[0 .. pos];
698 					advance(pos);
699 				}
700 			} else if(text[0] == '"' || text[0] == '\'' || text[0] == '`' ||
701 				// Also supporting double curly quoted strings: “foo” which nest. This is the utf 8 coding:
702 				(text.length >= 3 && text[0] == 0xe2 && text[1] == 0x80 && text[2] == 0x9c)) 
703 			{
704 				char end = text[0]; // support single quote and double quote strings the same
705 				int openCurlyQuoteCount = (end == 0xe2) ? 1 : 0;
706 				bool escapingAllowed = end != '`'; // `` strings are raw, they don't support escapes. the others do.
707 				token.type = ScriptToken.Type..string;
708 				int pos = openCurlyQuoteCount ? 3 : 1; // skip the opening dchar
709 				int started = pos;
710 				bool escaped = false;
711 				bool mustCopy = false;
712 
713 				bool allowInterpolation = text[0] == '"';
714 
715 				bool atEnd() {
716 					if(pos == text.length)
717 						return false;
718 					if(openCurlyQuoteCount) {
719 						if(openCurlyQuoteCount == 1)
720 							return (pos + 3 <= text.length && text[pos] == 0xe2 && text[pos+1] == 0x80 && text[pos+2] == 0x9d); // ”
721 						else // greater than one means we nest
722 							return false;
723 					} else
724 						return text[pos] == end;
725 				}
726 
727 				bool interpolationDetected = false;
728 				bool inInterpolate = false;
729 				int interpolateCount = 0;
730 
731 				while(pos < text.length && (escaped || inInterpolate || !atEnd())) {
732 					if(inInterpolate) {
733 						if(text[pos] == '{')
734 							interpolateCount++;
735 						else if(text[pos] == '}') {
736 							interpolateCount--;
737 							if(interpolateCount == 0)
738 								inInterpolate = false;
739 						}
740 						pos++;
741 						continue;
742 					}
743 
744 					if(escaped) {
745 						mustCopy = true;
746 						escaped = false;
747 					} else {
748 						if(text[pos] == '\\' && escapingAllowed)
749 							escaped = true;
750 						if(allowInterpolation && text[pos] == '#' && pos + 1 < text.length  && text[pos + 1] == '{') {
751 							interpolationDetected = true;
752 							inInterpolate = true;
753 						}
754 						if(openCurlyQuoteCount) {
755 							// also need to count curly quotes to support nesting
756 							if(pos + 3 <= text.length && text[pos+0] == 0xe2 && text[pos+1] == 0x80 && text[pos+2] == 0x9c) // “
757 								openCurlyQuoteCount++;
758 							if(pos + 3 <= text.length && text[pos+0] == 0xe2 && text[pos+1] == 0x80 && text[pos+2] == 0x9d) // ”
759 								openCurlyQuoteCount--;
760 						}
761 					}
762 					pos++;
763 				}
764 
765 				if(pos == text.length && (escaped || inInterpolate || !atEnd()))
766 					throw new ScriptCompileException("Unclosed string literal", token.scriptFilename, token.lineNumber);
767 
768 				if(mustCopy) {
769 					// there must be something escaped in there, so we need
770 					// to copy it and properly handle those cases
771 					string copy;
772 					copy.reserve(pos + 4);
773 
774 					escaped = false;
775 					foreach(idx, dchar ch; text[started .. pos]) {
776 						if(escaped) {
777 							escaped = false;
778 							switch(ch) {
779 								case '\\': copy ~= "\\"; break;
780 								case 'n': copy ~= "\n"; break;
781 								case 'r': copy ~= "\r"; break;
782 								case 'a': copy ~= "\a"; break;
783 								case 't': copy ~= "\t"; break;
784 								case '#': copy ~= "#"; break;
785 								case '"': copy ~= "\""; break;
786 								case '\'': copy ~= "'"; break;
787 								default:
788 									throw new ScriptCompileException("Unknown escape char " ~ cast(char) ch, token.scriptFilename, token.lineNumber);
789 							}
790 							continue;
791 						} else if(ch == '\\') {
792 							escaped = true;
793 							continue;
794 						}
795 						copy ~= ch;
796 					}
797 
798 					token.str = copy;
799 				} else {
800 					token.str = text[started .. pos];
801 				}
802 				if(interpolationDetected)
803 					token.wasSpecial = "\"";
804 				advance(pos + ((end == 0xe2) ? 3 : 1)); // skip the closing " too
805 			} else {
806 				// let's check all symbols
807 				bool found = false;
808 				foreach(symbol; symbols)
809 					if(text.length >= symbol.length && text[0 .. symbol.length] == symbol) {
810 
811 						if(symbol == "//") {
812 							// one line comment
813 							int pos = 0;
814 							while(pos < text.length && text[pos] != '\n' && text[0] != '\r')
815 								pos++;
816 							advance(pos);
817 							continue mainLoop;
818 						} else if(symbol == "/*") {
819 							int pos = 0;
820 							while(pos + 1 < text.length && text[pos..pos+2] != "*/")
821 								pos++;
822 
823 							if(pos + 1 == text.length)
824 								throw new ScriptCompileException("unclosed /* */ comment", token.scriptFilename, lineNumber);
825 
826 							advance(pos + 2);
827 							continue mainLoop;
828 
829 						} else if(symbol == "/+") {
830 							int open = 0;
831 							int pos = 0;
832 							while(pos + 1 < text.length) {
833 								if(text[pos..pos+2] == "/+") {
834 									open++;
835 									pos++;
836 								} else if(text[pos..pos+2] == "+/") {
837 									open--;
838 									pos++;
839 									if(open == 0)
840 										break;
841 								}
842 								pos++;
843 							}
844 
845 							if(pos + 1 == text.length)
846 								throw new ScriptCompileException("unclosed /+ +/ comment", token.scriptFilename, lineNumber);
847 
848 							advance(pos + 1);
849 							continue mainLoop;
850 						}
851 						// FIXME: documentation comments
852 
853 						found = true;
854 						token.type = ScriptToken.Type.symbol;
855 						token.str = symbol;
856 						advance(symbol.length);
857 						break;
858 					}
859 
860 				if(!found) {
861 					// FIXME: make sure this gives a valid utf-8 sequence
862 					throw new ScriptCompileException("unknown token " ~ text[0], token.scriptFilename, lineNumber);
863 				}
864 			}
865 
866 			next = token;
867 			return;
868 		}
869 
870 		textStream.popFront();
871 		if(!textStream.empty()) {
872 			text = textStream.front;
873 			goto mainLoop;
874 		}
875 
876 		return;
877 	}
878 
879 }
880 
881 TokenStream!TextStream lexScript(TextStream)(TextStream textStream, string scriptFilename) if(is(ElementType!TextStream == string)) {
882 	return new TokenStream!TextStream(textStream, scriptFilename);
883 }
884 
885 class MacroPrototype : PrototypeObject {
886 	var func;
887 
888 	// macros are basically functions that get special treatment for their arguments
889 	// they are passed as AST objects instead of interpreted
890 	// calling an AST object will interpret it in the script
891 	this(var func) {
892 		this.func = func;
893 		this._properties["opCall"] = (var _this, var[] args) {
894 			return func.apply(_this, args);
895 		};
896 	}
897 }
898 
899 alias helper(alias T) = T;
900 // alternative to virtual function for converting the expression objects to script objects
901 void addChildElementsOfExpressionToScriptExpressionObject(ClassInfo c, Expression _thisin, PrototypeObject sc, ref var obj) {
902 	foreach(itemName; __traits(allMembers, mixin(__MODULE__)))
903 	static if(__traits(compiles, __traits(getMember, mixin(__MODULE__), itemName))) {
904 		alias Class = helper!(__traits(getMember, mixin(__MODULE__), itemName));
905 		static if(is(Class : Expression)) if(c == typeid(Class)) {
906 			auto _this = cast(Class) _thisin;
907 			foreach(memberName; __traits(allMembers, Class)) {
908 				alias member = helper!(__traits(getMember, Class, memberName));
909 
910 				static if(is(typeof(member) : Expression)) {
911 					auto lol = __traits(getMember, _this, memberName);
912 					if(lol is null)
913 						obj[memberName] = null;
914 					else
915 						obj[memberName] = lol.toScriptExpressionObject(sc);
916 				}
917 				static if(is(typeof(member) : Expression[])) {
918 					obj[memberName] = var.emptyArray;
919 					foreach(m; __traits(getMember, _this, memberName))
920 						if(m !is null)
921 							obj[memberName] ~= m.toScriptExpressionObject(sc);
922 						else
923 							obj[memberName] ~= null;
924 				}
925 				static if(is(typeof(member) : string) || is(typeof(member) : long) || is(typeof(member) : real) || is(typeof(member) : bool)) {
926 					obj[memberName] = __traits(getMember, _this, memberName);
927 				}
928 			}
929 		}
930 	}
931 }
932 
933 struct InterpretResult {
934 	var value;
935 	PrototypeObject sc;
936 	enum FlowControl { Normal, Return, Continue, Break, Goto }
937 	FlowControl flowControl;
938 	string flowControlDetails; // which label
939 }
940 
941 class Expression {
942 	abstract InterpretResult interpret(PrototypeObject sc);
943 
944 	// this returns an AST object that can be inspected and possibly altered
945 	// by the script. Calling the returned object will interpret the object in
946 	// the original scope passed
947 	var toScriptExpressionObject(PrototypeObject sc) {
948 		var obj = var.emptyObject;
949 
950 		obj["type"] = typeid(this).name;
951 		obj["toSourceCode"] = (var _this, var[] args) {
952 			Expression e = this;
953 			return var(e.toString());
954 		};
955 		obj["opCall"] = (var _this, var[] args) {
956 			Expression e = this;
957 			// FIXME: if they changed the properties in the
958 			// script, we should update them here too.
959 			return e.interpret(sc).value;
960 		};
961 		obj["interpolate"] = (var _this, var[] args) {
962 			StringLiteralExpression e = cast(StringLiteralExpression) this;
963 			if(!e)
964 				return var(null);
965 			return e.interpolate(args.length ? args[0] : var(null), sc);
966 		};
967 
968 
969 		// adding structure is going to be a little bit magical
970 		// I could have done this with a virtual function, but I'm lazy.
971 		addChildElementsOfExpressionToScriptExpressionObject(typeid(this), this, sc, obj);
972 
973 		return obj;
974 	}
975 
976 	string toInterpretedString(PrototypeObject sc) {
977 		return toString();
978 	}
979 }
980 
981 class MixinExpression : Expression {
982 	Expression e1;
983 	this(Expression e1) {
984 		this.e1 = e1;
985 	}
986 
987 	override string toString() { return "mixin(" ~ e1.toString() ~ ")"; }
988 
989 	override InterpretResult interpret(PrototypeObject sc) {
990 		return InterpretResult(.interpret(e1.interpret(sc).value.get!string ~ ";", sc), sc);
991 	}
992 }
993 
994 class StringLiteralExpression : Expression {
995 	string content;
996 	bool allowInterpolation;
997 
998 	ScriptToken token;
999 
1000 	override string toString() {
1001 		import std.string : replace;
1002 		return "\"" ~ content.replace(`\`, `\\`).replace("\"", "\\\"") ~ "\"";
1003 	}
1004 
1005 	this(ScriptToken token) {
1006 		this.token = token;
1007 		this(token.str);
1008 		if(token.wasSpecial == "\"")
1009 			allowInterpolation = true;
1010 
1011 	}
1012 
1013 	this(string s) {
1014 		content = s;
1015 	}
1016 
1017 	var interpolate(var funcObj, PrototypeObject sc) {
1018 		import std.string : indexOf;
1019 		if(allowInterpolation) {
1020 			string r;
1021 
1022 			auto c = content;
1023 			auto idx = c.indexOf("#{");
1024 			while(idx != -1) {
1025 				r ~= c[0 .. idx];
1026 				c = c[idx + 2 .. $];
1027 				idx = 0;
1028 				int open = 1;
1029 				while(idx < c.length) {
1030 					if(c[idx] == '}')
1031 						open--;
1032 					else if(c[idx] == '{')
1033 						open++;
1034 					if(open == 0)
1035 						break;
1036 					idx++;
1037 				}
1038 				if(open != 0)
1039 					throw new ScriptRuntimeException("Unclosed interpolation thing", token.scriptFilename, token.lineNumber);
1040 				auto code = c[0 .. idx];
1041 
1042 				var result = .interpret(code, sc);
1043 
1044 				if(funcObj == var(null))
1045 					r ~= result.get!string;
1046 				else
1047 					r ~= funcObj(result).get!string;
1048 
1049 				c = c[idx + 1 .. $];
1050 				idx = c.indexOf("#{");
1051 			}
1052 
1053 			r ~= c;
1054 			return var(r);
1055 		} else {
1056 			return var(content);
1057 		}
1058 	}
1059 
1060 	override InterpretResult interpret(PrototypeObject sc) {
1061 		return InterpretResult(interpolate(var(null), sc), sc);
1062 	}
1063 }
1064 
1065 class BoolLiteralExpression : Expression {
1066 	bool literal;
1067 	this(string l) {
1068 		literal = to!bool(l);
1069 	}
1070 
1071 	override string toString() { return to!string(literal); }
1072 
1073 	override InterpretResult interpret(PrototypeObject sc) {
1074 		return InterpretResult(var(literal), sc);
1075 	}
1076 }
1077 
1078 class IntLiteralExpression : Expression {
1079 	long literal;
1080 
1081 	this(string s) {
1082 		literal = to!long(s);
1083 	}
1084 
1085 	override string toString() { return to!string(literal); }
1086 
1087 	override InterpretResult interpret(PrototypeObject sc) {
1088 		return InterpretResult(var(literal), sc);
1089 	}
1090 }
1091 class FloatLiteralExpression : Expression {
1092 	this(string s) {
1093 		literal = to!real(s);
1094 	}
1095 	real literal;
1096 	override string toString() { return to!string(literal); }
1097 	override InterpretResult interpret(PrototypeObject sc) {
1098 		return InterpretResult(var(literal), sc);
1099 	}
1100 }
1101 class NullLiteralExpression : Expression {
1102 	this() {}
1103 	override string toString() { return "null"; }
1104 
1105 	override InterpretResult interpret(PrototypeObject sc) {
1106 		var n;
1107 		return InterpretResult(n, sc);
1108 	}
1109 }
1110 class NegationExpression : Expression {
1111 	Expression e;
1112 	this(Expression e) { this.e = e;}
1113 	override string toString() { return "-" ~ e.toString(); }
1114 
1115 	override InterpretResult interpret(PrototypeObject sc) {
1116 		var n = e.interpret(sc).value;
1117 		return InterpretResult(-n, sc);
1118 	}
1119 }
1120 class NotExpression : Expression {
1121 	Expression e;
1122 	this(Expression e) { this.e = e;}
1123 	override string toString() { return "!" ~ e.toString(); }
1124 
1125 	override InterpretResult interpret(PrototypeObject sc) {
1126 		var n = e.interpret(sc).value;
1127 		return InterpretResult(var(!n), sc);
1128 	}
1129 }
1130 class BitFlipExpression : Expression {
1131 	Expression e;
1132 	this(Expression e) { this.e = e;}
1133 	override string toString() { return "~" ~ e.toString(); }
1134 
1135 	override InterpretResult interpret(PrototypeObject sc) {
1136 		var n = e.interpret(sc).value;
1137 		// possible FIXME given the size. but it is fuzzy when dynamic..
1138 		return InterpretResult(var(~(n.get!long)), sc);
1139 	}
1140 }
1141 
1142 class ArrayLiteralExpression : Expression {
1143 	this() {}
1144 
1145 	override string toString() {
1146 		string s = "[";
1147 		foreach(i, ele; elements) {
1148 			if(i) s ~= ", ";
1149 			s ~= ele.toString();
1150 		}
1151 		s ~= "]";
1152 		return s;
1153 	}
1154 
1155 	Expression[] elements;
1156 	override InterpretResult interpret(PrototypeObject sc) {
1157 		var n = var.emptyArray;
1158 		foreach(i, element; elements)
1159 			n[i] = element.interpret(sc).value;
1160 		return InterpretResult(n, sc);
1161 	}
1162 }
1163 class ObjectLiteralExpression : Expression {
1164 	Expression[string] elements;
1165 
1166 	override string toString() {
1167 		string s = "#{";
1168 		bool first = true;
1169 		foreach(k, e; elements) {
1170 			if(first)
1171 				first = false;
1172 			else
1173 				s ~= ", ";
1174 
1175 			s ~= "\"" ~ k ~ "\":"; // FIXME: escape if needed
1176 			s ~= e.toString();
1177 		}
1178 
1179 		s ~= "}";
1180 		return s;
1181 	}
1182 
1183 	PrototypeObject backing;
1184 	this(PrototypeObject backing = null) {
1185 		this.backing = backing;
1186 	}
1187 
1188 	override InterpretResult interpret(PrototypeObject sc) {
1189 		var n;
1190 		if(backing is null)
1191 			n = var.emptyObject;
1192 		else
1193 			n._object = backing;
1194 
1195 		foreach(k, v; elements)
1196 			n[k] = v.interpret(sc).value;
1197 
1198 		return InterpretResult(n, sc);
1199 	}
1200 }
1201 class FunctionLiteralExpression : Expression {
1202 	this() {
1203 		// we want this to not be null at all when we're interpreting since it is used as a comparison for a magic operation
1204 		if(DefaultArgumentDummyObject is null)
1205 			DefaultArgumentDummyObject = new PrototypeObject();
1206 	}
1207 
1208 	this(VariableDeclaration args, Expression bod, PrototypeObject lexicalScope = null) {
1209 		this();
1210 		this.arguments = args;
1211 		this.functionBody = bod;
1212 		this.lexicalScope = lexicalScope;
1213 	}
1214 
1215 	override string toString() {
1216 		string s = (isMacro ? "macro" : "function") ~ " (";
1217 		if(arguments !is null)
1218 			s ~= arguments.toString();
1219 
1220 		s ~= ") ";
1221 		s ~= functionBody.toString();
1222 		return s;
1223 	}
1224 
1225 	/*
1226 		function identifier (arg list) expression
1227 
1228 		so
1229 		var e = function foo() 10; // valid
1230 		var e = function foo() { return 10; } // also valid
1231 
1232 		// the return value is just the last expression's result that was evaluated
1233 		// to return void, be sure to do a "return;" at the end of the function
1234 	*/
1235 	VariableDeclaration arguments;
1236 	Expression functionBody; // can be a ScopeExpression btw
1237 
1238 	PrototypeObject lexicalScope;
1239 
1240 	bool isMacro;
1241 
1242 	override InterpretResult interpret(PrototypeObject sc) {
1243 		assert(DefaultArgumentDummyObject !is null);
1244 		var v;
1245 		v._metadata = new ScriptFunctionMetadata(this);
1246 		v._function = (var _this, var[] args) {
1247 			auto argumentsScope = new PrototypeObject();
1248 			PrototypeObject scToUse;
1249 			if(lexicalScope is null)
1250 				scToUse = sc;
1251 			else {
1252 				scToUse = lexicalScope;
1253 				scToUse._secondary = sc;
1254 			}
1255 
1256 			argumentsScope.prototype = scToUse;
1257 
1258 			argumentsScope._getMember("this", false, false) = _this;
1259 			argumentsScope._getMember("_arguments", false, false) = args;
1260 			argumentsScope._getMember("_thisfunc", false, false) = v;
1261 
1262 			if(arguments)
1263 			foreach(i, identifier; arguments.identifiers) {
1264 				argumentsScope._getMember(identifier, false, false); // create it in this scope...
1265 				if(i < args.length && !(args[i].payloadType() == var.Type.Object && args[i]._payload._object is DefaultArgumentDummyObject))
1266 					argumentsScope._getMember(identifier, false, true) = args[i];
1267 				else
1268 				if(arguments.initializers[i] !is null)
1269 					argumentsScope._getMember(identifier, false, true) = arguments.initializers[i].interpret(sc).value;
1270 			}
1271 
1272 			if(functionBody !is null)
1273 				return functionBody.interpret(argumentsScope).value;
1274 			else {
1275 				assert(0);
1276 			}
1277 		};
1278 		if(isMacro) {
1279 			var n = var.emptyObject;
1280 			n._object = new MacroPrototype(v);
1281 			v = n;
1282 		}
1283 		return InterpretResult(v, sc);
1284 	}
1285 }
1286 
1287 class CastExpression : Expression {
1288 	string type;
1289 	Expression e1;
1290 
1291 	override string toString() {
1292 		return "cast(" ~ type ~ ") " ~ e1.toString();
1293 	}
1294 
1295 	override InterpretResult interpret(PrototypeObject sc) {
1296 		var n = e1.interpret(sc).value;
1297 		foreach(possibleType; CtList!("int", "long", "float", "double", "real", "char", "dchar", "string", "int[]", "string[]", "float[]")) {
1298 			if(type == possibleType)
1299 				n = mixin("cast(" ~ possibleType ~ ") n");
1300 		}
1301 
1302 		return InterpretResult(n, sc);
1303 	}
1304 }
1305 
1306 class VariableDeclaration : Expression {
1307 	string[] identifiers;
1308 	Expression[] initializers;
1309 
1310 	this() {}
1311 
1312 	override string toString() {
1313 		string s = "";
1314 		foreach(i, ident; identifiers) {
1315 			if(i)
1316 				s ~= ", ";
1317 			s ~= "var " ~ ident;
1318 			if(initializers[i] !is null)
1319 				s ~= " = " ~ initializers[i].toString();
1320 		}
1321 		return s;
1322 	}
1323 
1324 
1325 	override InterpretResult interpret(PrototypeObject sc) {
1326 		var n;
1327 
1328 		foreach(i, identifier; identifiers) {
1329 			n = sc._getMember(identifier, false, false);
1330 			auto initializer = initializers[i];
1331 			if(initializer) {
1332 				n = initializer.interpret(sc).value;
1333 				sc._getMember(identifier, false, false) = n;
1334 			}
1335 		}
1336 		return InterpretResult(n, sc);
1337 	}
1338 }
1339 
1340 template CtList(T...) { alias CtList = T; }
1341 
1342 class BinaryExpression : Expression {
1343 	string op;
1344 	Expression e1;
1345 	Expression e2;
1346 
1347 	override string toString() {
1348 		return e1.toString() ~ " " ~ op ~ " " ~ e2.toString();
1349 	}
1350 
1351 	override string toInterpretedString(PrototypeObject sc) {
1352 		return e1.toInterpretedString(sc) ~ " " ~ op ~ " " ~ e2.toInterpretedString(sc);
1353 	}
1354 
1355 	this(string op, Expression e1, Expression e2) {
1356 		this.op = op;
1357 		this.e1 = e1;
1358 		this.e2 = e2;
1359 	}
1360 
1361 	override InterpretResult interpret(PrototypeObject sc) {
1362 		var left = e1.interpret(sc).value;
1363 		var right = e2.interpret(sc).value;
1364 
1365 		//writeln(left, " "~op~" ", right);
1366 
1367 		var n;
1368 		sw: switch(op) {
1369 			// I would actually kinda prefer this to be static foreach, but normal
1370 			// tuple foreach here has broaded compiler compatibility.
1371 			foreach(ctOp; CtList!("+", "-", "*", "/", "==", "!=", "<=", ">=", ">", "<", "~", "&&", "||", "&", "|", "^", "%")) //, ">>", "<<", ">>>")) // FIXME
1372 			case ctOp: {
1373 				n = mixin("left "~ctOp~" right");
1374 				break sw;
1375 			}
1376 			default:
1377 				assert(0, op);
1378 		}
1379 
1380 		return InterpretResult(n, sc);
1381 	}
1382 }
1383 
1384 class OpAssignExpression : Expression {
1385 	string op;
1386 	Expression e1;
1387 	Expression e2;
1388 
1389 	this(string op, Expression e1, Expression e2) {
1390 		this.op = op;
1391 		this.e1 = e1;
1392 		this.e2 = e2;
1393 	}
1394 
1395 	override string toString() {
1396 		return e1.toString() ~ " " ~ op ~ "= " ~ e2.toString();
1397 	}
1398 
1399 	override InterpretResult interpret(PrototypeObject sc) {
1400 
1401 		auto v = cast(VariableExpression) e1;
1402 		if(v is null)
1403 			throw new ScriptRuntimeException("not an lvalue", null, 0 /* FIXME */);
1404 
1405 		var right = e2.interpret(sc).value;
1406 
1407 		//writeln(left, " "~op~"= ", right);
1408 
1409 		var n;
1410 		foreach(ctOp; CtList!("+=", "-=", "*=", "/=", "~=", "&=", "|=", "^=", "%="))
1411 			if(ctOp[0..1] == op)
1412 				n = mixin("v.getVar(sc) "~ctOp~" right");
1413 
1414 		// FIXME: ensure the variable is updated in scope too
1415 
1416 		return InterpretResult(n, sc);
1417 
1418 	}
1419 }
1420 
1421 class PipelineExpression : Expression {
1422 	Expression e1;
1423 	Expression e2;
1424 	CallExpression ce;
1425 	ScriptLocation loc;
1426 
1427 	this(ScriptLocation loc, Expression e1, Expression e2) {
1428 		this.loc = loc;
1429 		this.e1 = e1;
1430 		this.e2 = e2;
1431 
1432 		if(auto ce = cast(CallExpression) e2) {
1433 			this.ce = new CallExpression(loc, ce.func);
1434 			this.ce.arguments = [e1] ~ ce.arguments;
1435 		} else {
1436 			this.ce = new CallExpression(loc, e2);
1437 			this.ce.arguments ~= e1;
1438 		}
1439 	}
1440 
1441 	override string toString() { return e1.toString() ~ " |> " ~ e2.toString(); }
1442 
1443 	override InterpretResult interpret(PrototypeObject sc) {
1444 		return ce.interpret(sc);
1445 	}
1446 }
1447 
1448 class AssignExpression : Expression {
1449 	Expression e1;
1450 	Expression e2;
1451 	bool suppressOverloading;
1452 
1453 	this(Expression e1, Expression e2, bool suppressOverloading = false) {
1454 		this.e1 = e1;
1455 		this.e2 = e2;
1456 		this.suppressOverloading = suppressOverloading;
1457 	}
1458 
1459 	override string toString() { return e1.toString() ~ " = " ~ e2.toString(); }
1460 
1461 	override InterpretResult interpret(PrototypeObject sc) {
1462 		auto v = cast(VariableExpression) e1;
1463 		if(v is null)
1464 			throw new ScriptRuntimeException("not an lvalue", null, 0 /* FIXME */);
1465 
1466 		auto ret = v.setVar(sc, e2.interpret(sc).value, false, suppressOverloading);
1467 
1468 		return InterpretResult(ret, sc);
1469 	}
1470 }
1471 class VariableExpression : Expression {
1472 	string identifier;
1473 	ScriptLocation loc;
1474 
1475 	this(string identifier, ScriptLocation loc = ScriptLocation.init) {
1476 		this.identifier = identifier;
1477 		this.loc = loc;
1478 	}
1479 
1480 	override string toString() {
1481 		return identifier;
1482 	}
1483 
1484 	override string toInterpretedString(PrototypeObject sc) {
1485 		return getVar(sc).get!string;
1486 	}
1487 
1488 	ref var getVar(PrototypeObject sc, bool recurse = true) {
1489 		try {
1490 			return sc._getMember(identifier, true /* FIXME: recurse?? */, true);
1491 		} catch(DynamicTypeException dte) {
1492 			dte.callStack ~= loc;
1493 			throw dte;
1494 		}
1495 	}
1496 
1497 	ref var setVar(PrototypeObject sc, var t, bool recurse = true, bool suppressOverloading = false) {
1498 		return sc._setMember(identifier, t, true /* FIXME: recurse?? */, true, suppressOverloading);
1499 	}
1500 
1501 	ref var getVarFrom(PrototypeObject sc, ref var v) {
1502 		return v[identifier];
1503 	}
1504 
1505 	override InterpretResult interpret(PrototypeObject sc) {
1506 		return InterpretResult(getVar(sc), sc);
1507 	}
1508 }
1509 
1510 class SuperExpression : Expression {
1511 	VariableExpression dot;
1512 	string origDot;
1513 	this(VariableExpression dot) {
1514 		if(dot !is null) {
1515 			origDot = dot.identifier;
1516 			//dot.identifier = "__super_" ~ dot.identifier; // omg this is so bad
1517 		}
1518 		this.dot = dot;
1519 	}
1520 
1521 	override string toString() {
1522 		if(dot is null)
1523 			return "super";
1524 		else
1525 			return "super." ~ origDot;
1526 	}
1527 
1528 	override InterpretResult interpret(PrototypeObject sc) {
1529 		var a = sc._getMember("super", true, true);
1530 		if(a._object is null)
1531 			throw new Exception("null proto for super");
1532 		PrototypeObject proto = a._object.prototype;
1533 		if(proto is null)
1534 			throw new Exception("no super");
1535 		//proto = proto.prototype;
1536 
1537 		if(dot !is null)
1538 			a = proto._getMember(dot.identifier, true, true);
1539 		else
1540 			a = proto._getMember("__ctor", true, true);
1541 		return InterpretResult(a, sc);
1542 	}
1543 }
1544 
1545 class DotVarExpression : VariableExpression {
1546 	Expression e1;
1547 	VariableExpression e2;
1548 	bool recurse = true;
1549 
1550 	this(Expression e1) {
1551 		this.e1 = e1;
1552 		super(null);
1553 	}
1554 
1555 	this(Expression e1, VariableExpression e2, bool recurse = true) {
1556 		this.e1 = e1;
1557 		this.e2 = e2;
1558 		this.recurse = recurse;
1559 		//assert(typeid(e2) == typeid(VariableExpression));
1560 		super("<do not use>");//e1.identifier ~ "." ~ e2.identifier);
1561 	}
1562 
1563 	override string toString() {
1564 		return e1.toString() ~ "." ~ e2.toString();
1565 	}
1566 
1567 	override ref var getVar(PrototypeObject sc, bool recurse = true) {
1568 		if(!this.recurse) {
1569 			// this is a special hack...
1570 			if(auto ve = cast(VariableExpression) e1) {
1571 				return ve.getVar(sc)._getOwnProperty(e2.identifier);
1572 			}
1573 			assert(0);
1574 		}
1575 
1576 		if(e2.identifier == "__source") {
1577 			auto val = e1.interpret(sc).value;
1578 			if(auto meta = cast(ScriptFunctionMetadata) val._metadata)
1579 				return *(new var(meta.convertToString()));
1580 			else
1581 				return *(new var(val.toJson()));
1582 		}
1583 
1584 		if(auto ve = cast(VariableExpression) e1) {
1585 			return this.getVarFrom(sc, ve.getVar(sc, recurse));
1586 		} else if(cast(StringLiteralExpression) e1 && e2.identifier == "interpolate") {
1587 			auto se = cast(StringLiteralExpression) e1;
1588 			var* functor = new var;
1589 			//if(!se.allowInterpolation)
1590 				//throw new ScriptRuntimeException("Cannot interpolate this string", se.token.lineNumber);
1591 			(*functor)._function = (var _this, var[] args) {
1592 				return se.interpolate(args.length ? args[0] : var(null), sc);
1593 			};
1594 			return *functor;
1595 		} else {
1596 			// make a temporary for the lhs
1597 			auto v = new var();
1598 			*v = e1.interpret(sc).value;
1599 			return this.getVarFrom(sc, *v);
1600 		}
1601 	}
1602 
1603 	override ref var setVar(PrototypeObject sc, var t, bool recurse = true, bool suppressOverloading = false) {
1604 		if(suppressOverloading)
1605 			return e1.interpret(sc).value.opIndexAssignNoOverload(t, e2.identifier);
1606 		else
1607 			return e1.interpret(sc).value.opIndexAssign(t, e2.identifier);
1608 	}
1609 
1610 
1611 	override ref var getVarFrom(PrototypeObject sc, ref var v) {
1612 		return e2.getVarFrom(sc, v);
1613 	}
1614 
1615 	override string toInterpretedString(PrototypeObject sc) {
1616 		return getVar(sc).get!string;
1617 	}
1618 }
1619 
1620 class IndexExpression : VariableExpression {
1621 	Expression e1;
1622 	Expression e2;
1623 
1624 	this(Expression e1, Expression e2) {
1625 		this.e1 = e1;
1626 		this.e2 = e2;
1627 		super(null);
1628 	}
1629 
1630 	override string toString() {
1631 		return e1.toString() ~ "[" ~ e2.toString() ~ "]";
1632 	}
1633 
1634 	override ref var getVar(PrototypeObject sc, bool recurse = true) {
1635 		if(auto ve = cast(VariableExpression) e1)
1636 			return ve.getVar(sc, recurse)[e2.interpret(sc).value];
1637 		else {
1638 			auto v = new var();
1639 			*v = e1.interpret(sc).value;
1640 			return this.getVarFrom(sc, *v);
1641 		}
1642 	}
1643 
1644 	override ref var setVar(PrototypeObject sc, var t, bool recurse = true, bool suppressOverloading = false) {
1645         	return getVar(sc,recurse) = t;
1646 	}
1647 }
1648 
1649 class SliceExpression : Expression {
1650 	// e1[e2 .. e3]
1651 	Expression e1;
1652 	Expression e2;
1653 	Expression e3;
1654 
1655 	this(Expression e1, Expression e2, Expression e3) {
1656 		this.e1 = e1;
1657 		this.e2 = e2;
1658 		this.e3 = e3;
1659 	}
1660 
1661 	override string toString() {
1662 		return e1.toString() ~ "[" ~ e2.toString() ~ " .. " ~ e3.toString() ~ "]";
1663 	}
1664 
1665 	override InterpretResult interpret(PrototypeObject sc) {
1666 		var lhs = e1.interpret(sc).value;
1667 
1668 		auto specialScope = new PrototypeObject();
1669 		specialScope.prototype = sc;
1670 		specialScope._getMember("$", false, false) = lhs.length;
1671 
1672 		return InterpretResult(lhs[e2.interpret(specialScope).value .. e3.interpret(specialScope).value], sc);
1673 	}
1674 }
1675 
1676 
1677 class LoopControlExpression : Expression {
1678 	InterpretResult.FlowControl op;
1679 	this(string op) {
1680 		if(op == "continue")
1681 			this.op = InterpretResult.FlowControl.Continue;
1682 		else if(op == "break")
1683 			this.op = InterpretResult.FlowControl.Break;
1684 		else assert(0, op);
1685 	}
1686 
1687 	override string toString() {
1688 		import std.string;
1689 		return to!string(this.op).toLower();
1690 	}
1691 
1692 	override InterpretResult interpret(PrototypeObject sc) {
1693 		return InterpretResult(var(null), sc, op);
1694 	}
1695 }
1696 
1697 
1698 class ReturnExpression : Expression {
1699 	Expression value;
1700 
1701 	this(Expression v) {
1702 		value = v;
1703 	}
1704 
1705 	override string toString() { return "return " ~ value.toString(); }
1706 
1707 	override InterpretResult interpret(PrototypeObject sc) {
1708 		return InterpretResult(value.interpret(sc).value, sc, InterpretResult.FlowControl.Return);
1709 	}
1710 }
1711 
1712 class ScopeExpression : Expression {
1713 	this(Expression[] expressions) {
1714 		this.expressions = expressions;
1715 	}
1716 
1717 	Expression[] expressions;
1718 
1719 	override string toString() {
1720 		string s;
1721 		s = "{\n";
1722 		foreach(expr; expressions) {
1723 			s ~= "\t";
1724 			s ~= expr.toString();
1725 			s ~= ";\n";
1726 		}
1727 		s ~= "}";
1728 		return s;
1729 	}
1730 
1731 	override InterpretResult interpret(PrototypeObject sc) {
1732 		var ret;
1733 
1734 		auto innerScope = new PrototypeObject();
1735 		innerScope.prototype = sc;
1736 
1737 		innerScope._getMember("__scope_exit", false, false) = var.emptyArray;
1738 		innerScope._getMember("__scope_success", false, false) = var.emptyArray;
1739 		innerScope._getMember("__scope_failure", false, false) = var.emptyArray;
1740 
1741 		scope(exit) {
1742 			foreach(func; innerScope._getMember("__scope_exit", false, true))
1743 				func();
1744 		}
1745 		scope(success) {
1746 			foreach(func; innerScope._getMember("__scope_success", false, true))
1747 				func();
1748 		}
1749 		scope(failure) {
1750 			foreach(func; innerScope._getMember("__scope_failure", false, true))
1751 				func();
1752 		}
1753 
1754 		foreach(expression; expressions) {
1755 			auto res = expression.interpret(innerScope);
1756 			ret = res.value;
1757 			if(res.flowControl != InterpretResult.FlowControl.Normal)
1758 				return InterpretResult(ret, sc, res.flowControl);
1759 		}
1760 		return InterpretResult(ret, sc);
1761 	}
1762 }
1763 
1764 class SwitchExpression : Expression {
1765 	Expression expr;
1766 	CaseExpression[] cases;
1767 	CaseExpression default_;
1768 
1769 	override InterpretResult interpret(PrototypeObject sc) {
1770 		auto e = expr.interpret(sc);
1771 
1772 		bool hitAny;
1773 		bool fallingThrough;
1774 		bool secondRun;
1775 
1776 		var last;
1777 
1778 		again:
1779 		foreach(c; cases) {
1780 			if(!secondRun && !fallingThrough && c is default_) continue;
1781 			if(fallingThrough || (secondRun && c is default_) || c.condition.interpret(sc) == e) {
1782 				fallingThrough = false;
1783 				if(!secondRun)
1784 					hitAny = true;
1785 				InterpretResult ret;
1786 				expr_loop: foreach(exp; c.expressions) {
1787 					ret = exp.interpret(sc);
1788 					with(InterpretResult.FlowControl)
1789 					final switch(ret.flowControl) {
1790 						case Normal:
1791 							last = ret.value;
1792 						break;
1793 						case Return:
1794 						case Goto:
1795 							return ret;
1796 						case Continue:
1797 							fallingThrough = true;
1798 							break expr_loop;
1799 						case Break:
1800 							return InterpretResult(last, sc);
1801 					}
1802 				}
1803 
1804 				if(!fallingThrough)
1805 					break;
1806 			}
1807 		}
1808 
1809 		if(!hitAny && !secondRun) {
1810 			secondRun = true;
1811 			goto again;
1812 		}
1813 
1814 		return InterpretResult(last, sc);
1815 	}
1816 }
1817 
1818 class CaseExpression : Expression {
1819 	this(Expression condition) {
1820 		this.condition = condition;
1821 	}
1822 	Expression condition;
1823 	Expression[] expressions;
1824 
1825 	override string toString() {
1826 		string code;
1827 		if(condition is null)
1828 			code = "default:";
1829 		else
1830 			code = "case " ~ condition.toString() ~ ":";
1831 
1832 		foreach(expr; expressions)
1833 			code ~= "\n" ~ expr.toString() ~ ";";
1834 
1835 		return code;
1836 	}
1837 
1838 	override InterpretResult interpret(PrototypeObject sc) {
1839 		// I did this inline up in the SwitchExpression above. maybe insane?!
1840 		assert(0);
1841 	}
1842 }
1843 
1844 unittest {
1845 	interpret(q{
1846 		var a = 10;
1847 		// case and break should work
1848 		var brk;
1849 
1850 		// var brk = switch doesn't parse, but this will.....
1851 		// (I kinda went everything is an expression but not all the way. this code SUX.)
1852 		brk = switch(a) {
1853 			case 10:
1854 				a = 30;
1855 			break;
1856 			case 30:
1857 				a = 40;
1858 			break;
1859 			default:
1860 				a = 0;
1861 		}
1862 
1863 		assert(a == 30);
1864 		assert(brk == 30); // value of switch set to last expression evaled inside
1865 
1866 		// so should default
1867 		switch(a) {
1868 			case 20:
1869 				a = 40;
1870 			break;
1871 			default:
1872 				a = 40;
1873 		}
1874 
1875 		assert(a == 40);
1876 
1877 		switch(a) {
1878 			case 40:
1879 				a = 50;
1880 			case 60: // no implicit fallthrough in this lang...
1881 				a = 60;
1882 		}
1883 
1884 		assert(a == 50);
1885 
1886 		var ret;
1887 
1888 		ret = switch(a) {
1889 			case 50:
1890 				a = 60;
1891 				continue; // request fallthrough. D uses "goto case", but I haven't implemented any goto yet so continue is best fit
1892 			case 90:
1893 				a = 70;
1894 		}
1895 
1896 		assert(a == 70); // the explicit `continue` requests fallthrough behavior
1897 		assert(ret == 70);
1898 	});
1899 }
1900 
1901 class ForeachExpression : Expression {
1902 	VariableDeclaration decl;
1903 	Expression subject;
1904 	Expression loopBody;
1905 
1906 	override string toString() {
1907 		return "foreach(" ~ decl.toString() ~ "; " ~ subject.toString() ~ ") " ~ loopBody.toString();
1908 	}
1909 
1910 	override InterpretResult interpret(PrototypeObject sc) {
1911 		var result;
1912 
1913 		assert(loopBody !is null);
1914 
1915 		auto loopScope = new PrototypeObject();
1916 		loopScope.prototype = sc;
1917 
1918 		InterpretResult.FlowControl flowControl;
1919 
1920 		static string doLoopBody() { return q{
1921 			if(decl.identifiers.length > 1) {
1922 				sc._getMember(decl.identifiers[0], false, false) = i;
1923 				sc._getMember(decl.identifiers[1], false, false) = item;
1924 			} else {
1925 				sc._getMember(decl.identifiers[0], false, false) = item;
1926 			}
1927 
1928 			auto res = loopBody.interpret(loopScope);
1929 			result = res.value;
1930 			flowControl = res.flowControl;
1931 			if(flowControl == InterpretResult.FlowControl.Break)
1932 				break;
1933 			if(flowControl == InterpretResult.FlowControl.Return)
1934 				break;
1935 			//if(flowControl == InterpretResult.FlowControl.Continue)
1936 				// this is fine, we still want to do the advancement
1937 		};}
1938 
1939 		var what = subject.interpret(sc).value;
1940 		foreach(i, item; what) {
1941 			mixin(doLoopBody());
1942 		}
1943 
1944 		if(flowControl != InterpretResult.FlowControl.Return)
1945 			flowControl = InterpretResult.FlowControl.Normal;
1946 
1947 		return InterpretResult(result, sc, flowControl);
1948 	}
1949 }
1950 
1951 class ForExpression : Expression {
1952 	Expression initialization;
1953 	Expression condition;
1954 	Expression advancement;
1955 	Expression loopBody;
1956 
1957 	this() {}
1958 
1959 	override InterpretResult interpret(PrototypeObject sc) {
1960 		var result;
1961 
1962 		assert(loopBody !is null);
1963 
1964 		auto loopScope = new PrototypeObject();
1965 		loopScope.prototype = sc;
1966 		if(initialization !is null)
1967 			initialization.interpret(loopScope);
1968 
1969 		InterpretResult.FlowControl flowControl;
1970 
1971 		static string doLoopBody() { return q{
1972 			auto res = loopBody.interpret(loopScope);
1973 			result = res.value;
1974 			flowControl = res.flowControl;
1975 			if(flowControl == InterpretResult.FlowControl.Break)
1976 				break;
1977 			if(flowControl == InterpretResult.FlowControl.Return)
1978 				break;
1979 			//if(flowControl == InterpretResult.FlowControl.Continue)
1980 				// this is fine, we still want to do the advancement
1981 			if(advancement)
1982 				advancement.interpret(loopScope);
1983 		};}
1984 
1985 		if(condition !is null) {
1986 			while(condition.interpret(loopScope).value) {
1987 				mixin(doLoopBody());
1988 			}
1989 		} else
1990 			while(true) {
1991 				mixin(doLoopBody());
1992 			}
1993 
1994 		if(flowControl != InterpretResult.FlowControl.Return)
1995 			flowControl = InterpretResult.FlowControl.Normal;
1996 
1997 		return InterpretResult(result, sc, flowControl);
1998 	}
1999 
2000 	override string toString() {
2001 		string code = "for(";
2002 		if(initialization !is null)
2003 			code ~= initialization.toString();
2004 		code ~= "; ";
2005 		if(condition !is null)
2006 			code ~= condition.toString();
2007 		code ~= "; ";
2008 		if(advancement !is null)
2009 			code ~= advancement.toString();
2010 		code ~= ") ";
2011 		code ~= loopBody.toString();
2012 
2013 		return code;
2014 	}
2015 }
2016 
2017 class IfExpression : Expression {
2018 	Expression condition;
2019 	Expression ifTrue;
2020 	Expression ifFalse;
2021 
2022 	this() {}
2023 
2024 	override InterpretResult interpret(PrototypeObject sc) {
2025 		InterpretResult result;
2026 		assert(condition !is null);
2027 
2028 		auto ifScope = new PrototypeObject();
2029 		ifScope.prototype = sc;
2030 
2031 		if(condition.interpret(ifScope).value) {
2032 			if(ifTrue !is null)
2033 				result = ifTrue.interpret(ifScope);
2034 		} else {
2035 			if(ifFalse !is null)
2036 				result = ifFalse.interpret(ifScope);
2037 		}
2038 		return InterpretResult(result.value, sc, result.flowControl);
2039 	}
2040 
2041 	override string toString() {
2042 		string code = "if ";
2043 		code ~= condition.toString();
2044 		code ~= " ";
2045 		if(ifTrue !is null)
2046 			code ~= ifTrue.toString();
2047 		else
2048 			code ~= " { }";
2049 		if(ifFalse !is null)
2050 			code ~= " else " ~ ifFalse.toString();
2051 		return code;
2052 	}
2053 }
2054 
2055 class TernaryExpression : Expression {
2056 	Expression condition;
2057 	Expression ifTrue;
2058 	Expression ifFalse;
2059 
2060 	this() {}
2061 
2062 	override InterpretResult interpret(PrototypeObject sc) {
2063 		InterpretResult result;
2064 		assert(condition !is null);
2065 
2066 		auto ifScope = new PrototypeObject();
2067 		ifScope.prototype = sc;
2068 
2069 		if(condition.interpret(ifScope).value) {
2070 			result = ifTrue.interpret(ifScope);
2071 		} else {
2072 			result = ifFalse.interpret(ifScope);
2073 		}
2074 		return InterpretResult(result.value, sc, result.flowControl);
2075 	}
2076 
2077 	override string toString() {
2078 		string code = "";
2079 		code ~= condition.toString();
2080 		code ~= " ? ";
2081 		code ~= ifTrue.toString();
2082 		code ~= " : ";
2083 		code ~= ifFalse.toString();
2084 		return code;
2085 	}
2086 }
2087 
2088 // this is kinda like a placement new, and currently isn't exposed inside the language,
2089 // but is used for class inheritance
2090 class ShallowCopyExpression : Expression {
2091 	Expression e1;
2092 	Expression e2;
2093 
2094 	this(Expression e1, Expression e2) {
2095 		this.e1 = e1;
2096 		this.e2 = e2;
2097 	}
2098 
2099 	override InterpretResult interpret(PrototypeObject sc) {
2100 		auto v = cast(VariableExpression) e1;
2101 		if(v is null)
2102 			throw new ScriptRuntimeException("not an lvalue", null, 0 /* FIXME */);
2103 
2104 		v.getVar(sc, false)._object.copyPropertiesFrom(e2.interpret(sc).value._object);
2105 
2106 		return InterpretResult(var(null), sc);
2107 	}
2108 
2109 }
2110 
2111 class NewExpression : Expression {
2112 	Expression what;
2113 	Expression[] args;
2114 	this(Expression w) {
2115 		what = w;
2116 	}
2117 
2118 	override InterpretResult interpret(PrototypeObject sc) {
2119 		assert(what !is null);
2120 
2121 		var[] args;
2122 		foreach(arg; this.args)
2123 			args ~= arg.interpret(sc).value;
2124 
2125 		var original = what.interpret(sc).value;
2126 		var n = original._copy_new;
2127 		if(n.payloadType() == var.Type.Object) {
2128 			var ctor = original.prototype ? original.prototype._getOwnProperty("__ctor") : var(null);
2129 			if(ctor)
2130 				ctor.apply(n, args);
2131 		}
2132 
2133 		return InterpretResult(n, sc);
2134 	}
2135 }
2136 
2137 class ThrowExpression : Expression {
2138 	Expression whatToThrow;
2139 	ScriptToken where;
2140 
2141 	this(Expression e, ScriptToken where) {
2142 		whatToThrow = e;
2143 		this.where = where;
2144 	}
2145 
2146 	override InterpretResult interpret(PrototypeObject sc) {
2147 		assert(whatToThrow !is null);
2148 		throw new ScriptException(whatToThrow.interpret(sc).value, ScriptLocation(where.scriptFilename, where.lineNumber));
2149 		assert(0);
2150 	}
2151 }
2152 
2153 class ExceptionBlockExpression : Expression {
2154 	Expression tryExpression;
2155 
2156 	string[] catchVarDecls;
2157 	Expression[] catchExpressions;
2158 
2159 	Expression[] finallyExpressions;
2160 
2161 	override InterpretResult interpret(PrototypeObject sc) {
2162 		InterpretResult result;
2163 		result.sc = sc;
2164 		assert(tryExpression !is null);
2165 		assert(catchVarDecls.length == catchExpressions.length);
2166 
2167 		if(catchExpressions.length || (catchExpressions.length == 0 && finallyExpressions.length == 0))
2168 			try {
2169 				result = tryExpression.interpret(sc);
2170 			} catch(NonScriptCatchableException e) {
2171 				// the script cannot catch these so it continues up regardless
2172 				throw e;
2173 			} catch(Exception e) {
2174 				var ex = var.emptyObject;
2175 				ex.type = typeid(e).name;
2176 				ex.msg = e.msg;
2177 				ex.file = e.file;
2178 				ex.line = e.line;
2179 
2180 				// FIXME: this only allows one but it might be nice to actually do different types at some point
2181 				if(catchExpressions.length)
2182 				foreach(i, ce; catchExpressions) {
2183 					auto catchScope = new PrototypeObject();
2184 					catchScope.prototype = sc;
2185 					catchScope._getMember(catchVarDecls[i], false, false) = ex;
2186 
2187 					result = ce.interpret(catchScope);
2188 				} else
2189 					result = InterpretResult(ex, sc);
2190 			} finally {
2191 				foreach(fe; finallyExpressions)
2192 					result = fe.interpret(sc);
2193 			}
2194 		else
2195 			try {
2196 				result = tryExpression.interpret(sc);
2197 			} finally {
2198 				foreach(fe; finallyExpressions)
2199 					result = fe.interpret(sc);
2200 			}
2201 
2202 		return result;
2203 	}
2204 }
2205 
2206 class ParentheticalExpression : Expression {
2207 	Expression inside;
2208 	this(Expression inside) {
2209 		this.inside = inside;
2210 	}
2211 
2212 	override string toString() {
2213 		return "(" ~ inside.toString() ~ ")";
2214 	}
2215 
2216 	override InterpretResult interpret(PrototypeObject sc) {
2217 		return InterpretResult(inside.interpret(sc).value, sc);
2218 	}
2219 }
2220 
2221 class AssertKeyword : Expression {
2222 	ScriptToken token;
2223 	this(ScriptToken token) {
2224 		this.token = token;
2225 	}
2226 	override string toString() {
2227 		return "assert";
2228 	}
2229 
2230 	override InterpretResult interpret(PrototypeObject sc) {
2231 		if(AssertKeywordObject is null)
2232 			AssertKeywordObject = new PrototypeObject();
2233 		var dummy;
2234 		dummy._object = AssertKeywordObject;
2235 		return InterpretResult(dummy, sc);
2236 	}
2237 }
2238 
2239 PrototypeObject AssertKeywordObject;
2240 PrototypeObject DefaultArgumentDummyObject;
2241 
2242 class CallExpression : Expression {
2243 	Expression func;
2244 	Expression[] arguments;
2245 	ScriptLocation loc;
2246 
2247 	override string toString() {
2248 		string s = func.toString() ~ "(";
2249 		foreach(i, arg; arguments) {
2250 			if(i) s ~= ", ";
2251 			s ~= arg.toString();
2252 		}
2253 
2254 		s ~= ")";
2255 		return s;
2256 	}
2257 
2258 	this(ScriptLocation loc, Expression func) {
2259 		this.loc = loc;
2260 		this.func = func;
2261 	}
2262 
2263 	override string toInterpretedString(PrototypeObject sc) {
2264 		return interpret(sc).value.get!string;
2265 	}
2266 
2267 	override InterpretResult interpret(PrototypeObject sc) {
2268 		if(auto asrt = cast(AssertKeyword) func) {
2269 			auto assertExpression = arguments[0];
2270 			Expression assertString;
2271 			if(arguments.length > 1)
2272 				assertString = arguments[1];
2273 
2274 			var v = assertExpression.interpret(sc).value;
2275 
2276 			if(!v)
2277 				throw new ScriptException(
2278 					var(this.toString() ~ " failed, got: " ~ assertExpression.toInterpretedString(sc)),
2279 					ScriptLocation(asrt.token.scriptFilename, asrt.token.lineNumber));
2280 
2281 			return InterpretResult(v, sc);
2282 		}
2283 
2284 		auto f = func.interpret(sc).value;
2285 		bool isMacro =  (f.payloadType == var.Type.Object && ((cast(MacroPrototype) f._payload._object) !is null));
2286 		var[] args;
2287 		foreach(argument; arguments)
2288 			if(argument !is null) {
2289 				if(isMacro) // macro, pass the argument as an expression object
2290 					args ~= argument.toScriptExpressionObject(sc);
2291 				else // regular function, interpret the arguments
2292 					args ~= argument.interpret(sc).value;
2293 			} else {
2294 				if(DefaultArgumentDummyObject is null)
2295 					DefaultArgumentDummyObject = new PrototypeObject();
2296 
2297 				var dummy;
2298 				dummy._object = DefaultArgumentDummyObject;
2299 
2300 				args ~= dummy;
2301 			}
2302 
2303 		var _this;
2304 		if(auto dve = cast(DotVarExpression) func) {
2305 			_this = dve.e1.interpret(sc).value;
2306 		} else if(auto ide = cast(IndexExpression) func) {
2307 			_this = ide.interpret(sc).value;
2308 		} else if(auto se = cast(SuperExpression) func) {
2309 			// super things are passed this object despite looking things up on the prototype
2310 			// so it calls the correct instance
2311 			_this = sc._getMember("this", true, true);
2312 		}
2313 
2314 		try {
2315 			return InterpretResult(f.apply(_this, args), sc);
2316 		} catch(DynamicTypeException dte) {
2317 			dte.callStack ~= loc;
2318 			throw dte;
2319 		} catch(ScriptException se) {
2320 			se.callStack ~= loc;
2321 			throw se;
2322 		}
2323 	}
2324 }
2325 
2326 ScriptToken requireNextToken(MyTokenStreamHere)(ref MyTokenStreamHere tokens, ScriptToken.Type type, string str = null, string file = __FILE__, size_t line = __LINE__) {
2327 	if(tokens.empty)
2328 		throw new ScriptCompileException("script ended prematurely", null, 0, file, line);
2329 	auto next = tokens.front;
2330 	if(next.type != type || (str !is null && next.str != str))
2331 		throw new ScriptCompileException("unexpected '"~next.str~"' while expecting " ~ to!string(type) ~ " " ~ str, next.scriptFilename, next.lineNumber, file, line);
2332 
2333 	tokens.popFront();
2334 	return next;
2335 }
2336 
2337 bool peekNextToken(MyTokenStreamHere)(MyTokenStreamHere tokens, ScriptToken.Type type, string str = null, string file = __FILE__, size_t line = __LINE__) {
2338 	if(tokens.empty)
2339 		return false;
2340 	auto next = tokens.front;
2341 	if(next.type != type || (str !is null && next.str != str))
2342 		return false;
2343 	return true;
2344 }
2345 
2346 VariableExpression parseVariableName(MyTokenStreamHere)(ref MyTokenStreamHere tokens) {
2347 	assert(!tokens.empty);
2348 	auto token = tokens.front;
2349 	if(token.type == ScriptToken.Type.identifier) {
2350 		tokens.popFront();
2351 		return new VariableExpression(token.str, ScriptLocation(token.scriptFilename, token.lineNumber));
2352 	}
2353 	throw new ScriptCompileException("Found "~token.str~" when expecting identifier", token.scriptFilename, token.lineNumber);
2354 }
2355 
2356 Expression parsePart(MyTokenStreamHere)(ref MyTokenStreamHere tokens) {
2357 	if(!tokens.empty) {
2358 		auto token = tokens.front;
2359 
2360 		Expression e;
2361 
2362 		if(token.str == "super") {
2363 			tokens.popFront();
2364 			VariableExpression dot;
2365 			if(!tokens.empty && tokens.front.str == ".") {
2366 				tokens.popFront();
2367 				dot = parseVariableName(tokens);
2368 			}
2369 			e = new SuperExpression(dot);
2370 		}
2371 		else if(token.type == ScriptToken.Type.identifier)
2372 			e = parseVariableName(tokens);
2373 		else if(token.type == ScriptToken.Type.symbol && (token.str == "-" || token.str == "+" || token.str == "!" || token.str == "~")) {
2374 			auto op = token.str;
2375 			tokens.popFront();
2376 
2377 			e = parsePart(tokens);
2378 			if(op == "-")
2379 				e = new NegationExpression(e);
2380 			else if(op == "!")
2381 				e = new NotExpression(e);
2382 			else if(op == "~")
2383 				e = new BitFlipExpression(e);
2384 		} else {
2385 			tokens.popFront();
2386 
2387 			if(token.type == ScriptToken.Type.int_number)
2388 				e = new IntLiteralExpression(token.str);
2389 			else if(token.type == ScriptToken.Type.float_number)
2390 				e = new FloatLiteralExpression(token.str);
2391 			else if(token.type == ScriptToken.Type..string)
2392 				e = new StringLiteralExpression(token);
2393 			else if(token.type == ScriptToken.Type.symbol || token.type == ScriptToken.Type.keyword) {
2394 				switch(token.str) {
2395 					case "true":
2396 					case "false":
2397 						e = new BoolLiteralExpression(token.str);
2398 					break;
2399 					case "new":
2400 						// FIXME: why is this needed here? maybe it should be here instead of parseExpression
2401 						tokens.pushFront(token);
2402 						return parseExpression(tokens);
2403 					case "(":
2404 						//tokens.popFront();
2405 						auto parenthetical = new ParentheticalExpression(parseExpression(tokens));
2406 						tokens.requireNextToken(ScriptToken.Type.symbol, ")");
2407 						return parenthetical;
2408 					case "[":
2409 						// array literal
2410 						auto arr = new ArrayLiteralExpression();
2411 
2412 						bool first = true;
2413 						moreElements:
2414 						if(tokens.empty)
2415 							throw new ScriptCompileException("unexpected end of file when reading array literal", token.scriptFilename, token.lineNumber);
2416 
2417 						auto peek = tokens.front;
2418 						if(peek.type == ScriptToken.Type.symbol && peek.str == "]") {
2419 							tokens.popFront();
2420 							return arr;
2421 						}
2422 
2423 						if(!first)
2424 							tokens.requireNextToken(ScriptToken.Type.symbol, ",");
2425 						else
2426 							first = false;
2427 
2428 						arr.elements ~= parseExpression(tokens);
2429 
2430 						goto moreElements;
2431 					case "json!q{":
2432 					case "#{":
2433 						// json object literal
2434 						auto obj = new ObjectLiteralExpression();
2435 						/*
2436 							these go
2437 
2438 							string or ident which is the key
2439 							then a colon
2440 							then an expression which is the value
2441 
2442 							then optionally a comma
2443 
2444 							then either } which finishes it, or another key
2445 						*/
2446 
2447 						if(tokens.empty)
2448 							throw new ScriptCompileException("unexpected end of file when reading object literal", token.scriptFilename, token.lineNumber);
2449 
2450 						moreKeys:
2451 						auto key = tokens.front;
2452 						tokens.popFront();
2453 						if(key.type == ScriptToken.Type.symbol && key.str == "}") {
2454 							// all done!
2455 							e = obj;
2456 							break;
2457 						}
2458 						if(key.type != ScriptToken.Type..string && key.type != ScriptToken.Type.identifier) {
2459 							throw new ScriptCompileException("unexpected '"~key.str~"' when reading object literal", key.scriptFilename, key.lineNumber);
2460 
2461 						}
2462 
2463 						tokens.requireNextToken(ScriptToken.Type.symbol, ":");
2464 
2465 						auto value = parseExpression(tokens);
2466 						if(tokens.empty)
2467 							throw new ScriptCompileException("unclosed object literal", key.scriptFilename, key.lineNumber);
2468 
2469 						if(tokens.peekNextToken(ScriptToken.Type.symbol, ","))
2470 							tokens.popFront();
2471 
2472 						obj.elements[key.str] = value;
2473 
2474 						goto moreKeys;
2475 					case "macro":
2476 					case "function":
2477 						tokens.requireNextToken(ScriptToken.Type.symbol, "(");
2478 
2479 						auto exp = new FunctionLiteralExpression();
2480 						if(!tokens.peekNextToken(ScriptToken.Type.symbol, ")"))
2481 							exp.arguments = parseVariableDeclaration(tokens, ")");
2482 
2483 						tokens.requireNextToken(ScriptToken.Type.symbol, ")");
2484 
2485 						exp.functionBody = parseExpression(tokens);
2486 						exp.isMacro = token.str == "macro";
2487 
2488 						e = exp;
2489 					break;
2490 					case "null":
2491 						e = new NullLiteralExpression();
2492 					break;
2493 					case "mixin":
2494 					case "eval":
2495 						tokens.requireNextToken(ScriptToken.Type.symbol, "(");
2496 						e = new MixinExpression(parseExpression(tokens));
2497 						tokens.requireNextToken(ScriptToken.Type.symbol, ")");
2498 					break;
2499 					default:
2500 						goto unknown;
2501 				}
2502 			} else {
2503 				unknown:
2504 				throw new ScriptCompileException("unexpected '"~token.str~"' when reading ident", token.scriptFilename, token.lineNumber);
2505 			}
2506 		}
2507 
2508 		funcLoop: while(!tokens.empty) {
2509 			auto peek = tokens.front;
2510 			if(peek.type == ScriptToken.Type.symbol) {
2511 				switch(peek.str) {
2512 					case "(":
2513 						e = parseFunctionCall(tokens, e);
2514 					break;
2515 					case "[":
2516 						tokens.popFront();
2517 						auto e1 = parseExpression(tokens);
2518 						if(tokens.peekNextToken(ScriptToken.Type.symbol, "..")) {
2519 							tokens.popFront();
2520 							e = new SliceExpression(e, e1, parseExpression(tokens));
2521 						} else {
2522 							e = new IndexExpression(e, e1);
2523 						}
2524 						tokens.requireNextToken(ScriptToken.Type.symbol, "]");
2525 					break;
2526 					case ".":
2527 						tokens.popFront();
2528 						e = new DotVarExpression(e, parseVariableName(tokens));
2529 					break;
2530 					default:
2531 						return e; // we don't know, punt it elsewhere
2532 				}
2533 			} else return e; // again, we don't know, so just punt it down the line
2534 		}
2535 		return e;
2536 	}
2537 
2538 	throw new ScriptCompileException("Ran out of tokens when trying to parsePart", null, 0);
2539 }
2540 
2541 Expression parseArguments(MyTokenStreamHere)(ref MyTokenStreamHere tokens, Expression exp, ref Expression[] where) {
2542 	// arguments.
2543 	auto peek = tokens.front;
2544 	if(peek.type == ScriptToken.Type.symbol && peek.str == ")") {
2545 		tokens.popFront();
2546 		return exp;
2547 	}
2548 
2549 	moreArguments:
2550 
2551 	if(tokens.peekNextToken(ScriptToken.Type.keyword, "default")) {
2552 		tokens.popFront();
2553 		where ~= null;
2554 	} else {
2555 		where ~= parseExpression(tokens);
2556 	}
2557 
2558 	if(tokens.empty)
2559 		throw new ScriptCompileException("unexpected end of file when parsing call expression", peek.scriptFilename, peek.lineNumber);
2560 	peek = tokens.front;
2561 	if(peek.type == ScriptToken.Type.symbol && peek.str == ",") {
2562 		tokens.popFront();
2563 		goto moreArguments;
2564 	} else if(peek.type == ScriptToken.Type.symbol && peek.str == ")") {
2565 		tokens.popFront();
2566 		return exp;
2567 	} else
2568 		throw new ScriptCompileException("unexpected '"~peek.str~"' when reading argument list", peek.scriptFilename, peek.lineNumber);
2569 
2570 }
2571 
2572 Expression parseFunctionCall(MyTokenStreamHere)(ref MyTokenStreamHere tokens, Expression e) {
2573 	assert(!tokens.empty);
2574 	auto peek = tokens.front;
2575 	auto exp = new CallExpression(ScriptLocation(peek.scriptFilename, peek.lineNumber), e);
2576 	tokens.popFront();
2577 	if(tokens.empty)
2578 		throw new ScriptCompileException("unexpected end of file when parsing call expression", peek.scriptFilename, peek.lineNumber);
2579 	return parseArguments(tokens, exp, exp.arguments);
2580 }
2581 
2582 Expression parseFactor(MyTokenStreamHere)(ref MyTokenStreamHere tokens) {
2583 	auto e1 = parsePart(tokens);
2584 	loop: while(!tokens.empty) {
2585 		auto peek = tokens.front;
2586 
2587 		if(peek.type == ScriptToken.Type.symbol) {
2588 			switch(peek.str) {
2589 				case "*":
2590 				case "/":
2591 				case "%":
2592 					tokens.popFront();
2593 					e1 = new BinaryExpression(peek.str, e1, parsePart(tokens));
2594 				break;
2595 				default:
2596 					break loop;
2597 			}
2598 		} else throw new Exception("Got " ~ peek.str ~ " when expecting symbol");
2599 	}
2600 
2601 	return e1;
2602 }
2603 
2604 Expression parseAddend(MyTokenStreamHere)(ref MyTokenStreamHere tokens) {
2605 	auto e1 = parseFactor(tokens);
2606 	loop: while(!tokens.empty) {
2607 		auto peek = tokens.front;
2608 
2609 		if(peek.type == ScriptToken.Type.symbol) {
2610 			switch(peek.str) {
2611 				case "..": // possible FIXME
2612 				case ")": // possible FIXME
2613 				case "]": // possible FIXME
2614 				case "}": // possible FIXME
2615 				case ",": // possible FIXME these are passed on to the next thing
2616 				case ";":
2617 				case ":": // idk
2618 					return e1;
2619 
2620 				case "|>":
2621 					tokens.popFront();
2622 					e1 = new PipelineExpression(ScriptLocation(peek.scriptFilename, peek.lineNumber), e1, parseFactor(tokens));
2623 				break;
2624 				case ".":
2625 					tokens.popFront();
2626 					e1 = new DotVarExpression(e1, parseVariableName(tokens));
2627 				break;
2628 				case "=":
2629 					tokens.popFront();
2630 					return new AssignExpression(e1, parseExpression(tokens));
2631 				case "&&": // thanks to mzfhhhh for fix
2632 				case "||":
2633 					tokens.popFront();
2634 					e1 = new BinaryExpression(peek.str, e1, parseExpression(tokens));
2635 					break;
2636 				case "?": // is this the right precedence?
2637 					auto e = new TernaryExpression();
2638 					e.condition = e1;
2639 					tokens.requireNextToken(ScriptToken.Type.symbol, "?");
2640 					e.ifTrue = parseExpression(tokens);
2641 					tokens.requireNextToken(ScriptToken.Type.symbol, ":");
2642 					e.ifFalse = parseExpression(tokens);
2643 					e1 = e;
2644 					break;
2645 				case "~":
2646 					// FIXME: make sure this has the right associativity
2647 
2648 				case "&":
2649 				case "|":
2650 				case "^":
2651 
2652 				case "&=":
2653 				case "|=":
2654 				case "^=":
2655 
2656 				case "+":
2657 				case "-":
2658 
2659 				case "==":
2660 				case "!=":
2661 				case "<=":
2662 				case ">=":
2663 				case "<":
2664 				case ">":
2665 					tokens.popFront();
2666 					e1 = new BinaryExpression(peek.str, e1, parseAddend(tokens));
2667 					break;
2668 				case "+=":
2669 				case "-=":
2670 				case "*=":
2671 				case "/=":
2672 				case "~=":
2673 				case "%=":
2674 					tokens.popFront();
2675 					return new OpAssignExpression(peek.str[0..1], e1, parseExpression(tokens));
2676 				default:
2677 					throw new ScriptCompileException("Parse error, unexpected " ~ peek.str ~ " when looking for operator", peek.scriptFilename, peek.lineNumber);
2678 			}
2679 		//} else if(peek.type == ScriptToken.Type.identifier || peek.type == ScriptToken.Type.number) {
2680 			//return parseFactor(tokens);
2681 		} else
2682 			throw new ScriptCompileException("Parse error, unexpected '" ~ peek.str ~ "'", peek.scriptFilename, peek.lineNumber);
2683 	}
2684 
2685 	return e1;
2686 }
2687 
2688 Expression parseExpression(MyTokenStreamHere)(ref MyTokenStreamHere tokens, bool consumeEnd = false) {
2689 	Expression ret;
2690 	ScriptToken first;
2691 	string expectedEnd = ";";
2692 	//auto e1 = parseFactor(tokens);
2693 
2694 		while(tokens.peekNextToken(ScriptToken.Type.symbol, ";")) {
2695 			tokens.popFront();
2696 		}
2697 	if(!tokens.empty) {
2698 		first = tokens.front;
2699 		if(tokens.peekNextToken(ScriptToken.Type.symbol, "{")) {
2700 			auto start = tokens.front;
2701 			tokens.popFront();
2702 			auto e = parseCompoundStatement(tokens, start.lineNumber, "}").array;
2703 			ret = new ScopeExpression(e);
2704 			expectedEnd = null; // {} don't need ; at the end
2705 		} else if(tokens.peekNextToken(ScriptToken.Type.keyword, "scope")) {
2706 			auto start = tokens.front;
2707 			tokens.popFront();
2708 			tokens.requireNextToken(ScriptToken.Type.symbol, "(");
2709 
2710 			auto ident = tokens.requireNextToken(ScriptToken.Type.identifier);
2711 			switch(ident.str) {
2712 				case "success":
2713 				case "failure":
2714 				case "exit":
2715 				break;
2716 				default:
2717 					throw new ScriptCompileException("unexpected " ~ ident.str ~ ". valid scope(idents) are success, failure, and exit", ident.scriptFilename, ident.lineNumber);
2718 			}
2719 
2720 			tokens.requireNextToken(ScriptToken.Type.symbol, ")");
2721 
2722 			string i = "__scope_" ~ ident.str;
2723 			auto literal = new FunctionLiteralExpression();
2724 			literal.functionBody = parseExpression(tokens);
2725 
2726 			auto e = new OpAssignExpression("~", new VariableExpression(i), literal);
2727 			ret = e;
2728 		} else if(tokens.peekNextToken(ScriptToken.Type.symbol, "(")) {
2729 			auto start = tokens.front;
2730 			tokens.popFront();
2731 			auto parenthetical = new ParentheticalExpression(parseExpression(tokens));
2732 			tokens.requireNextToken(ScriptToken.Type.symbol, ")");
2733 			if(tokens.peekNextToken(ScriptToken.Type.symbol, "(")) {
2734 				// we have a function call, e.g. (test)()
2735 				ret = parseFunctionCall(tokens, parenthetical);
2736 			} else
2737 				ret = parenthetical;
2738 		} else if(tokens.peekNextToken(ScriptToken.Type.keyword, "new")) {
2739 			auto start = tokens.front;
2740 			tokens.popFront();
2741 
2742 			auto expr = parseVariableName(tokens);
2743 			auto ne = new NewExpression(expr);
2744 			if(tokens.peekNextToken(ScriptToken.Type.symbol, "(")) {
2745 				tokens.popFront();
2746 				parseArguments(tokens, ne, ne.args);
2747 			}
2748 
2749 			ret = ne;
2750 		} else if(tokens.peekNextToken(ScriptToken.Type.keyword, "class")) {
2751 			auto start = tokens.front;
2752 			tokens.popFront();
2753 
2754 			Expression[] expressions;
2755 
2756 			// the way classes work is they are actually object literals with a different syntax. new foo then just copies it
2757 			/*
2758 				we create a prototype object
2759 				we create an object, with that prototype
2760 
2761 				set all functions and static stuff to the prototype
2762 				the rest goes to the object
2763 
2764 				the expression returns the object we made
2765 			*/
2766 
2767 			auto vars = new VariableDeclaration();
2768 			vars.identifiers = ["__proto", "__obj"];
2769 
2770 			auto staticScopeBacking = new PrototypeObject();
2771 			auto instanceScopeBacking = new PrototypeObject();
2772 
2773 			vars.initializers = [new ObjectLiteralExpression(staticScopeBacking), new ObjectLiteralExpression(instanceScopeBacking)];
2774 			expressions ~= vars;
2775 
2776 			 // FIXME: operators need to have their this be bound somehow since it isn't passed
2777 			 // OR the op rewrite could pass this
2778 
2779 			expressions ~= new AssignExpression(
2780 				new DotVarExpression(new VariableExpression("__obj"), new VariableExpression("prototype")),
2781 				new VariableExpression("__proto"));
2782 
2783 			auto classIdent = tokens.requireNextToken(ScriptToken.Type.identifier);
2784 
2785 			expressions ~= new AssignExpression(
2786 				new DotVarExpression(new VariableExpression("__proto"), new VariableExpression("__classname")),
2787 				new StringLiteralExpression(classIdent.str));
2788 
2789 			if(tokens.peekNextToken(ScriptToken.Type.symbol, ":")) {
2790 				tokens.popFront();
2791 				auto inheritFrom = tokens.requireNextToken(ScriptToken.Type.identifier);
2792 
2793 				// we set our prototype to the Foo prototype, thereby inheriting any static data that way (includes functions)
2794 				// the inheritFrom object itself carries instance  data that we need to copy onto our instance
2795 				expressions ~= new AssignExpression(
2796 					new DotVarExpression(new VariableExpression("__proto"), new VariableExpression("prototype")),
2797 					new DotVarExpression(new VariableExpression(inheritFrom.str), new VariableExpression("prototype")));
2798 
2799 				expressions ~= new AssignExpression(
2800 					new DotVarExpression(new VariableExpression("__proto"), new VariableExpression("super")),
2801 					new VariableExpression(inheritFrom.str)
2802 				);
2803 
2804 				// and copying the instance initializer from the parent
2805 				expressions ~= new ShallowCopyExpression(new VariableExpression("__obj"), new VariableExpression(inheritFrom.str));
2806 			}
2807 
2808 			tokens.requireNextToken(ScriptToken.Type.symbol, "{");
2809 
2810 			void addVarDecl(VariableDeclaration decl, string o) {
2811 				foreach(i, ident; decl.identifiers) {
2812 					// FIXME: make sure this goes on the instance, never the prototype!
2813 					expressions ~= new AssignExpression(
2814 						new DotVarExpression(
2815 							new VariableExpression(o),
2816 							new VariableExpression(ident),
2817 							false),
2818 						decl.initializers[i],
2819 						true // no overloading because otherwise an early opIndexAssign can mess up the decls
2820 					);
2821 				}
2822 			}
2823 
2824 			// FIXME: we could actually add private vars and just put them in this scope. maybe
2825 
2826 			while(!tokens.peekNextToken(ScriptToken.Type.symbol, "}")) {
2827 				if(tokens.peekNextToken(ScriptToken.Type.symbol, ";")) {
2828 					tokens.popFront();
2829 					continue;
2830 				}
2831 
2832 				if(tokens.peekNextToken(ScriptToken.Type.identifier, "this")) {
2833 					// ctor
2834 					tokens.popFront();
2835 					tokens.requireNextToken(ScriptToken.Type.symbol, "(");
2836 					auto args = parseVariableDeclaration(tokens, ")");
2837 					tokens.requireNextToken(ScriptToken.Type.symbol, ")");
2838 					auto bod = parseExpression(tokens);
2839 
2840 					expressions ~= new AssignExpression(
2841 						new DotVarExpression(
2842 							new VariableExpression("__proto"),
2843 							new VariableExpression("__ctor")),
2844 						new FunctionLiteralExpression(args, bod, staticScopeBacking));
2845 				} else if(tokens.peekNextToken(ScriptToken.Type.keyword, "var")) {
2846 					// instance variable
2847 					auto decl = parseVariableDeclaration(tokens, ";");
2848 					addVarDecl(decl, "__obj");
2849 				} else if(tokens.peekNextToken(ScriptToken.Type.keyword, "static")) {
2850 					// prototype var
2851 					tokens.popFront();
2852 					auto decl = parseVariableDeclaration(tokens, ";");
2853 					addVarDecl(decl, "__proto");
2854 				} else if(tokens.peekNextToken(ScriptToken.Type.keyword, "function")) {
2855 					// prototype function
2856 					tokens.popFront();
2857 					auto ident = tokens.requireNextToken(ScriptToken.Type.identifier);
2858 
2859 					tokens.requireNextToken(ScriptToken.Type.symbol, "(");
2860 					VariableDeclaration args;
2861 					if(!tokens.peekNextToken(ScriptToken.Type.symbol, ")"))
2862 						args = parseVariableDeclaration(tokens, ")");
2863 					tokens.requireNextToken(ScriptToken.Type.symbol, ")");
2864 					auto bod = parseExpression(tokens);
2865 
2866 					expressions ~= new AssignExpression(
2867 						new DotVarExpression(
2868 							new VariableExpression("__proto"),
2869 							new VariableExpression(ident.str),
2870 							false),
2871 						new FunctionLiteralExpression(args, bod, staticScopeBacking));
2872 				} else throw new ScriptCompileException("Unexpected " ~ tokens.front.str ~ " when reading class decl", tokens.front.scriptFilename, tokens.front.lineNumber);
2873 			}
2874 
2875 			tokens.requireNextToken(ScriptToken.Type.symbol, "}");
2876 
2877 			// returning he object from the scope...
2878 			expressions ~= new VariableExpression("__obj");
2879 
2880 			auto scopeExpr = new ScopeExpression(expressions);
2881 			auto classVarExpr = new VariableDeclaration();
2882 			classVarExpr.identifiers = [classIdent.str];
2883 			classVarExpr.initializers = [scopeExpr];
2884 
2885 			ret = classVarExpr;
2886 		} else if(tokens.peekNextToken(ScriptToken.Type.keyword, "if")) {
2887 			tokens.popFront();
2888 			auto e = new IfExpression();
2889 			e.condition = parseExpression(tokens);
2890 			e.ifTrue = parseExpression(tokens);
2891 			if(tokens.peekNextToken(ScriptToken.Type.symbol, ";")) {
2892 				tokens.popFront();
2893 			}
2894 			if(tokens.peekNextToken(ScriptToken.Type.keyword, "else")) {
2895 				tokens.popFront();
2896 				e.ifFalse = parseExpression(tokens);
2897 			}
2898 			ret = e;
2899 		} else if(tokens.peekNextToken(ScriptToken.Type.keyword, "switch")) {
2900 			tokens.popFront();
2901 			auto e = new SwitchExpression();
2902 			tokens.requireNextToken(ScriptToken.Type.symbol, "(");
2903 			e.expr = parseExpression(tokens);
2904 			tokens.requireNextToken(ScriptToken.Type.symbol, ")");
2905 
2906 			tokens.requireNextToken(ScriptToken.Type.symbol, "{");
2907 
2908 			while(!tokens.peekNextToken(ScriptToken.Type.symbol, "}")) {
2909 
2910 				if(tokens.peekNextToken(ScriptToken.Type.keyword, "case")) {
2911 					auto start = tokens.front;
2912 					tokens.popFront();
2913 					auto c = new CaseExpression(parseExpression(tokens));
2914 					e.cases ~= c;
2915 					tokens.requireNextToken(ScriptToken.Type.symbol, ":");
2916 
2917 					while(!tokens.peekNextToken(ScriptToken.Type.keyword, "default") && !tokens.peekNextToken(ScriptToken.Type.keyword, "case") && !tokens.peekNextToken(ScriptToken.Type.symbol, "}")) {
2918 						c.expressions ~= parseStatement(tokens);
2919 						while(tokens.peekNextToken(ScriptToken.Type.symbol, ";"))
2920 							tokens.popFront();
2921 					}
2922 				} else if(tokens.peekNextToken(ScriptToken.Type.keyword, "default")) {
2923 					tokens.popFront();
2924 					tokens.requireNextToken(ScriptToken.Type.symbol, ":");
2925 
2926 					auto c = new CaseExpression(null);
2927 
2928 					while(!tokens.peekNextToken(ScriptToken.Type.keyword, "case") && !tokens.peekNextToken(ScriptToken.Type.symbol, "}")) {
2929 						c.expressions ~= parseStatement(tokens);
2930 						while(tokens.peekNextToken(ScriptToken.Type.symbol, ";"))
2931 							tokens.popFront();
2932 					}
2933 
2934 					e.cases ~= c;
2935 					e.default_ = c;
2936 				} else throw new ScriptCompileException("A switch statement must consists of cases and a default, nothing else ", tokens.front.scriptFilename, tokens.front.lineNumber);
2937 			}
2938 
2939 			tokens.requireNextToken(ScriptToken.Type.symbol, "}");
2940 			expectedEnd = "";
2941 
2942 			ret = e;
2943 
2944 		} else if(tokens.peekNextToken(ScriptToken.Type.keyword, "foreach")) {
2945 			tokens.popFront();
2946 			auto e = new ForeachExpression();
2947 			tokens.requireNextToken(ScriptToken.Type.symbol, "(");
2948 			e.decl = parseVariableDeclaration(tokens, ";");
2949 			tokens.requireNextToken(ScriptToken.Type.symbol, ";");
2950 			e.subject = parseExpression(tokens);
2951 			tokens.requireNextToken(ScriptToken.Type.symbol, ")");
2952 			e.loopBody = parseExpression(tokens);
2953 			ret = e;
2954 
2955 			expectedEnd = "";
2956 		} else if(tokens.peekNextToken(ScriptToken.Type.keyword, "cast")) {
2957 			tokens.popFront();
2958 			auto e = new CastExpression();
2959 
2960 			tokens.requireNextToken(ScriptToken.Type.symbol, "(");
2961 			e.type = tokens.requireNextToken(ScriptToken.Type.identifier).str;
2962 			if(tokens.peekNextToken(ScriptToken.Type.symbol, "[")) {
2963 				e.type ~= "[]";
2964 				tokens.popFront();
2965 				tokens.requireNextToken(ScriptToken.Type.symbol, "]");
2966 			}
2967 			tokens.requireNextToken(ScriptToken.Type.symbol, ")");
2968 
2969 			e.e1 = parseExpression(tokens);
2970 			ret = e;
2971 		} else if(tokens.peekNextToken(ScriptToken.Type.keyword, "for")) {
2972 			tokens.popFront();
2973 			auto e = new ForExpression();
2974 			tokens.requireNextToken(ScriptToken.Type.symbol, "(");
2975 			e.initialization = parseStatement(tokens, ";");
2976 
2977 			tokens.requireNextToken(ScriptToken.Type.symbol, ";");
2978 
2979 			e.condition = parseExpression(tokens);
2980 			tokens.requireNextToken(ScriptToken.Type.symbol, ";");
2981 			e.advancement = parseExpression(tokens);
2982 			tokens.requireNextToken(ScriptToken.Type.symbol, ")");
2983 			e.loopBody = parseExpression(tokens);
2984 
2985 			ret = e;
2986 
2987 			expectedEnd = "";
2988 		} else if(tokens.peekNextToken(ScriptToken.Type.keyword, "while")) {
2989 			tokens.popFront();
2990 			auto e = new ForExpression();
2991 			e.condition = parseExpression(tokens);
2992 			e.loopBody = parseExpression(tokens);
2993 			ret = e;
2994 			expectedEnd = "";
2995 		} else if(tokens.peekNextToken(ScriptToken.Type.keyword, "break") || tokens.peekNextToken(ScriptToken.Type.keyword, "continue")) {
2996 			auto token = tokens.front;
2997 			tokens.popFront();
2998 			ret = new LoopControlExpression(token.str);
2999 		} else if(tokens.peekNextToken(ScriptToken.Type.keyword, "return")) {
3000 			tokens.popFront();
3001 			Expression retVal;
3002 			if(tokens.peekNextToken(ScriptToken.Type.symbol, ";"))
3003 				retVal = new NullLiteralExpression();
3004 			else
3005 				retVal = parseExpression(tokens);
3006 			ret = new ReturnExpression(retVal);
3007 		} else if(tokens.peekNextToken(ScriptToken.Type.keyword, "throw")) {
3008 			auto token = tokens.front;
3009 			tokens.popFront();
3010 			ret = new ThrowExpression(parseExpression(tokens), token);
3011 		} else if(tokens.peekNextToken(ScriptToken.Type.keyword, "try")) {
3012 			auto tryToken = tokens.front;
3013 			auto e = new ExceptionBlockExpression();
3014 			tokens.popFront();
3015 			e.tryExpression = parseExpression(tokens, true);
3016 
3017 			bool hadSomething = false;
3018 			while(tokens.peekNextToken(ScriptToken.Type.keyword, "catch")) {
3019 				if(hadSomething)
3020 					throw new ScriptCompileException("Only one catch block is allowed currently ", tokens.front.scriptFilename, tokens.front.lineNumber);
3021 				hadSomething = true;
3022 				tokens.popFront();
3023 				tokens.requireNextToken(ScriptToken.Type.symbol, "(");
3024 				if(tokens.peekNextToken(ScriptToken.Type.keyword, "var"))
3025 					tokens.popFront();
3026 				auto ident = tokens.requireNextToken(ScriptToken.Type.identifier);
3027 				e.catchVarDecls ~= ident.str;
3028 				tokens.requireNextToken(ScriptToken.Type.symbol, ")");
3029 				e.catchExpressions ~= parseExpression(tokens);
3030 			}
3031 			while(tokens.peekNextToken(ScriptToken.Type.keyword, "finally")) {
3032 				hadSomething = true;
3033 				tokens.popFront();
3034 				e.finallyExpressions ~= parseExpression(tokens);
3035 			}
3036 
3037 			//if(!hadSomething)
3038 				//throw new ScriptCompileException("Parse error, missing finally or catch after try", tryToken.lineNumber);
3039 
3040 			ret = e;
3041 		} else
3042 			ret = parseAddend(tokens);
3043 	} else {
3044 		//assert(0);
3045 		// return null;
3046 		throw new ScriptCompileException("Parse error, unexpected end of input when reading expression", null, 0);//token.lineNumber);
3047 	}
3048 
3049 	//writeln("parsed expression ", ret.toString());
3050 
3051 	if(expectedEnd.length && tokens.empty && consumeEnd) // going loose on final ; at the end of input for repl convenience
3052 		throw new ScriptCompileException("Parse error, unexpected end of input when reading expression, expecting " ~ expectedEnd, first.scriptFilename, first.lineNumber);
3053 
3054 	if(expectedEnd.length && consumeEnd) {
3055 		 if(tokens.peekNextToken(ScriptToken.Type.symbol, expectedEnd))
3056 			 tokens.popFront();
3057 		// FIXME
3058 		//if(tokens.front.type != ScriptToken.Type.symbol && tokens.front.str != expectedEnd)
3059 			//throw new ScriptCompileException("Parse error, missing "~expectedEnd~" at end of expression (starting on "~to!string(first.lineNumber)~"). Saw "~tokens.front.str~" instead", tokens.front.lineNumber);
3060 	//	tokens = tokens[1 .. $];
3061 	}
3062 
3063 	return ret;
3064 }
3065 
3066 VariableDeclaration parseVariableDeclaration(MyTokenStreamHere)(ref MyTokenStreamHere tokens, string termination) {
3067 	VariableDeclaration decl = new VariableDeclaration();
3068 	bool equalOk;
3069 	anotherVar:
3070 	assert(!tokens.empty);
3071 
3072 	auto firstToken = tokens.front;
3073 
3074 	// var a, var b is acceptable
3075 	if(tokens.peekNextToken(ScriptToken.Type.keyword, "var"))
3076 		tokens.popFront();
3077 
3078 	equalOk= true;
3079 	if(tokens.empty)
3080 		throw new ScriptCompileException("Parse error, dangling var at end of file", firstToken.scriptFilename, firstToken.lineNumber);
3081 
3082 	Expression initializer;
3083 	auto identifier = tokens.front;
3084 	if(identifier.type != ScriptToken.Type.identifier)
3085 		throw new ScriptCompileException("Parse error, found '"~identifier.str~"' when expecting var identifier", identifier.scriptFilename, identifier.lineNumber);
3086 
3087 	tokens.popFront();
3088 
3089 	tryTermination:
3090 	if(tokens.empty)
3091 		throw new ScriptCompileException("Parse error, missing ; after var declaration at end of file", firstToken.scriptFilename, firstToken.lineNumber);
3092 
3093 	auto peek = tokens.front;
3094 	if(peek.type == ScriptToken.Type.symbol) {
3095 		if(peek.str == "=") {
3096 			if(!equalOk)
3097 				throw new ScriptCompileException("Parse error, unexpected '"~identifier.str~"' after reading var initializer", peek.scriptFilename, peek.lineNumber);
3098 			equalOk = false;
3099 			tokens.popFront();
3100 			initializer = parseExpression(tokens);
3101 			goto tryTermination;
3102 		} else if(peek.str == ",") {
3103 			tokens.popFront();
3104 			decl.identifiers ~= identifier.str;
3105 			decl.initializers ~= initializer;
3106 			goto anotherVar;
3107 		} else if(peek.str == termination) {
3108 			decl.identifiers ~= identifier.str;
3109 			decl.initializers ~= initializer;
3110 			//tokens = tokens[1 .. $];
3111 			// we're done!
3112 		} else
3113 			throw new ScriptCompileException("Parse error, unexpected '"~peek.str~"' when reading var declaration", peek.scriptFilename, peek.lineNumber);
3114 	} else
3115 		throw new ScriptCompileException("Parse error, unexpected '"~peek.str~"' when reading var declaration", peek.scriptFilename, peek.lineNumber);
3116 
3117 	return decl;
3118 }
3119 
3120 Expression parseStatement(MyTokenStreamHere)(ref MyTokenStreamHere tokens, string terminatingSymbol = null) {
3121 	skip: // FIXME
3122 	if(tokens.empty)
3123 		return null;
3124 
3125 	if(terminatingSymbol !is null && (tokens.front.type == ScriptToken.Type.symbol && tokens.front.str == terminatingSymbol))
3126 		return null; // we're done
3127 
3128 	auto token = tokens.front;
3129 
3130 	// tokens = tokens[1 .. $];
3131 	final switch(token.type) {
3132 		case ScriptToken.Type.keyword:
3133 		case ScriptToken.Type.symbol:
3134 			switch(token.str) {
3135 				// assert
3136 				case "assert":
3137 					tokens.popFront();
3138 
3139 					return parseFunctionCall(tokens, new AssertKeyword(token));
3140 
3141 				//break;
3142 				// declarations
3143 				case "var":
3144 					return parseVariableDeclaration(tokens, ";");
3145 				case ";":
3146 					tokens.popFront(); // FIXME
3147 					goto skip;
3148 				// literals
3149 				case "function":
3150 				case "macro":
3151 					// function can be a literal, or a declaration.
3152 
3153 					tokens.popFront(); // we're peeking ahead
3154 
3155 					if(tokens.peekNextToken(ScriptToken.Type.identifier)) {
3156 						// decl style, rewrite it into var ident = function style
3157 						// tokens.popFront(); // skipping the function keyword // already done above with the popFront
3158 						auto ident = tokens.front;
3159 						tokens.popFront();
3160 
3161 						tokens.requireNextToken(ScriptToken.Type.symbol, "(");
3162 
3163 						auto exp = new FunctionLiteralExpression();
3164 						if(!tokens.peekNextToken(ScriptToken.Type.symbol, ")"))
3165 							exp.arguments = parseVariableDeclaration(tokens, ")");
3166 						tokens.requireNextToken(ScriptToken.Type.symbol, ")");
3167 
3168 						exp.functionBody = parseExpression(tokens);
3169 
3170 						// a ; should NOT be required here btw
3171 
3172 						auto e = new VariableDeclaration();
3173 						e.identifiers ~= ident.str;
3174 						e.initializers ~= exp;
3175 
3176 						exp.isMacro = token.str == "macro";
3177 
3178 						return e;
3179 
3180 					} else {
3181 						tokens.pushFront(token); // put it back since everyone expects us to have done that
3182 						goto case; // handle it like any other expression
3183 					}
3184 				case "json!{":
3185 				case "#{":
3186 				case "[":
3187 				case "(":
3188 				case "null":
3189 
3190 				// scope
3191 				case "{":
3192 				case "scope":
3193 
3194 				case "cast":
3195 
3196 				// classes
3197 				case "class":
3198 				case "new":
3199 
3200 				case "super":
3201 
3202 				// flow control
3203 				case "if":
3204 				case "while":
3205 				case "for":
3206 				case "foreach":
3207 				case "switch":
3208 
3209 				// exceptions
3210 				case "try":
3211 				case "throw":
3212 
3213 				// evals
3214 				case "eval":
3215 				case "mixin":
3216 
3217 				// flow
3218 				case "continue":
3219 				case "break":
3220 				case "return":
3221 					return parseExpression(tokens);
3222 				// unary prefix operators
3223 				case "!":
3224 				case "~":
3225 				case "-":
3226 					return parseExpression(tokens);
3227 
3228 				// BTW add custom object operator overloading to struct var
3229 				// and custom property overloading to PrototypeObject
3230 
3231 				default:
3232 					// whatever else keyword or operator related is actually illegal here
3233 					throw new ScriptCompileException("Parse error, unexpected " ~ token.str, token.scriptFilename, token.lineNumber);
3234 			}
3235 		// break;
3236 		case ScriptToken.Type.identifier:
3237 		case ScriptToken.Type..string:
3238 		case ScriptToken.Type.int_number:
3239 		case ScriptToken.Type.float_number:
3240 			return parseExpression(tokens);
3241 	}
3242 
3243 	assert(0);
3244 }
3245 
3246 struct CompoundStatementRange(MyTokenStreamHere) {
3247 	// FIXME: if MyTokenStreamHere is not a class, this fails!
3248 	MyTokenStreamHere tokens;
3249 	int startingLine;
3250 	string terminatingSymbol;
3251 	bool isEmpty;
3252 
3253 	this(MyTokenStreamHere t, int startingLine, string terminatingSymbol) {
3254 		tokens = t;
3255 		this.startingLine = startingLine;
3256 		this.terminatingSymbol = terminatingSymbol;
3257 		popFront();
3258 	}
3259 
3260 	bool empty() {
3261 		return isEmpty;
3262 	}
3263 
3264 	Expression got;
3265 
3266 	Expression front() {
3267 		return got;
3268 	}
3269 
3270 	void popFront() {
3271 		while(!tokens.empty && (terminatingSymbol is null || !(tokens.front.type == ScriptToken.Type.symbol && tokens.front.str == terminatingSymbol))) {
3272 			auto n = parseStatement(tokens, terminatingSymbol);
3273 			if(n is null)
3274 				continue;
3275 			got = n;
3276 			return;
3277 		}
3278 
3279 		if(tokens.empty && terminatingSymbol !is null) {
3280 			throw new ScriptCompileException("Reached end of file while trying to reach matching " ~ terminatingSymbol, null, startingLine);
3281 		}
3282 
3283 		if(terminatingSymbol !is null) {
3284 			assert(tokens.front.str == terminatingSymbol);
3285 			tokens.skipNext++;
3286 		}
3287 
3288 		isEmpty = true;
3289 	}
3290 }
3291 
3292 CompoundStatementRange!MyTokenStreamHere
3293 //Expression[]
3294 parseCompoundStatement(MyTokenStreamHere)(ref MyTokenStreamHere tokens, int startingLine = 1, string terminatingSymbol = null) {
3295 	return (CompoundStatementRange!MyTokenStreamHere(tokens, startingLine, terminatingSymbol));
3296 }
3297 
3298 auto parseScript(MyTokenStreamHere)(MyTokenStreamHere tokens) {
3299 	/*
3300 		the language's grammar is simple enough
3301 
3302 		maybe flow control should be statements though lol. they might not make sense inside.
3303 
3304 		Expressions:
3305 			var identifier;
3306 			var identifier = initializer;
3307 			var identifier, identifier2
3308 
3309 			return expression;
3310 			return ;
3311 
3312 			json!{ object literal }
3313 
3314 			{ scope expression }
3315 
3316 			[ array literal ]
3317 			other literal
3318 			function (arg list) other expression
3319 
3320 			( expression ) // parenthesized expression
3321 			operator expression  // unary expression
3322 
3323 			expression operator expression // binary expression
3324 			expression (other expression... args) // function call
3325 
3326 		Binary Operator precedence :
3327 			. []
3328 			* /
3329 			+ -
3330 			~
3331 			< > == !=
3332 			=
3333 	*/
3334 
3335 	return parseCompoundStatement(tokens);
3336 }
3337 
3338 var interpretExpressions(ExpressionStream)(ExpressionStream expressions, PrototypeObject variables) if(is(ElementType!ExpressionStream == Expression)) {
3339 	assert(variables !is null);
3340 	var ret;
3341 	foreach(expression; expressions) {
3342 		auto res = expression.interpret(variables);
3343 		variables = res.sc;
3344 		ret = res.value;
3345 	}
3346 	return ret;
3347 }
3348 
3349 var interpretStream(MyTokenStreamHere)(MyTokenStreamHere tokens, PrototypeObject variables) if(is(ElementType!MyTokenStreamHere == ScriptToken)) {
3350 	assert(variables !is null);
3351 	// this is an entry point that all others lead to, right before getting to interpretExpressions...
3352 
3353 	return interpretExpressions(parseScript(tokens), variables);
3354 }
3355 
3356 var interpretStream(MyTokenStreamHere)(MyTokenStreamHere tokens, var variables) if(is(ElementType!MyTokenStreamHere == ScriptToken)) {
3357 	return interpretStream(tokens,
3358 		(variables.payloadType() == var.Type.Object && variables._payload._object !is null) ? variables._payload._object : new PrototypeObject());
3359 }
3360 
3361 var interpret(string code, PrototypeObject variables, string scriptFilename = null) {
3362 	assert(variables !is null);
3363 	return interpretStream(lexScript(repeat(code, 1), scriptFilename), variables);
3364 }
3365 
3366 /++
3367 	This is likely your main entry point to the interpreter. It will interpret the script code
3368 	given, with the given global variable object (which will be modified by the script, meaning
3369 	you can pass it to subsequent calls to `interpret` to store context), and return the result
3370 	of the last expression given.
3371 
3372 	---
3373 	var globals = var.emptyObject; // the global object must be an object of some type
3374 	globals.x = 10;
3375 	globals.y = 15;
3376 	// you can also set global functions through this same style, etc
3377 
3378 	var result = interpret(`x + y`, globals);
3379 	assert(result == 25);
3380 	---
3381 
3382 
3383 	$(TIP
3384 		If you want to just call a script function, interpret the definition of it,
3385 		then just call it through the `globals` object you passed to it.
3386 
3387 		---
3388 		var globals = var.emptyObject;
3389 		interpret(`function foo(name) { return "hello, " ~ name ~ "!"; }`, globals);
3390 		var result = globals.foo()("world");
3391 		assert(result == "hello, world!");
3392 		---
3393 	)
3394 
3395 	Params:
3396 		code = the script source code you want to interpret
3397 		scriptFilename = the filename of the script, if you want to provide it. Gives nicer error messages if you provide one.
3398 		variables = The global object of the script context. It will be modified by the user script.
3399 
3400 	Returns:
3401 		the result of the last expression evaluated by the script engine
3402 +/
3403 var interpret(string code, var variables = null, string scriptFilename = null, string file = __FILE__, size_t line = __LINE__) {
3404 	if(scriptFilename is null)
3405 		scriptFilename = file ~ "@" ~ to!string(line);
3406 	return interpretStream(
3407 		lexScript(repeat(code, 1), scriptFilename),
3408 		(variables.payloadType() == var.Type.Object && variables._payload._object !is null) ? variables._payload._object : new PrototypeObject());
3409 }
3410 
3411 ///
3412 var interpretFile(File file, var globals) {
3413 	import std.algorithm;
3414 	return interpretStream(lexScript(file.byLine.map!((a) => a.idup), file.name),
3415 		(globals.payloadType() == var.Type.Object && globals._payload._object !is null) ? globals._payload._object : new PrototypeObject());
3416 }
3417 
3418 /// Enhanced repl uses arsd.terminal for better ux. Added April 26, 2020. Default just uses std.stdio.
3419 void repl(bool enhanced = false)(var globals) {
3420 	static if(enhanced) {
3421 		import arsd.terminal;
3422 		Terminal terminal = Terminal(ConsoleOutputMode.linear);
3423 		auto lines() {
3424 			struct Range {
3425 				string line;
3426 				string front() { return line; }
3427 				bool empty() { return line is null; }
3428 				void popFront() { line = terminal.getline(": "); terminal.writeln(); }
3429 			}
3430 			Range r;
3431 			r.popFront();
3432 			return r;
3433 			
3434 		}
3435 
3436 		void writeln(T...)(T t) {
3437 			terminal.writeln(t);
3438 			terminal.flush();
3439 		}
3440 	} else {
3441 		import std.stdio;
3442 		auto lines() { return stdin.byLine; }
3443 	}
3444 
3445 	bool exited;
3446 	if(globals == null)
3447 		globals = var.emptyObject;
3448 	globals.exit = () { exited = true; };
3449 
3450 	import std.algorithm;
3451 	auto variables = (globals.payloadType() == var.Type.Object && globals._payload._object !is null) ? globals._payload._object : new PrototypeObject();
3452 
3453 	// we chain to ensure the priming popFront succeeds so we don't throw here
3454 	auto tokens = lexScript(
3455 		chain(["var __skipme = 0;"], map!((a) => a.idup)(lines))
3456 	, "stdin");
3457 	auto expressions = parseScript(tokens);
3458 
3459 	while(!exited && !expressions.empty) {
3460 		try {
3461 			expressions.popFront;
3462 			auto expression = expressions.front;
3463 			auto res = expression.interpret(variables);
3464 			variables = res.sc;
3465 			writeln(">>> ", res.value);
3466 		} catch(ScriptCompileException e) {
3467 			writeln("*+* ", e.msg);
3468 			tokens.popFront(); // skip the one we threw on...
3469 		} catch(Exception e) {
3470 			writeln("*** ", e.msg);
3471 		}
3472 	}
3473 }
3474 
3475 class ScriptFunctionMetadata : VarMetadata {
3476 	FunctionLiteralExpression fle;
3477 	this(FunctionLiteralExpression fle) {
3478 		this.fle = fle;
3479 	}
3480 
3481 	string convertToString() {
3482 		return fle.toString();
3483 	}
3484 }