Ejemplo n.º 1
0
        public void Slice()
        {
            var state = new MondState();

            var arr = new MondValue(MondValueType.Array);
            var str = new MondValue("HelloWorld");

            arr.ArrayValue.AddRange(new MondValue[] { 1, 2, 3, 4, 5 });

            Assert.True(str.Slice(1, 3, 1).Equals(new MondValue("ell")));

            Assert.True(arr.Slice().Enumerate(state).SequenceEqual(arr.Enumerate(state)), "clone");

            Assert.True(arr.Slice(step: -1).Enumerate(state).SequenceEqual(new MondValue[] { 5, 4, 3, 2, 1 }), "reverse");

            Assert.True(arr.Slice(1, 3).Enumerate(state).SequenceEqual(new MondValue[] { 2, 3, 4 }), "range");
            Assert.True(arr.Slice(3, 1).Enumerate(state).SequenceEqual(new MondValue[] { 4, 3, 2 }), "reverse range");

            Assert.True(arr.Slice(0, 0).Enumerate(state).SequenceEqual(new MondValue[] { 1 }), "same start and end");

            Assert.True(arr.Slice(-4, -2).Enumerate(state).SequenceEqual(new MondValue[] { 2, 3, 4 }), "negative range");
            Assert.True(arr.Slice(-2, -4).Enumerate(state).SequenceEqual(new MondValue[] { 4, 3, 2 }), "negative range reverse");

            Assert.True(arr.Slice(step: 2).Enumerate(state).SequenceEqual(new MondValue[] { 1, 3, 5 }), "skip");
            Assert.True(arr.Slice(step: -2).Enumerate(state).SequenceEqual(new MondValue[] { 5, 3, 1 }), "skip negative");

            Assert.Throws <MondRuntimeException>(() => arr.Slice(-6, 0, "out of bounds 1"));
            Assert.Throws <MondRuntimeException>(() => arr.Slice(0, 5, "out of bounds 2"));

            Assert.Throws <MondRuntimeException>(() => arr.Slice(step: 0), "invalid step");

            Assert.Throws <MondRuntimeException>(() => arr.Slice(4, 0, 1), "invalid range");
            Assert.Throws <MondRuntimeException>(() => arr.Slice(0, 4, -1), "invalid range negative");

            Assert.Throws <MondRuntimeException>(() => MondValue.Undefined.Slice(), "slice non-array");

            var empty = new MondValue(MondValueType.Array);

            Assert.True(!empty.Slice().Enumerate(state).Any(), "clone empty");
        }
Ejemplo n.º 2
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 = new MondValue(_state);
                                Push(obj);
                                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] = Pop();
                                }

                                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();
                                    }

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

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

                                var closure = function.FunctionValue;

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

                                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 == null ? argCount : unpackedArgs.Count;

                                // 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 != null ? 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 = 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;
                            }
                        #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;

                var runtimeException = e as MondRuntimeException;
                if (runtimeException != null && runtimeException.InternalStackTrace != null)
                {
                    stackTraceBuilder = new StringBuilder(runtimeException.InternalStackTrace);

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

                    // skip the first frame because it's this method? need to verify
                    for (var i = 1; i < stackTrace.FrameCount; i++)
                    {
                        var method = stackTrace.GetFrame(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 ...]");
                }
                else
                {
                    stackTraceBuilder = new StringBuilder();
                }

                // first line of the stack trace is where we are running
                stackTraceBuilder.AppendLine(GetAddressDebugInfo(program, errorIp));

                // generate stack trace and reset stacks
                for (var i = Math.Min(_callStackSize - 1, CallStackCapacity - 1); i > initialCallDepth; i--)
                {
                    var returnAddress = _callStack[i];
                    stackTraceBuilder.AppendLine(GetAddressDebugInfo(returnAddress.Program, returnAddress.Address));
                }

                _callStackSize = initialCallDepth;
                for (var i = _callStackSize; i < CallStackCapacity; i++)
                {
                    _callStack[i] = default(ReturnAddress);
                }

                _localStackSize = initialLocalDepth;
                for (var i = _localStackSize; i < CallStackCapacity; i++)
                {
                    _localStack[i] = default(Frame);
                }

                _evalStackSize = initialEvalDepth;
                for (var i = _evalStackSize; i < EvalStackCapacity; i++)
                {
                    _evalStack[i] = default(MondValue);
                }

                throw new MondRuntimeException(message, e)
                {
                    InternalStackTrace = stackTraceBuilder.ToString()
                };
            }
        }