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) "~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) {
1583 		try {
1584 			return sc._getMember(identifier, true /* FIXME: recurse?? */, true);
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) {
1596 		return v[identifier];
1597 	}
1598 
1599 	override InterpretResult interpret(PrototypeObject sc) {
1600 		return InterpretResult(getVar(sc), sc);
1601 	}
1602 }
1603 
1604 class SuperExpression : Expression {
1605 	VariableExpression dot;
1606 	string origDot;
1607 	this(VariableExpression dot) {
1608 		if(dot !is null) {
1609 			origDot = dot.identifier;
1610 			//dot.identifier = "__super_" ~ dot.identifier; // omg this is so bad
1611 		}
1612 		this.dot = dot;
1613 	}
1614 
1615 	override string toString() {
1616 		if(dot is null)
1617 			return "super";
1618 		else
1619 			return "super." ~ origDot;
1620 	}
1621 
1622 	override InterpretResult interpret(PrototypeObject sc) {
1623 		var a = sc._getMember("super", true, true);
1624 		if(a._object is null)
1625 			throw new Exception("null proto for super");
1626 		PrototypeObject proto = a._object.prototype;
1627 		if(proto is null)
1628 			throw new Exception("no super");
1629 		//proto = proto.prototype;
1630 
1631 		if(dot !is null)
1632 			a = proto._getMember(dot.identifier, true, true);
1633 		else
1634 			a = proto._getMember("__ctor", true, true);
1635 		return InterpretResult(a, sc);
1636 	}
1637 }
1638 
1639 class DotVarExpression : VariableExpression {
1640 	Expression e1;
1641 	VariableExpression e2;
1642 	bool recurse = true;
1643 
1644 	this(Expression e1) {
1645 		this.e1 = e1;
1646 		super(null);
1647 	}
1648 
1649 	this(Expression e1, VariableExpression e2, bool recurse = true) {
1650 		this.e1 = e1;
1651 		this.e2 = e2;
1652 		this.recurse = recurse;
1653 		//assert(typeid(e2) == typeid(VariableExpression));
1654 		super("<do not use>");//e1.identifier ~ "." ~ e2.identifier);
1655 	}
1656 
1657 	override string toString() {
1658 		return e1.toString() ~ "." ~ e2.toString();
1659 	}
1660 
1661 	override ref var getVar(PrototypeObject sc, bool recurse = true) {
1662 		if(!this.recurse) {
1663 			// this is a special hack...
1664 			if(auto ve = cast(VariableExpression) e1) {
1665 				return ve.getVar(sc)._getOwnProperty(e2.identifier);
1666 			}
1667 			assert(0);
1668 		}
1669 
1670 		if(e2.identifier == "__source") {
1671 			auto val = e1.interpret(sc).value;
1672 			if(auto meta = cast(ScriptFunctionMetadata) val._metadata)
1673 				return *(new var(meta.convertToString()));
1674 			else
1675 				return *(new var(val.toJson()));
1676 		}
1677 
1678 		if(auto ve = cast(VariableExpression) e1) {
1679 			return this.getVarFrom(sc, ve.getVar(sc, recurse));
1680 		} else if(cast(StringLiteralExpression) e1 && e2.identifier == "interpolate") {
1681 			auto se = cast(StringLiteralExpression) e1;
1682 			var* functor = new var;
1683 			//if(!se.allowInterpolation)
1684 				//throw new ScriptRuntimeException("Cannot interpolate this string", se.token.lineNumber);
1685 			(*functor)._function = (var _this, var[] args) {
1686 				return se.interpolate(args.length ? args[0] : var(null), sc);
1687 			};
1688 			return *functor;
1689 		} else {
1690 			// make a temporary for the lhs
1691 			auto v = new var();
1692 			*v = e1.interpret(sc).value;
1693 			return this.getVarFrom(sc, *v);
1694 		}
1695 	}
1696 
1697 	override ref var setVar(PrototypeObject sc, var t, bool recurse = true, bool suppressOverloading = false) {
1698 		if(suppressOverloading)
1699 			return e1.interpret(sc).value.opIndexAssignNoOverload(t, e2.identifier);
1700 		else
1701 			return e1.interpret(sc).value.opIndexAssign(t, e2.identifier);
1702 	}
1703 
1704 
1705 	override ref var getVarFrom(PrototypeObject sc, ref var v) {
1706 		return e2.getVarFrom(sc, v);
1707 	}
1708 
1709 	override string toInterpretedString(PrototypeObject sc) {
1710 		return getVar(sc).get!string;
1711 	}
1712 }
1713 
1714 class IndexExpression : VariableExpression {
1715 	Expression e1;
1716 	Expression e2;
1717 
1718 	this(Expression e1, Expression e2) {
1719 		this.e1 = e1;
1720 		this.e2 = e2;
1721 		super(null);
1722 	}
1723 
1724 	override string toString() {
1725 		return e1.toString() ~ "[" ~ e2.toString() ~ "]";
1726 	}
1727 
1728 	override ref var getVar(PrototypeObject sc, bool recurse = true) {
1729 		if(auto ve = cast(VariableExpression) e1)
1730 			return ve.getVar(sc, recurse)[e2.interpret(sc).value];
1731 		else {
1732 			auto v = new var();
1733 			*v = e1.interpret(sc).value;
1734 			return this.getVarFrom(sc, *v);
1735 		}
1736 	}
1737 
1738 	override ref var setVar(PrototypeObject sc, var t, bool recurse = true, bool suppressOverloading = false) {
1739         	return getVar(sc,recurse) = t;
1740 	}
1741 }
1742 
1743 class SliceExpression : Expression {
1744 	// e1[e2 .. e3]
1745 	Expression e1;
1746 	Expression e2;
1747 	Expression e3;
1748 
1749 	this(Expression e1, Expression e2, Expression e3) {
1750 		this.e1 = e1;
1751 		this.e2 = e2;
1752 		this.e3 = e3;
1753 	}
1754 
1755 	override string toString() {
1756 		return e1.toString() ~ "[" ~ e2.toString() ~ " .. " ~ e3.toString() ~ "]";
1757 	}
1758 
1759 	override InterpretResult interpret(PrototypeObject sc) {
1760 		var lhs = e1.interpret(sc).value;
1761 
1762 		auto specialScope = new PrototypeObject();
1763 		specialScope.prototype = sc;
1764 		specialScope._getMember("$", false, false) = lhs.length;
1765 
1766 		return InterpretResult(lhs[e2.interpret(specialScope).value .. e3.interpret(specialScope).value], sc);
1767 	}
1768 }
1769 
1770 
1771 class LoopControlExpression : Expression {
1772 	InterpretResult.FlowControl op;
1773 	this(string op) {
1774 		if(op == "continue")
1775 			this.op = InterpretResult.FlowControl.Continue;
1776 		else if(op == "break")
1777 			this.op = InterpretResult.FlowControl.Break;
1778 		else assert(0, op);
1779 	}
1780 
1781 	override string toString() {
1782 		import std..string;
1783 		return to!string(this.op).toLower();
1784 	}
1785 
1786 	override InterpretResult interpret(PrototypeObject sc) {
1787 		return InterpretResult(var(null), sc, op);
1788 	}
1789 }
1790 
1791 
1792 class ReturnExpression : Expression {
1793 	Expression value;
1794 
1795 	this(Expression v) {
1796 		value = v;
1797 	}
1798 
1799 	override string toString() { return "return " ~ value.toString(); }
1800 
1801 	override InterpretResult interpret(PrototypeObject sc) {
1802 		return InterpretResult(value.interpret(sc).value, sc, InterpretResult.FlowControl.Return);
1803 	}
1804 }
1805 
1806 class ScopeExpression : Expression {
1807 	this(Expression[] expressions) {
1808 		this.expressions = expressions;
1809 	}
1810 
1811 	Expression[] expressions;
1812 
1813 	override string toString() {
1814 		string s;
1815 		s = "{\n";
1816 		foreach(expr; expressions) {
1817 			s ~= "\t";
1818 			s ~= expr.toString();
1819 			s ~= ";\n";
1820 		}
1821 		s ~= "}";
1822 		return s;
1823 	}
1824 
1825 	override InterpretResult interpret(PrototypeObject sc) {
1826 		var ret;
1827 
1828 		auto innerScope = new PrototypeObject();
1829 		innerScope.prototype = sc;
1830 
1831 		innerScope._getMember("__scope_exit", false, false) = var.emptyArray;
1832 		innerScope._getMember("__scope_success", false, false) = var.emptyArray;
1833 		innerScope._getMember("__scope_failure", false, false) = var.emptyArray;
1834 
1835 		scope(exit) {
1836 			foreach(func; innerScope._getMember("__scope_exit", false, true))
1837 				func();
1838 		}
1839 		scope(success) {
1840 			foreach(func; innerScope._getMember("__scope_success", false, true))
1841 				func();
1842 		}
1843 		scope(failure) {
1844 			foreach(func; innerScope._getMember("__scope_failure", false, true))
1845 				func();
1846 		}
1847 
1848 		foreach(expression; expressions) {
1849 			auto res = expression.interpret(innerScope);
1850 			ret = res.value;
1851 			if(res.flowControl != InterpretResult.FlowControl.Normal)
1852 				return InterpretResult(ret, sc, res.flowControl);
1853 		}
1854 		return InterpretResult(ret, sc);
1855 	}
1856 }
1857 
1858 class SwitchExpression : Expression {
1859 	Expression expr;
1860 	CaseExpression[] cases;
1861 	CaseExpression default_;
1862 
1863 	override InterpretResult interpret(PrototypeObject sc) {
1864 		auto e = expr.interpret(sc);
1865 
1866 		bool hitAny;
1867 		bool fallingThrough;
1868 		bool secondRun;
1869 
1870 		var last;
1871 
1872 		again:
1873 		foreach(c; cases) {
1874 			if(!secondRun && !fallingThrough && c is default_) continue;
1875 			if(fallingThrough || (secondRun && c is default_) || c.condition.interpret(sc) == e) {
1876 				fallingThrough = false;
1877 				if(!secondRun)
1878 					hitAny = true;
1879 				InterpretResult ret;
1880 				expr_loop: foreach(exp; c.expressions) {
1881 					ret = exp.interpret(sc);
1882 					with(InterpretResult.FlowControl)
1883 					final switch(ret.flowControl) {
1884 						case Normal:
1885 							last = ret.value;
1886 						break;
1887 						case Return:
1888 						case Goto:
1889 							return ret;
1890 						case Continue:
1891 							fallingThrough = true;
1892 							break expr_loop;
1893 						case Break:
1894 							return InterpretResult(last, sc);
1895 					}
1896 				}
1897 
1898 				if(!fallingThrough)
1899 					break;
1900 			}
1901 		}
1902 
1903 		if(!hitAny && !secondRun) {
1904 			secondRun = true;
1905 			goto again;
1906 		}
1907 
1908 		return InterpretResult(last, sc);
1909 	}
1910 }
1911 
1912 class CaseExpression : Expression {
1913 	this(Expression condition) {
1914 		this.condition = condition;
1915 	}
1916 	Expression condition;
1917 	Expression[] expressions;
1918 
1919 	override string toString() {
1920 		string code;
1921 		if(condition is null)
1922 			code = "default:";
1923 		else
1924 			code = "case " ~ condition.toString() ~ ":";
1925 
1926 		foreach(expr; expressions)
1927 			code ~= "\n" ~ expr.toString() ~ ";";
1928 
1929 		return code;
1930 	}
1931 
1932 	override InterpretResult interpret(PrototypeObject sc) {
1933 		// I did this inline up in the SwitchExpression above. maybe insane?!
1934 		assert(0);
1935 	}
1936 }
1937 
1938 unittest {
1939 	interpret(q{
1940 		var a = 10;
1941 		// case and break should work
1942 		var brk;
1943 
1944 		// var brk = switch doesn't parse, but this will.....
1945 		// (I kinda went everything is an expression but not all the way. this code SUX.)
1946 		brk = switch(a) {
1947 			case 10:
1948 				a = 30;
1949 			break;
1950 			case 30:
1951 				a = 40;
1952 			break;
1953 			default:
1954 				a = 0;
1955 		}
1956 
1957 		assert(a == 30);
1958 		assert(brk == 30); // value of switch set to last expression evaled inside
1959 
1960 		// so should default
1961 		switch(a) {
1962 			case 20:
1963 				a = 40;
1964 			break;
1965 			default:
1966 				a = 40;
1967 		}
1968 
1969 		assert(a == 40);
1970 
1971 		switch(a) {
1972 			case 40:
1973 				a = 50;
1974 			case 60: // no implicit fallthrough in this lang...
1975 				a = 60;
1976 		}
1977 
1978 		assert(a == 50);
1979 
1980 		var ret;
1981 
1982 		ret = switch(a) {
1983 			case 50:
1984 				a = 60;
1985 				continue; // request fallthrough. D uses "goto case", but I haven't implemented any goto yet so continue is best fit
1986 			case 90:
1987 				a = 70;
1988 		}
1989 
1990 		assert(a == 70); // the explicit `continue` requests fallthrough behavior
1991 		assert(ret == 70);
1992 	});
1993 }
1994 
1995 unittest {
1996 	// overloads
1997 	interpret(q{
1998 		function foo(int a) { return 10 + a; }
1999 		function foo(float a) { return 100 + a; }
2000 		function foo(string a) { return "string " ~ a; }
2001 
2002 		assert(foo(4) == 14);
2003 		assert(foo(4.5) == 104.5);
2004 		assert(foo("test") == "string test");
2005 
2006 		// can redefine specific override
2007 		function foo(int a) { return a; }
2008 		assert(foo(4) == 4);
2009 		// leaving others in place
2010 		assert(foo(4.5) == 104.5);
2011 		assert(foo("test") == "string test");
2012 	});
2013 }
2014 
2015 unittest {
2016 	// catching objects
2017 	interpret(q{
2018 		class Foo {}
2019 		class Bar : Foo {}
2020 
2021 		var res = try throw new Bar(); catch(Bar b) { 2 } catch(e) { 1 };
2022 		assert(res == 2);
2023 
2024 		var res = try throw new Foo(); catch(Bar b) { 2 } catch(e) { 1 };
2025 		assert(res == 1);
2026 
2027 		var res = try throw Foo; catch(Foo b) { 2 } catch(e) { 1 };
2028 		assert(res == 2);
2029 	});
2030 }
2031 
2032 unittest {
2033 	// ternary precedence
2034 	interpret(q{
2035 		assert(0 == 0 ? true : false == true);
2036 		assert((0 == 0) ? true : false == true);
2037 		// lol FIXME
2038 		//assert(((0 == 0) ? true : false) == true);
2039 	});
2040 }
2041 
2042 unittest {
2043 	// new nested class
2044 	interpret(q{
2045 		class A {}
2046 		A.b = class B { var c; this(a) { this.c = a; } }
2047 		var c = new A.b(5);
2048 		assert(A.b.c == null);
2049 		assert(c.c == 5);
2050 	});
2051 }
2052 
2053 class ForeachExpression : Expression {
2054 	VariableDeclaration decl;
2055 	Expression subject;
2056 	Expression subject2;
2057 	Expression loopBody;
2058 
2059 	override string toString() {
2060 		return "foreach(" ~ decl.toString() ~ "; " ~ subject.toString() ~ ((subject2 is null) ? "" : (".." ~ subject2.toString)) ~ ") " ~ loopBody.toString();
2061 	}
2062 
2063 	override InterpretResult interpret(PrototypeObject sc) {
2064 		var result;
2065 
2066 		assert(loopBody !is null);
2067 
2068 		auto loopScope = new PrototypeObject();
2069 		loopScope.prototype = sc;
2070 
2071 		InterpretResult.FlowControl flowControl;
2072 
2073 		static string doLoopBody() { return q{
2074 			if(decl.identifiers.length > 1) {
2075 				sc._getMember(decl.identifiers[0], false, false) = i;
2076 				sc._getMember(decl.identifiers[1], false, false) = item;
2077 			} else {
2078 				sc._getMember(decl.identifiers[0], false, false) = item;
2079 			}
2080 
2081 			auto res = loopBody.interpret(loopScope);
2082 			result = res.value;
2083 			flowControl = res.flowControl;
2084 			if(flowControl == InterpretResult.FlowControl.Break)
2085 				break;
2086 			if(flowControl == InterpretResult.FlowControl.Return)
2087 				break;
2088 			//if(flowControl == InterpretResult.FlowControl.Continue)
2089 				// this is fine, we still want to do the advancement
2090 		};}
2091 
2092 		var what = subject.interpret(sc).value;
2093 		var termination = subject2 is null ? var(null) : subject2.interpret(sc).value;
2094 		if(what.payloadType == var.Type.Integral && subject2 is null) {
2095 			// loop from 0 to what
2096 			int end = what.get!int;
2097 			foreach(item; 0 .. end) {
2098 				auto i = item;
2099 				mixin(doLoopBody());
2100 			}
2101 		} else if(what.payloadType == var.Type.Integral && termination.payloadType == var.Type.Integral) {
2102 			// loop what .. termination
2103 			int start = what.get!int;
2104 			int end = termination.get!int;
2105 			int stride;
2106 			if(end < start) {
2107 				stride = -1;
2108 			} else {
2109 				stride = 1;
2110 			}
2111 			int i = -1;
2112 			for(int item = start; item != end; item += stride) {
2113 				i++;
2114 				mixin(doLoopBody());
2115 			}
2116 		} else {
2117 			if(subject2 !is null)
2118 				throw new ScriptRuntimeException("foreach( a .. b ) invalid unless a is an integer", null, 0); // FIXME
2119 			foreach(i, item; what) {
2120 				mixin(doLoopBody());
2121 			}
2122 		}
2123 
2124 		if(flowControl != InterpretResult.FlowControl.Return)
2125 			flowControl = InterpretResult.FlowControl.Normal;
2126 
2127 		return InterpretResult(result, sc, flowControl);
2128 	}
2129 }
2130 
2131 class ForExpression : Expression {
2132 	Expression initialization;
2133 	Expression condition;
2134 	Expression advancement;
2135 	Expression loopBody;
2136 
2137 	this() {}
2138 
2139 	override InterpretResult interpret(PrototypeObject sc) {
2140 		var result;
2141 
2142 		assert(loopBody !is null);
2143 
2144 		auto loopScope = new PrototypeObject();
2145 		loopScope.prototype = sc;
2146 		if(initialization !is null)
2147 			initialization.interpret(loopScope);
2148 
2149 		InterpretResult.FlowControl flowControl;
2150 
2151 		static string doLoopBody() { return q{
2152 			auto res = loopBody.interpret(loopScope);
2153 			result = res.value;
2154 			flowControl = res.flowControl;
2155 			if(flowControl == InterpretResult.FlowControl.Break)
2156 				break;
2157 			if(flowControl == InterpretResult.FlowControl.Return)
2158 				break;
2159 			//if(flowControl == InterpretResult.FlowControl.Continue)
2160 				// this is fine, we still want to do the advancement
2161 			if(advancement)
2162 				advancement.interpret(loopScope);
2163 		};}
2164 
2165 		if(condition !is null) {
2166 			while(condition.interpret(loopScope).value) {
2167 				mixin(doLoopBody());
2168 			}
2169 		} else
2170 			while(true) {
2171 				mixin(doLoopBody());
2172 			}
2173 
2174 		if(flowControl != InterpretResult.FlowControl.Return)
2175 			flowControl = InterpretResult.FlowControl.Normal;
2176 
2177 		return InterpretResult(result, sc, flowControl);
2178 	}
2179 
2180 	override string toString() {
2181 		string code = "for(";
2182 		if(initialization !is null)
2183 			code ~= initialization.toString();
2184 		code ~= "; ";
2185 		if(condition !is null)
2186 			code ~= condition.toString();
2187 		code ~= "; ";
2188 		if(advancement !is null)
2189 			code ~= advancement.toString();
2190 		code ~= ") ";
2191 		code ~= loopBody.toString();
2192 
2193 		return code;
2194 	}
2195 }
2196 
2197 class IfExpression : Expression {
2198 	Expression condition;
2199 	Expression ifTrue;
2200 	Expression ifFalse;
2201 
2202 	this() {}
2203 
2204 	override InterpretResult interpret(PrototypeObject sc) {
2205 		InterpretResult result;
2206 		assert(condition !is null);
2207 
2208 		auto ifScope = new PrototypeObject();
2209 		ifScope.prototype = sc;
2210 
2211 		if(condition.interpret(ifScope).value) {
2212 			if(ifTrue !is null)
2213 				result = ifTrue.interpret(ifScope);
2214 		} else {
2215 			if(ifFalse !is null)
2216 				result = ifFalse.interpret(ifScope);
2217 		}
2218 		return InterpretResult(result.value, sc, result.flowControl);
2219 	}
2220 
2221 	override string toString() {
2222 		string code = "if ";
2223 		code ~= condition.toString();
2224 		code ~= " ";
2225 		if(ifTrue !is null)
2226 			code ~= ifTrue.toString();
2227 		else
2228 			code ~= " { }";
2229 		if(ifFalse !is null)
2230 			code ~= " else " ~ ifFalse.toString();
2231 		return code;
2232 	}
2233 }
2234 
2235 class TernaryExpression : Expression {
2236 	Expression condition;
2237 	Expression ifTrue;
2238 	Expression ifFalse;
2239 
2240 	this() {}
2241 
2242 	override InterpretResult interpret(PrototypeObject sc) {
2243 		InterpretResult result;
2244 		assert(condition !is null);
2245 
2246 		auto ifScope = new PrototypeObject();
2247 		ifScope.prototype = sc;
2248 
2249 		if(condition.interpret(ifScope).value) {
2250 			result = ifTrue.interpret(ifScope);
2251 		} else {
2252 			result = ifFalse.interpret(ifScope);
2253 		}
2254 		return InterpretResult(result.value, sc, result.flowControl);
2255 	}
2256 
2257 	override string toString() {
2258 		string code = "";
2259 		code ~= condition.toString();
2260 		code ~= " ? ";
2261 		code ~= ifTrue.toString();
2262 		code ~= " : ";
2263 		code ~= ifFalse.toString();
2264 		return code;
2265 	}
2266 }
2267 
2268 // this is kinda like a placement new, and currently isn't exposed inside the language,
2269 // but is used for class inheritance
2270 class ShallowCopyExpression : Expression {
2271 	Expression e1;
2272 	Expression e2;
2273 
2274 	this(Expression e1, Expression e2) {
2275 		this.e1 = e1;
2276 		this.e2 = e2;
2277 	}
2278 
2279 	override InterpretResult interpret(PrototypeObject sc) {
2280 		auto v = cast(VariableExpression) e1;
2281 		if(v is null)
2282 			throw new ScriptRuntimeException("not an lvalue", null, 0 /* FIXME */);
2283 
2284 		v.getVar(sc, false)._object.copyPropertiesFrom(e2.interpret(sc).value._object);
2285 
2286 		return InterpretResult(var(null), sc);
2287 	}
2288 
2289 }
2290 
2291 class NewExpression : Expression {
2292 	Expression what;
2293 	Expression[] args;
2294 	this(Expression w) {
2295 		what = w;
2296 	}
2297 
2298 	override InterpretResult interpret(PrototypeObject sc) {
2299 		assert(what !is null);
2300 
2301 		var[] args;
2302 		foreach(arg; this.args)
2303 			args ~= arg.interpret(sc).value;
2304 
2305 		var original = what.interpret(sc).value;
2306 		var n = original._copy_new;
2307 		if(n.payloadType() == var.Type.Object) {
2308 			var ctor = original.prototype ? original.prototype._getOwnProperty("__ctor") : var(null);
2309 			if(ctor)
2310 				ctor.apply(n, args);
2311 		}
2312 
2313 		return InterpretResult(n, sc);
2314 	}
2315 }
2316 
2317 class ThrowExpression : Expression {
2318 	Expression whatToThrow;
2319 	ScriptToken where;
2320 
2321 	this(Expression e, ScriptToken where) {
2322 		whatToThrow = e;
2323 		this.where = where;
2324 	}
2325 
2326 	override InterpretResult interpret(PrototypeObject sc) {
2327 		assert(whatToThrow !is null);
2328 		throw new ScriptException(whatToThrow.interpret(sc).value, ScriptLocation(where.scriptFilename, where.lineNumber));
2329 		assert(0);
2330 	}
2331 }
2332 
2333 bool isCompatibleType(var v, string specifier, PrototypeObject sc) {
2334 	var t = toType(specifier, sc);
2335 	auto score = typeCompatibilityScore(v, t);
2336 	return score > 0;
2337 }
2338 
2339 var toType(string specifier, PrototypeObject sc) {
2340 	switch(specifier) {
2341 		case "int", "long": return var(0);
2342 		case "float", "double": return var(0.0);
2343 		case "string": return var("");
2344 		default:
2345 			auto got = sc._peekMember(specifier, true);
2346 			if(got)
2347 				return *got;
2348 			else
2349 				return var.init;
2350 	}
2351 }
2352 
2353 var[] toTypes(string[] specifiers, PrototypeObject sc) {
2354 	var[] arr;
2355 	foreach(s; specifiers)
2356 		arr ~= toType(s, sc);
2357 	return arr;
2358 }
2359 
2360 
2361 class ExceptionBlockExpression : Expression {
2362 	Expression tryExpression;
2363 
2364 	string[] catchVarDecls;
2365 	string[] catchVarTypeSpecifiers;
2366 	Expression[] catchExpressions;
2367 
2368 	Expression[] finallyExpressions;
2369 
2370 	override InterpretResult interpret(PrototypeObject sc) {
2371 		InterpretResult result;
2372 		result.sc = sc;
2373 		assert(tryExpression !is null);
2374 		assert(catchVarDecls.length == catchExpressions.length);
2375 
2376 		void caught(var ex) {
2377 			if(catchExpressions.length)
2378 			foreach(i, ce; catchExpressions) {
2379 				if(catchVarTypeSpecifiers[i].length == 0 || isCompatibleType(ex, catchVarTypeSpecifiers[i], sc)) {
2380 					auto catchScope = new PrototypeObject();
2381 					catchScope.prototype = sc;
2382 					catchScope._getMember(catchVarDecls[i], false, false) = ex;
2383 
2384 					result = ce.interpret(catchScope);
2385 					break;
2386 				}
2387 			} else
2388 				result = InterpretResult(ex, sc);
2389 		}
2390 
2391 		if(catchExpressions.length || (catchExpressions.length == 0 && finallyExpressions.length == 0))
2392 			try {
2393 				result = tryExpression.interpret(sc);
2394 			} catch(NonScriptCatchableException e) {
2395 				// the script cannot catch these so it continues up regardless
2396 				throw e;
2397 			} catch(ScriptException e) {
2398 				// FIXME: what about the other information here? idk.
2399 				caught(e.payload);
2400 			} catch(Exception e) {
2401 				var ex = var.emptyObject;
2402 				ex.type = typeid(e).name;
2403 				ex.msg = e.msg;
2404 				ex.file = e.file;
2405 				ex.line = e.line;
2406 
2407 				caught(ex);
2408 			} finally {
2409 				foreach(fe; finallyExpressions)
2410 					result = fe.interpret(sc);
2411 			}
2412 		else
2413 			try {
2414 				result = tryExpression.interpret(sc);
2415 			} finally {
2416 				foreach(fe; finallyExpressions)
2417 					result = fe.interpret(sc);
2418 			}
2419 
2420 		return result;
2421 	}
2422 }
2423 
2424 class ParentheticalExpression : Expression {
2425 	Expression inside;
2426 	this(Expression inside) {
2427 		this.inside = inside;
2428 	}
2429 
2430 	override string toString() {
2431 		return "(" ~ inside.toString() ~ ")";
2432 	}
2433 
2434 	override InterpretResult interpret(PrototypeObject sc) {
2435 		return InterpretResult(inside.interpret(sc).value, sc);
2436 	}
2437 }
2438 
2439 class AssertKeyword : Expression {
2440 	ScriptToken token;
2441 	this(ScriptToken token) {
2442 		this.token = token;
2443 	}
2444 	override string toString() {
2445 		return "assert";
2446 	}
2447 
2448 	override InterpretResult interpret(PrototypeObject sc) {
2449 		if(AssertKeywordObject is null)
2450 			AssertKeywordObject = new PrototypeObject();
2451 		var dummy;
2452 		dummy._object = AssertKeywordObject;
2453 		return InterpretResult(dummy, sc);
2454 	}
2455 }
2456 
2457 PrototypeObject AssertKeywordObject;
2458 PrototypeObject DefaultArgumentDummyObject;
2459 
2460 class CallExpression : Expression {
2461 	Expression func;
2462 	Expression[] arguments;
2463 	ScriptLocation loc;
2464 
2465 	override string toString() {
2466 		string s = func.toString() ~ "(";
2467 		foreach(i, arg; arguments) {
2468 			if(i) s ~= ", ";
2469 			s ~= arg.toString();
2470 		}
2471 
2472 		s ~= ")";
2473 		return s;
2474 	}
2475 
2476 	this(ScriptLocation loc, Expression func) {
2477 		this.loc = loc;
2478 		this.func = func;
2479 	}
2480 
2481 	override string toInterpretedString(PrototypeObject sc) {
2482 		return interpret(sc).value.get!string;
2483 	}
2484 
2485 	override InterpretResult interpret(PrototypeObject sc) {
2486 		if(auto asrt = cast(AssertKeyword) func) {
2487 			auto assertExpression = arguments[0];
2488 			Expression assertString;
2489 			if(arguments.length > 1)
2490 				assertString = arguments[1];
2491 
2492 			var v = assertExpression.interpret(sc).value;
2493 
2494 			if(!v)
2495 				throw new ScriptException(
2496 					var(this.toString() ~ " failed, got: " ~ assertExpression.toInterpretedString(sc)),
2497 					ScriptLocation(asrt.token.scriptFilename, asrt.token.lineNumber));
2498 
2499 			return InterpretResult(v, sc);
2500 		}
2501 
2502 		auto f = func.interpret(sc).value;
2503 		bool isMacro =  (f.payloadType == var.Type.Object && ((cast(MacroPrototype) f._payload._object) !is null));
2504 		var[] args;
2505 		foreach(argument; arguments)
2506 			if(argument !is null) {
2507 				if(isMacro) // macro, pass the argument as an expression object
2508 					args ~= argument.toScriptExpressionObject(sc);
2509 				else // regular function, interpret the arguments
2510 					args ~= argument.interpret(sc).value;
2511 			} else {
2512 				if(DefaultArgumentDummyObject is null)
2513 					DefaultArgumentDummyObject = new PrototypeObject();
2514 
2515 				var dummy;
2516 				dummy._object = DefaultArgumentDummyObject;
2517 
2518 				args ~= dummy;
2519 			}
2520 
2521 		var _this;
2522 		if(auto dve = cast(DotVarExpression) func) {
2523 			_this = dve.e1.interpret(sc).value;
2524 		} else if(auto ide = cast(IndexExpression) func) {
2525 			_this = ide.interpret(sc).value;
2526 		} else if(auto se = cast(SuperExpression) func) {
2527 			// super things are passed this object despite looking things up on the prototype
2528 			// so it calls the correct instance
2529 			_this = sc._getMember("this", true, true);
2530 		}
2531 
2532 		try {
2533 			return InterpretResult(f.apply(_this, args), sc);
2534 		} catch(DynamicTypeException dte) {
2535 			dte.callStack ~= loc;
2536 			throw dte;
2537 		} catch(ScriptException se) {
2538 			se.callStack ~= loc;
2539 			throw se;
2540 		}
2541 	}
2542 }
2543 
2544 ScriptToken requireNextToken(MyTokenStreamHere)(ref MyTokenStreamHere tokens, ScriptToken.Type type, string str = null, string file = __FILE__, size_t line = __LINE__) {
2545 	if(tokens.empty)
2546 		throw new ScriptCompileException("script ended prematurely", null, 0, file, line);
2547 	auto next = tokens.front;
2548 	if(next.type != type || (str !is null && next.str != str))
2549 		throw new ScriptCompileException("unexpected '"~next.str~"' while expecting " ~ to!string(type) ~ " " ~ str, next.scriptFilename, next.lineNumber, file, line);
2550 
2551 	tokens.popFront();
2552 	return next;
2553 }
2554 
2555 bool peekNextToken(MyTokenStreamHere)(MyTokenStreamHere tokens, ScriptToken.Type type, string str = null, string file = __FILE__, size_t line = __LINE__) {
2556 	if(tokens.empty)
2557 		return false;
2558 	auto next = tokens.front;
2559 	if(next.type != type || (str !is null && next.str != str))
2560 		return false;
2561 	return true;
2562 }
2563 
2564 VariableExpression parseVariableName(MyTokenStreamHere)(ref MyTokenStreamHere tokens) {
2565 	assert(!tokens.empty);
2566 	auto token = tokens.front;
2567 	if(token.type == ScriptToken.Type.identifier) {
2568 		tokens.popFront();
2569 		return new VariableExpression(token.str, ScriptLocation(token.scriptFilename, token.lineNumber));
2570 	}
2571 	throw new ScriptCompileException("Found "~token.str~" when expecting identifier", token.scriptFilename, token.lineNumber);
2572 }
2573 
2574 Expression parseDottedVariableName(MyTokenStreamHere)(ref MyTokenStreamHere tokens) {
2575 	assert(!tokens.empty);
2576 
2577 	auto ve = parseVariableName(tokens);
2578 
2579 	auto token = tokens.front;
2580 	if(token.type == ScriptToken.Type.symbol && token.str == ".") {
2581 		tokens.popFront();
2582 		return new DotVarExpression(ve, parseVariableName(tokens));
2583 	}
2584 	return ve;
2585 }
2586 
2587 
2588 Expression parsePart(MyTokenStreamHere)(ref MyTokenStreamHere tokens) {
2589 	if(!tokens.empty) {
2590 		auto token = tokens.front;
2591 
2592 		Expression e;
2593 
2594 		if(token.str == "super") {
2595 			tokens.popFront();
2596 			VariableExpression dot;
2597 			if(!tokens.empty && tokens.front.str == ".") {
2598 				tokens.popFront();
2599 				dot = parseVariableName(tokens);
2600 			}
2601 			e = new SuperExpression(dot);
2602 		}
2603 		else if(token.type == ScriptToken.Type.identifier)
2604 			e = parseVariableName(tokens);
2605 		else if(token.type == ScriptToken.Type.symbol && (token.str == "-" || token.str == "+" || token.str == "!" || token.str == "~")) {
2606 			auto op = token.str;
2607 			tokens.popFront();
2608 
2609 			e = parsePart(tokens);
2610 			if(op == "-")
2611 				e = new NegationExpression(e);
2612 			else if(op == "!")
2613 				e = new NotExpression(e);
2614 			else if(op == "~")
2615 				e = new BitFlipExpression(e);
2616 		} else {
2617 			tokens.popFront();
2618 
2619 			if(token.type == ScriptToken.Type.int_number)
2620 				e = new IntLiteralExpression(token.str);
2621 			else if(token.type == ScriptToken.Type.float_number)
2622 				e = new FloatLiteralExpression(token.str);
2623 			else if(token.type == ScriptToken.Type..string)
2624 				e = new StringLiteralExpression(token);
2625 			else if(token.type == ScriptToken.Type.symbol || token.type == ScriptToken.Type.keyword) {
2626 				switch(token.str) {
2627 					case "true":
2628 					case "false":
2629 						e = new BoolLiteralExpression(token.str);
2630 					break;
2631 					case "new":
2632 						// FIXME: why is this needed here? maybe it should be here instead of parseExpression
2633 						tokens.pushFront(token);
2634 						return parseExpression(tokens);
2635 					case "(":
2636 						//tokens.popFront();
2637 						auto parenthetical = new ParentheticalExpression(parseExpression(tokens));
2638 						tokens.requireNextToken(ScriptToken.Type.symbol, ")");
2639 
2640 						return parenthetical;
2641 					case "[":
2642 						// array literal
2643 						auto arr = new ArrayLiteralExpression();
2644 
2645 						bool first = true;
2646 						moreElements:
2647 						if(tokens.empty)
2648 							throw new ScriptCompileException("unexpected end of file when reading array literal", token.scriptFilename, token.lineNumber);
2649 
2650 						auto peek = tokens.front;
2651 						if(peek.type == ScriptToken.Type.symbol && peek.str == "]") {
2652 							tokens.popFront();
2653 							return arr;
2654 						}
2655 
2656 						if(!first)
2657 							tokens.requireNextToken(ScriptToken.Type.symbol, ",");
2658 						else
2659 							first = false;
2660 
2661 						arr.elements ~= parseExpression(tokens);
2662 
2663 						goto moreElements;
2664 					case "json!q{":
2665 					case "#{":
2666 						// json object literal
2667 						auto obj = new ObjectLiteralExpression();
2668 						/*
2669 							these go
2670 
2671 							string or ident which is the key
2672 							then a colon
2673 							then an expression which is the value
2674 
2675 							then optionally a comma
2676 
2677 							then either } which finishes it, or another key
2678 						*/
2679 
2680 						if(tokens.empty)
2681 							throw new ScriptCompileException("unexpected end of file when reading object literal", token.scriptFilename, token.lineNumber);
2682 
2683 						moreKeys:
2684 						auto key = tokens.front;
2685 						tokens.popFront();
2686 						if(key.type == ScriptToken.Type.symbol && key.str == "}") {
2687 							// all done!
2688 							e = obj;
2689 							break;
2690 						}
2691 						if(key.type != ScriptToken.Type..string && key.type != ScriptToken.Type.identifier) {
2692 							throw new ScriptCompileException("unexpected '"~key.str~"' when reading object literal", key.scriptFilename, key.lineNumber);
2693 
2694 						}
2695 
2696 						tokens.requireNextToken(ScriptToken.Type.symbol, ":");
2697 
2698 						auto value = parseExpression(tokens);
2699 						if(tokens.empty)
2700 							throw new ScriptCompileException("unclosed object literal", key.scriptFilename, key.lineNumber);
2701 
2702 						if(tokens.peekNextToken(ScriptToken.Type.symbol, ","))
2703 							tokens.popFront();
2704 
2705 						obj.elements[key.str] = value;
2706 
2707 						goto moreKeys;
2708 					case "macro":
2709 					case "function":
2710 						tokens.requireNextToken(ScriptToken.Type.symbol, "(");
2711 
2712 						auto exp = new FunctionLiteralExpression();
2713 						if(!tokens.peekNextToken(ScriptToken.Type.symbol, ")"))
2714 							exp.arguments = parseVariableDeclaration(tokens, ")");
2715 
2716 						tokens.requireNextToken(ScriptToken.Type.symbol, ")");
2717 
2718 						exp.functionBody = parseExpression(tokens);
2719 						exp.isMacro = token.str == "macro";
2720 
2721 						e = exp;
2722 					break;
2723 					case "null":
2724 						e = new NullLiteralExpression();
2725 					break;
2726 					case "mixin":
2727 					case "eval":
2728 						tokens.requireNextToken(ScriptToken.Type.symbol, "(");
2729 						e = new MixinExpression(parseExpression(tokens));
2730 						tokens.requireNextToken(ScriptToken.Type.symbol, ")");
2731 					break;
2732 					default:
2733 						goto unknown;
2734 				}
2735 			} else {
2736 				unknown:
2737 				throw new ScriptCompileException("unexpected '"~token.str~"' when reading ident", token.scriptFilename, token.lineNumber);
2738 			}
2739 		}
2740 
2741 		funcLoop: while(!tokens.empty) {
2742 			auto peek = tokens.front;
2743 			if(peek.type == ScriptToken.Type.symbol) {
2744 				switch(peek.str) {
2745 					case "(":
2746 						e = parseFunctionCall(tokens, e);
2747 					break;
2748 					case "[":
2749 						tokens.popFront();
2750 						auto e1 = parseExpression(tokens);
2751 						if(tokens.peekNextToken(ScriptToken.Type.symbol, "..")) {
2752 							tokens.popFront();
2753 							e = new SliceExpression(e, e1, parseExpression(tokens));
2754 						} else {
2755 							e = new IndexExpression(e, e1);
2756 						}
2757 						tokens.requireNextToken(ScriptToken.Type.symbol, "]");
2758 					break;
2759 					case ".":
2760 						tokens.popFront();
2761 						e = new DotVarExpression(e, parseVariableName(tokens));
2762 					break;
2763 					default:
2764 						return e; // we don't know, punt it elsewhere
2765 				}
2766 			} else return e; // again, we don't know, so just punt it down the line
2767 		}
2768 		return e;
2769 	}
2770 
2771 	throw new ScriptCompileException("Ran out of tokens when trying to parsePart", null, 0);
2772 }
2773 
2774 Expression parseArguments(MyTokenStreamHere)(ref MyTokenStreamHere tokens, Expression exp, ref Expression[] where) {
2775 	// arguments.
2776 	auto peek = tokens.front;
2777 	if(peek.type == ScriptToken.Type.symbol && peek.str == ")") {
2778 		tokens.popFront();
2779 		return exp;
2780 	}
2781 
2782 	moreArguments:
2783 
2784 	if(tokens.peekNextToken(ScriptToken.Type.keyword, "default")) {
2785 		tokens.popFront();
2786 		where ~= null;
2787 	} else {
2788 		where ~= parseExpression(tokens);
2789 	}
2790 
2791 	if(tokens.empty)
2792 		throw new ScriptCompileException("unexpected end of file when parsing call expression", peek.scriptFilename, peek.lineNumber);
2793 	peek = tokens.front;
2794 	if(peek.type == ScriptToken.Type.symbol && peek.str == ",") {
2795 		tokens.popFront();
2796 		goto moreArguments;
2797 	} else if(peek.type == ScriptToken.Type.symbol && peek.str == ")") {
2798 		tokens.popFront();
2799 		return exp;
2800 	} else
2801 		throw new ScriptCompileException("unexpected '"~peek.str~"' when reading argument list", peek.scriptFilename, peek.lineNumber);
2802 
2803 }
2804 
2805 Expression parseFunctionCall(MyTokenStreamHere)(ref MyTokenStreamHere tokens, Expression e) {
2806 	assert(!tokens.empty);
2807 	auto peek = tokens.front;
2808 	auto exp = new CallExpression(ScriptLocation(peek.scriptFilename, peek.lineNumber), e);
2809 	tokens.popFront();
2810 	if(tokens.empty)
2811 		throw new ScriptCompileException("unexpected end of file when parsing call expression", peek.scriptFilename, peek.lineNumber);
2812 	return parseArguments(tokens, exp, exp.arguments);
2813 }
2814 
2815 Expression parseFactor(MyTokenStreamHere)(ref MyTokenStreamHere tokens) {
2816 	auto e1 = parsePart(tokens);
2817 	loop: while(!tokens.empty) {
2818 		auto peek = tokens.front;
2819 
2820 		if(peek.type == ScriptToken.Type.symbol) {
2821 			switch(peek.str) {
2822 				case "*":
2823 				case "/":
2824 				case "%":
2825 					tokens.popFront();
2826 					e1 = new BinaryExpression(peek.str, e1, parsePart(tokens));
2827 				break;
2828 				default:
2829 					break loop;
2830 			}
2831 		} else throw new Exception("Got " ~ peek.str ~ " when expecting symbol");
2832 	}
2833 
2834 	return e1;
2835 }
2836 
2837 Expression parseAddend(MyTokenStreamHere)(ref MyTokenStreamHere tokens) {
2838 	auto e1 = parseFactor(tokens);
2839 	loop: while(!tokens.empty) {
2840 		auto peek = tokens.front;
2841 
2842 		if(peek.type == ScriptToken.Type.symbol) {
2843 			switch(peek.str) {
2844 				case "..": // possible FIXME
2845 				case ")": // possible FIXME
2846 				case "]": // possible FIXME
2847 				case "}": // possible FIXME
2848 				case ",": // possible FIXME these are passed on to the next thing
2849 				case ";":
2850 				case ":": // idk
2851 				case "?":
2852 					return e1;
2853 
2854 				case "|>":
2855 					tokens.popFront();
2856 					e1 = new PipelineExpression(ScriptLocation(peek.scriptFilename, peek.lineNumber), e1, parseFactor(tokens));
2857 				break;
2858 				case ".":
2859 					tokens.popFront();
2860 					e1 = new DotVarExpression(e1, parseVariableName(tokens));
2861 				break;
2862 				case "=":
2863 					tokens.popFront();
2864 					return new AssignExpression(e1, parseExpression(tokens));
2865 				case "&&": // thanks to mzfhhhh for fix
2866 				case "||":
2867 					tokens.popFront();
2868 					e1 = new BinaryExpression(peek.str, e1, parseExpression(tokens));
2869 					break;
2870 				case "~":
2871 					// FIXME: make sure this has the right associativity
2872 
2873 				case "&":
2874 				case "|":
2875 				case "^":
2876 
2877 				case "&=":
2878 				case "|=":
2879 				case "^=":
2880 
2881 				case "+":
2882 				case "-":
2883 
2884 				case "==":
2885 				case "!=":
2886 				case "<=":
2887 				case ">=":
2888 				case "<":
2889 				case ">":
2890 					tokens.popFront();
2891 					e1 = new BinaryExpression(peek.str, e1, parseAddend(tokens));
2892 					break;
2893 				case "+=":
2894 				case "-=":
2895 				case "*=":
2896 				case "/=":
2897 				case "~=":
2898 				case "%=":
2899 					tokens.popFront();
2900 					return new OpAssignExpression(peek.str[0..1], e1, parseExpression(tokens));
2901 				default:
2902 					throw new ScriptCompileException("Parse error, unexpected " ~ peek.str ~ " when looking for operator", peek.scriptFilename, peek.lineNumber);
2903 			}
2904 		//} else if(peek.type == ScriptToken.Type.identifier || peek.type == ScriptToken.Type.number) {
2905 			//return parseFactor(tokens);
2906 		} else
2907 			throw new ScriptCompileException("Parse error, unexpected '" ~ peek.str ~ "'", peek.scriptFilename, peek.lineNumber);
2908 	}
2909 
2910 	return e1;
2911 }
2912 
2913 Expression parseExpression(MyTokenStreamHere)(ref MyTokenStreamHere tokens, bool consumeEnd = false) {
2914 	Expression ret;
2915 	ScriptToken first;
2916 	string expectedEnd = ";";
2917 	//auto e1 = parseFactor(tokens);
2918 
2919 		while(tokens.peekNextToken(ScriptToken.Type.symbol, ";")) {
2920 			tokens.popFront();
2921 		}
2922 	if(!tokens.empty) {
2923 		first = tokens.front;
2924 		if(tokens.peekNextToken(ScriptToken.Type.symbol, "{")) {
2925 			auto start = tokens.front;
2926 			tokens.popFront();
2927 			auto e = parseCompoundStatement(tokens, start.lineNumber, "}").array;
2928 			ret = new ScopeExpression(e);
2929 			expectedEnd = null; // {} don't need ; at the end
2930 		} else if(tokens.peekNextToken(ScriptToken.Type.keyword, "scope")) {
2931 			auto start = tokens.front;
2932 			tokens.popFront();
2933 			tokens.requireNextToken(ScriptToken.Type.symbol, "(");
2934 
2935 			auto ident = tokens.requireNextToken(ScriptToken.Type.identifier);
2936 			switch(ident.str) {
2937 				case "success":
2938 				case "failure":
2939 				case "exit":
2940 				break;
2941 				default:
2942 					throw new ScriptCompileException("unexpected " ~ ident.str ~ ". valid scope(idents) are success, failure, and exit", ident.scriptFilename, ident.lineNumber);
2943 			}
2944 
2945 			tokens.requireNextToken(ScriptToken.Type.symbol, ")");
2946 
2947 			string i = "__scope_" ~ ident.str;
2948 			auto literal = new FunctionLiteralExpression();
2949 			literal.functionBody = parseExpression(tokens);
2950 
2951 			auto e = new OpAssignExpression("~", new VariableExpression(i), literal);
2952 			ret = e;
2953 		} else if(tokens.peekNextToken(ScriptToken.Type.symbol, "(")) {
2954 			auto start = tokens.front;
2955 			tokens.popFront();
2956 			auto parenthetical = new ParentheticalExpression(parseExpression(tokens));
2957 			tokens.requireNextToken(ScriptToken.Type.symbol, ")");
2958 			if(tokens.peekNextToken(ScriptToken.Type.symbol, "(")) {
2959 				// we have a function call, e.g. (test)()
2960 				ret = parseFunctionCall(tokens, parenthetical);
2961 			} else
2962 				ret = parenthetical;
2963 		} else if(tokens.peekNextToken(ScriptToken.Type.keyword, "new")) {
2964 			auto start = tokens.front;
2965 			tokens.popFront();
2966 
2967 			auto expr = parseDottedVariableName(tokens);
2968 			auto ne = new NewExpression(expr);
2969 			if(tokens.peekNextToken(ScriptToken.Type.symbol, "(")) {
2970 				tokens.popFront();
2971 				parseArguments(tokens, ne, ne.args);
2972 			}
2973 
2974 			ret = ne;
2975 		} else if(tokens.peekNextToken(ScriptToken.Type.keyword, "class")) {
2976 			auto start = tokens.front;
2977 			tokens.popFront();
2978 
2979 			Expression[] expressions;
2980 
2981 			// the way classes work is they are actually object literals with a different syntax. new foo then just copies it
2982 			/*
2983 				we create a prototype object
2984 				we create an object, with that prototype
2985 
2986 				set all functions and static stuff to the prototype
2987 				the rest goes to the object
2988 
2989 				the expression returns the object we made
2990 			*/
2991 
2992 			auto vars = new VariableDeclaration();
2993 			vars.identifiers = ["__proto", "__obj"];
2994 
2995 			auto staticScopeBacking = new PrototypeObject();
2996 			auto instanceScopeBacking = new PrototypeObject();
2997 
2998 			vars.initializers = [new ObjectLiteralExpression(staticScopeBacking), new ObjectLiteralExpression(instanceScopeBacking)];
2999 			expressions ~= vars;
3000 
3001 			 // FIXME: operators need to have their this be bound somehow since it isn't passed
3002 			 // OR the op rewrite could pass this
3003 
3004 			expressions ~= new AssignExpression(
3005 				new DotVarExpression(new VariableExpression("__obj"), new VariableExpression("prototype")),
3006 				new VariableExpression("__proto"));
3007 
3008 			auto classIdent = tokens.requireNextToken(ScriptToken.Type.identifier);
3009 
3010 			expressions ~= new AssignExpression(
3011 				new DotVarExpression(new VariableExpression("__proto"), new VariableExpression("__classname")),
3012 				new StringLiteralExpression(classIdent.str));
3013 
3014 			if(tokens.peekNextToken(ScriptToken.Type.symbol, ":")) {
3015 				tokens.popFront();
3016 				auto inheritFrom = tokens.requireNextToken(ScriptToken.Type.identifier);
3017 
3018 				// we set our prototype to the Foo prototype, thereby inheriting any static data that way (includes functions)
3019 				// the inheritFrom object itself carries instance  data that we need to copy onto our instance
3020 				expressions ~= new AssignExpression(
3021 					new DotVarExpression(new VariableExpression("__proto"), new VariableExpression("prototype")),
3022 					new DotVarExpression(new VariableExpression(inheritFrom.str), new VariableExpression("prototype")));
3023 
3024 				expressions ~= new AssignExpression(
3025 					new DotVarExpression(new VariableExpression("__proto"), new VariableExpression("super")),
3026 					new VariableExpression(inheritFrom.str)
3027 				);
3028 
3029 				// and copying the instance initializer from the parent
3030 				expressions ~= new ShallowCopyExpression(new VariableExpression("__obj"), new VariableExpression(inheritFrom.str));
3031 			}
3032 
3033 			tokens.requireNextToken(ScriptToken.Type.symbol, "{");
3034 
3035 			void addVarDecl(VariableDeclaration decl, string o) {
3036 				foreach(i, ident; decl.identifiers) {
3037 					// FIXME: make sure this goes on the instance, never the prototype!
3038 					expressions ~= new AssignExpression(
3039 						new DotVarExpression(
3040 							new VariableExpression(o),
3041 							new VariableExpression(ident),
3042 							false),
3043 						decl.initializers[i],
3044 						true // no overloading because otherwise an early opIndexAssign can mess up the decls
3045 					);
3046 				}
3047 			}
3048 
3049 			// FIXME: we could actually add private vars and just put them in this scope. maybe
3050 
3051 			while(!tokens.peekNextToken(ScriptToken.Type.symbol, "}")) {
3052 				if(tokens.peekNextToken(ScriptToken.Type.symbol, ";")) {
3053 					tokens.popFront();
3054 					continue;
3055 				}
3056 
3057 				if(tokens.peekNextToken(ScriptToken.Type.identifier, "this")) {
3058 					// ctor
3059 					tokens.popFront();
3060 					tokens.requireNextToken(ScriptToken.Type.symbol, "(");
3061 					auto args = parseVariableDeclaration(tokens, ")");
3062 					tokens.requireNextToken(ScriptToken.Type.symbol, ")");
3063 					auto bod = parseExpression(tokens);
3064 
3065 					expressions ~= new AssignExpression(
3066 						new DotVarExpression(
3067 							new VariableExpression("__proto"),
3068 							new VariableExpression("__ctor")),
3069 						new FunctionLiteralExpression(args, bod, staticScopeBacking));
3070 				} else if(tokens.peekNextToken(ScriptToken.Type.keyword, "var")) {
3071 					// instance variable
3072 					auto decl = parseVariableDeclaration(tokens, ";");
3073 					addVarDecl(decl, "__obj");
3074 				} else if(tokens.peekNextToken(ScriptToken.Type.keyword, "static")) {
3075 					// prototype var
3076 					tokens.popFront();
3077 					auto decl = parseVariableDeclaration(tokens, ";");
3078 					addVarDecl(decl, "__proto");
3079 				} else if(tokens.peekNextToken(ScriptToken.Type.keyword, "function")) {
3080 					// prototype function
3081 					tokens.popFront();
3082 					auto ident = tokens.requireNextToken(ScriptToken.Type.identifier);
3083 
3084 					tokens.requireNextToken(ScriptToken.Type.symbol, "(");
3085 					VariableDeclaration args;
3086 					if(!tokens.peekNextToken(ScriptToken.Type.symbol, ")"))
3087 						args = parseVariableDeclaration(tokens, ")");
3088 					tokens.requireNextToken(ScriptToken.Type.symbol, ")");
3089 					auto bod = parseExpression(tokens);
3090 
3091 					expressions ~= new FunctionDeclaration(
3092 						new DotVarExpression(
3093 							new VariableExpression("__proto"),
3094 							new VariableExpression(ident.str),
3095 							false),
3096 						ident.str,
3097 						new FunctionLiteralExpression(args, bod, staticScopeBacking)
3098 					);
3099 				} else throw new ScriptCompileException("Unexpected " ~ tokens.front.str ~ " when reading class decl", tokens.front.scriptFilename, tokens.front.lineNumber);
3100 			}
3101 
3102 			tokens.requireNextToken(ScriptToken.Type.symbol, "}");
3103 
3104 			// returning he object from the scope...
3105 			expressions ~= new VariableExpression("__obj");
3106 
3107 			auto scopeExpr = new ScopeExpression(expressions);
3108 			auto classVarExpr = new VariableDeclaration();
3109 			classVarExpr.identifiers = [classIdent.str];
3110 			classVarExpr.initializers = [scopeExpr];
3111 
3112 			ret = classVarExpr;
3113 		} else if(tokens.peekNextToken(ScriptToken.Type.keyword, "if")) {
3114 			tokens.popFront();
3115 			auto e = new IfExpression();
3116 			e.condition = parseExpression(tokens);
3117 			e.ifTrue = parseExpression(tokens);
3118 			if(tokens.peekNextToken(ScriptToken.Type.symbol, ";")) {
3119 				tokens.popFront();
3120 			}
3121 			if(tokens.peekNextToken(ScriptToken.Type.keyword, "else")) {
3122 				tokens.popFront();
3123 				e.ifFalse = parseExpression(tokens);
3124 			}
3125 			ret = e;
3126 		} else if(tokens.peekNextToken(ScriptToken.Type.keyword, "switch")) {
3127 			tokens.popFront();
3128 			auto e = new SwitchExpression();
3129 			tokens.requireNextToken(ScriptToken.Type.symbol, "(");
3130 			e.expr = parseExpression(tokens);
3131 			tokens.requireNextToken(ScriptToken.Type.symbol, ")");
3132 
3133 			tokens.requireNextToken(ScriptToken.Type.symbol, "{");
3134 
3135 			while(!tokens.peekNextToken(ScriptToken.Type.symbol, "}")) {
3136 
3137 				if(tokens.peekNextToken(ScriptToken.Type.keyword, "case")) {
3138 					auto start = tokens.front;
3139 					tokens.popFront();
3140 					auto c = new CaseExpression(parseExpression(tokens));
3141 					e.cases ~= c;
3142 					tokens.requireNextToken(ScriptToken.Type.symbol, ":");
3143 
3144 					while(!tokens.peekNextToken(ScriptToken.Type.keyword, "default") && !tokens.peekNextToken(ScriptToken.Type.keyword, "case") && !tokens.peekNextToken(ScriptToken.Type.symbol, "}")) {
3145 						c.expressions ~= parseStatement(tokens);
3146 						while(tokens.peekNextToken(ScriptToken.Type.symbol, ";"))
3147 							tokens.popFront();
3148 					}
3149 				} else if(tokens.peekNextToken(ScriptToken.Type.keyword, "default")) {
3150 					tokens.popFront();
3151 					tokens.requireNextToken(ScriptToken.Type.symbol, ":");
3152 
3153 					auto c = new CaseExpression(null);
3154 
3155 					while(!tokens.peekNextToken(ScriptToken.Type.keyword, "case") && !tokens.peekNextToken(ScriptToken.Type.symbol, "}")) {
3156 						c.expressions ~= parseStatement(tokens);
3157 						while(tokens.peekNextToken(ScriptToken.Type.symbol, ";"))
3158 							tokens.popFront();
3159 					}
3160 
3161 					e.cases ~= c;
3162 					e.default_ = c;
3163 				} else throw new ScriptCompileException("A switch statement must consists of cases and a default, nothing else ", tokens.front.scriptFilename, tokens.front.lineNumber);
3164 			}
3165 
3166 			tokens.requireNextToken(ScriptToken.Type.symbol, "}");
3167 			expectedEnd = "";
3168 
3169 			ret = e;
3170 
3171 		} else if(tokens.peekNextToken(ScriptToken.Type.keyword, "foreach")) {
3172 			tokens.popFront();
3173 			auto e = new ForeachExpression();
3174 			tokens.requireNextToken(ScriptToken.Type.symbol, "(");
3175 			e.decl = parseVariableDeclaration(tokens, ";");
3176 			tokens.requireNextToken(ScriptToken.Type.symbol, ";");
3177 			e.subject = parseExpression(tokens);
3178 
3179 			if(tokens.peekNextToken(ScriptToken.Type.symbol, "..")) {
3180 				tokens.popFront;
3181 				e.subject2 = parseExpression(tokens);
3182 			}
3183 
3184 			tokens.requireNextToken(ScriptToken.Type.symbol, ")");
3185 			e.loopBody = parseExpression(tokens);
3186 			ret = e;
3187 
3188 			expectedEnd = "";
3189 		} else if(tokens.peekNextToken(ScriptToken.Type.keyword, "cast")) {
3190 			tokens.popFront();
3191 			auto e = new CastExpression();
3192 
3193 			tokens.requireNextToken(ScriptToken.Type.symbol, "(");
3194 			e.type = tokens.requireNextToken(ScriptToken.Type.identifier).str;
3195 			if(tokens.peekNextToken(ScriptToken.Type.symbol, "[")) {
3196 				e.type ~= "[]";
3197 				tokens.popFront();
3198 				tokens.requireNextToken(ScriptToken.Type.symbol, "]");
3199 			}
3200 			tokens.requireNextToken(ScriptToken.Type.symbol, ")");
3201 
3202 			e.e1 = parseExpression(tokens);
3203 			ret = e;
3204 		} else if(tokens.peekNextToken(ScriptToken.Type.keyword, "for")) {
3205 			tokens.popFront();
3206 			auto e = new ForExpression();
3207 			tokens.requireNextToken(ScriptToken.Type.symbol, "(");
3208 			e.initialization = parseStatement(tokens, ";");
3209 
3210 			tokens.requireNextToken(ScriptToken.Type.symbol, ";");
3211 
3212 			e.condition = parseExpression(tokens);
3213 			tokens.requireNextToken(ScriptToken.Type.symbol, ";");
3214 			e.advancement = parseExpression(tokens);
3215 			tokens.requireNextToken(ScriptToken.Type.symbol, ")");
3216 			e.loopBody = parseExpression(tokens);
3217 
3218 			ret = e;
3219 
3220 			expectedEnd = "";
3221 		} else if(tokens.peekNextToken(ScriptToken.Type.keyword, "while")) {
3222 			tokens.popFront();
3223 			auto e = new ForExpression();
3224 			e.condition = parseExpression(tokens);
3225 			e.loopBody = parseExpression(tokens);
3226 			ret = e;
3227 			expectedEnd = "";
3228 		} else if(tokens.peekNextToken(ScriptToken.Type.keyword, "break") || tokens.peekNextToken(ScriptToken.Type.keyword, "continue")) {
3229 			auto token = tokens.front;
3230 			tokens.popFront();
3231 			ret = new LoopControlExpression(token.str);
3232 		} else if(tokens.peekNextToken(ScriptToken.Type.keyword, "return")) {
3233 			tokens.popFront();
3234 			Expression retVal;
3235 			if(tokens.peekNextToken(ScriptToken.Type.symbol, ";"))
3236 				retVal = new NullLiteralExpression();
3237 			else
3238 				retVal = parseExpression(tokens);
3239 			ret = new ReturnExpression(retVal);
3240 		} else if(tokens.peekNextToken(ScriptToken.Type.keyword, "throw")) {
3241 			auto token = tokens.front;
3242 			tokens.popFront();
3243 			ret = new ThrowExpression(parseExpression(tokens), token);
3244 		} else if(tokens.peekNextToken(ScriptToken.Type.keyword, "try")) {
3245 			auto tryToken = tokens.front;
3246 			auto e = new ExceptionBlockExpression();
3247 			tokens.popFront();
3248 			e.tryExpression = parseExpression(tokens, true);
3249 
3250 			bool hadFinally = false;
3251 			while(tokens.peekNextToken(ScriptToken.Type.keyword, "catch")) {
3252 				if(hadFinally)
3253 					throw new ScriptCompileException("Catch must come before finally", tokens.front.scriptFilename, tokens.front.lineNumber);
3254 				tokens.popFront();
3255 				tokens.requireNextToken(ScriptToken.Type.symbol, "(");
3256 				if(tokens.peekNextToken(ScriptToken.Type.keyword, "var"))
3257 					tokens.popFront();
3258 
3259 				auto ident = tokens.requireNextToken(ScriptToken.Type.identifier);
3260 				if(tokens.empty) throw new ScriptCompileException("Catch specifier not closed", ident.scriptFilename, ident.lineNumber);
3261 				auto next = tokens.front;
3262 				if(next.type == ScriptToken.Type.identifier) {
3263 					auto type = ident;
3264 					ident = next;
3265 
3266 					e.catchVarTypeSpecifiers ~= type.str;
3267 					e.catchVarDecls ~= ident.str;
3268 
3269 					tokens.popFront();
3270 
3271 					tokens.requireNextToken(ScriptToken.Type.symbol, ")");
3272 				} else {
3273 					e.catchVarTypeSpecifiers ~= null;
3274 					e.catchVarDecls ~= ident.str;
3275 					if(next.type != ScriptToken.Type.symbol || next.str != ")")
3276 						throw new ScriptCompileException("ss Unexpected " ~ next.str ~ " when expecting ')'", next.scriptFilename, next.lineNumber);
3277 					tokens.popFront();
3278 				}
3279 				e.catchExpressions ~= parseExpression(tokens);
3280 			}
3281 			while(tokens.peekNextToken(ScriptToken.Type.keyword, "finally")) {
3282 				hadFinally = true;
3283 				tokens.popFront();
3284 				e.finallyExpressions ~= parseExpression(tokens);
3285 			}
3286 
3287 			//if(!hadSomething)
3288 				//throw new ScriptCompileException("Parse error, missing finally or catch after try", tryToken.lineNumber);
3289 
3290 			ret = e;
3291 		} else {
3292 			ret = parseAddend(tokens);
3293 		}
3294 
3295 		if(!tokens.empty && tokens.peekNextToken(ScriptToken.Type.symbol, "?")) {
3296 			auto e = new TernaryExpression();
3297 			e.condition = ret;
3298 			tokens.requireNextToken(ScriptToken.Type.symbol, "?");
3299 			e.ifTrue = parseExpression(tokens);
3300 			tokens.requireNextToken(ScriptToken.Type.symbol, ":");
3301 			e.ifFalse = parseExpression(tokens);
3302 			ret = e;
3303 		}
3304 	} else {
3305 		//assert(0);
3306 		// return null;
3307 		throw new ScriptCompileException("Parse error, unexpected end of input when reading expression", null, 0);//token.lineNumber);
3308 	}
3309 
3310 	//writeln("parsed expression ", ret.toString());
3311 
3312 	if(expectedEnd.length && tokens.empty && consumeEnd) // going loose on final ; at the end of input for repl convenience
3313 		throw new ScriptCompileException("Parse error, unexpected end of input when reading expression, expecting " ~ expectedEnd, first.scriptFilename, first.lineNumber);
3314 
3315 	if(expectedEnd.length && consumeEnd) {
3316 		 if(tokens.peekNextToken(ScriptToken.Type.symbol, expectedEnd))
3317 			 tokens.popFront();
3318 		// FIXME
3319 		//if(tokens.front.type != ScriptToken.Type.symbol && tokens.front.str != expectedEnd)
3320 			//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);
3321 	//	tokens = tokens[1 .. $];
3322 	}
3323 
3324 	return ret;
3325 }
3326 
3327 VariableDeclaration parseVariableDeclaration(MyTokenStreamHere)(ref MyTokenStreamHere tokens, string termination) {
3328 	VariableDeclaration decl = new VariableDeclaration();
3329 	bool equalOk;
3330 	anotherVar:
3331 	assert(!tokens.empty);
3332 
3333 	auto firstToken = tokens.front;
3334 
3335 	// var a, var b is acceptable
3336 	if(tokens.peekNextToken(ScriptToken.Type.keyword, "var"))
3337 		tokens.popFront();
3338 
3339 	equalOk= true;
3340 	if(tokens.empty)
3341 		throw new ScriptCompileException("Parse error, dangling var at end of file", firstToken.scriptFilename, firstToken.lineNumber);
3342 
3343 	string type;
3344 
3345 	auto next = tokens.front;
3346 	tokens.popFront;
3347 	if(tokens.empty)
3348 		throw new ScriptCompileException("Parse error, incomplete var declaration at end of file", firstToken.scriptFilename, firstToken.lineNumber);
3349 	auto next2 = tokens.front;
3350 
3351 	ScriptToken typeSpecifier;
3352 
3353 	/* if there's two identifiers back to back, it is a type specifier. otherwise just a name */
3354 
3355 	if(next.type == ScriptToken.Type.identifier && next2.type == ScriptToken.Type.identifier) {
3356 		// type ident;
3357 		typeSpecifier = next;
3358 		next = next2;
3359 		// get past the type
3360 		tokens.popFront();
3361 	} else {
3362 		// no type, just carry on with the next thing
3363 	}
3364 
3365 	Expression initializer;
3366 	auto identifier = next;
3367 	if(identifier.type != ScriptToken.Type.identifier)
3368 		throw new ScriptCompileException("Parse error, found '"~identifier.str~"' when expecting var identifier", identifier.scriptFilename, identifier.lineNumber);
3369 
3370 	//tokens.popFront();
3371 
3372 	tryTermination:
3373 	if(tokens.empty)
3374 		throw new ScriptCompileException("Parse error, missing ; after var declaration at end of file", firstToken.scriptFilename, firstToken.lineNumber);
3375 
3376 	auto peek = tokens.front;
3377 	if(peek.type == ScriptToken.Type.symbol) {
3378 		if(peek.str == "=") {
3379 			if(!equalOk)
3380 				throw new ScriptCompileException("Parse error, unexpected '"~identifier.str~"' after reading var initializer", peek.scriptFilename, peek.lineNumber);
3381 			equalOk = false;
3382 			tokens.popFront();
3383 			initializer = parseExpression(tokens);
3384 			goto tryTermination;
3385 		} else if(peek.str == ",") {
3386 			tokens.popFront();
3387 			decl.identifiers ~= identifier.str;
3388 			decl.initializers ~= initializer;
3389 			decl.typeSpecifiers ~= typeSpecifier.str;
3390 			goto anotherVar;
3391 		} else if(peek.str == termination) {
3392 			decl.identifiers ~= identifier.str;
3393 			decl.initializers ~= initializer;
3394 			decl.typeSpecifiers ~= typeSpecifier.str;
3395 			//tokens = tokens[1 .. $];
3396 			// we're done!
3397 		} else
3398 			throw new ScriptCompileException("Parse error, unexpected '"~peek.str~"' when reading var declaration symbol", peek.scriptFilename, peek.lineNumber);
3399 	} else
3400 		throw new ScriptCompileException("Parse error, unexpected non-symbol '"~peek.str~"' when reading var declaration", peek.scriptFilename, peek.lineNumber);
3401 
3402 	return decl;
3403 }
3404 
3405 Expression parseStatement(MyTokenStreamHere)(ref MyTokenStreamHere tokens, string terminatingSymbol = null) {
3406 	skip: // FIXME
3407 	if(tokens.empty)
3408 		return null;
3409 
3410 	if(terminatingSymbol !is null && (tokens.front.type == ScriptToken.Type.symbol && tokens.front.str == terminatingSymbol))
3411 		return null; // we're done
3412 
3413 	auto token = tokens.front;
3414 
3415 	// tokens = tokens[1 .. $];
3416 	final switch(token.type) {
3417 		case ScriptToken.Type.keyword:
3418 		case ScriptToken.Type.symbol:
3419 			switch(token.str) {
3420 				// assert
3421 				case "assert":
3422 					tokens.popFront();
3423 
3424 					return parseFunctionCall(tokens, new AssertKeyword(token));
3425 
3426 				//break;
3427 				// declarations
3428 				case "var":
3429 					return parseVariableDeclaration(tokens, ";");
3430 				case ";":
3431 					tokens.popFront(); // FIXME
3432 					goto skip;
3433 				// literals
3434 				case "function":
3435 				case "macro":
3436 					// function can be a literal, or a declaration.
3437 
3438 					tokens.popFront(); // we're peeking ahead
3439 
3440 					if(tokens.peekNextToken(ScriptToken.Type.identifier)) {
3441 						// decl style, rewrite it into var ident = function style
3442 						// tokens.popFront(); // skipping the function keyword // already done above with the popFront
3443 						auto ident = tokens.front;
3444 						tokens.popFront();
3445 
3446 						tokens.requireNextToken(ScriptToken.Type.symbol, "(");
3447 
3448 						auto exp = new FunctionLiteralExpression();
3449 						if(!tokens.peekNextToken(ScriptToken.Type.symbol, ")"))
3450 							exp.arguments = parseVariableDeclaration(tokens, ")");
3451 						tokens.requireNextToken(ScriptToken.Type.symbol, ")");
3452 
3453 						exp.functionBody = parseExpression(tokens);
3454 
3455 						// a ; should NOT be required here btw
3456 
3457 						exp.isMacro = token.str == "macro";
3458 
3459 						auto e = new FunctionDeclaration(null, ident.str, exp);
3460 
3461 						return e;
3462 
3463 					} else {
3464 						tokens.pushFront(token); // put it back since everyone expects us to have done that
3465 						goto case; // handle it like any other expression
3466 					}
3467 
3468 				case "true":
3469 				case "false":
3470 
3471 				case "json!{":
3472 				case "#{":
3473 				case "[":
3474 				case "(":
3475 				case "null":
3476 
3477 				// scope
3478 				case "{":
3479 				case "scope":
3480 
3481 				case "cast":
3482 
3483 				// classes
3484 				case "class":
3485 				case "new":
3486 
3487 				case "super":
3488 
3489 				// flow control
3490 				case "if":
3491 				case "while":
3492 				case "for":
3493 				case "foreach":
3494 				case "switch":
3495 
3496 				// exceptions
3497 				case "try":
3498 				case "throw":
3499 
3500 				// evals
3501 				case "eval":
3502 				case "mixin":
3503 
3504 				// flow
3505 				case "continue":
3506 				case "break":
3507 				case "return":
3508 
3509 					return parseExpression(tokens);
3510 				// unary prefix operators
3511 				case "!":
3512 				case "~":
3513 				case "-":
3514 					return parseExpression(tokens);
3515 
3516 				// BTW add custom object operator overloading to struct var
3517 				// and custom property overloading to PrototypeObject
3518 
3519 				default:
3520 					// whatever else keyword or operator related is actually illegal here
3521 					throw new ScriptCompileException("Parse error, unexpected " ~ token.str, token.scriptFilename, token.lineNumber);
3522 			}
3523 		// break;
3524 		case ScriptToken.Type.identifier:
3525 		case ScriptToken.Type..string:
3526 		case ScriptToken.Type.int_number:
3527 		case ScriptToken.Type.float_number:
3528 			return parseExpression(tokens);
3529 	}
3530 
3531 	assert(0);
3532 }
3533 
3534 struct CompoundStatementRange(MyTokenStreamHere) {
3535 	// FIXME: if MyTokenStreamHere is not a class, this fails!
3536 	MyTokenStreamHere tokens;
3537 	int startingLine;
3538 	string terminatingSymbol;
3539 	bool isEmpty;
3540 
3541 	this(MyTokenStreamHere t, int startingLine, string terminatingSymbol) {
3542 		tokens = t;
3543 		this.startingLine = startingLine;
3544 		this.terminatingSymbol = terminatingSymbol;
3545 		popFront();
3546 	}
3547 
3548 	bool empty() {
3549 		return isEmpty;
3550 	}
3551 
3552 	Expression got;
3553 
3554 	Expression front() {
3555 		return got;
3556 	}
3557 
3558 	void popFront() {
3559 		while(!tokens.empty && (terminatingSymbol is null || !(tokens.front.type == ScriptToken.Type.symbol && tokens.front.str == terminatingSymbol))) {
3560 			auto n = parseStatement(tokens, terminatingSymbol);
3561 			if(n is null)
3562 				continue;
3563 			got = n;
3564 			return;
3565 		}
3566 
3567 		if(tokens.empty && terminatingSymbol !is null) {
3568 			throw new ScriptCompileException("Reached end of file while trying to reach matching " ~ terminatingSymbol, null, startingLine);
3569 		}
3570 
3571 		if(terminatingSymbol !is null) {
3572 			assert(tokens.front.str == terminatingSymbol);
3573 			tokens.skipNext++;
3574 		}
3575 
3576 		isEmpty = true;
3577 	}
3578 }
3579 
3580 CompoundStatementRange!MyTokenStreamHere
3581 //Expression[]
3582 parseCompoundStatement(MyTokenStreamHere)(ref MyTokenStreamHere tokens, int startingLine = 1, string terminatingSymbol = null) {
3583 	return (CompoundStatementRange!MyTokenStreamHere(tokens, startingLine, terminatingSymbol));
3584 }
3585 
3586 auto parseScript(MyTokenStreamHere)(MyTokenStreamHere tokens) {
3587 	/*
3588 		the language's grammar is simple enough
3589 
3590 		maybe flow control should be statements though lol. they might not make sense inside.
3591 
3592 		Expressions:
3593 			var identifier;
3594 			var identifier = initializer;
3595 			var identifier, identifier2
3596 
3597 			return expression;
3598 			return ;
3599 
3600 			json!{ object literal }
3601 
3602 			{ scope expression }
3603 
3604 			[ array literal ]
3605 			other literal
3606 			function (arg list) other expression
3607 
3608 			( expression ) // parenthesized expression
3609 			operator expression  // unary expression
3610 
3611 			expression operator expression // binary expression
3612 			expression (other expression... args) // function call
3613 
3614 		Binary Operator precedence :
3615 			. []
3616 			* /
3617 			+ -
3618 			~
3619 			< > == !=
3620 			=
3621 	*/
3622 
3623 	return parseCompoundStatement(tokens);
3624 }
3625 
3626 var interpretExpressions(ExpressionStream)(ExpressionStream expressions, PrototypeObject variables) if(is(ElementType!ExpressionStream == Expression)) {
3627 	assert(variables !is null);
3628 	var ret;
3629 	foreach(expression; expressions) {
3630 		auto res = expression.interpret(variables);
3631 		variables = res.sc;
3632 		ret = res.value;
3633 	}
3634 	return ret;
3635 }
3636 
3637 var interpretStream(MyTokenStreamHere)(MyTokenStreamHere tokens, PrototypeObject variables) if(is(ElementType!MyTokenStreamHere == ScriptToken)) {
3638 	assert(variables !is null);
3639 	// this is an entry point that all others lead to, right before getting to interpretExpressions...
3640 
3641 	return interpretExpressions(parseScript(tokens), variables);
3642 }
3643 
3644 var interpretStream(MyTokenStreamHere)(MyTokenStreamHere tokens, var variables) if(is(ElementType!MyTokenStreamHere == ScriptToken)) {
3645 	return interpretStream(tokens,
3646 		(variables.payloadType() == var.Type.Object && variables._payload._object !is null) ? variables._payload._object : new PrototypeObject());
3647 }
3648 
3649 var interpret(string code, PrototypeObject variables, string scriptFilename = null) {
3650 	assert(variables !is null);
3651 	return interpretStream(lexScript(repeat(code, 1), scriptFilename), variables);
3652 }
3653 
3654 /++
3655 	This is likely your main entry point to the interpreter. It will interpret the script code
3656 	given, with the given global variable object (which will be modified by the script, meaning
3657 	you can pass it to subsequent calls to `interpret` to store context), and return the result
3658 	of the last expression given.
3659 
3660 	---
3661 	var globals = var.emptyObject; // the global object must be an object of some type
3662 	globals.x = 10;
3663 	globals.y = 15;
3664 	// you can also set global functions through this same style, etc
3665 
3666 	var result = interpret(`x + y`, globals);
3667 	assert(result == 25);
3668 	---
3669 
3670 
3671 	$(TIP
3672 		If you want to just call a script function, interpret the definition of it,
3673 		then just call it through the `globals` object you passed to it.
3674 
3675 		---
3676 		var globals = var.emptyObject;
3677 		interpret(`function foo(name) { return "hello, " ~ name ~ "!"; }`, globals);
3678 		var result = globals.foo()("world");
3679 		assert(result == "hello, world!");
3680 		---
3681 	)
3682 
3683 	Params:
3684 		code = the script source code you want to interpret
3685 		scriptFilename = the filename of the script, if you want to provide it. Gives nicer error messages if you provide one.
3686 		variables = The global object of the script context. It will be modified by the user script.
3687 
3688 	Returns:
3689 		the result of the last expression evaluated by the script engine
3690 +/
3691 var interpret(string code, var variables = null, string scriptFilename = null, string file = __FILE__, size_t line = __LINE__) {
3692 	if(scriptFilename is null)
3693 		scriptFilename = file ~ "@" ~ to!string(line);
3694 	return interpretStream(
3695 		lexScript(repeat(code, 1), scriptFilename),
3696 		(variables.payloadType() == var.Type.Object && variables._payload._object !is null) ? variables._payload._object : new PrototypeObject());
3697 }
3698 
3699 ///
3700 var interpretFile(File file, var globals) {
3701 	import std.algorithm;
3702 	return interpretStream(lexScript(file.byLine.map!((a) => a.idup), file.name),
3703 		(globals.payloadType() == var.Type.Object && globals._payload._object !is null) ? globals._payload._object : new PrototypeObject());
3704 }
3705 
3706 /// Enhanced repl uses arsd.terminal for better ux. Added April 26, 2020. Default just uses std.stdio.
3707 void repl(bool enhanced = false)(var globals) {
3708 	static if(enhanced) {
3709 		import arsd.terminal;
3710 		Terminal terminal = Terminal(ConsoleOutputMode.linear);
3711 		auto lines() {
3712 			struct Range {
3713 				string line;
3714 				string front() { return line; }
3715 				bool empty() { return line is null; }
3716 				void popFront() { line = terminal.getline(": "); terminal.writeln(); }
3717 			}
3718 			Range r;
3719 			r.popFront();
3720 			return r;
3721 			
3722 		}
3723 
3724 		void writeln(T...)(T t) {
3725 			terminal.writeln(t);
3726 			terminal.flush();
3727 		}
3728 	} else {
3729 		import std.stdio;
3730 		auto lines() { return stdin.byLine; }
3731 	}
3732 
3733 	bool exited;
3734 	if(globals == null)
3735 		globals = var.emptyObject;
3736 	globals.exit = () { exited = true; };
3737 
3738 	import std.algorithm;
3739 	auto variables = (globals.payloadType() == var.Type.Object && globals._payload._object !is null) ? globals._payload._object : new PrototypeObject();
3740 
3741 	// we chain to ensure the priming popFront succeeds so we don't throw here
3742 	auto tokens = lexScript(
3743 		chain(["var __skipme = 0;"], map!((a) => a.idup)(lines))
3744 	, "stdin");
3745 	auto expressions = parseScript(tokens);
3746 
3747 	while(!exited && !expressions.empty) {
3748 		try {
3749 			expressions.popFront;
3750 			auto expression = expressions.front;
3751 			auto res = expression.interpret(variables);
3752 			variables = res.sc;
3753 			writeln(">>> ", res.value);
3754 		} catch(ScriptCompileException e) {
3755 			writeln("*+* ", e.msg);
3756 			tokens.popFront(); // skip the one we threw on...
3757 		} catch(Exception e) {
3758 			writeln("*** ", e.msg);
3759 		}
3760 	}
3761 }
3762 
3763 class ScriptFunctionMetadata : VarMetadata {
3764 	FunctionLiteralExpression fle;
3765 	this(FunctionLiteralExpression fle) {
3766 		this.fle = fle;
3767 	}
3768 
3769 	string convertToString() {
3770 		return fle.toString();
3771 	}
3772 }
Suggestion Box / Bug Report