1 /**
2 This module implements the Interpreter class, the main class used by host applications to run scripts
3 
4 ────────────────────────────────────────────────────────────────────────────────
5 
6 Copyright (C) 2021 pillager86.rf.gd
7 
8 This program is free software: you can redistribute it and/or modify it under 
9 the terms of the GNU General Public License as published by the Free Software 
10 Foundation, either version 3 of the License, or (at your option) any later 
11 version.
12 
13 This program is distributed in the hope that it will be useful, but WITHOUT ANY
14 WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
15 PARTICULAR PURPOSE.  See the GNU General Public License for more details.
16 
17 You should have received a copy of the GNU General Public License along with 
18 this program.  If not, see <https://www.gnu.org/licenses/>.
19 */
20 module mildew.interpreter;
21 
22 import std.typecons;
23 import std.variant;
24 
25 import mildew.compiler;
26 import mildew.environment;
27 import mildew.exceptions: ScriptRuntimeException;
28 import mildew.lexer: Token, Lexer;
29 import mildew.nodes;
30 import mildew.parser;
31 import mildew.types;
32 import mildew.visitors;
33 import mildew.vm;
34 
35 /**
36  * This is the main interface for the host application to interact with scripts. It can run scripts in
37  * interpreted mode by walking the syntax tree (deprecated), or if given the useVM option, can run a compiler to
38  * compile scripts into bytecode which is then executed by a VirtualMachine. Note that interpreted mode
39  * is deprecated.
40  */
41 class Interpreter : INodeVisitor
42 {
43 public:
44 
45     /**
46      * Constructs a new Interpreter with a global environment. Note that all calls to evaluate
47      * run in a new environment below the global environment. This allows keywords such as let and const
48      * to not pollute the global namespace. However, scripts can use var to declare variables that
49      * are global.
50      * Params:
51      *  useVM = whether or not compilation to bytecode and the VM should be used instead of tree walking.
52      *  printVMDebugInfo = if useVM is true, this option prints very verbose data while executing bytecode.
53      */
54     this(bool useVM = false, bool printVMDebugInfo = true)
55     {
56         _globalEnvironment = new Environment(this);
57         _currentEnvironment = _globalEnvironment;
58         if(useVM)
59         {
60             _compiler = new Compiler();
61             _printVMDebugInfo = printVMDebugInfo;
62             _vm = new VirtualMachine(_globalEnvironment);
63         }
64     }
65 
66     /**
67      * Initializes the Mildew standard library, such as Object, Math, and console namespaces. This
68      * is optional and is not called by the constructor. For a script to use these methods such as
69      * console.log this must be called first. It is also possible to only call specific
70      * initialize*Library functions and/or force set globals from them to UNDEFINED.
71      */
72     void initializeStdlib()
73     {
74         import mildew.types.bindings: initializeTypesLibrary;
75         import mildew.stdlib.global: initializeGlobalLibrary;
76         import mildew.stdlib.console: initializeConsoleLibrary;
77         import mildew.stdlib.date: initializeDateLibrary;
78         import mildew.stdlib.generator: initializeGeneratorLibrary;
79         import mildew.stdlib.math: initializeMathLibrary;
80         import mildew.stdlib.regexp: initializeRegExpLibrary;
81         import mildew.stdlib.system: initializeSystemLib;
82         initializeTypesLibrary(this);
83         initializeGlobalLibrary(this);
84         initializeConsoleLibrary(this);
85         initializeDateLibrary(this);
86         initializeGeneratorLibrary(this);
87         initializeMathLibrary(this);
88         initializeRegExpLibrary(this);
89         initializeSystemLib(this);
90     }
91 
92     /**
93      * Calls a script function. Can throw ScriptRuntimeException.
94      */
95     deprecated ScriptAny callFunction(ScriptFunction func, ScriptAny thisObj, ScriptAny[] args, bool useVM=false)
96     {
97         auto vr = callFn(func, thisObj, args, false, useVM);
98         if(vr.exception)
99             throw vr.exception;
100         return vr.result;
101     }
102 
103     /**
104      * This is the main entry point for evaluating a script program. If the useVM option was set in the
105      * constructor, bytecode compilation and execution will be used, otherwise tree walking.
106      * Params:
107      *  code = This is the source code of a script to be executed.
108      *  printDisasm = If VM mode is set, print the disassembly of bytecode before running if true.
109      * Returns:
110      *  If the script has a return statement with an expression, this value will be the result of that expression
111      *  otherwise it will be ScriptAny.UNDEFINED
112      */
113     ScriptAny evaluate(in string code, bool printDisasm=false)
114     {
115         if(_compiler is null)
116         {
117             auto lexer = Lexer(code);
118             auto tokens = lexer.tokenize();
119             auto parser = Parser(tokens);
120             auto programBlock = parser.parseProgram();
121             auto vr = programBlock.accept(this).get!VisitResult;
122             if(vr.exception !is null)
123                 throw vr.exception;
124             if(vr.returnFlag)
125                 return vr.result;
126             return ScriptAny.UNDEFINED;
127         }
128         else
129         {
130             auto chunk = _compiler.compile(code);
131             if(printDisasm)
132                 _vm.printChunk(chunk, true);
133 
134             return _vm.run(chunk, _printVMDebugInfo);
135         }
136     }
137 
138     /**
139      * Evaluates a file that can be either binary bytecode or textual source code.
140      * Params:
141      *  pathName = the location of the code file in the file system.
142      * Returns:
143      *  The result of evaluating the file, undefined if no return statement.
144      */
145     ScriptAny evaluateFile(in string pathName, bool printDisasm=false)
146     {
147         import std.stdio: File, writefln;
148         import mildew.util.encode: decode;
149 
150         File inputFile = File(pathName, "rb");
151         auto raw = new ubyte[inputFile.size];
152         raw = inputFile.rawRead(raw);
153         if(raw.length > 0 && raw[0] == 0x01)
154         {
155             if(_vm is null)
156                 throw new ScriptRuntimeException("This file can only be run in VM mode");
157             auto chunk = Chunk.deserialize(raw);
158             if(printDisasm)
159                 _vm.printChunk(chunk);
160             return _vm.run(chunk, _printVMDebugInfo);
161         }
162         else
163         {
164             auto source = cast(string)raw;
165             return evaluate(source, printDisasm);
166         }
167     }
168 
169     // TODO: Read script from file
170 
171     // TODO: Create an evaluate function with default exception handling with file name info
172 
173     /**
174      * Sets a global variable or constant without checking whether or not the variable or const was already
175      * declared. This is used by host applications to define custom functions or objects.
176      * Params:
177      *  name = The name of the variable.
178      *  value = The value the variable should be set to.
179      *  isConst = Whether or not the script can overwrite the global.
180      */
181     void forceSetGlobal(T)(in string name, T value, bool isConst=false)
182     {
183         _globalEnvironment.forceSetVarOrConst(name, ScriptAny(value), isConst);
184     }
185 
186     /**
187      * Unsets a variable or constant in the global environment. Used by host applications to remove
188      * items that were loaded by the standard library load functions. Specific functions of
189      * script classes can be removed by modifying the "prototype" field of their constructor.
190      */
191     void forceUnsetGlobal(in string name)
192     {
193         _globalEnvironment.forceRemoveVarOrConst(name);
194     }
195 
196     /// whether or not debug info should be printed between each VM instruction
197     bool printVMDebugInfo() const 
198     {
199         return _printVMDebugInfo;
200     }
201 	
202     /// whether or not VM option was set when created
203     bool usingVM() const 
204     {
205         return _vm !is null;
206     }
207 
208 // The next functions are internal and deprecated and only public due to D language constraints.
209 
210 	/// extract a VisitResult from a LiteralNode
211 	deprecated Variant visitLiteralNode(LiteralNode lnode)
212 	{
213 		return Variant(VisitResult(lnode.value));
214 	}
215 	
216     /// handles function literals
217     deprecated Variant visitFunctionLiteralNode(FunctionLiteralNode flnode)
218     {
219         auto func = new ScriptFunction("<anonymous function>", flnode.argList, flnode.statements, _currentEnvironment);
220         return Variant(VisitResult(ScriptAny(func)));
221     }
222 
223     /// handle lambda
224     deprecated Variant visitLambdaNode(LambdaNode lnode)
225     {
226         FunctionLiteralNode flnode;
227         if(lnode.returnExpression)
228         {
229             flnode = new FunctionLiteralNode(lnode.argList, [
230                     new ReturnStatementNode(lnode.arrowToken.position.line, lnode.returnExpression)
231                 ], "<lambda>", false);
232         }
233         else
234         {
235             flnode = new FunctionLiteralNode(lnode.argList, lnode.statements, "<lambda>", false);
236         }
237         return flnode.accept(this);
238     }
239 
240     /// handle template literal nodes
241     deprecated Variant visitTemplateStringNode(TemplateStringNode tsnode)
242     {
243         VisitResult vr;
244         string result = "";
245         foreach(node ; tsnode.nodes)
246         {
247             vr = node.accept(this).get!VisitResult;
248             if(vr.exception)
249                 return Variant(vr);
250             result ~= vr.result.toString();
251         }
252         return Variant(VisitResult(result));
253     }
254 
255 	/// return an array from an array literal node
256 	deprecated Variant visitArrayLiteralNode(ArrayLiteralNode alnode)
257 	{
258 		VisitResult vr;
259         ScriptAny[] values = [];
260         foreach(expression ; alnode.valueNodes)
261         {
262             vr = expression.accept(this).get!VisitResult;
263             if(vr.exception !is null)
264                 return Variant(vr);
265             values ~= vr.result;
266         }
267         vr.result = values;
268         return Variant(vr);     
269 	}
270 	
271 	/// generates object from object literal node
272 	deprecated Variant visitObjectLiteralNode(ObjectLiteralNode olnode)
273 	{
274 		if(olnode.keys.length != olnode.valueNodes.length)
275             throw new Exception("Error with object literal node");
276         ScriptAny[] vals = [];
277         VisitResult vr;
278         foreach(valueNode ; olnode.valueNodes)
279         {
280             vr = valueNode.accept(this).get!VisitResult;
281             if(vr.exception !is null)
282                 return Variant(vr);
283             vals ~= vr.result;
284         }
285         auto obj = new ScriptObject("object", null, null);
286         for(size_t i = 0; i < olnode.keys.length; ++i)
287         {
288             obj.assignField(olnode.keys[i], vals[i]);
289         }
290         vr.result = obj;
291         return Variant(vr);
292 	}
293 	
294     /// handle class literals
295 	deprecated Variant visitClassLiteralNode(ClassLiteralNode clnode)
296 	{
297 		VisitResult vr;
298 
299         try 
300         {
301             vr.result = clnode.classDefinition.create(_currentEnvironment);
302         }
303         catch(ScriptRuntimeException ex)
304         {
305             vr.exception = ex;
306         }
307 		        
308         return Variant(vr);
309 	}
310 	
311 	/// processes binary operations including assignment
312 	deprecated Variant visitBinaryOpNode(BinaryOpNode bonode)
313 	{
314 		import std.conv: to;
315 
316         auto lhsResult = bonode.leftNode.accept(this).get!VisitResult;
317         auto rhsResult = bonode.rightNode.accept(this).get!VisitResult;
318 
319         if(lhsResult.exception !is null)
320             return Variant(lhsResult);
321         if(rhsResult.exception !is null)
322             return Variant(rhsResult);
323 
324         VisitResult finalResult;
325 
326         if(bonode.opToken.isAssignmentOperator)
327         {
328             // if an anonymous class or function is being assigned we need to update its name
329             if(rhsResult.result.type == ScriptAny.Type.FUNCTION)
330             {
331                 auto func = rhsResult.result.toValue!ScriptFunction;
332                 if(func.functionName == "<anonymous function>" || func.functionName == "<anonymous class>")
333                     func.functionName = bonode.leftNode.toString;
334             }
335             final switch(lhsResult.accessType)
336             {
337             case VisitResult.AccessType.NO_ACCESS:
338                 finalResult.exception = new ScriptRuntimeException("Invalid left hand assignment");
339                 return Variant(finalResult);
340             case VisitResult.AccessType.VAR_ACCESS:
341                 return Variant(handleVarReassignment(bonode.opToken, lhsResult.memberOrVarToAccess, rhsResult.result));
342             case VisitResult.AccessType.ARRAY_ACCESS:
343                 return Variant(handleArrayReassignment(bonode.opToken, lhsResult.objectToAccess, 
344                         lhsResult.indexToAccess, rhsResult.result));
345             case VisitResult.AccessType.OBJECT_ACCESS:
346                 return Variant(handleObjectReassignment(bonode.opToken, lhsResult.objectToAccess, 
347 				        lhsResult.memberOrVarToAccess, rhsResult.result));
348             }
349         }
350 
351         auto lhs = lhsResult.result;
352         auto rhs = rhsResult.result;
353 
354         switch(bonode.opToken.type)
355         {
356         case Token.Type.POW:
357             return Variant(VisitResult(lhs ^^ rhs));
358         case Token.Type.STAR:
359             return Variant(VisitResult(lhs * rhs));
360         case Token.Type.FSLASH:
361             return Variant(VisitResult(lhs / rhs));
362         case Token.Type.PERCENT:
363             return Variant(VisitResult(lhs % rhs));
364         case Token.Type.PLUS:
365             return Variant(VisitResult(lhs + rhs));
366         case Token.Type.DASH:
367             return Variant(VisitResult(lhs - rhs));
368         case Token.Type.BIT_LSHIFT:
369             return Variant(VisitResult(lhs << rhs));
370         case Token.Type.BIT_RSHIFT:
371             return Variant(VisitResult(lhs >> rhs));
372         case Token.Type.BIT_URSHIFT:
373             return Variant(VisitResult(lhs >>> rhs));
374         case Token.Type.GT:
375             return Variant(VisitResult(lhs > rhs));
376         case Token.Type.GE:
377             return Variant(VisitResult(lhs >= rhs));
378         case Token.Type.LT:
379             return Variant(VisitResult(lhs < rhs));
380         case Token.Type.LE:
381             return Variant(VisitResult(lhs <= rhs));
382         case Token.Type.EQUALS:
383             return Variant(VisitResult(lhs == rhs));
384         case Token.Type.NEQUALS:
385             return Variant(VisitResult(lhs != rhs));
386         case Token.Type.STRICT_EQUALS:
387             return Variant(VisitResult(lhs.strictEquals(rhs)));
388         case Token.Type.STRICT_NEQUALS:
389             return Variant(VisitResult(!lhs.strictEquals(rhs)));
390         case Token.Type.BIT_AND:
391             return Variant(VisitResult(lhs & rhs));
392         case Token.Type.BIT_XOR:
393             return Variant(VisitResult(lhs ^ rhs));
394         case Token.Type.BIT_OR:
395             return Variant(VisitResult(lhs | rhs));
396         case Token.Type.AND:
397             return Variant(VisitResult(lhs && rhs));
398         case Token.Type.OR:
399             return Variant(VisitResult(lhs.orOp(rhs)));
400         default:
401             if(bonode.opToken.isKeyword("instanceof"))
402             {
403                 if(!lhs.isObject)
404                     return Variant(VisitResult(false));
405                 if(rhs.type != ScriptAny.Type.FUNCTION)
406                     return Variant(VisitResult(false));
407                 auto lhsObj = lhs.toValue!ScriptObject; // @suppress(dscanner.suspicious.unmodified)
408                 auto rhsFunc = rhs.toValue!ScriptFunction; // @suppress(dscanner.suspicious.unmodified)
409                 auto proto = lhsObj.prototype;
410                 while(proto !is null)
411                 {
412                     if(proto["constructor"].toValue!ScriptFunction is rhsFunc)
413                         return Variant(VisitResult(true));
414                     proto = proto.prototype;
415                 }
416                 return Variant(VisitResult(false));
417             }
418             else
419                 throw new Exception("Forgot to implement missing binary operator " 
420                     ~ bonode.opToken.type.to!string ~ " for " ~ this.toString());
421         }
422 	}
423 	
424     /// returns a value from a unary operation
425 	deprecated Variant visitUnaryOpNode(UnaryOpNode uonode)
426 	{
427 		auto vr = uonode.operandNode.accept(this).get!VisitResult;
428         if(vr.exception !is null)
429             return Variant(vr);
430         auto value = vr.result;
431         int incOrDec = 0;
432         switch(uonode.opToken.type)
433         {
434         case Token.Type.BIT_NOT:
435             return Variant(VisitResult(~value));
436         case Token.Type.NOT:
437             return Variant(VisitResult(!value));
438         case Token.Type.PLUS:
439             return Variant(VisitResult(value));
440         case Token.Type.DASH:
441             return Variant(VisitResult(-value));
442         case Token.Type.DEC:
443             incOrDec = -1;
444             break;
445         case Token.Type.INC:
446             incOrDec = 1;
447             break;
448         default:
449             if(uonode.opToken.isKeyword("typeof"))
450                 return Variant(VisitResult(value.typeToString()));
451             return Variant(VisitResult(ScriptAny.UNDEFINED));
452         }
453 
454         if(incOrDec != 0)
455         {
456             // TODO: fix this to allow constructs such as ++foo++
457             if(vr.accessType == VisitResult.AccessType.VAR_ACCESS)
458                 return Variant(handleVarReassignment(Token.createFakeToken(Token.Type.PLUS_ASSIGN,""), 
459                         vr.memberOrVarToAccess, ScriptAny(incOrDec)));
460             else if(vr.accessType == VisitResult.AccessType.ARRAY_ACCESS)
461                 return Variant(handleArrayReassignment(Token.createFakeToken(Token.Type.PLUS_ASSIGN,""), 
462                         vr.objectToAccess, vr.indexToAccess, ScriptAny(incOrDec)));
463             else if(vr.accessType == VisitResult.AccessType.OBJECT_ACCESS)
464                 return Variant(handleObjectReassignment(Token.createFakeToken(Token.Type.PLUS_ASSIGN,""), 
465                         vr.objectToAccess, vr.memberOrVarToAccess, ScriptAny(incOrDec)));
466             else
467                 vr.exception = new ScriptRuntimeException("Invalid operand for " ~ uonode.opToken.symbol);
468         }
469         return Variant(vr);
470 	}
471 	
472     /// handle constructs such as i++ and i--
473 	deprecated Variant visitPostfixOpNode(PostfixOpNode ponode)
474 	{
475 		// first get the operand's original value that will be returned
476         VisitResult vr = ponode.operandNode.accept(this).get!VisitResult;
477         if(vr.exception !is null)
478             return Variant(vr);
479         auto incOrDec = 0;
480         if(ponode.opToken.type == Token.Type.INC)
481             incOrDec = 1;
482         else if(ponode.opToken.type == Token.Type.DEC)
483             incOrDec = -1;
484         else
485             throw new Exception("Impossible parse state: invalid postfix operator");
486         // now perform an increment or decrement assignment based on object access type
487         VisitResult errVR;
488         if(vr.accessType == VisitResult.AccessType.VAR_ACCESS)
489             errVR = handleVarReassignment(Token.createFakeToken(Token.Type.PLUS_ASSIGN,""), 
490                 vr.memberOrVarToAccess, ScriptAny(incOrDec));
491         else if(vr.accessType == VisitResult.AccessType.ARRAY_ACCESS)
492             errVR = handleArrayReassignment(Token.createFakeToken(Token.Type.PLUS_ASSIGN,""), 
493                 vr.objectToAccess, vr.indexToAccess, ScriptAny(incOrDec));
494         else if(vr.accessType == VisitResult.AccessType.OBJECT_ACCESS)
495             errVR = handleObjectReassignment(Token.createFakeToken(Token.Type.PLUS_ASSIGN,""), 
496                 vr.objectToAccess, vr.memberOrVarToAccess, ScriptAny(incOrDec));
497         else
498             vr.exception = new ScriptRuntimeException("Invalid post operand for " ~ ponode.opToken.symbol);
499         if(errVR.exception !is null)
500             return Variant(errVR);
501         return Variant(vr);
502 	}
503 	
504     /// handles : ? operator
505 	deprecated Variant visitTerniaryOpNode(TerniaryOpNode tonode)
506 	{
507 		// first evaluate the condition
508         auto vr = tonode.conditionNode.accept(this).get!VisitResult;
509         if(vr.exception !is null)
510             return Variant(vr);
511         if(vr.result)
512             vr = tonode.onTrueNode.accept(this).get!VisitResult;
513         else
514             vr = tonode.onFalseNode.accept(this).get!VisitResult;
515         return Variant(vr);
516 	}
517 	
518     /// handles variable access
519 	deprecated Variant visitVarAccessNode(VarAccessNode vanode)
520 	{
521 		VisitResult vr;
522         vr.accessType = VisitResult.AccessType.VAR_ACCESS;
523         vr.memberOrVarToAccess = vanode.varToken.text;
524         bool _; // @suppress(dscanner.suspicious.unmodified)
525         immutable ptr = cast(immutable)_currentEnvironment.lookupVariableOrConst(vanode.varToken.text, _);
526         if(ptr == null)
527             vr.exception = new ScriptRuntimeException("Undefined variable lookup " ~ vanode.varToken.text);
528         else
529             vr.result = *ptr;
530         return Variant(vr);
531 	}
532 
533     /// handles function calls
534 	deprecated Variant visitFunctionCallNode(FunctionCallNode fcnode)
535 	{
536         ScriptAny thisObj;
537         auto vr = fcnode.functionToCall.accept(this).get!VisitResult;
538 
539         if(cast(SuperNode)fcnode.functionToCall)
540         {
541             vr.result = vr.result["constructor"];
542         }
543 
544         if(vr.exception !is null)
545             return Variant(vr);
546 
547         // if not a new expression pull this
548         if(!fcnode.returnThis)
549         {
550             if(vr.accessType == VisitResult.AccessType.OBJECT_ACCESS)
551             {
552                 auto man = cast(MemberAccessNode)fcnode.functionToCall;
553                 if(cast(SuperNode)man.objectNode)
554                     thisObj = getLocalThis();
555                 else
556                     thisObj = vr.objectToAccess;
557             }
558             else if(vr.accessType == VisitResult.AccessType.ARRAY_ACCESS)
559             {
560                 auto ain = cast(ArrayIndexNode)fcnode.functionToCall;
561                 if(cast(SuperNode)ain.objectNode)
562                     thisObj = getLocalThis();
563                 else
564                     thisObj = vr.objectToAccess;
565             }
566             else
567             {
568                 thisObj = getLocalThis();
569             }
570         }
571 
572         auto fnToCall = vr.result;
573         if(fnToCall.type == ScriptAny.Type.FUNCTION)
574         {
575             ScriptAny[] args;
576             vr = convertExpressionsToArgs(fcnode.expressionArgs, args);
577             if(vr.exception !is null)
578                 return Variant(vr);
579             auto fn = fnToCall.toValue!ScriptFunction;
580             vr = callFn(fn, thisObj, args, fcnode.returnThis);
581             return Variant(vr);
582         }
583         else 
584         {
585             vr.result = ScriptAny.UNDEFINED;
586             vr.exception = new ScriptRuntimeException("Unable to call non function " ~ fnToCall.toString);
587             return Variant(vr);
588         }
589 	}
590 	
591     /// handle array index
592 	deprecated Variant visitArrayIndexNode(ArrayIndexNode ainode)
593 	{
594         import std.utf: UTFException;
595 
596 		VisitResult vr = ainode.indexValueNode.accept(this).get!VisitResult;
597         if(vr.exception !is null)
598             return Variant(vr);
599         auto index = vr.result;
600         auto objVR = ainode.objectNode.accept(this).get!VisitResult;
601         if(objVR.exception !is null)
602             return Variant(objVR);
603 
604         // also need to validate that the object can be accessed
605         if(!objVR.result.isObject)
606         {
607             vr.exception = new ScriptRuntimeException("Cannot index value " ~ objVR.result.toString);
608             return Variant(vr);
609         }
610 
611         if(index.type == ScriptAny.Type.STRING)
612         {
613             // we have to be accessing an object or trying to
614             auto indexAsStr = index.toString();
615             vr.accessType = VisitResult.AccessType.OBJECT_ACCESS;
616             vr.memberOrVarToAccess = index.toString();
617             vr.objectToAccess = objVR.result;
618             vr.result = vr.objectToAccess.lookupField(indexAsStr);
619         }
620         else if(index.isNumber)
621         {
622             auto indexAsNum = index.toValue!size_t;
623             vr.accessType = VisitResult.AccessType.ARRAY_ACCESS;
624             vr.indexToAccess = indexAsNum;
625             vr.objectToAccess = objVR.result;
626             if(auto asString = objVR.result.toValue!ScriptString)
627             {
628                 auto str = asString.toString();
629                 if(indexAsNum >= str.length)
630                     vr.result = ScriptAny.UNDEFINED;
631                 else
632                 {
633                     try 
634                     {
635                         vr.result = ScriptAny([ str[indexAsNum] ]);
636                     }
637                     catch(UTFException)
638                     {
639                         vr.result = ScriptAny.UNDEFINED;
640                     }
641                 }
642             }
643             else if(auto asArray = objVR.result.toValue!ScriptArray)
644             {
645                 if(indexAsNum >= asArray.array.length)
646                     vr.result = ScriptAny.UNDEFINED;
647                 else
648                     vr.result = asArray.array[indexAsNum];
649             }
650             else
651             {
652                 vr.exception = new ScriptRuntimeException("Attempt to index a non-string or non-array");
653             }
654         }
655         else
656         {
657             vr.exception = new ScriptRuntimeException("Invalid index type for array or object access");
658         }
659         return Variant(vr);
660 	}
661 	
662     /// handle dot operator
663 	deprecated Variant visitMemberAccessNode(MemberAccessNode manode)
664 	{
665         VisitResult vr;
666         string memberName = "";
667         if(auto van = cast(VarAccessNode)manode.memberNode)
668         {
669             memberName = van.varToken.text;
670         }
671         else
672         {
673             vr.exception = new ScriptRuntimeException("Invalid operand for object member access");
674             return Variant(vr);
675         }
676 
677         auto objVR = manode.objectNode.accept(this).get!VisitResult;
678         if(objVR.exception !is null)
679             return Variant(objVR);
680         // validate that objVR.result is of type object so that it can even be accessed
681         if(!objVR.result.isObject)
682         {
683             vr.exception = new ScriptRuntimeException("Cannot access non-object " 
684                 ~ objVR.result.toString() ~ ": " ~ this.toString());
685             return Variant(vr);
686         }
687 
688         // set the fields
689         vr.accessType = VisitResult.AccessType.OBJECT_ACCESS;
690         vr.objectToAccess = objVR.result;
691         vr.memberOrVarToAccess = memberName;
692         // if this is a get property we need to use the getter otherwise we lookup field
693         auto obj = vr.objectToAccess.toValue!ScriptObject;
694         if(obj.hasGetter(memberName))
695         {
696             VisitResult gvr;
697             gvr = getObjectProperty(obj, memberName);
698             if(gvr.exception !is null)
699                 return Variant(gvr);
700             vr.result = gvr.result;
701         }
702         else
703             vr.result = objVR.result.lookupField(memberName);
704         return Variant(vr);
705 	}
706 	
707     /// handles new expression
708 	deprecated Variant visitNewExpressionNode(NewExpressionNode nenode)
709 	{
710 		// fce should be a valid function call with its returnThis flag already set by the parser
711         auto vr = nenode.functionCallExpression.accept(this);
712         return vr; // caller will check for any exceptions.
713 	}
714 
715     /// this should only be directly visited when used by itself
716     deprecated Variant visitSuperNode(SuperNode snode)
717     {
718         auto thisObj = getLocalThis;
719         return Variant(VisitResult(thisObj["__super__"]));
720     }
721 	
722     deprecated Variant visitYieldNode(YieldNode ynode)
723     {
724         throw new Exception("Yield is not supported in interpreted mode");
725     }
726     
727     /// handles var, let, and const declarations
728 	deprecated Variant visitVarDeclarationStatementNode(VarDeclarationStatementNode vdsnode)
729 	{
730 		VisitResult visitResult;
731         foreach(varNode; vdsnode.varAccessOrAssignmentNodes)
732         {
733             if(auto v = cast(VarAccessNode)varNode)
734             {
735                 auto varName = v.varToken.text;
736                 visitResult = handleVarDeclaration(vdsnode.qualifier.text, varName, ScriptAny.UNDEFINED);
737                 if(visitResult.exception !is null)
738                     return Variant(visitResult);
739             }
740             else if(auto binNode = cast(BinaryOpNode)varNode)
741             {
742                 // auto binNode = cast(BinaryOpNode)varNode;
743                 visitResult = binNode.rightNode.accept(this).get!VisitResult;
744                 if(visitResult.exception !is null)
745                     return Variant(visitResult);
746                 auto valueToAssign = visitResult.result;
747                 // we checked this before so should be safe
748                 if(auto van = cast(VarAccessNode)(binNode.leftNode))
749                 {
750                     auto varName = van.varToken.text;
751                     visitResult = handleVarDeclaration(vdsnode.qualifier.text, varName, valueToAssign);
752                     if(visitResult.exception !is null)
753                         return Variant(visitResult);
754                     // success so make sure anon function name matches
755                     if(valueToAssign.type == ScriptAny.Type.FUNCTION)
756                     {
757                         auto func = valueToAssign.toValue!ScriptFunction;
758                         if(func.functionName == "<anonymous function>" || func.functionName == "<anonymous class>")
759                             func.functionName = varName;
760                     }
761                 }
762             }
763             else 
764                 throw new Exception("Invalid declaration got past the parser");
765         }
766         return Variant(VisitResult(ScriptAny.UNDEFINED));
767 	}
768 	
769     /// handles {block} statement
770 	deprecated Variant visitBlockStatementNode(BlockStatementNode bsnode)
771 	{
772         Environment oldEnvironment = _currentEnvironment; // @suppress(dscanner.suspicious.unmodified)
773 		_currentEnvironment = new Environment(_currentEnvironment, "<scope>");
774         auto result = VisitResult(ScriptAny.UNDEFINED);
775         foreach(statement ; bsnode.statementNodes)
776         {
777             result = statement.accept(this).get!VisitResult;
778             if(result.returnFlag || result.breakFlag || result.continueFlag || result.exception !is null)
779             {
780                 if(result.exception)
781                     result.exception.scriptTraceback ~= tuple(statement.line, statement.toString());
782                 break;
783             }
784         }   
785         _currentEnvironment = oldEnvironment;
786         return Variant(result);
787 	}
788 	
789     /// handles if statements
790 	deprecated Variant visitIfStatementNode(IfStatementNode isnode)
791 	{
792 		auto vr = isnode.conditionNode.accept(this).get!VisitResult;
793         if(vr.exception !is null)
794             return Variant(vr);
795 
796         if(vr.result)
797         {
798             vr = isnode.onTrueStatement.accept(this).get!VisitResult;
799         }
800         else 
801         {
802             if(isnode.onFalseStatement !is null)
803                 vr = isnode.onFalseStatement.accept(this).get!VisitResult;
804         }
805         return Variant(vr);
806 	}
807 	
808     /// handles switch case statements
809 	deprecated Variant visitSwitchStatementNode(SwitchStatementNode ssnode)
810 	{
811 		auto vr = ssnode.expressionNode.accept(this).get!VisitResult;
812         if(vr.exception !is null)
813             return Variant(vr);
814         size_t jumpStatement = ssnode.switchBody.defaultStatementID;
815         if(vr.result in ssnode.switchBody.jumpTable)
816         {
817             jumpStatement = ssnode.switchBody.jumpTable[vr.result];
818         }
819         if(jumpStatement < ssnode.switchBody.statementNodes.length)
820         {
821             for(size_t i = jumpStatement; i < ssnode.switchBody.statementNodes.length; ++i)
822             {
823                 vr = ssnode.switchBody.statementNodes[i].accept(this).get!VisitResult;
824                 if(vr.returnFlag || vr.continueFlag || vr.breakFlag || vr.exception !is null)
825                     return Variant(vr);
826             }
827         }
828         return Variant(vr);
829 	}
830 	
831     /// handles while statements
832 	deprecated Variant visitWhileStatementNode(WhileStatementNode wsnode)
833 	{
834 		if(wsnode.label != "")
835             _currentEnvironment.insertLabel(wsnode.label);
836         auto vr = wsnode.conditionNode.accept(this).get!VisitResult;
837         while(vr.result && vr.exception is null)
838         {
839             vr = wsnode.bodyNode.accept(this).get!VisitResult;
840             if(vr.breakFlag)
841             {
842                 if(vr.labelName == "")
843                     vr.breakFlag = false;
844                 else
845                 {
846                     if(_currentEnvironment.labelExists(vr.labelName))
847                     {
848                         if(wsnode.label == vr.labelName)
849                             vr.breakFlag = false;
850                     }
851                     else 
852                         vr.exception = new ScriptRuntimeException("Label " ~ vr.labelName ~ " doesn't exist");
853                 }
854                 break;
855             }
856             if(vr.continueFlag)
857             {
858                 if(vr.labelName == "")
859                     vr.continueFlag = false;
860                 else
861                 {
862                     if(_currentEnvironment.labelExists(vr.labelName))
863                     {
864                         if(wsnode.label == vr.labelName)
865                             vr.continueFlag = false;
866                         else
867                             break;
868                     }
869                     else
870                     {
871                         vr.exception = new ScriptRuntimeException("Label " ~ vr.labelName ~ " doesn't exist");
872                         break;
873                     }
874                 }
875             }
876             if(vr.exception !is null || vr.returnFlag)
877                 break;
878             vr = wsnode.conditionNode.accept(this).get!VisitResult;
879         }
880         if(wsnode.label != "")
881             _currentEnvironment.removeLabelFromCurrent(wsnode.label);
882         return Variant(vr);
883 	}
884 	
885     /// handles do-while statement
886 	deprecated Variant visitDoWhileStatementNode(DoWhileStatementNode dwsnode)
887 	{
888 		auto vr = VisitResult(ScriptAny.UNDEFINED);
889         if(dwsnode.label != "")
890             _currentEnvironment.insertLabel(dwsnode.label);
891         do 
892         {
893             vr = dwsnode.bodyNode.accept(this).get!VisitResult;
894             if(vr.breakFlag)
895             {
896                 if(vr.labelName == "")
897                     vr.breakFlag = false;
898                 else
899                 {
900                     if(_currentEnvironment.labelExists(vr.labelName))
901                     {
902                         if(dwsnode.label == vr.labelName)
903                             vr.breakFlag = false;
904                     }
905                     else 
906                         vr.exception = new ScriptRuntimeException("Label " ~ vr.labelName ~ " doesn't exist");
907                 }
908                 break;
909             }
910             if(vr.continueFlag)
911             {
912                 if(vr.labelName == "")
913                     vr.continueFlag = false;
914                 else
915                 {
916                     if(_currentEnvironment.labelExists(vr.labelName))
917                     {
918                         if(dwsnode.label == vr.labelName)
919                             vr.continueFlag = false;
920                         else
921                             break;
922                     }
923                     else
924                     {
925                         vr.exception = new ScriptRuntimeException("Label " ~ vr.labelName ~ " doesn't exist");
926                         break;
927                     }
928                 }
929             }
930             if(vr.exception !is null || vr.returnFlag)
931                 break; 
932             vr = dwsnode.conditionNode.accept(this).get!VisitResult;
933         }
934         while(vr.result && vr.exception is null);
935         if(dwsnode.label != "")
936             _currentEnvironment.removeLabelFromCurrent(dwsnode.label);
937         return Variant(vr);
938 	}
939 	
940     /// handles for(;;) statements
941 	deprecated Variant visitForStatementNode(ForStatementNode fsnode)
942 	{
943         Environment oldEnvironment = _currentEnvironment; // @suppress(dscanner.suspicious.unmodified)
944 		_currentEnvironment = new Environment(_currentEnvironment, "<outer_for_loop>");
945         if(fsnode.label != "")
946             _currentEnvironment.insertLabel(fsnode.label);
947         auto vr = VisitResult(ScriptAny.UNDEFINED);
948         if(fsnode.varDeclarationStatement !is null)
949             vr = fsnode.varDeclarationStatement.accept(this).get!VisitResult;
950         if(vr.exception is null)
951         {
952             vr = fsnode.conditionNode.accept(this).get!VisitResult;
953             while(vr.result && vr.exception is null)
954             {
955                 vr = fsnode.bodyNode.accept(this).get!VisitResult;
956                 if(vr.breakFlag)
957                 {
958                     if(vr.labelName == "")
959                         vr.breakFlag = false;
960                     else
961                     {
962                         if(_currentEnvironment.labelExists(vr.labelName))
963                         {
964                             if(fsnode.label == vr.labelName)
965                                 vr.breakFlag = false;
966                         }
967                         else 
968                             vr.exception = new ScriptRuntimeException("Label " ~ vr.labelName ~ " doesn't exist");
969                     }
970                     break;
971                 }
972                 if(vr.continueFlag)
973                 {
974                     if(vr.labelName == "")
975                         vr.continueFlag = false;
976                     else
977                     {
978                         if(_currentEnvironment.labelExists(vr.labelName))
979                         {
980                             if(fsnode.label == vr.labelName)
981                                 vr.continueFlag = false;
982                             else
983                                 break;
984                         }
985                         else
986                         {
987                             vr.exception = new ScriptRuntimeException("Label " ~ vr.labelName ~ " doesn't exist");
988                             break;
989                         }
990                     }
991                 }
992                 if(vr.exception !is null || vr.returnFlag)
993                     break; 
994                 vr = fsnode.incrementNode.accept(this).get!VisitResult;
995                 if(vr.exception !is null)
996                     break;
997                 vr = fsnode.conditionNode.accept(this).get!VisitResult;
998             }
999         }
1000         if(fsnode.label != "")
1001             _currentEnvironment.removeLabelFromCurrent(fsnode.label);
1002         _currentEnvironment = oldEnvironment;
1003         return Variant(vr);
1004 	}
1005 	
1006     /// handles for-of (and for-in) loops. TODO rewrite with iterators and implement string
1007 	deprecated Variant visitForOfStatementNode(ForOfStatementNode fosnode)
1008 	{
1009 		auto vr = fosnode.objectToIterateNode.accept(this).get!VisitResult;
1010         // make sure this is iterable
1011         if(vr.exception !is null)
1012             return Variant(vr);
1013 
1014         if(fosnode.label != "")
1015             _currentEnvironment.insertLabel(fosnode.label);
1016 
1017         // FOR NOW no distinguish "of" and "in"
1018         if(vr.result.type == ScriptAny.Type.ARRAY)
1019         {
1020             auto arr = vr.result.toValue!(ScriptAny[]);
1021             for(size_t i = 0; i < arr.length; ++i)
1022             {
1023                 // TODO optimize this to reassign variables instead of creating new environments each iteration
1024                 auto oldEnvironment = _currentEnvironment; // @suppress(dscanner.suspicious.unmodified)
1025                 _currentEnvironment = new Environment(_currentEnvironment, "<for_of_loop>");
1026                 // if one var access node, then value, otherwise index then value
1027                 if(fosnode.varAccessNodes.length == 1)
1028                 {
1029                     _currentEnvironment.declareVariableOrConst(fosnode.varAccessNodes[0].varToken.text,
1030                         arr[i], fosnode.qualifierToken.text == "const"? true: false);
1031                 }
1032                 else 
1033                 {
1034                     _currentEnvironment.declareVariableOrConst(fosnode.varAccessNodes[0].varToken.text,
1035                         ScriptAny(i), fosnode.qualifierToken.text == "const"? true: false);
1036                     _currentEnvironment.declareVariableOrConst(fosnode.varAccessNodes[1].varToken.text,
1037                         arr[i], fosnode.qualifierToken.text == "const"? true: false);
1038                 }
1039                 vr = fosnode.bodyNode.accept(this).get!VisitResult;
1040                 _currentEnvironment = oldEnvironment;
1041                 if(vr.breakFlag)
1042                 {
1043                     if(vr.labelName == "")
1044                         vr.breakFlag = false;
1045                     else
1046                     {
1047                         if(_currentEnvironment.labelExists(vr.labelName))
1048                         {
1049                             if(fosnode.label == vr.labelName)
1050                                 vr.breakFlag = false;
1051                         }
1052                         else 
1053                             vr.exception = new ScriptRuntimeException("Label " ~ vr.labelName ~ " doesn't exist");
1054                     }
1055                     break;
1056                 }
1057                 if(vr.continueFlag)
1058                 {
1059                     if(vr.labelName == "")
1060                         vr.continueFlag = false;
1061                     else
1062                     {
1063                         if(_currentEnvironment.labelExists(vr.labelName))
1064                         {
1065                             if(fosnode.label == vr.labelName)
1066                                 vr.continueFlag = false;
1067                             else
1068                                 break;
1069                         }
1070                         else
1071                         {
1072                             vr.exception = new ScriptRuntimeException("Label " ~ vr.labelName ~ " doesn't exist");
1073                             break;
1074                         }
1075                     }
1076                 }
1077                 if(vr.exception !is null || vr.returnFlag)
1078                     break;
1079             }
1080         }
1081         else if(vr.result.isObject)
1082         {
1083             auto obj = vr.result.toValue!ScriptObject;
1084             // first value is key, second value is value if there
1085             foreach(key, val; obj.dictionary)
1086             {
1087                 // TODO optimize this to reassign variables instead of creating new ones each iteration
1088                 auto oldEnvironment = _currentEnvironment; // @suppress(dscanner.suspicious.unmodified)
1089                 _currentEnvironment = new Environment(_currentEnvironment, "<for_of_loop>");
1090                 _currentEnvironment.declareVariableOrConst(fosnode.varAccessNodes[0].varToken.text,
1091                     ScriptAny(key), fosnode.qualifierToken.text == "const" ? true: false);
1092                 if(fosnode.varAccessNodes.length > 1)
1093                     _currentEnvironment.declareVariableOrConst(fosnode.varAccessNodes[1].varToken.text,
1094                         ScriptAny(val), fosnode.qualifierToken.text == "const" ? true: false);
1095                 vr = fosnode.bodyNode.accept(this).get!VisitResult;              
1096                 _currentEnvironment = oldEnvironment;
1097                 if(vr.breakFlag)
1098                 {
1099                     if(vr.labelName == "")
1100                         vr.breakFlag = false;
1101                     else
1102                     {
1103                         if(_currentEnvironment.labelExists(vr.labelName))
1104                         {
1105                             if(fosnode.label == vr.labelName)
1106                                 vr.breakFlag = false;
1107                         }
1108                         else 
1109                             vr.exception = new ScriptRuntimeException("Label " ~ vr.labelName ~ " doesn't exist");
1110                     }
1111                     break;
1112                 }
1113                 if(vr.continueFlag)
1114                 {
1115                     if(vr.labelName == "")
1116                         vr.continueFlag = false;
1117                     else
1118                     {
1119                         if(_currentEnvironment.labelExists(vr.labelName))
1120                         {
1121                             if(fosnode.label == vr.labelName)
1122                                 vr.continueFlag = false;
1123                             else
1124                                 break;
1125                         }
1126                         else
1127                         {
1128                             vr.exception = new ScriptRuntimeException("Label " ~ vr.labelName ~ " doesn't exist");
1129                             break;
1130                         }
1131                     }
1132                 }
1133                 if(vr.exception !is null || vr.returnFlag)
1134                     break; 
1135                 if(vr.exception !is null)
1136                     break;
1137             }
1138         }
1139 
1140         else 
1141         {
1142             vr.exception = new ScriptRuntimeException("Cannot iterate over " ~ fosnode.objectToIterateNode.toString);
1143         }
1144 
1145         if(fosnode.label != "")
1146             _currentEnvironment.removeLabelFromCurrent(fosnode.label);
1147 
1148         return Variant(vr);
1149 	}
1150 	
1151     /// handle break statements
1152 	deprecated Variant visitBreakStatementNode(BreakStatementNode bsnode)
1153 	{
1154 		auto vr = VisitResult(ScriptAny.UNDEFINED);
1155         vr.breakFlag = true;
1156         vr.labelName = bsnode.label;
1157         return Variant(vr);
1158 	}
1159 	
1160     /// handle continue statements
1161 	deprecated Variant visitContinueStatementNode(ContinueStatementNode csnode)
1162 	{
1163         auto vr = VisitResult(ScriptAny.UNDEFINED);
1164         vr.continueFlag = true;
1165         vr.labelName = csnode.label;
1166         return Variant(vr);
1167 	}
1168 	
1169     /// handles return statements
1170 	deprecated Variant visitReturnStatementNode(ReturnStatementNode rsnode)
1171 	{
1172 		VisitResult vr = VisitResult(ScriptAny.UNDEFINED);
1173         if(rsnode.expressionNode !is null)
1174         {
1175             vr = rsnode.expressionNode.accept(this).get!VisitResult;
1176             if(vr.exception !is null)
1177             {
1178                 return Variant(vr);
1179             }
1180         }
1181         vr.returnFlag = true;
1182         return Variant(vr);
1183 	}
1184 	
1185     /// handle function declarations
1186 	deprecated Variant visitFunctionDeclarationStatementNode(FunctionDeclarationStatementNode fdsnode)
1187 	{
1188 		auto func = new ScriptFunction(fdsnode.name, fdsnode.argNames, fdsnode.statementNodes, _currentEnvironment);
1189         immutable okToDeclare = _currentEnvironment.declareVariableOrConst(fdsnode.name, ScriptAny(func), false);
1190         VisitResult vr = VisitResult(ScriptAny.UNDEFINED);
1191         if(!okToDeclare)
1192         {
1193             vr.exception = new ScriptRuntimeException("Cannot redeclare variable or const " ~ fdsnode.name 
1194                 ~ " with a function declaration");
1195         }
1196         return Variant(vr);
1197 	}
1198 	
1199     /// handles throw statements
1200 	deprecated Variant visitThrowStatementNode(ThrowStatementNode tsnode)
1201 	{
1202 		auto vr = tsnode.expressionNode.accept(this).get!VisitResult;
1203         if(vr.exception !is null)
1204         {
1205             return Variant(vr);
1206         }
1207         vr.exception = new ScriptRuntimeException("Uncaught script exception");
1208         vr.exception.thrownValue = vr.result;
1209         vr.result = ScriptAny.UNDEFINED;
1210         return Variant(vr);
1211 	}
1212 	
1213     /// handle try catch block statements
1214 	deprecated Variant visitTryCatchBlockStatementNode(TryCatchBlockStatementNode tcbsnode)
1215 	{
1216 		auto vr = tcbsnode.tryBlockNode.accept(this).get!VisitResult;
1217         // if there was an exception we need to start a new environment and set it as a local variable
1218         if(vr.exception !is null && tcbsnode.catchBlockNode !is null)
1219         {
1220             auto oldEnvironment = _currentEnvironment; // @suppress(dscanner.suspicious.unmodified)
1221             _currentEnvironment = new Environment(_currentEnvironment, "<catch>");
1222             if(vr.exception.thrownValue != ScriptAny.UNDEFINED)
1223                 _currentEnvironment.forceSetVarOrConst(tcbsnode.exceptionName, vr.exception.thrownValue, false);
1224             else 
1225                 _currentEnvironment.forceSetVarOrConst(tcbsnode.exceptionName, ScriptAny(vr.exception.message), false);
1226             vr.exception = null;
1227             // if another exception is thrown in the catch block, it will propagate through this return value
1228             vr = tcbsnode.catchBlockNode.accept(this).get!VisitResult;
1229             _currentEnvironment = oldEnvironment;
1230         }
1231         if(tcbsnode.finallyBlockNode)
1232         {
1233             auto finVR = tcbsnode.finallyBlockNode.accept(this).get!VisitResult;
1234             if(finVR.exception)
1235                 return Variant(finVR);
1236         }
1237         return Variant(vr);
1238 	}
1239 	
1240     /// handle delete statement
1241 	deprecated Variant visitDeleteStatementNode(DeleteStatementNode dsnode)
1242 	{
1243 		auto vr = dsnode.memberAccessOrArrayIndexNode.accept(this).get!VisitResult;
1244         // TODO handle array
1245         if(vr.accessType != VisitResult.AccessType.OBJECT_ACCESS)
1246         {
1247             vr.exception = new ScriptRuntimeException("Invalid operand for delete operator");
1248             return Variant(vr);
1249         }
1250         if(vr.objectToAccess.isObject)
1251         {
1252             auto obj = vr.objectToAccess.toValue!ScriptObject;
1253             obj.dictionary.remove(vr.memberOrVarToAccess);
1254         }
1255         vr.result = ScriptAny.UNDEFINED;
1256         return Variant(vr);
1257 	}
1258 	
1259     /// handle class declaration
1260 	deprecated Variant visitClassDeclarationStatementNode(ClassDeclarationStatementNode cdsnode)
1261 	{
1262 		VisitResult vr;
1263         // generate class
1264         try 
1265         {
1266             vr.result = cdsnode.classDefinition.create(_currentEnvironment);
1267         }
1268         catch (ScriptRuntimeException ex)
1269         {
1270             vr.exception = ex;
1271             return Variant(vr);
1272         }
1273         auto ctor = vr.result;
1274         // first try to assign the constructor as a local function
1275         immutable ok = _currentEnvironment.declareVariableOrConst(cdsnode.classDefinition.className, 
1276                 ctor, false);
1277         if(!ok)
1278         {
1279             vr.exception = new ScriptRuntimeException("Class declaration " ~ cdsnode.classDefinition.className 
1280                 ~ " may not overwrite local variable or const");
1281             return Variant(vr);
1282         }
1283 		
1284         return Variant(VisitResult(ScriptAny.UNDEFINED)); // everything was ok
1285 	}
1286 	
1287     /// handle expression statements
1288 	deprecated Variant visitExpressionStatementNode(ExpressionStatementNode esnode)
1289 	{
1290 		VisitResult vr;
1291         if(esnode.expressionNode !is null)
1292             vr = esnode.expressionNode.accept(this).get!VisitResult;
1293         vr.result = ScriptAny.UNDEFINED; // they should never return a result
1294         return Variant(vr); // caller will handle any exception
1295 	}
1296 
1297     /// Virtual machine property, may be null
1298     VirtualMachine vm() { return _vm; }
1299 	
1300 package:
1301 	/// holds information from visiting nodes TODO redesign this as a union
1302 	deprecated struct VisitResult
1303 	{
1304 		enum AccessType { NO_ACCESS=0, VAR_ACCESS, ARRAY_ACCESS, OBJECT_ACCESS }
1305 
1306 		this(T)(T val)
1307 		{
1308 			result = ScriptAny(val);
1309 		}
1310 
1311 		this(T : ScriptAny)(T val)
1312 		{
1313 			result = val;
1314 		}
1315 
1316 		ScriptAny result;
1317 
1318 		AccessType accessType;
1319 		ScriptAny objectToAccess;
1320 		string memberOrVarToAccess;
1321 		size_t indexToAccess;
1322 
1323 		bool returnFlag, breakFlag, continueFlag;
1324 		string labelName;
1325 		ScriptRuntimeException exception;
1326 	}
1327 
1328 private:
1329 
1330     deprecated VisitResult callFn(ScriptFunction func, ScriptAny thisObj, ScriptAny[] args, 
1331                        bool returnThis = false, bool useVM = false)
1332 	{
1333 		VisitResult vr;
1334 		if(returnThis)
1335 		{
1336 			if(!thisObj.isObject)
1337 				thisObj = new ScriptObject(func.functionName, func["prototype"].toValue!ScriptObject, null);
1338 		}
1339 		// handle script functions
1340 		if(func.type == ScriptFunction.Type.SCRIPT_FUNCTION)
1341 		{
1342 			auto prevEnvironment = _currentEnvironment; // @suppress(dscanner.suspicious.unmodified)
1343 			_currentEnvironment = new Environment(func.closure, func.functionName);
1344 			// set args as locals
1345 			for(size_t i = 0; i < func.argNames.length; ++i)
1346 			{
1347 				if(i < args.length)
1348 					_currentEnvironment.forceSetVarOrConst(func.argNames[i], args[i], false);
1349 				else
1350 					_currentEnvironment.forceSetVarOrConst(func.argNames[i], ScriptAny.UNDEFINED, false);
1351 			}
1352 			// put all arguments inside "arguments" local
1353 			_currentEnvironment.forceSetVarOrConst("arguments", ScriptAny(args), false);
1354 			// set up "this" local
1355 			_currentEnvironment.forceSetVarOrConst("this", thisObj, true);
1356 			foreach(statement ; func.statementNodes)
1357 			{
1358 				vr = statement.accept(this).get!VisitResult;
1359 				if(vr.breakFlag) // TODO add enum stack to parser to prevent validation of breaks inside functions without loop
1360 					vr.breakFlag = false;
1361 				if(vr.continueFlag) // likewise
1362 					vr.continueFlag = false;
1363 				if(vr.returnFlag || vr.exception !is null)
1364 				{
1365 					if(vr.exception !is null)
1366 						vr.exception.scriptTraceback ~= tuple(statement.line, statement.toString());
1367 					vr.returnFlag = false;
1368 					break;
1369 				}
1370 			}
1371 			if(returnThis)
1372 			{
1373 				bool _; // @suppress(dscanner.suspicious.unmodified)
1374 				immutable thisPtr = cast(immutable)_currentEnvironment.lookupVariableOrConst("this", _);
1375 				if(thisPtr != null)
1376 					vr.result = *thisPtr;
1377 			}
1378 			_currentEnvironment = prevEnvironment;
1379 			return vr;
1380 		}
1381 		else 
1382 		{
1383 			ScriptAny returnValue;
1384 			auto nfe = NativeFunctionError.NO_ERROR;
1385 			if(func.type == ScriptFunction.Type.NATIVE_FUNCTION)
1386 			{
1387 				auto nativefn = func.nativeFunction;
1388 				returnValue = nativefn(_currentEnvironment, &thisObj, args, nfe);
1389 			}
1390 			else
1391 			{
1392 				auto nativedg = func.nativeDelegate;
1393 				returnValue = nativedg(_currentEnvironment, &thisObj, args, nfe);
1394 			}
1395 			if(returnThis)
1396 				vr.result = thisObj;
1397 			else
1398 				vr.result = returnValue;
1399 			// check NFE
1400 			final switch(nfe)
1401 			{
1402 			case NativeFunctionError.NO_ERROR:
1403 				break;
1404 			case NativeFunctionError.WRONG_NUMBER_OF_ARGS:
1405 				vr.exception = new ScriptRuntimeException("Incorrect number of args to native method or function");
1406 				break;
1407 			case NativeFunctionError.WRONG_TYPE_OF_ARG:
1408 				vr.exception = new ScriptRuntimeException("Wrong argument type to native method or function");
1409 				break;
1410 			case NativeFunctionError.RETURN_VALUE_IS_EXCEPTION:
1411 				vr.exception = new ScriptRuntimeException(returnValue.toString());
1412 				break;
1413 			}
1414 
1415 			return vr;
1416 		}
1417 	}
1418 
1419 	deprecated VisitResult convertExpressionsToArgs(ExpressionNode[] exprs, out ScriptAny[] args)
1420 	{
1421 		args = [];
1422 		VisitResult vr;
1423 		foreach(expr ; exprs)
1424 		{
1425 			vr = expr.accept(this).get!VisitResult;
1426 			if(vr.exception !is null)
1427 			{
1428 				args = [];
1429 				return vr;
1430 			}
1431 			args ~= vr.result;
1432 		}
1433 		return vr;
1434 	}
1435 
1436     deprecated ScriptAny getLocalThis()
1437     {
1438         bool _; // @suppress(dscanner.suspicious.unmodified)
1439         if(!_currentEnvironment.variableOrConstExists("this"))
1440             return ScriptAny.UNDEFINED;
1441         auto thisObj = *(_currentEnvironment.lookupVariableOrConst("this", _));
1442         return thisObj;
1443     }
1444 
1445 	deprecated VisitResult getObjectProperty(ScriptObject obj, in string propName)
1446 	{
1447 		VisitResult vr;
1448 		ScriptObject objToSearch = obj;
1449 		while(objToSearch !is null)
1450 		{
1451 			if(propName in objToSearch.getters)
1452 			{
1453 				vr = callFn(objToSearch.getters[propName], ScriptAny(obj), [], false);
1454 				return vr;
1455 			}
1456 			objToSearch = objToSearch.prototype;
1457 		}
1458 		vr.exception = new ScriptRuntimeException("Object " ~ obj.toString() ~ " has no get property `" ~ propName ~ "`");
1459 		return vr;
1460 	}
1461 
1462 	deprecated VisitResult handleArrayReassignment(Token opToken, ScriptAny arr, size_t index, ScriptAny value)
1463 	{
1464 		VisitResult vr;
1465 		if(arr.type != ScriptAny.Type.ARRAY)
1466 		{
1467 			vr.exception = new ScriptRuntimeException("Cannot assign to index of non-array");
1468 			return vr;
1469 		}
1470 		auto scriptArray = arr.toValue!ScriptArray;
1471 		if(index >= scriptArray.length)
1472 		{
1473 			vr.exception = new ScriptRuntimeException("Out of bounds array assignment");
1474 			return vr;
1475 		}
1476 
1477 		switch(opToken.type)
1478 		{
1479 		case Token.Type.ASSIGN:
1480 			scriptArray.array[index] = value;
1481 			break;
1482 		case Token.Type.PLUS_ASSIGN:
1483 			scriptArray.array[index] = scriptArray.array[index] + value;
1484 			break;
1485 		case Token.Type.DASH_ASSIGN:
1486 			scriptArray.array[index] = scriptArray.array[index] - value;
1487 			break;
1488 		default:
1489 			throw new Exception("Unhandled assignment operator");
1490 		}
1491 		vr.result = scriptArray.array[index];
1492 		return vr;
1493 	}
1494 
1495 	deprecated VisitResult handleObjectReassignment(Token opToken, ScriptAny objToAccess, in string index, ScriptAny value)
1496 	{
1497 		VisitResult vr;
1498 		if(!objToAccess.isObject)
1499 		{
1500 			vr.exception = new ScriptRuntimeException("Cannot index non-object");
1501 			return vr;
1502 		}
1503 		auto obj = objToAccess.toValue!ScriptObject;
1504 		// we may need the original value
1505 		ScriptAny originalValue, newValue;
1506 		if(obj.hasGetter(index))
1507 		{
1508 			// if getter with no setter this is an error
1509 			if(!obj.hasSetter(index))
1510 			{
1511 				vr.exception = new ScriptRuntimeException("Object " ~ obj.toString() ~ " has no set property `" ~ index ~ "`");
1512 				return vr;
1513 			}
1514 			vr = getObjectProperty(obj, index);
1515 			if(vr.exception !is null)
1516 				return vr;
1517 			originalValue = vr.result;
1518 		}
1519 		else
1520 		{
1521 			originalValue = obj[index];
1522 		}
1523 
1524 		switch(opToken.type)
1525 		{
1526 		case Token.Type.ASSIGN:
1527 			newValue = value;
1528 			break;
1529 		case Token.Type.PLUS_ASSIGN:
1530 			newValue = originalValue + value;
1531 			break;
1532 		case Token.Type.DASH_ASSIGN:
1533 			newValue = originalValue - value;
1534 			break;
1535 		default:
1536 			throw new Exception("Unhandled assignment operator");
1537 		}
1538 		if(obj.hasSetter(index))
1539 		{
1540 			setObjectProperty(obj, index, newValue);
1541 			if(obj.hasGetter(index))
1542 				vr.result = newValue;
1543 		}
1544 		else
1545 		{
1546 			obj.assignField(index, newValue);
1547 			vr.result = newValue;
1548 		}
1549 		return vr;
1550 	}
1551 
1552 	deprecated VisitResult handleVarDeclaration(in string qual, in string varName, ScriptAny value)
1553 	{
1554 		VisitResult vr;
1555 		bool ok = false;
1556 		string msg = "";
1557 		if(qual == "var")
1558 		{
1559 			ok = _globalEnvironment.declareVariableOrConst(varName, value, false);
1560 			if(!ok)
1561 				msg = "Unable to redeclare global " ~ varName;
1562 		}
1563 		else if(qual == "let")
1564 		{
1565 			ok = _currentEnvironment.declareVariableOrConst(varName, value, false);
1566 			if(!ok)
1567 				msg = "Unable to redeclare local variable " ~ varName;
1568 		}
1569 		else if(qual == "const")
1570 		{
1571 			ok = _currentEnvironment.declareVariableOrConst(varName, value, true);
1572 			if(!ok)
1573 				msg = "Unable to redeclare local const " ~ varName;
1574 		}
1575 		if(!ok)
1576 			vr.exception = new ScriptRuntimeException(msg);
1577 		return vr;
1578 	}
1579 
1580 	deprecated VisitResult handleVarReassignment(Token opToken, in string varName, ScriptAny value)
1581 	{
1582 		bool isConst; // @suppress(dscanner.suspicious.unmodified)
1583 		auto ptr = _currentEnvironment.lookupVariableOrConst(varName, isConst);
1584 		VisitResult vr;
1585 		if(isConst)
1586 			vr.exception = new ScriptRuntimeException("Unable to reassign const " ~ varName);
1587 		else if(ptr == null)
1588 			vr.exception = new ScriptRuntimeException("Unable to reassign undefined variable " ~ varName);
1589 		
1590 		if(vr.exception)
1591 			return vr;
1592 
1593 		switch(opToken.type)
1594 		{
1595 		case Token.Type.ASSIGN:
1596 			*ptr = value;
1597 			break;
1598 		case Token.Type.PLUS_ASSIGN:
1599 			*ptr = *ptr + value;
1600 			break;
1601 		case Token.Type.DASH_ASSIGN:
1602 			*ptr = *ptr - value;
1603 			break;
1604 		default:
1605 			throw new Exception("Unhandled reassignment operator");
1606 		}
1607 		vr.result = *ptr;
1608 		return vr;
1609 	}
1610 
1611 	deprecated VisitResult setObjectProperty(ScriptObject obj, string propName, ScriptAny value)
1612 	{
1613 		VisitResult vr;
1614         auto objectToSearch = obj;
1615         while(objectToSearch !is null)
1616         {
1617             if(propName in objectToSearch.setters)
1618 			{
1619 				vr = callFn(objectToSearch.setters[propName], ScriptAny(obj), [value], false);
1620 				return vr;
1621 			}
1622             objectToSearch = objectToSearch.prototype;
1623         }
1624 		vr.exception = new ScriptRuntimeException("Object " ~ obj.toString() ~ " has no set property `" ~ propName ~ "`");
1625         return vr;
1626 	}
1627 
1628     Compiler _compiler;
1629     bool _printVMDebugInfo;
1630     VirtualMachine _vm;
1631     Environment _globalEnvironment;
1632     Environment _currentEnvironment;
1633 }
1634