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