The OpenD Programming Language

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