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