Beispiel #1
0
        private MondValue Run()
        {
            var functionAddress = PeekCall();
            var program         = functionAddress.Program;
            var code            = program.Bytecode;

            var initialCallDepth  = _callStackSize - 1; // "- 1" to not include values pushed by Call()
            var initialLocalDepth = _localStackSize - 1;
            var initialEvalDepth  = _evalStackSize;

            var ip      = functionAddress.Address;
            var errorIp = 0;

            var   args   = functionAddress.Arguments;
            Frame locals = null;

            try
            {
                while (true)
                {
                    if (Debugger != null)
                    {
                        var skip = _debugSkip;
                        _debugSkip = false;

                        var shouldStopAtStmt =
                            (_debugAction == MondDebugAction.StepInto) ||
                            (_debugAction == MondDebugAction.StepOver && _debugDepth == 0);

                        var shouldBreak =
                            (_debugAlign && program.DebugInfo == null) ||
                            (_debugAlign && program.DebugInfo.IsStatementStart(ip)) ||
                            (Debugger.ShouldBreak(program, ip)) ||
                            (shouldStopAtStmt && program.DebugInfo != null && program.DebugInfo.IsStatementStart(ip));

                        if (!skip && shouldBreak)
                        {
                            DebuggerBreak(program, locals, args, ip, initialCallDepth);
                        }
                    }

                    errorIp = ip;

                    switch (code[ip++])
                    {
                        #region Stack Manipulation
                    case (int)InstructionType.Dup:
                    {
                        Push(Peek());
                        break;
                    }

                    case (int)InstructionType.Dup2:
                    {
                        var value2 = Pop();
                        var value1 = Pop();
                        Push(value1);
                        Push(value2);
                        Push(value1);
                        Push(value2);
                        break;
                    }

                    case (int)InstructionType.Drop:
                    {
                        Pop();
                        break;
                    }

                    case (int)InstructionType.Swap:
                    {
                        var value1 = Pop();
                        var value2 = Pop();
                        Push(value1);
                        Push(value2);
                        break;
                    }

                    case (int)InstructionType.Swap1For2:
                    {
                        var one  = Pop();
                        var two2 = Pop();
                        var two1 = Pop();
                        Push(one);
                        Push(two1);
                        Push(two2);
                        break;
                    }
                        #endregion

                        #region Constants
                    case (int)InstructionType.LdUndef:
                    {
                        Push(MondValue.Undefined);
                        break;
                    }

                    case (int)InstructionType.LdNull:
                    {
                        Push(MondValue.Null);
                        break;
                    }

                    case (int)InstructionType.LdTrue:
                    {
                        Push(MondValue.True);
                        break;
                    }

                    case (int)InstructionType.LdFalse:
                    {
                        Push(MondValue.False);
                        break;
                    }

                    case (int)InstructionType.LdNum:
                    {
                        var numId = ReadInt32(code, ref ip);
                        Push(program.Numbers[numId]);
                        break;
                    }

                    case (int)InstructionType.LdStr:
                    {
                        var strId = ReadInt32(code, ref ip);
                        Push(program.Strings[strId]);
                        break;
                    }

                    case (int)InstructionType.LdGlobal:
                    {
                        Push(Global);
                        break;
                    }
                        #endregion

                        #region Storables
                    case (int)InstructionType.LdLocF:
                    {
                        var index = ReadInt32(code, ref ip);
                        Push(locals.Values[index]);
                        break;
                    }

                    case (int)InstructionType.StLocF:
                    {
                        var index = ReadInt32(code, ref ip);
                        locals.Values[index] = Pop();
                        break;
                    }

                    case (int)InstructionType.LdLoc:
                    {
                        var depth = ReadInt32(code, ref ip);
                        var index = ReadInt32(code, ref ip);

                        if (depth < 0)
                        {
                            Push(args.Get(-depth, index));
                        }
                        else
                        {
                            Push(locals.Get(depth, index));
                        }

                        break;
                    }

                    case (int)InstructionType.StLoc:
                    {
                        var depth = ReadInt32(code, ref ip);
                        var index = ReadInt32(code, ref ip);

                        if (depth < 0)
                        {
                            args.Set(-depth, index, Pop());
                        }
                        else
                        {
                            locals.Set(depth, index, Pop());
                        }

                        break;
                    }

                    case (int)InstructionType.LdFld:
                    {
                        var obj = Pop();
                        Push(obj[program.Strings[ReadInt32(code, ref ip)]]);
                        break;
                    }

                    case (int)InstructionType.StFld:
                    {
                        var obj   = Pop();
                        var value = Pop();

                        obj[program.Strings[ReadInt32(code, ref ip)]] = value;
                        break;
                    }

                    case (int)InstructionType.LdArr:
                    {
                        var index = Pop();
                        var array = Pop();
                        Push(array[index]);
                        break;
                    }

                    case (int)InstructionType.StArr:
                    {
                        var index = Pop();
                        var array = Pop();
                        var value = Pop();
                        array[index] = value;
                        break;
                    }

                    case (int)InstructionType.LdState:
                    {
                        var depth = ReadInt32(code, ref ip);
                        var frame = locals.GetFrame(depth);
                        locals = frame.StoredFrame;

                        PopLocal();
                        PushLocal(locals);

                        var evals = frame.StoredEvals;
                        if (evals != null)
                        {
                            for (var i = evals.Count - 1; i >= 0; i--)
                            {
                                Push(evals[i]);
                            }

                            evals.Clear();
                        }

                        break;
                    }

                    case (int)InstructionType.StState:
                    {
                        var depth = ReadInt32(code, ref ip);
                        var frame = locals.GetFrame(depth);
                        frame.StoredFrame = locals;

                        var initialEvals = _callStackSize > 0 ? PeekCall().EvalDepth : 0;
                        var currentEvals = _evalStackSize;

                        if (currentEvals != initialEvals)
                        {
                            var evals = frame.StoredEvals ?? (frame.StoredEvals = new List <MondValue>());

                            while (currentEvals != initialEvals)
                            {
                                evals.Add(Pop());
                                currentEvals--;
                            }
                        }

                        break;
                    }
                        #endregion

                        #region Object Creation
                    case (int)InstructionType.NewObject:
                    {
                        var obj = MondValue.Object(_state);
                        Push(obj);
                        break;
                    }

                    case (int)InstructionType.NewArray:
                    {
                        var count = ReadInt32(code, ref ip);
                        var array = MondValue.Array();
                        array.ArrayValue.Capacity = count;

                        for (var i = 0; i < count; i++)
                        {
                            array.ArrayValue.Add(default(MondValue));
                        }

                        Push(array);
                        break;
                    }

                    case (int)InstructionType.Slice:
                    {
                        var step  = Pop();
                        var end   = Pop();
                        var start = Pop();
                        var array = Pop();

                        Push(array.Slice(start, end, step));
                        break;
                    }
                        #endregion

                        #region Math
                    case (int)InstructionType.Add:
                    {
                        var right = Pop();
                        var left  = Pop();
                        Push(left + right);
                        break;
                    }

                    case (int)InstructionType.Sub:
                    {
                        var right = Pop();
                        var left  = Pop();
                        Push(left - right);
                        break;
                    }

                    case (int)InstructionType.Mul:
                    {
                        var right = Pop();
                        var left  = Pop();
                        Push(left * right);
                        break;
                    }

                    case (int)InstructionType.Div:
                    {
                        var right = Pop();
                        var left  = Pop();
                        Push(left / right);
                        break;
                    }

                    case (int)InstructionType.Mod:
                    {
                        var right = Pop();
                        var left  = Pop();
                        Push(left % right);
                        break;
                    }

                    case (int)InstructionType.Exp:
                    {
                        var right = Pop();
                        var left  = Pop();
                        Push(left.Pow(right));
                        break;
                    }

                    case (int)InstructionType.BitLShift:
                    {
                        var right = Pop();
                        var left  = Pop();
                        Push(left.LShift(right));
                        break;
                    }

                    case (int)InstructionType.BitRShift:
                    {
                        var right = Pop();
                        var left  = Pop();
                        Push(left.RShift(right));
                        break;
                    }

                    case (int)InstructionType.BitAnd:
                    {
                        var right = Pop();
                        var left  = Pop();
                        Push(left & right);
                        break;
                    }

                    case (int)InstructionType.BitOr:
                    {
                        var right = Pop();
                        var left  = Pop();
                        Push(left | right);
                        break;
                    }

                    case (int)InstructionType.BitXor:
                    {
                        var right = Pop();
                        var left  = Pop();
                        Push(left ^ right);
                        break;
                    }

                    case (int)InstructionType.Neg:
                    {
                        Push(-Pop());
                        break;
                    }

                    case (int)InstructionType.BitNot:
                    {
                        Push(~Pop());
                        break;
                    }
                        #endregion

                        #region Logic
                    case (int)InstructionType.Eq:
                    {
                        var right = Pop();
                        var left  = Pop();
                        Push(left == right);
                        break;
                    }

                    case (int)InstructionType.Neq:
                    {
                        var right = Pop();
                        var left  = Pop();
                        Push(left != right);
                        break;
                    }

                    case (int)InstructionType.Gt:
                    {
                        var right = Pop();
                        var left  = Pop();
                        Push(left > right);
                        break;
                    }

                    case (int)InstructionType.Gte:
                    {
                        var right = Pop();
                        var left  = Pop();
                        Push(left >= right);
                        break;
                    }

                    case (int)InstructionType.Lt:
                    {
                        var right = Pop();
                        var left  = Pop();
                        Push(left < right);
                        break;
                    }

                    case (int)InstructionType.Lte:
                    {
                        var right = Pop();
                        var left  = Pop();
                        Push(left <= right);
                        break;
                    }

                    case (int)InstructionType.Not:
                    {
                        Push(!Pop());
                        break;
                    }

                    case (int)InstructionType.In:
                    {
                        var right = Pop();
                        var left  = Pop();
                        Push(right.Contains(left));
                        break;
                    }

                    case (int)InstructionType.NotIn:
                    {
                        var right = Pop();
                        var left  = Pop();
                        Push(!right.Contains(left));
                        break;
                    }
                        #endregion

                        #region Functions
                    case (int)InstructionType.Closure:
                    {
                        var address = ReadInt32(code, ref ip);
                        Push(new MondValue(new Closure(program, address, args, locals)));
                        break;
                    }

                    case (int)InstructionType.Call:
                    {
                        var argCount    = ReadInt32(code, ref ip);
                        var unpackCount = code[ip++];

                        var function = Pop();

                        List <MondValue> unpackedArgs = null;

                        if (unpackCount > 0)
                        {
                            unpackedArgs = UnpackArgs(code, ref ip, argCount, unpackCount);
                        }

                        var returnAddress = ip;

                        if (function.Type == MondValueType.Object)
                        {
                            MondValue[] argArr;

                            if (unpackedArgs == null)
                            {
                                argArr = new MondValue[argCount + 1];

                                for (var i = argCount; i >= 1; i--)
                                {
                                    argArr[i] = Pop();
                                }

                                argArr[0] = function;
                            }
                            else
                            {
                                unpackedArgs.Insert(0, function);
                                argArr = unpackedArgs.ToArray();
                            }

                            if (function.TryDispatch("__call", out var result, argArr))
                            {
                                Push(result);
                                break;
                            }
                        }

                        if (function.Type != MondValueType.Function)
                        {
                            var ldFldBase = ip - 1 - 4 - 1 - 4 - 1;
                            if (ldFldBase >= 0 && code[ldFldBase] == (int)InstructionType.LdFld)
                            {
                                var ldFldIdx     = ldFldBase + 1;
                                var fieldNameIdx = ReadInt32(code, ref ldFldIdx);

                                if (fieldNameIdx >= 0 && fieldNameIdx < program.Strings.Count)
                                {
                                    var fieldName = program.Strings[fieldNameIdx];
                                    throw new MondRuntimeException(RuntimeError.FieldNotCallable, (string)fieldName);
                                }
                            }

                            throw new MondRuntimeException(RuntimeError.ValueNotCallable, function.Type.GetName());
                        }

                        var closure = function.FunctionValue;

                        var argFrame      = function.FunctionValue.Arguments;
                        var argFrameCount = unpackedArgs?.Count ?? argCount;

                        if (argFrame == null)
                        {
                            argFrame = new Frame(1, null, argFrameCount);
                        }
                        else
                        {
                            argFrame = new Frame(argFrame.Depth + 1, argFrame, argFrameCount);
                        }

                        // copy arguments into frame
                        if (unpackedArgs == null)
                        {
                            for (var i = argFrameCount - 1; i >= 0; i--)
                            {
                                argFrame.Values[i] = Pop();
                            }
                        }
                        else
                        {
                            for (var i = 0; i < argFrameCount; i++)
                            {
                                argFrame.Values[i] = unpackedArgs[i];
                            }
                        }

                        switch (closure.Type)
                        {
                        case ClosureType.Mond:
                            PushCall(new ReturnAddress(program, returnAddress, argFrame, _evalStackSize));
                            PushLocal(closure.Locals);

                            program = closure.Program;
                            code    = program.Bytecode;
                            ip      = closure.Address;
                            args    = argFrame;
                            locals  = closure.Locals;

                            if (Debugger != null)
                            {
                                DebuggerCheckCall();
                            }

                            break;

                        case ClosureType.Native:
                            var result = closure.NativeFunction(_state, argFrame.Values);
                            Push(result);
                            break;

                        default:
                            throw new MondRuntimeException(RuntimeError.UnhandledClosureType);
                        }

                        break;
                    }

                    case (int)InstructionType.TailCall:
                    {
                        var argCount    = ReadInt32(code, ref ip);
                        var address     = ReadInt32(code, ref ip);
                        var unpackCount = code[ip++];

                        List <MondValue> unpackedArgs = null;

                        if (unpackCount > 0)
                        {
                            unpackedArgs = UnpackArgs(code, ref ip, argCount, unpackCount);
                        }

                        var returnAddress = PopCall();
                        var argFrame      = returnAddress.Arguments;
                        var argFrameCount = unpackedArgs?.Count ?? argCount;

                        // make sure we have the correct number of values
                        if (argFrameCount != argFrame.Values.Length)
                        {
                            argFrame.Values = new MondValue[argFrameCount];
                        }

                        // copy arguments into frame
                        if (unpackedArgs == null)
                        {
                            for (var i = argFrameCount - 1; i >= 0; i--)
                            {
                                argFrame.Values[i] = Pop();
                            }
                        }
                        else
                        {
                            for (var i = 0; i < argFrameCount; i++)
                            {
                                argFrame.Values[i] = unpackedArgs[i];
                            }
                        }

                        // get rid of old locals
                        PushLocal(PopLocal().Previous);

                        PushCall(new ReturnAddress(returnAddress.Program, returnAddress.Address, argFrame, _evalStackSize));

                        ip = address;
                        break;
                    }

                    case (int)InstructionType.Enter:
                    {
                        var localCount = ReadInt32(code, ref ip);

                        var frame = PopLocal();
                        frame = new Frame(frame?.Depth + 1 ?? 0, frame, localCount);

                        PushLocal(frame);
                        locals = frame;
                        break;
                    }

                    case (int)InstructionType.Leave:
                    {
                        var frame = PopLocal();
                        frame = frame.Previous;

                        PushLocal(frame);
                        locals = frame;
                        break;
                    }

                    case (int)InstructionType.Ret:
                    {
                        var returnAddress = PopCall();
                        PopLocal();

                        program = returnAddress.Program;
                        code    = program.Bytecode;
                        ip      = returnAddress.Address;

                        args   = _callStackSize > 0 ? PeekCall().Arguments : null;
                        locals = _localStackSize > 0 ? PeekLocal() : null;

                        if (_callStackSize == initialCallDepth)
                        {
                            return(Pop());
                        }

                        if (Debugger != null && DebuggerCheckReturn())
                        {
                            DebuggerBreak(program, locals, args, ip, initialCallDepth);
                        }

                        break;
                    }

                    case (int)InstructionType.VarArgs:
                    {
                        var fixedCount = ReadInt32(code, ref ip);
                        var varArgs    = MondValue.Array();

                        for (var i = fixedCount; i < args.Values.Length; i++)
                        {
                            varArgs.ArrayValue.Add(args.Values[i]);
                        }

                        args.Set(args.Depth, fixedCount, varArgs);
                        break;
                    }
                        #endregion

                        #region Branching
                    case (int)InstructionType.Jmp:
                    {
                        var address = ReadInt32(code, ref ip);
                        ip = address;
                        break;
                    }

                    case (int)InstructionType.JmpTrueP:
                    {
                        var address = ReadInt32(code, ref ip);

                        if (Peek())
                        {
                            ip = address;
                        }

                        break;
                    }

                    case (int)InstructionType.JmpFalseP:
                    {
                        var address = ReadInt32(code, ref ip);

                        if (!Peek())
                        {
                            ip = address;
                        }

                        break;
                    }

                    case (int)InstructionType.JmpTrue:
                    {
                        var address = ReadInt32(code, ref ip);

                        if (Pop())
                        {
                            ip = address;
                        }

                        break;
                    }

                    case (int)InstructionType.JmpFalse:
                    {
                        var address = ReadInt32(code, ref ip);

                        if (!Pop())
                        {
                            ip = address;
                        }

                        break;
                    }

                    case (int)InstructionType.JmpTable:
                    {
                        var start = ReadInt32(code, ref ip);
                        var count = ReadInt32(code, ref ip);

                        var endIp = ip + count * 4;

                        var value = Pop();
                        if (value.Type == MondValueType.Number)
                        {
                            var number    = (double)value;
                            var numberInt = (int)number;

                            if (number >= start && number < start + count &&
                                Math.Abs(number - numberInt) <= double.Epsilon)
                            {
                                ip += (numberInt - start) * 4;
                                ip  = ReadInt32(code, ref ip);
                                break;
                            }
                        }

                        ip = endIp;
                        break;
                    }
                        #endregion

                    case (int)InstructionType.Breakpoint:
                    {
                        if (Debugger == null)
                        {
                            break;
                        }

                        DebuggerBreak(program, locals, args, ip, initialCallDepth);

                        // we stop for the statement *after* the debugger statement so we
                        // skip the next break opportunity, otherwise we break twice
                        _debugSkip = true;
                        break;
                    }

                    default:
                        throw new MondRuntimeException(RuntimeError.UnhandledOpcode);
                    }
                }
            }
            catch (Exception e)
            {
                var message = e.Message.Trim();

                // we skip the OOB checks in the stack methods because the CLR has issues eliminating
                // its own checks, so we let it throw and check here for a bit of a speed boost
                if (e is IndexOutOfRangeException)
                {
                    if (_callStackSize >= CallStackCapacity || _localStackSize >= CallStackCapacity || _evalStackSize >= EvalStackCapacity)
                    {
                        message = RuntimeError.StackOverflow;
                    }
                    else if (_callStackSize < 0 || _localStackSize < 0 || _evalStackSize < 0)
                    {
                        message = RuntimeError.StackEmpty;
                    }
                }

                StringBuilder stackTraceBuilder;

                if (e is MondRuntimeException runtimeException &&
                    runtimeException.MondStackTrace != null)
                {
                    stackTraceBuilder = new StringBuilder(runtimeException.MondStackTrace);

                    // check if we are running in a wrapped function
                    var stackTrace   = new System.Diagnostics.StackTrace(e, false);
                    var frames       = stackTrace.GetFrames();
                    var foundWrapper = false;

                    // skip the first frame because it's this method? need to verify
                    for (var i = 1; i < frames.Length; i++)
                    {
                        var method = frames[i].GetMethod();
                        if (method == null)
                        {
                            continue; // ???
                        }
                        var type = method.DeclaringType;

                        // stop at the next call to Machine.Run because it can be recursive
                        if (type == typeof(Machine) && method.Name == "Run")
                        {
                            break;
                        }

                        // the wrapper is a lambda so it's in a compiler generated type, which will be nested
                        var parentType = type.DeclaringType;
                        if (parentType == null)
                        {
                            continue;
                        }

                        // the type and method are compiler generated so they have a weird (and compiler specific) name
                        const string wrapperMagic = "<CheckWrapFunction>";

                        // make sure the type is nested in MondValue and check both the type and method name
                        if (parentType == typeof(MondValue) && (method.Name.Contains(wrapperMagic) || type.Name.Contains(wrapperMagic)))
                        {
                            foundWrapper = true;
                            break;
                        }
                    }

                    // don't show a native transition for wrappers
                    if (!foundWrapper)
                    {
                        stackTraceBuilder.AppendLine("[... native ...]");
                    }
                }
Beispiel #2
0
        public MondValue Run()
        {
            var functionAddress = _callStack.Peek();
            var program         = functionAddress.Program;
            var code            = program.Bytecode;

            var initialCallDepth  = _callStack.Count - 1;
            var initialLocalDepth = _localStack.Count;
            var initialEvalDepth  = _evalStack.Count;

            var ip      = functionAddress.Address;
            var errorIp = 0;

            var   args   = functionAddress.Arguments;
            Frame locals = null;

            try
            {
                while (true)
                {
                    errorIp = ip;

                    /*if (program.DebugInfo != null)
                     * {
                     *  var line = program.DebugInfo.FindLine(errorIp);
                     *  if (line.HasValue)
                     *      Console.WriteLine("{0:X4} {1} line {2}: {3}", errorIp, program.Strings[line.Value.FileName], line.Value.LineNumber, (InstructionType)code[ip]);
                     *  else
                     *      Console.WriteLine("{0:X4}: {1}", errorIp, (InstructionType)code[ip]);
                     * }*/

                    switch (code[ip++])
                    {
                        #region Stack Manipulation
                    case (int)InstructionType.Dup:
                    {
                        _evalStack.Push(_evalStack.Peek());
                        break;
                    }

                    case (int)InstructionType.Drop:
                    {
                        _evalStack.Pop();
                        break;
                    }

                    case (int)InstructionType.Swap:
                    {
                        var value1 = _evalStack.Pop();
                        var value2 = _evalStack.Pop();
                        _evalStack.Push(value1);
                        _evalStack.Push(value2);
                        break;
                    }
                        #endregion

                        #region Constants
                    case (int)InstructionType.LdUndef:
                    {
                        _evalStack.Push(MondValue.Undefined);
                        break;
                    }

                    case (int)InstructionType.LdNull:
                    {
                        _evalStack.Push(MondValue.Null);
                        break;
                    }

                    case (int)InstructionType.LdTrue:
                    {
                        _evalStack.Push(MondValue.True);
                        break;
                    }

                    case (int)InstructionType.LdFalse:
                    {
                        _evalStack.Push(MondValue.False);
                        break;
                    }

                    case (int)InstructionType.LdNum:
                    {
                        var numId = ReadInt32(code, ref ip);
                        _evalStack.Push(program.Numbers[numId]);
                        break;
                    }

                    case (int)InstructionType.LdStr:
                    {
                        var strId = ReadInt32(code, ref ip);
                        _evalStack.Push(program.Strings[strId]);
                        break;
                    }

                    case (int)InstructionType.LdGlobal:
                    {
                        _evalStack.Push(Global);
                        break;
                    }
                        #endregion

                        #region Storables
                    case (int)InstructionType.LdLoc:
                    {
                        var depth = ReadInt32(code, ref ip);
                        var index = ReadInt32(code, ref ip);

                        if (depth < 0)
                        {
                            _evalStack.Push(args.Get(Math.Abs(depth), index));
                        }
                        else
                        {
                            _evalStack.Push(locals.Get(depth, index));
                        }

                        break;
                    }

                    case (int)InstructionType.StLoc:
                    {
                        var depth = ReadInt32(code, ref ip);
                        var index = ReadInt32(code, ref ip);

                        if (depth < 0)
                        {
                            args.Set(Math.Abs(depth), index, _evalStack.Pop());
                        }
                        else
                        {
                            locals.Set(depth, index, _evalStack.Pop());
                        }

                        break;
                    }

                    case (int)InstructionType.LdFld:
                    {
                        var obj = _evalStack.Pop();
                        _evalStack.Push(obj[program.Strings[ReadInt32(code, ref ip)]]);
                        break;
                    }

                    case (int)InstructionType.StFld:
                    {
                        var obj   = _evalStack.Pop();
                        var value = _evalStack.Pop();

                        obj[program.Strings[ReadInt32(code, ref ip)]] = value;
                        break;
                    }

                    case (int)InstructionType.LdArr:
                    {
                        var index = _evalStack.Pop();
                        var array = _evalStack.Pop();

                        _evalStack.Push(array[index]);
                        break;
                    }

                    case (int)InstructionType.StArr:
                    {
                        var index = _evalStack.Pop();
                        var array = _evalStack.Pop();
                        var value = _evalStack.Pop();

                        array[index] = value;
                        break;
                    }
                        #endregion

                        #region Object Creation
                    case (int)InstructionType.NewObject:
                    {
                        _evalStack.Push(new MondValue(MondValueType.Object));
                        break;
                    }

                    case (int)InstructionType.NewArray:
                    {
                        var count = ReadInt32(code, ref ip);
                        var array = new MondValue(MondValueType.Array);

                        for (var i = 0; i < count; i++)
                        {
                            array.ArrayValue.Add(default(MondValue));
                        }

                        for (var i = count - 1; i >= 0; i--)
                        {
                            array.ArrayValue[i] = _evalStack.Pop();
                        }

                        _evalStack.Push(array);
                        break;
                    }
                        #endregion

                        #region Math
                    case (int)InstructionType.Add:
                    {
                        var left  = _evalStack.Pop();
                        var right = _evalStack.Pop();
                        _evalStack.Push(left + right);
                        break;
                    }

                    case (int)InstructionType.Sub:
                    {
                        var left  = _evalStack.Pop();
                        var right = _evalStack.Pop();
                        _evalStack.Push(left - right);
                        break;
                    }

                    case (int)InstructionType.Mul:
                    {
                        var left  = _evalStack.Pop();
                        var right = _evalStack.Pop();
                        _evalStack.Push(left * right);
                        break;
                    }

                    case (int)InstructionType.Div:
                    {
                        var left  = _evalStack.Pop();
                        var right = _evalStack.Pop();
                        _evalStack.Push(left / right);
                        break;
                    }

                    case (int)InstructionType.Mod:
                    {
                        var left  = _evalStack.Pop();
                        var right = _evalStack.Pop();
                        _evalStack.Push(left % right);
                        break;
                    }

                    case (int)InstructionType.Exp:
                    {
                        var left  = _evalStack.Pop();
                        var right = _evalStack.Pop();
                        _evalStack.Push(MondValue.Pow(left, right));
                        break;
                    }

                    case (int)InstructionType.BitLShift:
                    {
                        var left  = _evalStack.Pop();
                        var right = (int)_evalStack.Pop();
                        _evalStack.Push(left << right);
                        break;
                    }

                    case (int)InstructionType.BitRShift:
                    {
                        var left  = _evalStack.Pop();
                        var right = (int)_evalStack.Pop();
                        _evalStack.Push(left >> right);
                        break;
                    }

                    case (int)InstructionType.BitAnd:
                    {
                        var left  = _evalStack.Pop();
                        var right = _evalStack.Pop();
                        _evalStack.Push(left & right);
                        break;
                    }

                    case (int)InstructionType.BitOr:
                    {
                        var left  = _evalStack.Pop();
                        var right = _evalStack.Pop();
                        _evalStack.Push(left | right);
                        break;
                    }

                    case (int)InstructionType.BitXor:
                    {
                        var left  = _evalStack.Pop();
                        var right = _evalStack.Pop();
                        _evalStack.Push(left ^ right);
                        break;
                    }

                    case (int)InstructionType.Neg:
                    {
                        _evalStack.Push(-_evalStack.Pop());
                        break;
                    }

                    case (int)InstructionType.BitNot:
                    {
                        _evalStack.Push(~_evalStack.Pop());
                        break;
                    }
                        #endregion

                        #region Logic
                    case (int)InstructionType.Eq:
                    {
                        var left  = _evalStack.Pop();
                        var right = _evalStack.Pop();
                        _evalStack.Push(left == right);
                        break;
                    }

                    case (int)InstructionType.Neq:
                    {
                        var left  = _evalStack.Pop();
                        var right = _evalStack.Pop();
                        _evalStack.Push(left != right);
                        break;
                    }

                    case (int)InstructionType.Gt:
                    {
                        var left  = _evalStack.Pop();
                        var right = _evalStack.Pop();
                        _evalStack.Push(left > right);
                        break;
                    }

                    case (int)InstructionType.Gte:
                    {
                        var left  = _evalStack.Pop();
                        var right = _evalStack.Pop();
                        _evalStack.Push(left >= right);
                        break;
                    }

                    case (int)InstructionType.Lt:
                    {
                        var left  = _evalStack.Pop();
                        var right = _evalStack.Pop();
                        _evalStack.Push(left < right);
                        break;
                    }

                    case (int)InstructionType.Lte:
                    {
                        var left  = _evalStack.Pop();
                        var right = _evalStack.Pop();
                        _evalStack.Push(left <= right);
                        break;
                    }

                    case (int)InstructionType.Not:
                    {
                        _evalStack.Push(!_evalStack.Pop());
                        break;
                    }

                    case (int)InstructionType.In:
                    {
                        var left  = _evalStack.Pop();
                        var right = _evalStack.Pop();
                        _evalStack.Push(right.Contains(left));
                        break;
                    }
                        #endregion

                        #region Functions
                    case (int)InstructionType.Closure:
                    {
                        var address = ReadInt32(code, ref ip);
                        _evalStack.Push(new MondValue(new Closure(program, address, args, locals)));
                        break;
                    }

                    case (int)InstructionType.Call:
                    {
                        var argCount      = ReadInt32(code, ref ip);
                        var returnAddress = ip;
                        var function      = _evalStack.Pop();

                        if (function.Type != MondValueType.Function)
                        {
                            throw new MondRuntimeException(RuntimeError.ValueNotCallable, function.Type);
                        }

                        var closure = function.FunctionValue;

                        var argFrame = function.FunctionValue.Arguments;
                        if (argFrame == null)
                        {
                            argFrame = new Frame(1, null, argCount);
                        }
                        else
                        {
                            argFrame = new Frame(argFrame.Depth + 1, argFrame, argCount);
                        }

                        for (var i = argCount - 1; i >= 0; i--)
                        {
                            argFrame.Values[i] = _evalStack.Pop();
                        }

                        switch (closure.Type)
                        {
                        case ClosureType.Mond:
                            if (_callStack.Count >= MaxCallDepth)
                            {
                                throw new MondRuntimeException(RuntimeError.StackOverflow);
                            }

                            _callStack.Push(new ReturnAddress(program, returnAddress, argFrame));
                            _localStack.Push(closure.Locals);
                            program = closure.Program;
                            code    = program.Bytecode;
                            ip      = closure.Address;
                            args    = argFrame;
                            locals  = closure.Locals;
                            break;

                        case ClosureType.Native:
                            var result = closure.NativeFunction(_state, argFrame.Values);
                            _evalStack.Push(result);
                            break;

                        default:
                            throw new MondRuntimeException(RuntimeError.UnhandledClosureType);
                        }

                        break;
                    }

                    case (int)InstructionType.TailCall:
                    {
                        var argCount = ReadInt32(code, ref ip);
                        var address  = ReadInt32(code, ref ip);

                        var returnAddress = _callStack.Pop();
                        var argFrame      = returnAddress.Arguments;

                        // copy arguments into frame
                        for (var i = argCount - 1; i >= 0; i--)
                        {
                            argFrame.Values[i] = _evalStack.Pop();
                        }

                        // clear other arguments
                        for (var i = argCount; i < argFrame.Values.Length; i++)
                        {
                            argFrame.Values[i] = MondValue.Undefined;
                        }

                        // get rid of old locals
                        _localStack.Push(_localStack.Pop().Previous);

                        _callStack.Push(new ReturnAddress(returnAddress.Program, returnAddress.Address, argFrame));

                        ip = address;
                        break;
                    }

                    case (int)InstructionType.Enter:
                    {
                        var localCount = ReadInt32(code, ref ip);

                        var frame = _localStack.Pop();
                        frame = new Frame(frame != null ? frame.Depth + 1 : 0, frame, localCount);

                        _localStack.Push(frame);
                        locals = frame;
                        break;
                    }

                    case (int)InstructionType.Ret:
                    {
                        var returnAddress = _callStack.Pop();
                        _localStack.Pop();

                        program = returnAddress.Program;
                        code    = program.Bytecode;
                        ip      = returnAddress.Address;

                        args   = _callStack.Count > 0 ? _callStack.Peek().Arguments : null;
                        locals = _localStack.Count > 0 ? _localStack.Peek() : null;

                        if (_callStack.Count == initialCallDepth)
                        {
                            return(_evalStack.Pop());
                        }

                        break;
                    }

                    case (int)InstructionType.VarArgs:
                    {
                        var fixedCount = ReadInt32(code, ref ip);
                        var varArgs    = new MondValue(MondValueType.Array);

                        for (var i = fixedCount; i < args.Values.Length; i++)
                        {
                            varArgs.ArrayValue.Add(args.Values[i]);
                        }

                        args.Set(args.Depth, fixedCount, varArgs);
                        break;
                    }

                    case (int)InstructionType.JmpTable:
                    {
                        var start = ReadInt32(code, ref ip);
                        var count = ReadInt32(code, ref ip);

                        var endIp = ip + count * 4;

                        var value = _evalStack.Pop();
                        if (value.Type == MondValueType.Number)
                        {
                            var number    = (double)value;
                            var numberInt = (int)number;

                            if (number >= start && number < start + count &&
                                Math.Abs(number - numberInt) <= double.Epsilon)
                            {
                                ip += (numberInt - start) * 4;
                                ip  = ReadInt32(code, ref ip);
                                break;
                            }
                        }

                        ip = endIp;
                        break;
                    }
                        #endregion

                        #region Branching
                    case (int)InstructionType.Jmp:
                    {
                        var address = ReadInt32(code, ref ip);
                        ip = address;
                        break;
                    }

                    case (int)InstructionType.JmpTrueP:
                    {
                        var address = ReadInt32(code, ref ip);

                        if (_evalStack.Peek())
                        {
                            ip = address;
                        }

                        break;
                    }

                    case (int)InstructionType.JmpFalseP:
                    {
                        var address = ReadInt32(code, ref ip);

                        if (!_evalStack.Peek())
                        {
                            ip = address;
                        }

                        break;
                    }

                    case (int)InstructionType.JmpTrue:
                    {
                        var address = ReadInt32(code, ref ip);

                        if (_evalStack.Pop())
                        {
                            ip = address;
                        }

                        break;
                    }

                    case (int)InstructionType.JmpFalse:
                    {
                        var address = ReadInt32(code, ref ip);

                        if (!_evalStack.Pop())
                        {
                            ip = address;
                        }

                        break;
                    }
                        #endregion

                    default:
                        throw new MondRuntimeException(RuntimeError.UnhandledOpcode);
                    }
                }
            }
            catch (Exception e)
            {
                var errorBuilder = new StringBuilder();

                errorBuilder.AppendLine(e.Message.Trim());

                var runtimeException = e as MondRuntimeException;
                if (runtimeException != null && runtimeException.HasStackTrace)
                {
                    errorBuilder.AppendLine("[... native ...]");
                }
                else
                {
                    errorBuilder.AppendLine();
                }

                errorBuilder.AppendLine(GetAddressDebugInfo(program, errorIp));

                while (_callStack.Count > initialCallDepth + 1)
                {
                    var returnAddress = _callStack.Pop();

                    errorBuilder.AppendLine(GetAddressDebugInfo(returnAddress.Program, returnAddress.Address));
                }

                _callStack.Pop();

                while (_localStack.Count > initialLocalDepth)
                {
                    _localStack.Pop();
                }

                while (_evalStack.Count > initialEvalDepth)
                {
                    _evalStack.Pop();
                }

                throw new MondRuntimeException(errorBuilder.ToString(), e, true);
            }
        }