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