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