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