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