1 /**
2 This module implements the VirtualMachine that executes compiled bytecode.
3 ────────────────────────────────────────────────────────────────────────────────
4 Copyright (C) 2021 pillager86.rf.gd
5 
6 This program is free software: you can redistribute it and/or modify it under 
7 the terms of the GNU General Public License as published by the Free Software 
8 Foundation, either version 3 of the License, or (at your option) any later 
9 version.
10 
11 This program is distributed in the hope that it will be useful, but WITHOUT ANY
12 WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
13 PARTICULAR PURPOSE.  See the GNU General Public License for more details.
14 
15 You should have received a copy of the GNU General Public License along with 
16 this program.  If not, see <https://www.gnu.org/licenses/>.
17 */
18 module mildew.vm.virtualmachine;
19 
20 import core.sync.semaphore;
21 import core.thread.fiber;
22 import std.concurrency;
23 import std.container: SList;
24 import std.conv: to;
25 import std.parallelism;
26 import std.stdio;
27 import std..string;
28 import std.typecons;
29 
30 import mildew.environment;
31 import mildew.exceptions;
32 import mildew.stdlib.generator;
33 import mildew.stdlib.map;
34 import mildew.stdlib.regexp;
35 import mildew.types;
36 import mildew.util.encode;
37 import mildew.util.regex: extract;
38 import mildew.util.stack;
39 import mildew.vm.chunk;
40 import mildew.vm.consttable;
41 import mildew.vm.fiber;
42 
43 /// 8-bit opcodes
44 enum OpCode : ubyte 
45 {
46     NOP, // nop() -> ip += 1
47     CONST, // const(uint) : load a const by index from the const table
48     CONST_0, // const0() : load long(0) on to stack
49     CONST_1, // const1() : push 1 to the stack
50     CONST_N1, // constN1() : push -1 to the stack
51     PUSH, // push(int) : push a stack value, can start at -1
52     POP, // pop() : remove exactly one value from stack
53     POPN, // pop(uint) : remove n values from stack
54     SET, // set(uint) : set index of stack to value at top without popping stack
55     STACK, // stack(uint) : push n number of undefines to stack
56     STACK_1, // stack1() : push one undefined to stack
57     ARRAY, // array(uint) : pops n items to create array and pushes to top
58     OBJECT, // array(uint) : create an object from key-value pairs starting with stack[-n] so n must be even
59     CLASS, // class(ubyte,ubyte,ubyte,ubyte) : first arg is how many normal method pairs, second arg is how many getters,
60             // third arg is how many setter pairs, 4th arg is static method pairs, and at stack[-1] base class.
61             // the finalized class constructor is pushed after all those values are popped. String group before method group
62             // stack[-2] is constructor
63     REGEX, // creates a regex from a string at stack[-1] such as /foo/g
64     ITER, // pushes a function that returns {value:..., done:bool} performed on pop()
65     DEL, // delete member stack[-1]:string from object stack[2]. pops 2
66     NEW, // similar to call(uint) except only func, arg1, arg2, etc.
67     THIS, // pushes local "this" or undefined if not found
68     OPENSCOPE, // openscope() : open an environment scope
69     CLOSESCOPE, // closescope() : close an environment scope
70     DECLVAR, // declvar(uint) : declare pop() to a global described by a const string
71     DECLLET, // decllet(uint) : declare pop() to a local described by a const string
72     DECLCONST, // declconst(uint) : declare pop() as stack[-2] to a local const described by a const
73     GETVAR, // getvar(uint) : push variable described by const table string on to stack
74     SETVAR, // setvar(uint) : store top() in variable described by const table index string leave value on top
75     OBJGET, // objget() : retrieves stack[-2][stack[-1]], popping 2 and pushing 1
76     OBJSET, // objset() : sets stack[-3][stack[-2]] to stack[-1], pops 3 and pushes the value that was set
77     CALL, // call(uint) : stack should be this, func, arg1, arg2, arg3 and arg would be 3
78     JMPFALSE, // jmpfalse(int) : relative jump
79     JMP,  // jmp(int) -> : relative jump
80     SWITCH, // switch(uint) -> arg=abs jmp, stack[-2] jmp table stack[-1] value to test
81     GOTO, // goto(uint, ubyte) : absolute ip. second param is number of scopes to subtract
82     THROW, // throw() : throws pop() as a script runtime exception
83     RETHROW, // rethrow() : rethrow the exception flag, should only be generated with try-finally
84     TRY, // try(uint) : parameter is ip to goto for catch (or sometimes finally if finally only)
85     ENDTRY, // pop unconsumed try-entry from try-entry list
86     LOADEXC, // loads the current exception on to stack, either message or thrown value
87     
88     // special ops
89     CONCAT, // concat(uint) : concat N elements on stack and push resulting string
90 
91     // binary and unary and terniary ops
92     BITNOT, // bitwise not
93     NOT, // not top()
94     NEGATE, // negate top()
95     TYPEOF, // typeof operator
96     INSTANCEOF, // instanceof operator
97     POW, // exponent operation
98     MUL, // multiplication
99     DIV, // division
100     MOD, // modulo
101     ADD, // add() : adds stack[-2,-1], pops 2, push 1
102     SUB, // minus
103     BITLSH, // bit shift left top
104     BITRSH, // bit shift right top
105     BITURSH, // bit shift right unsigned
106     LT, // less than
107     LE, // less than or equal
108     GT, // greater than
109     GE, // greater than or equal
110     EQUALS, // equals
111     NEQUALS, // !equals
112     STREQUALS, // strict equals (===)
113     BITAND, // bitwise and
114     BITOR, // bitwise or
115     BITXOR, // bitwise xor
116     AND, // and
117     OR, // or
118     TERN, // : ? looks at stack[-3..-1]
119 
120     RETURN, // return from a function, should leave exactly one value on stack
121     HALT, // completely stop the vm
122 }
123 
124 alias OpCodeFunction = int function(VirtualMachine, Chunk chunk);
125 
126 private ScriptAny getLocalThis(Environment env)
127 {
128     bool _; // @suppress(dscanner.suspicious.unmodified)
129     auto thisPtr = env.lookupVariableOrConst("this", _);
130     if(thisPtr == null)
131         return ScriptAny.UNDEFINED;
132     else
133         return *thisPtr;
134 }
135 
136 /// helper function. TODO rework entire call stack system
137 private int throwRuntimeError(in string message, VirtualMachine vm, Chunk chunk, 
138                             ScriptAny thrownValue = ScriptAny.UNDEFINED, 
139                             ScriptRuntimeException rethrow = null)
140 {
141     vm._exc = new ScriptRuntimeException(message);
142     if(thrownValue != ScriptAny.UNDEFINED)
143         vm._exc.thrownValue = thrownValue;
144     if(rethrow)
145         vm._exc = rethrow;
146     // unwind stack starting with current
147     if(chunk.bytecode in chunk.debugMap)
148     {
149         immutable lineNum = chunk.debugMap[chunk.bytecode].getLineNumber(vm._ip);
150         vm._exc.scriptTraceback ~= tuple(lineNum, chunk.debugMap[chunk.bytecode].getSourceLine(lineNum));
151     }
152     // consume latest try-data entry if available
153     if(vm._tryData.length > 0)
154     {
155         immutable tryData = vm._tryData[$-1];
156         vm._tryData = vm._tryData[0..$-1];
157         immutable depthToReduce = vm._environment.depth - tryData.depth;
158         for(int i = 0; i < depthToReduce; ++i)
159             vm._environment = vm._environment.parent;
160         vm._stack.size = tryData.stackSize;
161         vm._ip = tryData.catchGoto;
162         return 1;
163     }
164     while(vm._callStack.size > 0)
165     {
166         auto csData = vm._callStack.pop(); // @suppress(dscanner.suspicious.unmodified)
167         vm._ip = csData.ip;
168         chunk.bytecode = csData.bc;
169         vm._tryData = csData.tryData;
170         vm._environment = csData.env;
171         if(chunk.bytecode in chunk.debugMap)
172         {
173             immutable lineNum = chunk.debugMap[chunk.bytecode].getLineNumber(vm._ip);
174             vm._exc.scriptTraceback ~= tuple(lineNum, chunk.debugMap[chunk.bytecode].getSourceLine(lineNum));
175         }
176         // look for a try-data entry from the popped call stack
177         if(vm._tryData.length > 0)
178         {
179             immutable tryData = vm._tryData[$-1];
180             vm._tryData = vm._tryData[0..$-1];
181             immutable depthToReduce = vm._environment.depth - tryData.depth;
182             for(int i = 0; i < depthToReduce; ++i)
183                 vm._environment = vm._environment.parent;
184             vm._stack.size = tryData.stackSize;
185             vm._ip = tryData.catchGoto;
186             return 1;
187         }
188     }
189     if(vm._parent)
190     {
191         vm._parent._exc = vm._exc;
192     }
193     // there is no available script exception handler found so clear stack and throw the exception
194     vm._stack.size = 0;
195     throw vm._exc;
196 }
197 
198 private string opCodeToString(const OpCode op)
199 {
200     return op.to!string().toLower();
201 }
202 
203 pragma(inline, true)
204 private int opNop(VirtualMachine vm, Chunk chunk)
205 {
206     ++vm._ip;
207     return 0;
208 }
209 
210 pragma(inline, true)
211 private int opConst(VirtualMachine vm, Chunk chunk)
212 {
213     immutable constID = decode!uint(chunk.bytecode[vm._ip + 1..$]);
214     auto value = chunk.constTable.get(constID);
215     if(value.type == ScriptAny.Type.FUNCTION)
216         value = value.toValue!ScriptFunction().copyCompiled(vm._environment);
217     vm._stack.push(value);
218     vm._ip += 1 + uint.sizeof;
219     return 0;
220 }
221 
222 pragma(inline, true)
223 private int opConst0(VirtualMachine vm, Chunk chunk)
224 {
225     vm._stack.push(ScriptAny(0));
226     ++vm._ip;
227     return 0;
228 }
229 
230 pragma(inline, true)
231 private int opConst1(VirtualMachine vm, Chunk chunk)
232 {
233     vm._stack.push(ScriptAny(1));
234     ++vm._ip;
235     return 0;
236 }
237 
238 pragma(inline, true)
239 private int opPush(VirtualMachine vm, Chunk chunk)
240 {
241     immutable index = decode!int(chunk.bytecode[vm._ip + 1..$]);
242     if(index < 0)
243         vm._stack.push(vm._stack.array[$ + index]);
244     else
245         vm._stack.push(vm._stack.array[index]);
246     vm._ip += 1 + int.sizeof;
247     return 0;
248 }
249 
250 pragma(inline, true)
251 private int opPop(VirtualMachine vm, Chunk chunk)
252 {
253     vm._lastValuePopped = vm._stack.pop();
254     ++vm._ip;
255     return 0;
256 }
257 
258 pragma(inline, true)
259 private int opPopN(VirtualMachine vm, Chunk chunk)
260 {
261     immutable amount = decode!uint(chunk.bytecode[vm._ip + 1..$]);
262     vm._stack.pop(amount);
263     vm._ip += 1 + uint.sizeof;
264     return 0;
265 }
266 
267 pragma(inline, true)
268 private int opSet(VirtualMachine vm, Chunk chunk)
269 {
270     immutable index = decode!uint(chunk.bytecode[vm._ip + 1..$]);
271     vm._stack.array[index] = vm._stack.array[$-1];
272     vm._ip += 1 + uint.sizeof;
273     return 0;
274 }
275 
276 pragma(inline, true)
277 private int opStack(VirtualMachine vm, Chunk chunk)
278 {
279     immutable n = decode!uint(chunk.bytecode[vm._ip + 1..$]);
280     ScriptAny[] undefineds = new ScriptAny[n];
281     vm._stack.push(undefineds);
282     vm._ip += 1 + uint.sizeof;
283     return 0;
284 }
285 
286 pragma(inline, true)
287 private int opStack1(VirtualMachine vm, Chunk chunk)
288 {
289     vm._stack.push(ScriptAny.UNDEFINED);
290     ++vm._ip;
291     return 0;
292 }
293 
294 pragma(inline, true)
295 private int opArray(VirtualMachine vm, Chunk chunk)
296 {
297     immutable n = decode!uint(chunk.bytecode[vm._ip + 1..$]);
298     auto arr = vm._stack.pop(n);
299     vm._stack.push(ScriptAny(arr));
300     vm._ip += 1 + uint.sizeof;
301     return 0;
302 }
303 
304 pragma(inline, true)
305 private int opObject(VirtualMachine vm, Chunk chunk)
306 {
307     immutable n = decode!uint(chunk.bytecode[vm._ip + 1..$]) * 2;
308     auto pairList = vm._stack.pop(n);
309     auto obj = new ScriptObject("object", null, null);
310     for(uint i = 0; i < n; i += 2)
311         obj[pairList[i].toString()] = pairList[i+1];
312     vm._stack.push(ScriptAny(obj));
313     vm._ip += 1 + uint.sizeof;
314     return 0;
315 }
316 
317 pragma(inline, true)
318 private int opClass(VirtualMachine vm, Chunk chunk)
319 {
320     immutable numMethods = decode!ubyte(chunk.bytecode[vm._ip + 1..$]);
321     immutable numGetters = decode!ubyte(chunk.bytecode[vm._ip + 2..$]);
322     immutable numSetters = decode!ubyte(chunk.bytecode[vm._ip + 3..$]);
323     immutable numStatics = decode!ubyte(chunk.bytecode[vm._ip + 4..$]);
324     auto baseClass = vm._stack.pop();
325     auto ctor = vm._stack.pop(); // @suppress(dscanner.suspicious.unmodified)
326     auto statics = vm._stack.pop(numStatics);
327     auto staticNames = vm._stack.pop(numStatics);
328     auto setters = vm._stack.pop(numSetters);
329     auto setterNames = vm._stack.pop(numSetters);
330     auto getters = vm._stack.pop(numGetters);
331     auto getterNames = vm._stack.pop(numGetters);
332     auto methods = vm._stack.pop(numMethods);
333     auto methodNames = vm._stack.pop(numMethods);
334 
335     auto constructor = ctor.toValue!ScriptFunction;
336     if(constructor is null)
337         throw new VMException("Malformed class instruction: invalid constructor", vm._ip, OpCode.CLASS);
338 
339     for(auto i = 0; i < numMethods; ++i)
340     {
341         auto method = methods[i].toValue!ScriptFunction; // @suppress(dscanner.suspicious.unmodified)
342         if(method is null)
343             throw new VMException("Malformed class instruction: invalid method", vm._ip, OpCode.CLASS);
344         constructor["prototype"][methodNames[i].toString] = method;
345     }
346 
347     for(auto i = 0; i < numGetters; ++i)
348     {
349         auto getter = getters[i].toValue!ScriptFunction;
350         if(getter is null)
351             throw new VMException("Malformed class instruction: invalid get property", vm._ip, OpCode.CLASS);
352         constructor["prototype"].addGetterProperty(getterNames[i].toString(), getter);
353     }
354 
355     for(auto i = 0; i < numSetters; ++i)
356     {
357         auto setter = setters[i].toValue!ScriptFunction;
358         if(setter is null)
359             throw new VMException("Malformed class instruction: invalid set property", vm._ip, OpCode.CLASS);
360         constructor["prototype"].addSetterProperty(setterNames[i].toString(), setter);
361     }
362 
363     for(auto i = 0; i < numStatics; ++i)
364     {
365         constructor[staticNames[i].toString()] = statics[i];
366     }
367 
368     if(baseClass)
369     {
370         auto baseClassCtor = baseClass.toValue!ScriptFunction;
371         if(baseClassCtor is null)
372             return throwRuntimeError("Invalid base class " ~ baseClass.toString(), vm, chunk);
373         auto ctorPrototype = constructor["prototype"].toValue!ScriptObject;
374         ctorPrototype.prototype = baseClassCtor["prototype"].toValue!ScriptObject;
375         constructor.prototype = baseClassCtor;
376     }
377 
378     // push the resulting modified constructor
379     vm._stack.push(ScriptAny(constructor));
380 
381     vm._ip += 1 + 4 * ubyte.sizeof;
382     return 0;
383 }
384 
385 pragma(inline, true)
386 private int opRegex(VirtualMachine vm, Chunk chunk)
387 {
388     auto regexString = vm._stack.pop().toString();
389     auto parts = extract(regexString);
390     auto regexResult = ScriptAny(new ScriptObject("RegExp", getRegExpProto, new ScriptRegExp(parts[0], parts[1])));
391     vm._stack.push(regexResult);
392     ++vm._ip;
393     return 0;
394 }
395 
396 pragma(inline, true)
397 private int opIter(VirtualMachine vm, Chunk chunk)
398 {
399     auto objToIterate = vm._stack.pop();
400     // can be a string, array, or object
401     // FUTURE: a Generator returned by a Generator function
402     if(!(objToIterate.isObject))
403         return throwRuntimeError("Cannot iterate over non-object " ~ objToIterate.toString, vm, chunk);
404     if(objToIterate.type == ScriptAny.Type.STRING)
405     {
406         immutable elements = objToIterate.toValue!string;
407         auto generator = new Generator!(Tuple!(size_t,dstring))({
408             size_t indexCounter = 0;
409             foreach(dchar ele ; elements)
410             {
411                 ++indexCounter;
412                 yield(tuple(indexCounter-1,ele.to!dstring));
413             }
414         });
415         vm._stack.push(ScriptAny(new ScriptFunction("next", 
416             delegate ScriptAny(Environment env, ScriptAny* thisObj, ScriptAny[] args, ref NativeFunctionError){
417                 auto retVal = new ScriptObject("iteration", null, null);
418                 if(generator.empty)
419                 {
420                     retVal.assignField("done", ScriptAny(true));
421                 }
422                 else 
423                 {
424                     auto result = generator.front();
425                     retVal.assignField("key", ScriptAny(result[0]));
426                     retVal.assignField("value", ScriptAny(result[1]));
427                     generator.popFront();
428                 }
429                 return ScriptAny(retVal);
430             }, 
431             false)));
432     }
433     else if(objToIterate.type == ScriptAny.Type.ARRAY)
434     {
435         auto elements = objToIterate.toValue!(ScriptAny[]); // @suppress(dscanner.suspicious.unmodified)
436         auto generator = new Generator!(Tuple!(size_t, ScriptAny))({
437             size_t indexCounter = 0;
438             foreach(item ; elements)
439             {
440                 ++indexCounter;
441                 yield(tuple(indexCounter-1, item));
442             }
443         });
444         vm._stack.push(ScriptAny(new ScriptFunction("next",
445             delegate ScriptAny(Environment env, ScriptAny* thisObj, ScriptAny[] args, ref NativeFunctionError) {
446                 auto retVal = new ScriptObject("iteration", null, null);
447                 if(generator.empty)
448                 {
449                     retVal.assignField("done", ScriptAny(true));
450                 }
451                 else 
452                 {
453                     auto result = generator.front();
454                     retVal.assignField("key", ScriptAny(result[0]));
455                     retVal.assignField("value", ScriptAny(result[1]));
456                     generator.popFront();
457                 }
458                 return ScriptAny(retVal);
459             })));
460     }
461     else if(objToIterate.isObject)
462     {
463         if(objToIterate.isNativeObjectType!ScriptGenerator)
464         {
465             auto func = new ScriptFunction("next", &native_Generator_next, false);
466             func.bind(objToIterate);
467             vm._stack.push(ScriptAny(func));
468         }
469         else if(objToIterate.isNativeObjectType!ScriptMap)
470         {
471             auto map = objToIterate.toNativeObject!ScriptMap;
472             auto generator = new Generator!(Tuple!(ScriptAny, ScriptAny))({
473                 foreach(key, value ; map.entries)
474                     yield(tuple(key, value));
475             });
476             vm._stack.push(ScriptAny(new ScriptFunction("next",
477                 delegate ScriptAny(Environment env, ScriptAny* thisObj, ScriptAny[] args, ref NativeFunctionError) {
478                     auto retVal = new ScriptObject("iteration", null, null);
479                     if(generator.empty)
480                     {
481                         retVal.assignField("done", ScriptAny(true));
482                     }
483                     else
484                     {
485                         auto result = generator.front();
486                         retVal.assignField("key", result[0]);
487                         retVal.assignField("value", result[1]);
488                         generator.popFront();
489                     }
490                     return ScriptAny(retVal);
491                 }
492             )));
493         }
494         else
495         {
496             auto obj = objToIterate.toValue!ScriptObject;
497             auto generator = new Generator!(Tuple!(string, ScriptAny))({
498                 foreach(k, v ; obj.dictionary)
499                     yield(tuple(k,v));
500             });
501             vm._stack.push(ScriptAny(new ScriptFunction("next", 
502                 delegate ScriptAny(Environment env, ScriptAny* thisObj, ScriptAny[] args, ref NativeFunctionError){
503                     auto retVal = new ScriptObject("iteration", null, null);
504                     if(generator.empty)
505                     {
506                         retVal.assignField("done", ScriptAny(true));
507                     }
508                     else
509                     {
510                         auto result = generator.front();
511                         retVal.assignField("key", ScriptAny(result[0]));
512                         retVal.assignField("value", result[1]);
513                         generator.popFront();
514                     }
515                     return ScriptAny(retVal);
516             })));
517         }
518     }
519     ++vm._ip;
520     return 0;
521 }
522 
523 pragma(inline, true)
524 private int opDel(VirtualMachine vm, Chunk chunk)
525 {
526     auto memberToDelete = vm._stack.pop().toString();
527     auto objToDelete = vm._stack.pop();
528     auto obj = objToDelete.toValue!ScriptObject;
529     if(obj is null)
530         return throwRuntimeError("Cannot delete member of non-object " ~ objToDelete.toString,
531             vm, chunk);
532     obj.dictionary.remove(memberToDelete);
533     ++vm._ip;
534     return 0;
535 }
536 
537 pragma(inline, true)
538 private int opNew(VirtualMachine vm, Chunk chunk)
539 {
540     immutable n = decode!uint(chunk.bytecode[vm._ip + 1..$]) + 1;
541     auto callInfo = vm._stack.pop(n);
542     auto funcAny = callInfo[0];
543     auto args = callInfo[1..$];
544     
545     NativeFunctionError nfe = NativeFunctionError.NO_ERROR;
546     if(funcAny.type != ScriptAny.Type.FUNCTION)
547         return throwRuntimeError("Unable to instantiate new object from non-function " ~ funcAny.toString(), vm, chunk);
548     auto func = funcAny.toValue!ScriptFunction; // @suppress(dscanner.suspicious.unmodified)
549 
550     ScriptAny thisObj = new ScriptObject(func.functionName, func["prototype"].toValue!ScriptObject, null);
551 
552     if(func.type == ScriptFunction.Type.SCRIPT_FUNCTION)
553     {
554         if(func.compiled.length == 0)
555             throw new VMException("Empty script function cannot be called", vm._ip, OpCode.CALL);
556         if(func.isGenerator)
557             return throwRuntimeError("Cannot use new with a Generator Function", vm, chunk);
558         vm._callStack.push(VirtualMachine.CallData(VirtualMachine.FuncCallType.NEW, chunk.bytecode, 
559                 vm._ip, vm._environment, vm._tryData));
560         vm._environment = new Environment(func.closure, func.functionName);
561         vm._ip = 0;
562         chunk.bytecode = func.compiled;
563         vm._tryData = [];
564         // set this
565         vm._environment.forceSetVarOrConst("this", thisObj, true);
566         // set args
567         for(size_t i = 0; i < func.argNames.length; ++i)
568         {
569             if(i >= args.length)
570                 vm._environment.forceSetVarOrConst(func.argNames[i], ScriptAny.UNDEFINED, false);
571             else
572                 vm._environment.forceSetVarOrConst(func.argNames[i], args[i], false);
573         }
574         vm._environment.forceSetVarOrConst("arguments", ScriptAny(args), false);
575         return 0;
576     }
577     else if(func.type == ScriptFunction.Type.NATIVE_FUNCTION)
578     {
579         auto nativeFunc = func.nativeFunction;
580         auto possibleError = nativeFunc(vm._environment, &thisObj, args, nfe);
581         if(nfe == NativeFunctionError.RETURN_VALUE_IS_EXCEPTION)
582             vm._stack.push(possibleError);
583         else
584             vm._stack.push(thisObj);
585     }
586     else if(func.type == ScriptFunction.Type.NATIVE_DELEGATE)
587     {
588         auto nativeDelegate = func.nativeDelegate;
589         auto possibleError = nativeDelegate(vm._environment, &thisObj, args, nfe);
590         if(nfe == NativeFunctionError.RETURN_VALUE_IS_EXCEPTION)
591             vm._stack.push(possibleError);
592         else
593             vm._stack.push(thisObj);
594     }
595     final switch(nfe)
596     {
597     case NativeFunctionError.NO_ERROR:
598         break;
599     case NativeFunctionError.RETURN_VALUE_IS_EXCEPTION:
600         return throwRuntimeError(vm._stack.pop().toString(), vm, chunk);
601     case NativeFunctionError.WRONG_NUMBER_OF_ARGS:
602         return throwRuntimeError("Wrong number of arguments to native function", vm, chunk);
603     case NativeFunctionError.WRONG_TYPE_OF_ARG:
604         return throwRuntimeError("Wrong type of argument to native function", vm, chunk);
605     }
606     if(vm._exc)
607         return throwRuntimeError(null, vm, chunk, ScriptAny.UNDEFINED, vm._exc);
608     vm._ip += 1 + uint.sizeof;
609     return 0;
610 }
611 
612 pragma(inline, true)
613 private int opThis(VirtualMachine vm, Chunk chunk)
614 {
615     bool _; // @suppress(dscanner.suspicious.unmodified)
616     auto thisPtr = vm._environment.lookupVariableOrConst("this", _);
617     if(thisPtr == null)
618         vm._stack.push(ScriptAny.UNDEFINED);
619     else
620         vm._stack.push(*thisPtr);
621     ++vm._ip;
622     return 0;
623 }
624 
625 pragma(inline, true)
626 private int opOpenScope(VirtualMachine vm, Chunk chunk)
627 {
628     vm._environment = new Environment(vm._environment);
629     // debug writefln("Opening environment from parent %s", vm._environment.parent);
630     // debug writefln("VM{ environment depth=%s", vm._environment.depth);
631     ++vm._ip;
632     return 0;
633 }
634 
635 pragma(inline, true)
636 private int opCloseScope(VirtualMachine vm, Chunk chunk)
637 {
638     vm._environment = vm._environment.parent;
639     // debug writefln("Closing environment: %s", vm._environment);
640     // debug writefln("VM} environment depth=%s", vm._environment.depth);
641     ++vm._ip;
642     return 0;
643 }
644 
645 pragma(inline, true)
646 private int opDeclVar(VirtualMachine vm, Chunk chunk)
647 {
648     auto constID = decode!uint(chunk.bytecode[vm._ip + 1..$]);
649     auto varName = chunk.constTable.get(constID).toString();
650     auto value = vm._stack.pop();
651     immutable ok = vm._globals.declareVariableOrConst(varName, value, false);
652     if(!ok)
653         return throwRuntimeError("Cannot redeclare global " ~ varName, vm, chunk);
654     vm._ip += 1 + uint.sizeof;
655     return 0;
656 }
657 
658 pragma(inline, true)
659 private int opDeclLet(VirtualMachine vm, Chunk chunk)
660 {
661     auto constID = decode!uint(chunk.bytecode[vm._ip + 1..$]);
662     auto varName = chunk.constTable.get(constID).toString();
663     auto value = vm._stack.pop();
664     immutable ok = vm._environment.declareVariableOrConst(varName, value, false);
665     if(!ok)
666         return throwRuntimeError("Cannot redeclare local " ~ varName, vm, chunk);
667     vm._ip += 1 + uint.sizeof;
668     return 0;
669 }
670 
671 pragma(inline, true)
672 private int opDeclConst(VirtualMachine vm, Chunk chunk)
673 {
674     auto constID = decode!uint(chunk.bytecode[vm._ip + 1..$]);
675     auto varName = chunk.constTable.get(constID).toString();
676     auto value = vm._stack.pop();
677     immutable ok = vm._environment.declareVariableOrConst(varName, value, true);
678     if(!ok)
679         return throwRuntimeError("Cannot redeclare const " ~ varName, vm, chunk);
680     vm._ip += 1 + uint.sizeof;
681     return 0;
682 }
683 
684 pragma(inline, true)
685 private int opGetVar(VirtualMachine vm, Chunk chunk)
686 {
687     auto constID = decode!uint(chunk.bytecode[vm._ip + 1..$]);   
688     auto varName = chunk.constTable.get(constID).toString();
689     bool isConst; // @suppress(dscanner.suspicious.unmodified)
690     auto valuePtr = vm._environment.lookupVariableOrConst(varName, isConst);
691     if(valuePtr == null)
692         return throwRuntimeError("Variable lookup failed: " ~ varName, vm, chunk);
693     vm._stack.push(*valuePtr);
694     vm._ip += 1 + uint.sizeof;
695     return 0;
696 }
697 
698 pragma(inline, true)
699 private int opSetVar(VirtualMachine vm, Chunk chunk)
700 {
701     auto constID = decode!uint(chunk.bytecode[vm._ip + 1..$]);
702     auto varName = chunk.constTable.get(constID).toString();
703     bool isConst; // @suppress(dscanner.suspicious.unmodified)
704     auto varPtr = vm._environment.lookupVariableOrConst(varName, isConst);
705     if(varPtr == null)
706     {
707         return throwRuntimeError("Cannot assign to undefined variable: " ~ varName, vm, chunk);
708     }
709     auto value = vm._stack.peek(); // @suppress(dscanner.suspicious.unmodified)
710     if(value == ScriptAny.UNDEFINED)
711         vm._environment.unsetVariable(varName);
712     else
713         *varPtr = value;
714     vm._ip += 1 + uint.sizeof;
715     return 0;
716 }
717 
718 pragma(inline, true)
719 private int opObjGet(VirtualMachine vm, Chunk chunk)
720 {
721     import std.utf: UTFException;
722 
723     auto objToAccess = vm._stack.array[$-2];
724     auto field = vm._stack.array[$-1]; // @suppress(dscanner.suspicious.unmodified)
725     vm._stack.pop(2);
726     // TODO handle getters
727     // if field is integer it is array access
728     if(field.isNumber 
729     && (objToAccess.type == ScriptAny.Type.ARRAY || objToAccess.type == ScriptAny.Type.STRING))
730     {
731         auto index = field.toValue!long;
732         if(objToAccess.type == ScriptAny.Type.ARRAY)
733         {
734             auto arr = objToAccess.toValue!(ScriptAny[]);
735             if(index < 0)
736                 index = arr.length + index;
737             if(index < 0 || index >= arr.length)
738                 return throwRuntimeError("Out of bounds array access", vm, chunk);
739             vm._stack.push(arr[index]);
740         }
741         else if(objToAccess.type == ScriptAny.Type.STRING)
742         {
743             auto str = objToAccess.toValue!(ScriptString)().toString();
744             if(index < 0)
745                 index = str.length + index;
746             if(index < 0 || index >= str.length)
747                 return throwRuntimeError("Out of bounds string access", vm, chunk);
748             try 
749             {
750                 vm._stack.push(ScriptAny([str[index]]));
751             }
752             catch(UTFException)
753             {
754                 vm._stack.push(ScriptAny.UNDEFINED);
755             }
756         }
757     }
758     else // else object field or property access
759     {
760         auto index = field.toString();
761         if(objToAccess.isObject)
762         {
763             auto obj = objToAccess.toValue!ScriptObject; // @suppress(dscanner.suspicious.unmodified)
764             auto getter = obj.findGetter(index);
765             if(getter)
766             {
767                 // this might be a super property call
768                 auto thisObj = getLocalThis(vm._environment);
769                 ScriptAny retVal;
770                 if(ScriptFunction.isInstanceOf(thisObj.toValue!ScriptObject, 
771                   objToAccess["constructor"].toValue!ScriptFunction))
772                 {
773                     retVal = vm.runFunction(getter, thisObj, []);
774                 }
775                 else
776                 {
777                     retVal = vm.runFunction(getter, objToAccess, []);
778                 }
779                 vm._stack.push(retVal);
780                 if(vm._exc)
781                     throwRuntimeError(null, vm, chunk, ScriptAny.UNDEFINED, vm._exc);
782             }
783             else
784                 vm._stack.push(objToAccess[index]);
785         }
786         else
787             throwRuntimeError("Unable to access members of non-object " ~ objToAccess.toString(),
788                     vm, chunk);
789     }
790     ++vm._ip;
791     return 0;
792 }
793 
794 pragma(inline, true)
795 private int opObjSet(VirtualMachine vm, Chunk chunk)
796 {
797     auto objToAccess = vm._stack.array[$-3];
798     auto fieldToAssign = vm._stack.array[$-2]; // @suppress(dscanner.suspicious.unmodified)
799     auto value = vm._stack.array[$-1];
800     vm._stack.pop(3);
801     if(fieldToAssign.isNumber 
802     && (objToAccess.type == ScriptAny.Type.ARRAY || objToAccess.type == ScriptAny.Type.STRING))
803     {
804         auto index = fieldToAssign.toValue!long;
805         if(objToAccess.type == ScriptAny.Type.ARRAY)
806         {
807             auto arr = objToAccess.toValue!(ScriptAny[]);
808             if(index < 0)
809                 index = arr.length + index;
810             if(index < 0 || index >= arr.length)
811                 return throwRuntimeError("Out of bounds array assignment", vm, chunk);
812             arr[index] = value;
813             vm._stack.push(value);
814         }
815     }
816     else
817     {
818         auto index = fieldToAssign.toValue!string;
819         if(!objToAccess.isObject)
820             return throwRuntimeError("Unable to assign member of non-object " ~ objToAccess.toString(), vm, chunk);
821         auto obj = objToAccess.toValue!ScriptObject; // @suppress(dscanner.suspicious.unmodified)
822         auto setter = obj.findSetter(index);
823         if(setter)
824         {
825             auto thisObj = getLocalThis(vm._environment);
826             immutable isSuperProp = ScriptFunction.isInstanceOf(thisObj.toValue!ScriptObject, 
827                     objToAccess["constructor"].toValue!ScriptFunction);
828             if(isSuperProp)
829                 vm.runFunction(setter, thisObj, [value]);
830             else
831                 vm.runFunction(setter, objToAccess, [value]);
832             if(vm._exc)
833                 throwRuntimeError(null, vm, chunk, ScriptAny.UNDEFINED, vm._exc);
834             // if getter push that or else undefined
835             auto getter = obj.findGetter(index);
836             if(getter)
837             {
838                 if(isSuperProp)
839                     vm._stack.push(vm.runFunction(getter, thisObj, []));
840                 else
841                     vm._stack.push(vm.runFunction(getter, objToAccess, []));
842             }
843             else
844             {
845                 vm._stack.push(ScriptAny.UNDEFINED);
846             }
847             
848             if(vm._exc)
849                 return throwRuntimeError(null, vm, chunk, ScriptAny.UNDEFINED, vm._exc);
850         }
851         else
852         {
853             if(obj.hasGetter(index))
854                 return throwRuntimeError("Object " ~ obj.toString() ~ " has getter for property "
855                     ~ index ~ " but no setter.", vm, chunk);
856             objToAccess[index] = value;
857             vm._stack.push(value);
858         }
859     }
860     ++vm._ip;
861     return 0;
862 }
863 
864 pragma(inline, true)
865 private int opCall(VirtualMachine vm, Chunk chunk)
866 {
867     immutable n = decode!uint(chunk.bytecode[vm._ip + 1..$]) + 2;
868     if(vm._stack.size < n)
869     {
870         vm.printStack();
871         throw new VMException("opCall failure, stack < " ~ n.to!string, vm._ip, OpCode.CALL);
872     }
873     auto callInfo = vm._stack.pop(n);
874     auto thisObj = callInfo[0]; // @suppress(dscanner.suspicious.unmodified)
875     auto funcAny = callInfo[1];
876     auto args = callInfo[2..$];
877     NativeFunctionError nfe = NativeFunctionError.NO_ERROR;
878     if(funcAny.type != ScriptAny.Type.FUNCTION)
879         return throwRuntimeError("Unable to call non-function " ~ funcAny.toString(), vm, chunk);
880     auto func = funcAny.toValue!ScriptFunction; // @suppress(dscanner.suspicious.unmodified)
881     if(func.boundThis != ScriptAny.UNDEFINED)
882         thisObj = func.boundThis;
883     if(func.type == ScriptFunction.Type.SCRIPT_FUNCTION)
884     {
885         if(func.compiled.length == 0)
886             throw new VMException("Empty script function cannot be called", vm._ip, OpCode.CALL);
887         
888         if(!func.isGenerator) // only script functions can be marked as generators
889         {
890             vm._callStack.push(VirtualMachine.CallData(VirtualMachine.FuncCallType.NORMAL, chunk.bytecode, 
891                     vm._ip, vm._environment, vm._tryData));
892             vm._environment = new Environment(func.closure, func.functionName);
893             vm._ip = 0;
894             chunk.bytecode = func.compiled;
895             vm._tryData = [];
896             // set this
897             vm._environment.forceSetVarOrConst("this", thisObj, true);
898             // set args
899             for(size_t i = 0; i < func.argNames.length; ++i)
900             {
901                 if(i >= args.length)
902                     vm._environment.forceSetVarOrConst(func.argNames[i], ScriptAny.UNDEFINED, false);
903                 else
904                     vm._environment.forceSetVarOrConst(func.argNames[i], args[i], false);
905             }
906             // set arguments variable
907             vm._environment.forceSetVarOrConst("arguments", ScriptAny(args), false);
908             // check exc in case exception was thrown during call or apply
909             if(vm._exc)
910                 return throwRuntimeError(null, vm, chunk, ScriptAny.UNDEFINED, vm._exc);
911             return 0;
912         }
913         else
914         {
915             ScriptObject newGen = new ScriptObject("Generator", getGeneratorPrototype,
916                     new ScriptGenerator(vm._environment, func, args, thisObj));
917             vm._stack.push(ScriptAny(newGen));
918             vm._ip += 1 + uint.sizeof;
919             return 0;
920         }
921     }
922     else if(func.type == ScriptFunction.Type.NATIVE_FUNCTION)
923     {
924         auto nativeFunc = func.nativeFunction;
925         vm._stack.push(nativeFunc(vm._environment, &thisObj, args, nfe));
926     }
927     else if(func.type == ScriptFunction.Type.NATIVE_DELEGATE)
928     {
929         auto nativeDelegate = func.nativeDelegate;
930         vm._stack.push(nativeDelegate(vm._environment, &thisObj, args, nfe));
931     }
932     final switch(nfe)
933     {
934     case NativeFunctionError.NO_ERROR:
935         break;
936     case NativeFunctionError.RETURN_VALUE_IS_EXCEPTION:
937         return throwRuntimeError(vm._stack.peek().toString(), vm, chunk);
938     case NativeFunctionError.WRONG_NUMBER_OF_ARGS:
939         return throwRuntimeError("Wrong number of arguments to native function", vm, chunk);
940     case NativeFunctionError.WRONG_TYPE_OF_ARG:
941         return throwRuntimeError("Wrong type of argument to native function", vm, chunk);
942     }
943     if(vm._exc)
944         return throwRuntimeError(null, vm, chunk, ScriptAny.UNDEFINED, vm._exc);
945     vm._ip += 1 + uint.sizeof;
946     return 0;
947 }
948 
949 pragma(inline, true)
950 private int opJmpFalse(VirtualMachine vm, Chunk chunk)
951 {
952     immutable jmpAmount = decode!int(chunk.bytecode[vm._ip + 1..$]);
953     immutable shouldJump = vm._stack.pop();
954     if(!shouldJump)
955         vm._ip += jmpAmount;
956     else
957         vm._ip += 1 + int.sizeof;
958     return 0;
959 }
960 
961 pragma(inline, true)
962 private int opJmp(VirtualMachine vm, Chunk chunk)
963 {
964     immutable jmpAmount = decode!int(chunk.bytecode[vm._ip + 1..$]);
965     vm._ip += jmpAmount;
966     return 0;
967 }
968 
969 pragma(inline, true)
970 private int opSwitch(VirtualMachine vm, Chunk chunk)
971 {
972     immutable relAbsJmp = decode!uint(chunk.bytecode[vm._ip + 1..$]);
973     auto valueToTest = vm._stack.pop();
974     auto jumpTableArray = vm._stack.pop();
975     // build the jump table out of the entries
976     if(jumpTableArray.type != ScriptAny.Type.ARRAY)
977         throw new VMException("Invalid jump table", vm._ip, OpCode.SWITCH);
978     int[ScriptAny] jmpTable;
979     foreach(entry ; jumpTableArray.toValue!(ScriptAny[]))
980     {
981         if(entry.type != ScriptAny.Type.ARRAY)
982             throw new VMException("Invalid jump table entry", vm._ip, OpCode.SWITCH);
983         auto entryArray = entry.toValue!(ScriptAny[]);
984         if(entryArray.length < 2)
985             throw new VMException("Invalid jump table entry size", vm._ip, OpCode.SWITCH);
986         jmpTable[entryArray[0]] = entryArray[1].toValue!int;
987     }
988     if(valueToTest in jmpTable)
989         vm._ip = jmpTable[valueToTest];
990     else
991         vm._ip = relAbsJmp;
992     return 0;
993 }
994 
995 pragma(inline, true)
996 private int opGoto(VirtualMachine vm, Chunk chunk)
997 {
998     immutable address = decode!uint(chunk.bytecode[vm._ip + 1..$]);
999     immutable depth = decode!ubyte(chunk.bytecode[vm._ip+1+uint.sizeof..$]);
1000     for(ubyte i = 0; i < depth; ++i)
1001     {
1002         vm._environment = vm._environment.parent;
1003     }
1004     vm._ip = address;
1005     return 0;
1006 }
1007 
1008 pragma(inline, true)
1009 private int opThrow(VirtualMachine vm, Chunk chunk)
1010 {
1011     auto valToThrow = vm._stack.pop();
1012     return throwRuntimeError("Uncaught script exception", vm, chunk, valToThrow);
1013 }
1014 
1015 pragma(inline, true)
1016 private int opRethrow(VirtualMachine vm, Chunk chunk)
1017 {
1018     if(vm._exc)
1019         return throwRuntimeError(vm._exc.msg, vm, chunk, vm._exc.thrownValue, vm._exc);
1020     ++vm._ip;
1021     return 0;
1022 }
1023 
1024 pragma(inline, true)
1025 private int opTry(VirtualMachine vm, Chunk chunk)
1026 {
1027     immutable catchGoto = decode!uint(chunk.bytecode[vm._ip + 1..$]);
1028     immutable depth = cast(int)vm._environment.depth();
1029     vm._tryData ~= VirtualMachine.TryData(depth, vm._stack.size, catchGoto);
1030     vm._ip += 1 + uint.sizeof;
1031     return 0;
1032 }
1033 
1034 pragma(inline, true)
1035 private int opEndTry(VirtualMachine vm, Chunk chunk)
1036 {
1037     vm._tryData = vm._tryData[0..$-1];
1038     ++vm._ip;
1039     return 0;
1040 }
1041 
1042 pragma(inline, true)
1043 private int opLoadExc(VirtualMachine vm, Chunk chunk)
1044 {
1045     if(vm._exc is null)
1046         throw new VMException("An exception was never thrown", vm._ip, OpCode.LOADEXC);
1047     if(vm._exc.thrownValue != ScriptAny.UNDEFINED)
1048         vm._stack.push(vm._exc.thrownValue);
1049     else
1050         vm._stack.push(ScriptAny(vm._exc.msg));
1051     vm._exc = null; // once loaded by a catch block it should be cleared
1052     ++vm._ip;
1053     return 0;
1054 }
1055 
1056 pragma(inline, true)
1057 private int opConcat(VirtualMachine vm, Chunk chunk)
1058 {
1059     immutable n = decode!uint(chunk.bytecode[vm._ip + 1..$]);
1060     string result = "";
1061     auto values = vm._stack.pop(n);
1062     foreach(value ; values)
1063         result ~= value.toString();
1064     vm._stack.push(ScriptAny(result));
1065     vm._ip += 1 + uint.sizeof;
1066     return 0;
1067 }
1068 
1069 pragma(inline, true)
1070 private int opBitNot(VirtualMachine vm, Chunk chunk)
1071 {
1072     vm._stack.array[$-1] = ~vm._stack.array[$-1];
1073     ++vm._ip;
1074     return 0;
1075 }
1076 
1077 pragma(inline, true)
1078 private int opNot(VirtualMachine vm, Chunk chunk)
1079 {
1080     vm._stack.array[$-1] = ScriptAny(!vm._stack.array[$-1]);
1081     ++vm._ip;
1082     return 0;
1083 }
1084 
1085 pragma(inline, true)
1086 private int opNegate(VirtualMachine vm, Chunk chunk)
1087 {
1088     vm._stack.array[$-1] = -vm._stack.array[$-1];
1089     ++vm._ip;
1090     return 0;
1091 }
1092 
1093 pragma(inline, true)
1094 private int opTypeof(VirtualMachine vm, Chunk chunk)
1095 {
1096     vm._stack.array[$-1] = ScriptAny(vm._stack.array[$-1].typeToString());
1097     ++vm._ip;
1098     return 0;
1099 }
1100 
1101 pragma(inline, true)
1102 private int opInstanceOf(VirtualMachine vm, Chunk chunk)
1103 {
1104     auto operands = vm._stack.pop(2);
1105     if(!operands[0].isObject)
1106         vm._stack.push(ScriptAny(false));
1107     else if(operands[1].type != ScriptAny.Type.FUNCTION)
1108         vm._stack.push(ScriptAny(false));
1109     else
1110     {
1111         auto lhsObj = operands[0].toValue!ScriptObject; // @suppress(dscanner.suspicious.unmodified)
1112         auto rhsFunc = operands[1].toValue!ScriptFunction; // @suppress(dscanner.suspicious.unmodified)
1113         auto proto = lhsObj.prototype;
1114         while(proto !is null)
1115         {
1116             if(proto["constructor"].toValue!ScriptFunction is rhsFunc)
1117             {
1118                 vm._stack.push(ScriptAny(true));
1119                 ++vm._ip;
1120                 return 0;
1121             }
1122             proto = proto.prototype;
1123         }
1124     }
1125     vm._stack.push(ScriptAny(false));
1126     ++vm._ip;
1127     return 0;
1128 }
1129 
1130 private string DEFINE_BIN_OP(string name, string op)()
1131 {
1132     import std.format: format;
1133     return format(q{
1134 pragma(inline, true)
1135 private int %1$s(VirtualMachine vm, Chunk chunk)
1136 {
1137     auto operands = vm._stack.pop(2);
1138     vm._stack.push(operands[0] %2$s operands[1]);
1139     ++vm._ip;
1140     return 0;
1141 }
1142     }, name, op);
1143 }
1144 
1145 mixin(DEFINE_BIN_OP!("opPow", "^^"));
1146 mixin(DEFINE_BIN_OP!("opMul", "*"));
1147 mixin(DEFINE_BIN_OP!("opDiv", "/"));
1148 mixin(DEFINE_BIN_OP!("opMod", "%"));
1149 mixin(DEFINE_BIN_OP!("opAdd", "+"));
1150 mixin(DEFINE_BIN_OP!("opSub", "-"));
1151 mixin(DEFINE_BIN_OP!("opBitRSh", ">>"));
1152 mixin(DEFINE_BIN_OP!("opBitURSh", ">>>"));
1153 mixin(DEFINE_BIN_OP!("opBitLSh", "<<"));
1154 
1155 private string DEFINE_BIN_BOOL_OP(string name, string op)()
1156 {
1157     import std.format: format;
1158     return format(q{
1159 pragma(inline, true)
1160 private int %1$s(VirtualMachine vm, Chunk chunk)
1161 {
1162     auto operands = vm._stack.pop(2);
1163     vm._stack.push(ScriptAny(operands[0] %2$s operands[1]));
1164     ++vm._ip;
1165     return 0;
1166 }
1167     }, name, op);
1168 }
1169 
1170 mixin(DEFINE_BIN_BOOL_OP!("opLT", "<"));
1171 mixin(DEFINE_BIN_BOOL_OP!("opLE", "<="));
1172 mixin(DEFINE_BIN_BOOL_OP!("opGT", ">"));
1173 mixin(DEFINE_BIN_BOOL_OP!("opGE", ">="));
1174 mixin(DEFINE_BIN_BOOL_OP!("opEQ", "=="));
1175 mixin(DEFINE_BIN_BOOL_OP!("opNEQ", "!="));
1176 
1177 pragma(inline, true)
1178 private int opStrictEquals(VirtualMachine vm, Chunk chunk)
1179 {
1180     auto operands = vm._stack.pop(2);
1181     vm._stack.push(ScriptAny(operands[0].strictEquals(operands[1])));
1182     ++vm._ip;
1183     return 0;
1184 }
1185 
1186 mixin(DEFINE_BIN_OP!("opBitAnd", "&"));
1187 mixin(DEFINE_BIN_OP!("opBitOr", "|"));
1188 mixin(DEFINE_BIN_OP!("opBitXor", "^"));
1189 
1190 mixin(DEFINE_BIN_BOOL_OP!("opAnd", "&&"));
1191 
1192 pragma(inline, true)
1193 private int opOr(VirtualMachine vm, Chunk chunk)
1194 {
1195     auto operands = vm._stack.pop(2);
1196     vm._stack.push(operands[0].orOp(operands[1]));
1197     ++vm._ip;
1198     return 0;
1199 }
1200 
1201 pragma(inline, true)
1202 private int opTern(VirtualMachine vm, Chunk chunk)
1203 {
1204     auto operands = vm._stack.pop(3);
1205     if(operands[0])
1206         vm._stack.push(operands[1]);
1207     else
1208         vm._stack.push(operands[2]);
1209     ++vm._ip;
1210     return 0;
1211 }
1212 
1213 pragma(inline, true)
1214 private int opReturn(VirtualMachine vm, Chunk chunk)
1215 {
1216     if(vm._stack.size < 1)
1217         throw new VMException("Return value missing from return", vm._ip, OpCode.RETURN);
1218 
1219     if(vm._callStack.size > 0)
1220     {
1221         auto fcdata = vm._callStack.pop(); // @suppress(dscanner.suspicious.unmodified)
1222         chunk.bytecode = fcdata.bc;
1223         if(fcdata.fct == VirtualMachine.FuncCallType.NEW)
1224         {
1225             // pop whatever was returned and push the "this"
1226             vm._stack.pop();
1227             bool _;
1228             vm._stack.push(*vm._environment.lookupVariableOrConst("this", _));
1229         }
1230         vm._ip = fcdata.ip;
1231         vm._environment = fcdata.env;
1232         vm._tryData = fcdata.tryData;
1233     }
1234     else
1235     {
1236         vm._ip = chunk.bytecode.length;
1237     }
1238     vm._ip += 1 + uint.sizeof; // the size of call()    
1239     return 0;
1240 }
1241 
1242 pragma(inline, true)
1243 private int opHalt(VirtualMachine vm, Chunk chunk)
1244 {
1245     vm._stopped = true;
1246     ++vm._ip;
1247     return 0;
1248 }
1249 
1250 /**
1251  * This class implements a virtual machine that runs Chunks of bytecode. This class is not thread
1252  * safe and a copy() of the VM should be instantiated to run scripts in multiple threads. The
1253  * Environments are shallow copied and not guaranteed to be thread safe either.
1254  */
1255 class VirtualMachine
1256 {
1257     /// ctor
1258     this(Environment globalEnv)
1259     {
1260         _environment = globalEnv;
1261         _globals = globalEnv;
1262         _ops[] = &opNop;
1263         _ops[OpCode.CONST] = &opConst;
1264         _ops[OpCode.CONST_0] = &opConst0;
1265         _ops[OpCode.CONST_1] = &opConst1;
1266         _ops[OpCode.PUSH] = &opPush;
1267         _ops[OpCode.POP] = &opPop;
1268         _ops[OpCode.POPN] = &opPopN;
1269         _ops[OpCode.SET] = &opSet;
1270         _ops[OpCode.STACK] = &opStack;
1271         _ops[OpCode.STACK_1] = &opStack1;
1272         _ops[OpCode.ARRAY] = &opArray;
1273         _ops[OpCode.OBJECT] = &opObject;
1274         _ops[OpCode.CLASS] = &opClass;
1275         _ops[OpCode.REGEX] = &opRegex;
1276         _ops[OpCode.ITER] = &opIter;
1277         _ops[OpCode.DEL] = &opDel;
1278         _ops[OpCode.NEW] = &opNew;
1279         _ops[OpCode.THIS] = &opThis;
1280         _ops[OpCode.OPENSCOPE] = &opOpenScope;
1281         _ops[OpCode.CLOSESCOPE] = &opCloseScope;
1282         _ops[OpCode.DECLVAR] = &opDeclVar;
1283         _ops[OpCode.DECLLET] = &opDeclLet;
1284         _ops[OpCode.DECLCONST] = &opDeclConst;
1285         _ops[OpCode.GETVAR] = &opGetVar;
1286         _ops[OpCode.SETVAR] = &opSetVar;
1287         _ops[OpCode.OBJGET] = &opObjGet;
1288         _ops[OpCode.OBJSET] = &opObjSet;
1289         _ops[OpCode.CALL] = &opCall;
1290         _ops[OpCode.JMPFALSE] = &opJmpFalse;
1291         _ops[OpCode.JMP] = &opJmp;
1292         _ops[OpCode.SWITCH] = &opSwitch;
1293         _ops[OpCode.GOTO] = &opGoto;
1294         _ops[OpCode.THROW] = &opThrow;
1295         _ops[OpCode.RETHROW] = &opRethrow;
1296         _ops[OpCode.TRY] = &opTry;
1297         _ops[OpCode.ENDTRY] = &opEndTry;
1298         _ops[OpCode.LOADEXC] = &opLoadExc;
1299         _ops[OpCode.CONCAT] = &opConcat;
1300         _ops[OpCode.BITNOT] = &opBitNot;
1301         _ops[OpCode.NOT] = &opNot;
1302         _ops[OpCode.NEGATE] = &opNegate;
1303         _ops[OpCode.TYPEOF] = &opTypeof;
1304         _ops[OpCode.INSTANCEOF] = &opInstanceOf;
1305         _ops[OpCode.POW] = &opPow;
1306         _ops[OpCode.MUL] = &opMul;
1307         _ops[OpCode.DIV] = &opDiv;
1308         _ops[OpCode.MOD] = &opMod;
1309         _ops[OpCode.ADD] = &opAdd;
1310         _ops[OpCode.SUB] = &opSub;
1311         _ops[OpCode.BITRSH] = &opBitRSh;
1312         _ops[OpCode.BITURSH] = &opBitURSh;
1313         _ops[OpCode.BITLSH] = &opBitLSh;
1314         _ops[OpCode.LT] = &opLT;
1315         _ops[OpCode.LE] = &opLE;
1316         _ops[OpCode.GT] = &opGT;
1317         _ops[OpCode.GE] = &opGE;
1318         _ops[OpCode.EQUALS] = &opEQ;
1319         _ops[OpCode.NEQUALS] = &opNEQ;
1320         _ops[OpCode.STREQUALS] = &opStrictEquals;
1321         _ops[OpCode.BITAND] = &opBitAnd;
1322         _ops[OpCode.BITOR] = &opBitOr;
1323         _ops[OpCode.BITXOR] = &opBitXor;
1324         _ops[OpCode.AND] = &opAnd;
1325         _ops[OpCode.OR] = &opOr;
1326         _ops[OpCode.TERN] = &opTern;
1327         _ops[OpCode.RETURN] = &opReturn;
1328         _ops[OpCode.HALT] = &opHalt;
1329         _stack.reserve(256);
1330     }
1331 
1332     /// print a chunk instruction by instruction, using the const table to indicate values
1333     void printChunk(Chunk chunk, bool printConstTable=false)
1334     {
1335         if(printConstTable)
1336         {
1337             writeln("===== CONST TABLE =====");
1338             foreach(index, value ; chunk.constTable)
1339             {
1340                 writef("#%s: ", index);
1341                 if(value.type == ScriptAny.Type.FUNCTION)
1342                 {
1343                     auto fn = value.toValue!ScriptFunction;
1344                     writeln("<function> " ~ fn.functionName);
1345                     auto funcChunk = new Chunk();
1346                     funcChunk.constTable = chunk.constTable;
1347                     funcChunk.bytecode = fn.compiled;
1348                     printChunk(funcChunk, false);
1349                 }
1350                 else
1351                 {
1352                     write("<" ~ value.typeToString() ~ "> ");
1353                     if(value.toString().length < 100)
1354                         writeln(value.toString());
1355                     else
1356                         writeln();
1357                 }
1358             }
1359         }
1360         if(printConstTable)
1361             writeln("===== DISASSEMBLY =====");
1362         size_t ip = 0;
1363         while(ip < chunk.bytecode.length)
1364         {
1365             auto op = cast(OpCode)chunk.bytecode[ip];
1366             printInstruction(ip, chunk);
1367             switch(op)
1368             {
1369             case OpCode.NOP:
1370                 ++ip;
1371                 break;
1372             case OpCode.CONST:
1373                 ip += 1 + uint.sizeof;
1374                 break;
1375             case OpCode.CONST_0:
1376             case OpCode.CONST_1:
1377                 ++ip;
1378                 break;
1379             case OpCode.PUSH:
1380                 ip += 1 + int.sizeof;
1381                 break;
1382             case OpCode.POP:
1383                 ++ip;
1384                 break;
1385             case OpCode.POPN:
1386                 ip += 1 + uint.sizeof;
1387                 break;
1388             case OpCode.SET: 
1389                 ip += 1 + uint.sizeof;
1390                 break;
1391             case OpCode.STACK:
1392                 ip += 1 + uint.sizeof;
1393                 break;
1394             case OpCode.STACK_1:
1395                 ++ip;
1396                 break;
1397             case OpCode.ARRAY:
1398                 ip += 1 + uint.sizeof;
1399                 break;
1400             case OpCode.OBJECT:
1401                 ip += 1 + uint.sizeof;
1402                 break;
1403             case OpCode.CLASS:
1404                 ip += 1 + 4 * ubyte.sizeof;
1405                 break;
1406             case OpCode.ITER:
1407             case OpCode.DEL:
1408                 ++ip;
1409                 break;
1410             case OpCode.NEW:
1411                 ip += 1 + uint.sizeof;
1412                 break;
1413             case OpCode.THIS:
1414                 ++ip;
1415                 break;
1416             case OpCode.OPENSCOPE:
1417                 ++ip;
1418                 break;
1419             case OpCode.CLOSESCOPE:
1420                 ++ip;
1421                 break;
1422             case OpCode.DECLVAR:
1423             case OpCode.DECLLET:
1424             case OpCode.DECLCONST:
1425                 ip += 1 + uint.sizeof;
1426                 break;
1427             case OpCode.GETVAR:
1428             case OpCode.SETVAR:
1429                 ip += 1 + uint.sizeof;
1430                 break;
1431             case OpCode.OBJGET:
1432             case OpCode.OBJSET:
1433                 ++ip;
1434                 break;
1435             case OpCode.CALL:
1436                 ip += 1 + uint.sizeof;
1437                 break;
1438             case OpCode.JMPFALSE:
1439             case OpCode.JMP:
1440                 ip += 1 + int.sizeof;
1441                 break;
1442             case OpCode.SWITCH:
1443                 ip += 1 + uint.sizeof;
1444                 break;
1445             case OpCode.GOTO:
1446                 ip += 1 + uint.sizeof + ubyte.sizeof;
1447                 break;
1448             case OpCode.THROW:
1449             case OpCode.RETHROW:
1450                 ++ip;
1451                 break;
1452             case OpCode.TRY:
1453                 ip += 1 + uint.sizeof;
1454                 break;
1455             case OpCode.ENDTRY:
1456             case OpCode.LOADEXC:
1457                 ++ip;
1458                 break;
1459             case OpCode.CONCAT:
1460                 ip += 1 + uint.sizeof;
1461                 break;
1462             case OpCode.BITNOT:
1463             case OpCode.NOT:
1464             case OpCode.NEGATE:
1465             case OpCode.TYPEOF:
1466             case OpCode.INSTANCEOF:
1467             case OpCode.POW:
1468             case OpCode.MUL:
1469             case OpCode.DIV:
1470             case OpCode.MOD:
1471             case OpCode.ADD:
1472             case OpCode.SUB:
1473             case OpCode.LT:
1474             case OpCode.LE:
1475             case OpCode.GT:
1476             case OpCode.GE:
1477             case OpCode.EQUALS:
1478             case OpCode.NEQUALS:
1479             case OpCode.STREQUALS:
1480             case OpCode.BITAND:
1481             case OpCode.BITOR:
1482             case OpCode.BITXOR:
1483             case OpCode.AND:
1484             case OpCode.OR:
1485             case OpCode.TERN:
1486                 ++ip;
1487                 break;
1488             case OpCode.RETURN:
1489             case OpCode.HALT:
1490                 ++ip;
1491                 break;
1492             default:
1493                 ++ip;
1494             }
1495         }
1496         writeln("=======================");
1497     }
1498 
1499     /// prints an individual instruction without moving the ip
1500     void printInstruction(in size_t ip, Chunk chunk)
1501     {
1502         auto op = cast(OpCode)chunk.bytecode[ip];
1503         switch(op)
1504         {
1505         case OpCode.NOP:
1506             writefln("%05d: %s", ip, op.opCodeToString);
1507             break;
1508         case OpCode.CONST: {
1509             immutable constID = decode!uint(chunk.bytecode[ip + 1..$]);
1510             printInstructionWithConstID(ip, op, constID, chunk);
1511             break;
1512         }
1513         case OpCode.CONST_0:
1514         case OpCode.CONST_1:
1515             writefln("%05d: %s", ip, op.opCodeToString);
1516             break;
1517         case OpCode.PUSH: {
1518             immutable index = decode!int(chunk.bytecode[ip + 1..$]);
1519             writefln("%05d: %s index=%s", ip, op.opCodeToString, index);
1520             break;
1521         }
1522         case OpCode.POP:
1523             writefln("%05d: %s", ip, op.opCodeToString);
1524             break;
1525         case OpCode.POPN: {
1526             immutable amount = decode!uint(chunk.bytecode[ip + 1..$]);
1527             writefln("%05d: %s amount=%s", ip, op.opCodeToString, amount);
1528             break;
1529         }
1530         case OpCode.SET: {
1531             immutable index = decode!uint(chunk.bytecode[ip + 1..$]);
1532             writefln("%05d: %s index=%s", ip, op.opCodeToString, index);
1533             break;
1534         }
1535         case OpCode.STACK: {
1536             immutable n = decode!uint(chunk.bytecode[ip + 1..$]);
1537             writefln("%05d: %s n=%s", ip, op.opCodeToString, n);
1538             break;
1539         }
1540         case OpCode.STACK_1:
1541             writefln("%05d: %s", ip, op.opCodeToString);
1542             break;
1543         case OpCode.ARRAY: {
1544             immutable n = decode!uint(chunk.bytecode[ip + 1..$]);
1545             writefln("%05d: %s n=%s", ip, op.opCodeToString, n);
1546             break;
1547         }
1548         case OpCode.OBJECT: {
1549             immutable n = decode!uint(chunk.bytecode[ip + 1..$]);
1550             writefln("%05d: %s n=%s", ip, op.opCodeToString, n);
1551             break;
1552         }
1553         case OpCode.CLASS: {
1554             immutable numMethods = decode!ubyte(chunk.bytecode[ip + 1..$]);
1555             immutable numGetters = decode!ubyte(chunk.bytecode[ip + 2..$]);
1556             immutable numSetters = decode!ubyte(chunk.bytecode[ip + 3..$]);
1557             immutable numStatics = decode!ubyte(chunk.bytecode[ip + 4..$]);
1558             writefln("%05d: %s %s,%s,%s,%s", ip, op.opCodeToString, numMethods, numGetters, numSetters, numStatics);
1559             break;
1560         }
1561         case OpCode.ITER:
1562         case OpCode.DEL:
1563             writefln("%05d: %s", ip, op.opCodeToString);
1564             break;
1565         case OpCode.NEW: {
1566             immutable args = decode!uint(chunk.bytecode[ip + 1..$]);
1567             writefln("%05d: %s args=%s", ip, op.opCodeToString, args);
1568             break;
1569         }
1570         case OpCode.THIS:
1571             writefln("%05d: %s", ip, op.opCodeToString);
1572             break;
1573         case OpCode.OPENSCOPE:
1574         case OpCode.CLOSESCOPE:
1575             writefln("%05d: %s", ip, op.opCodeToString);
1576             break;
1577         case OpCode.DECLVAR: 
1578         case OpCode.DECLLET:
1579         case OpCode.DECLCONST: {
1580             immutable constID = decode!uint(chunk.bytecode[ip + 1..$]);
1581             printInstructionWithConstID(ip, op, constID, chunk);
1582             break;
1583         }
1584         case OpCode.GETVAR:
1585         case OpCode.SETVAR: {
1586             immutable constID = decode!uint(chunk.bytecode[ip + 1..$]);
1587             printInstructionWithConstID(ip, op, constID, chunk);
1588             break;
1589         }
1590         case OpCode.OBJSET:
1591         case OpCode.OBJGET:
1592             writefln("%05d: %s", ip, op.opCodeToString);
1593             break;
1594         case OpCode.CALL: {
1595             immutable args = decode!uint(chunk.bytecode[ip + 1..$]);
1596             writefln("%05d: %s args=%s", ip, op.opCodeToString, args);
1597             break;
1598         }
1599         case OpCode.JMPFALSE: 
1600         case OpCode.JMP: {
1601             immutable jump = decode!int(chunk.bytecode[ip + 1..$]);
1602             writefln("%05d: %s jump=%s", ip, op.opCodeToString, jump);
1603             break;
1604         }
1605         case OpCode.SWITCH: {
1606             immutable def = decode!uint(chunk.bytecode[ip + 1..$]);
1607             writefln("%05d: %s default=%s", ip, op.opCodeToString, def);
1608             break;
1609         }
1610         case OpCode.GOTO: {
1611             immutable instruction = decode!uint(chunk.bytecode[ip + 1..$]);
1612             immutable depth = decode!ubyte(chunk.bytecode[ip + 1 + uint.sizeof..$]);
1613             writefln("%05d: %s instruction=%s, depth=%s", ip, op.opCodeToString, instruction, depth);
1614             break;
1615         }
1616         case OpCode.THROW:
1617         case OpCode.RETHROW:
1618             writefln("%05d: %s", ip, op.opCodeToString);
1619             break;
1620         case OpCode.TRY: {
1621             immutable catchGoto = decode!uint(chunk.bytecode[ip + 1..$]);
1622             writefln("%05d: %s catch=%s", ip, op.opCodeToString, catchGoto);
1623             break;
1624         }
1625         case OpCode.ENDTRY:
1626         case OpCode.LOADEXC:
1627             writefln("%05d: %s", ip, op.opCodeToString);
1628             break;
1629         case OpCode.CONCAT: {
1630             immutable n = decode!uint(chunk.bytecode[ip + 1..$]);
1631             writefln("%05d: %s n=%s", ip, op.opCodeToString, n);
1632             break;
1633         }
1634         case OpCode.BITNOT:
1635         case OpCode.NOT:
1636         case OpCode.NEGATE:
1637         case OpCode.TYPEOF:
1638         case OpCode.INSTANCEOF:
1639         case OpCode.POW:
1640         case OpCode.MUL:
1641         case OpCode.DIV:
1642         case OpCode.MOD:
1643         case OpCode.ADD:
1644         case OpCode.SUB:
1645         case OpCode.LT:
1646         case OpCode.LE:
1647         case OpCode.GT:
1648         case OpCode.GE:
1649         case OpCode.EQUALS:
1650         case OpCode.NEQUALS:
1651         case OpCode.STREQUALS:
1652         case OpCode.BITAND:
1653         case OpCode.BITOR:
1654         case OpCode.BITXOR:
1655         case OpCode.AND:
1656         case OpCode.OR:
1657         case OpCode.TERN:
1658             writefln("%05d: %s", ip, op.opCodeToString);
1659             break;
1660         case OpCode.RETURN:
1661         case OpCode.HALT:
1662             writefln("%05d: %s", ip, op.opCodeToString);
1663             break;
1664         default:
1665             writefln("%05d: ??? (%s)", ip, cast(ubyte)op);
1666         }  
1667     }
1668 
1669     /// print the current contents of the stack
1670     void printStack()
1671     {
1672         write("Stack: [");
1673         for(size_t i = 0; i < _stack.size; ++i)
1674         {
1675             if(_stack.array[i].type == ScriptAny.Type.STRING)
1676             {
1677                 auto str = _stack.array[i].toString();
1678                 if(str.length < 100)
1679                     write("\"" ~ str ~ "\"");
1680                 else
1681                     write("[string too long to display]");
1682             }
1683             else if(_stack.array[i].type == ScriptAny.Type.ARRAY)
1684             {
1685                 immutable arrLen = _stack.array[i].toValue!(ScriptAny[]).length;
1686                 if(arrLen < 100)
1687                     write(_stack.array[i].toString());
1688                 else
1689                     write("[array too long to display]");
1690             }
1691             else
1692             {
1693                 write(_stack.array[i].toString());
1694             }
1695             if(i < _stack.size - 1)
1696                 write(", ");
1697         }
1698         writeln("]");
1699     }
1700 
1701     /// run a chunk of bytecode with a given const table
1702     ScriptAny run(Chunk chunk, bool printDebugInfo=false, bool retCS=false)
1703     {
1704         _ip = 0;
1705         ubyte op;
1706         _stopped = false;
1707         _exc = null;
1708         _currentConstTable = chunk.constTable;
1709         int retCSCallDepth = 1;
1710         while(_ip < chunk.bytecode.length && !_stopped)
1711         {
1712             op = chunk.bytecode[_ip];
1713             // handle runFunction
1714             if(retCS)
1715             {
1716                 if(op == OpCode.RETURN)
1717                 {
1718                     --retCSCallDepth;
1719                     if(retCSCallDepth <= 0)
1720                         return _stack.pop();
1721                 }
1722                 else if(op == OpCode.CALL || op == OpCode.NEW)
1723                     ++retCSCallDepth;
1724             }
1725             if(printDebugInfo)
1726                 printInstruction(_ip, chunk);
1727             _ops[op](this, chunk);
1728             if(printDebugInfo)
1729                 printStack();
1730         }
1731         // if something is on the stack, that's the return value
1732         ScriptAny retVal;
1733         if(_stack.size > 0)
1734             retVal = _stack.pop();
1735         
1736         // runQueue();
1737 
1738         return retVal;
1739     }
1740 
1741     /// For calling script functions with call or apply. Ugly hackish function that needs
1742     /// to be reworked.
1743     package(mildew) ScriptAny runFunction(ScriptFunction func, ScriptAny thisObj, ScriptAny[] args, 
1744                                           ScriptAny yieldFunc=ScriptAny.UNDEFINED) // this parameter is necessary
1745     {
1746         ScriptAny result;
1747         NativeFunctionError nfe;
1748         if(func.type == ScriptFunction.Type.SCRIPT_FUNCTION)
1749         {
1750             auto chunk = new Chunk();
1751             chunk.constTable = _currentConstTable;
1752             chunk.bytecode = func.compiled;
1753             auto oldTryData = _tryData; // @suppress(dscanner.suspicious.unmodified)
1754             _tryData = [];
1755             immutable oldIP = _ip;
1756             auto oldEnv = _environment; // @suppress(dscanner.suspicious.unmodified)
1757             _environment = new Environment(func.closure);
1758             // THIS MAY NOT BE TEMPORARY
1759             if(yieldFunc != ScriptAny.UNDEFINED)
1760             {
1761                 _environment.forceSetVarOrConst("yield", yieldFunc, true);
1762                 // allow for __yield__ syntax inside of manually created Generators
1763                 _environment.forceSetVarOrConst("__yield__", yieldFunc, true);
1764             }
1765             // ^
1766             _environment.forceSetVarOrConst("this", thisObj, false);
1767             _environment.forceSetVarOrConst("arguments", ScriptAny(args), false);
1768             for(size_t i = 0; i < func.argNames.length; ++i)
1769             {
1770                 if(i >= args.length)
1771                     _environment.forceSetVarOrConst(func.argNames[i], ScriptAny.UNDEFINED, false);
1772                 else
1773                     _environment.forceSetVarOrConst(func.argNames[i], args[i], false);
1774             }
1775             try
1776             {
1777                 // debug writefln("_environment=%s", _environment);
1778                 // debug writefln("_environment.g.interpreter=%s", _environment.getGlobalEnvironment.interpreter);
1779                 result = run(chunk, _globals.interpreter.printVMDebugInfo, true);
1780 
1781             }
1782             catch(ScriptRuntimeException ex)
1783             {
1784                 _exc = ex;
1785             }
1786             finally 
1787             {
1788                 _environment = oldEnv;
1789                 _ip = oldIP;
1790                 _tryData = oldTryData;
1791             }
1792             return result;
1793         }
1794         else if(func.type == ScriptFunction.Type.NATIVE_FUNCTION)
1795         {
1796             auto nf = func.nativeFunction;
1797             result = nf(_environment, &thisObj, args, nfe);
1798         }
1799         else if(func.type == ScriptFunction.Type.NATIVE_DELEGATE)
1800         {
1801             auto nd = func.nativeDelegate;
1802             result = nd(_environment, &thisObj, args, nfe);
1803         }
1804         final switch(nfe)
1805         {
1806         case NativeFunctionError.NO_ERROR:
1807             break;
1808         case NativeFunctionError.RETURN_VALUE_IS_EXCEPTION:
1809             _exc = new ScriptRuntimeException(result.toString);
1810             break;
1811         case NativeFunctionError.WRONG_NUMBER_OF_ARGS:
1812             _exc = new ScriptRuntimeException("Wrong number of argumentss to native function");
1813             break;
1814         case NativeFunctionError.WRONG_TYPE_OF_ARG:
1815             _exc = new ScriptRuntimeException("Wrong type of arguments to native function");
1816             break;
1817         }
1818         return result;
1819     }
1820 
1821     /**
1822      * For coroutines and threads.
1823      */
1824     VirtualMachine copy(bool copyStack = false)
1825     {
1826         auto vm = new VirtualMachine(_globals);
1827         vm._environment = _environment;
1828         vm._currentConstTable = _currentConstTable;
1829         vm._ops = _ops;
1830         if(copyStack)
1831             vm._stack = _stack;
1832         vm._parent = this;
1833         return vm;
1834     }
1835 
1836     /// Get the last value from opPop
1837     ScriptAny lastValuePopped() 
1838     {
1839         auto retVal = _lastValuePopped; 
1840         _lastValuePopped = ScriptAny.UNDEFINED; // to avoid memory leaks
1841         return retVal; 
1842     }
1843 
1844     /// Queue a fiber. TODO rewrite to accept a ScriptFiber
1845     ScriptObject async(string name, ScriptFunction func, ScriptAny thisToUse, ScriptAny[] args)
1846     {
1847         auto fiber = new ScriptFiber(name, this, func, thisToUse, args);
1848         _fibersQueued.insert(fiber);
1849         return new ScriptObject(name, null, fiber);
1850     }
1851 
1852     /**
1853      * Removes a ScriptFiber from the queue
1854      * Params:
1855      *  fiber = The ScriptFiber to remove. This is an object returned by async
1856      * Returns:
1857      *  Whether or not the fiber was successfully removed.
1858      */
1859     bool removeFiber(ScriptFiber fiber)
1860     {
1861         return _fibersQueued.linearRemoveElement(fiber);
1862     }
1863 
1864     // TODO await for when it is possible to await a async function?
1865 
1866     /**
1867      * Runs the asyncs queued up. This is called by Interpreter.runVMFibers
1868      */
1869     void runQueue()
1870     {
1871         _gSync = new Semaphore;
1872         
1873         while(!_fibersQueued.empty)
1874         {
1875             auto fibersRunning = _fibersQueued;
1876             _fibersQueued = SList!ScriptFiber();
1877             foreach(fiber ; fibersRunning)
1878             {
1879                 fiber.call();
1880                 if(fiber.state != Fiber.State.TERM)
1881                     _fibersQueued.insert(fiber);
1882             }
1883 
1884             if(_gWaitingOnThreads > 0)
1885                 _gSync.wait();
1886         }
1887     }
1888 
1889 private:
1890 
1891     enum FuncCallType { NORMAL, NEW }
1892     struct CallData
1893     {
1894         FuncCallType fct;
1895         ubyte[] bc;
1896         size_t ip;
1897         Environment env;
1898         TryData[] tryData;
1899     }
1900 
1901     struct TryData
1902     {
1903         int depth;
1904         size_t stackSize;
1905         uint catchGoto;
1906     }
1907 
1908     void printInstructionWithConstID(size_t ip, OpCode op, uint constID, Chunk chunk)
1909     {
1910         if(chunk.constTable.get(constID).toString().length < 100)
1911             writefln("%05d: %s #%s // <%s> %s", 
1912                 ip, op.opCodeToString, constID, chunk.constTable.get(constID).typeToString(),
1913                 chunk.constTable.get(constID));
1914         else
1915             writefln("%05d: %s #%s // <%s>", 
1916                 ip, op.opCodeToString, constID, chunk.constTable.get(constID).typeToString());
1917     }
1918 
1919     Stack!CallData _callStack;
1920     Environment _environment;
1921     ConstTable _currentConstTable; // for running functions from call and apply
1922     ScriptRuntimeException _exc; // exception flag
1923     Environment _globals;
1924     size_t _ip;
1925     OpCodeFunction[ubyte.max + 1] _ops;
1926     Stack!ScriptAny _stack;
1927     TryData[] _tryData;
1928     VirtualMachine _parent = null; // the VM that spawned this VM
1929     ScriptAny _lastValuePopped;
1930     SList!ScriptFiber _fibersQueued = SList!ScriptFiber();
1931     size_t _gWaitingOnThreads = 0;
1932     __gshared Semaphore _gSync;
1933 
1934     /// stops the machine
1935     bool _stopped;
1936 }
1937 
1938 class VMException : Exception
1939 {
1940     this(string msg, size_t iptr, OpCode op, string file = __FILE__, size_t line = __LINE__)
1941     {
1942         super(msg, file, line);
1943         ip = iptr;
1944         opcode = op;
1945     }
1946 
1947     override string toString() const
1948     {
1949         import std.format: format;
1950         return msg ~ " at instruction " ~ format("%x", ip) ~ " (" ~ opcode.opCodeToString ~ ")";
1951     }
1952 
1953     size_t ip;
1954     OpCode opcode;
1955 }
1956 
1957 unittest
1958 {
1959     auto vm = new VirtualMachine(new Environment(null, "<global>"));
1960     vm = vm.copy();
1961     auto chunk = new Chunk();
1962 
1963     ubyte[] getConst(T)(T value)
1964     {
1965         return encode(chunk.constTable.addValueUint(ScriptAny(value)));
1966     }
1967 
1968     ScriptAny native_tpropGet(Environment env, ScriptAny* thisObj, ScriptAny[] args, ref NativeFunctionError nfe)
1969     {
1970         writeln("in native_tpropGet");
1971         return ScriptAny(1000);
1972     }
1973 
1974     ScriptAny native_tpropSet(Environment env, ScriptAny* thisObj, ScriptAny[] args, ref NativeFunctionError nfe)
1975     {
1976         writeln("Setting value to " ~ args[0].toString);
1977         return ScriptAny.UNDEFINED;
1978     }
1979 
1980     /*
1981 
1982     */
1983     auto testObj = new ScriptObject("test", null, null);
1984     testObj.addGetterProperty("tprop", new ScriptFunction("tpropGet", &native_tpropGet));
1985     testObj.addSetterProperty("tprop", new ScriptFunction("tpropSet", &native_tpropSet));
1986 
1987     chunk.bytecode ~= OpCode.CONST ~ getConst(testObj);
1988     chunk.bytecode ~= OpCode.CONST ~ getConst("tprop");
1989     chunk.bytecode ~= OpCode.OBJGET;
1990     chunk.bytecode ~= OpCode.CONST ~ getConst(testObj);
1991     chunk.bytecode ~= OpCode.CONST ~ getConst("tprop");
1992     chunk.bytecode ~= OpCode.CONST_1;
1993     chunk.bytecode ~= OpCode.OBJSET;
1994     
1995     vm.printChunk(chunk);
1996     vm.run(chunk);
1997 }