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