/// <summary> /// Do the actual work of the Execute() method. This was pulled out /// to a separate static method so that others can call it without needing /// an actual popscope object. Everything OpcodePopScope.Execute() does /// should actually be done here, so as to ensure that external callers of /// this get exactly the same behaviour as a full popstack opcode. /// </summary> /// <param name="cpuObj">the shared.cpu to operate on.</param> /// <param name="levels">number of levels to popscope.</param> public static void DoPopScope(ICpu cpuObj, Int16 levels) { cpuObj.PopAboveStack(levels); }
public override void Execute(ICpu cpu) { // Return value should be atop the stack. // Pop it, eval it, and push it back, // i.e. if the statement was RETURN X, and X is 2, then you want // it to return the number 2, not the variable name $x, which could // be a variable local to this function which is about to go out of scope // so the caller can't access it: object returnVal = cpu.PopValue(); // Now dig down through the stack until the argbottom is found. // anything still leftover above that should be unread parameters we // should throw away: object shouldBeArgMarker = 0; // just a temp to force the loop to execute at least once. while (shouldBeArgMarker == null || (shouldBeArgMarker.GetType() != OpcodeCall.ArgMarkerType)) { if (cpu.GetStackSize() <= 0) { throw new KOSArgumentMismatchException( string.Format("Something is wrong with the stack - no arg bottom mark when doing a return. This is an internal problem with kOS") ); } shouldBeArgMarker = cpu.PopStack(); } cpu.PushStack(Structure.FromPrimitive(returnVal)); // Now, after the eval was done, NOW finally pop the scope, after we don't need local vars anymore: if( Depth > 0 ) OpcodePopScope.DoPopScope(cpu, Depth); // The only thing on the "above stack" now that is allowed to get in the way of // finding the context record that tells us where to jump back to, are the potential // closure scope frames that might have been pushed if this subroutine was // called via a delegate reference. Consume any of those that are in // the way, then expect the context record. Any other pattern encountered // is proof the stack alignment got screwed up: bool okay; VariableScope peeked = cpu.PeekRaw(-1, out okay) as VariableScope; while (okay && peeked != null && peeked.IsClosure) { cpu.PopAboveStack(1); peeked = cpu.PeekRaw(-1, out okay) as VariableScope; } object shouldBeContextRecord = cpu.PopAboveStack(1); if ( !(shouldBeContextRecord is SubroutineContext) ) { // This should never happen with any user code: throw new Exception( "kOS internal error: Stack misalignment detected when returning from routine."); } var contextRecord = shouldBeContextRecord as SubroutineContext; // Special case for when the subroutine was really being called as an interrupt // trigger from the kOS CPU itself. In that case we don't want to leave the // return value atop the stack, and instead want to pop it and use it to decide // whether or not the re-insert the trigger for next time: if (contextRecord.IsTrigger) { cpu.PopStack(); // already got the return value up above, just ignore it. if (returnVal is bool || returnVal is BooleanValue ) if (Convert.ToBoolean(returnVal)) cpu.AddTrigger(contextRecord.TriggerPointer); } int destinationPointer = contextRecord.CameFromInstPtr; int currentPointer = cpu.InstructionPointer; DeltaInstructionPointer = destinationPointer - currentPointer; }
public override void Execute(ICpu cpu) { // Return value should be atop the stack - we have to pop it so that // we can reach the arg start marker under it: object returnVal = cpu.PopValue(); // The next thing on the stack under the return value should be the marker that indicated where // the parameters started. It should be thrown away now. If the next thing is NOT the marker // of where the parameters started, that is proof the stack is misaligned, probably because the // number of args passed didn't match the number of DECLARE PARAMETER statements in the function: string shouldBeArgMarker = cpu.PopStack() as string; if ( (shouldBeArgMarker == null) || (!(shouldBeArgMarker.Equals(OpcodeCall.ARG_MARKER_STRING))) ) { throw new KOSArgumentMismatchException( string.Format("(detected when returning from function and the stack still had {0} on it)", (shouldBeArgMarker ?? "a non-string value")) ); } // If the proper argument marker was found, then it's all okay, so put the return value // back, where it belongs, now that the arg start marker was popped off: cpu.PushStack(returnVal); // Now, after the eval was done, NOW finally pop the scope, after we don't need local vars anymore: if( Depth > 0 ) OpcodePopScope.DoPopScope(cpu, Depth); // The only thing on the "above stack" now that is allowed to get in the way of // finding the context record that tells us where to jump back to, are the potential // closure scope frames that might have been pushed if this subroutine was // called via a delegate reference. Consume any of those that are in // the way, then expect the context record. Any other pattern encountered // is proof the stack alignment got screwed up: bool okay; VariableScope peeked = cpu.PeekRaw(-1, out okay) as VariableScope; while (okay && peeked != null && peeked.IsClosure) { cpu.PopAboveStack(1); peeked = cpu.PeekRaw(-1, out okay) as VariableScope; } object shouldBeContextRecord = cpu.PopAboveStack(1); if ( !(shouldBeContextRecord is SubroutineContext) ) { // This should never happen with any user code: throw new Exception( "kOS internal error: Stack misalignment detected when returning from routine."); } var contextRecord = shouldBeContextRecord as SubroutineContext; int destinationPointer = contextRecord.CameFromInstPtr; int currentPointer = cpu.InstructionPointer; DeltaInstructionPointer = destinationPointer - currentPointer; }