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