/// <summary> /// Take the topmost arguments down to the ARG_MARKER_STRING, pop them off, and then /// put them back again in reversed order so a function can read them in normal order. /// Note that if this is an indirect call, it will also consume the thing just under /// the ARG_MARKER, since that's expected to be the delegate or KOSDelegate that we already /// read and pulled the needed information from. /// <param name="cpu">the cpu we are running on, fur stack manipulation purposes</param> /// <param name="direct">need to know if this was a direct or indirect call. If indirect, /// then that means it also needs to consume the indirect reference off the stack just under /// the args</param> /// </summary> public static void ReverseStackArgs(ICpu cpu, bool direct) { List <object> args = new List <object>(); object arg = cpu.PopValue(); while (cpu.GetStackSize() > 0 && arg.GetType() != ArgMarkerType) { args.Add(arg); // It's important to dereference with PopValue, not using PopStack, because the function // being called might not even be able to see the variable in scope anyway. // In other words, if calling a function like so: // declare foo to 3. // myfunc(foo). // The code inside myfunc needs to see that as being identical to just saying: // myfunc(3). // It has to be unaware of the fact that the name of the argument was 'foo'. It just needs to // see the contents that were inside foo. arg = cpu.PopValue(); } if (!direct) { cpu.PopStack(); // throw away the delegate or KOSDelegate info - we already snarfed it by now. } // Push the arg marker back on again. cpu.PushStack(new KOSArgMarkerType()); // Push the arguments back on again, which will invert their order: foreach (object item in args) { cpu.PushStack(item); } }
/// <summary> /// Take the topmost arguments down to the ARG_MARKER_STRING, pop them off, and then /// put them back again in reversed order so a function can read them in normal order. /// Note that if this is an indirect call, it will also consume the thing just under /// the ARG_MARKER, since that's expected to be the delegate or KOSDelegate that we already /// read and pulled the needed information from. /// <param name="cpu">the cpu we are running on, fur stack manipulation purposes</param> /// <param name="direct">need to know if this was a direct or indirect call. If indirect, /// then that means it also needs to consume the indirect reference off the stack just under /// the args</param> /// </summary> public static void ReverseStackArgs(ICpu cpu, bool direct) { List<object> args = new List<object>(); object arg = cpu.PopValue(); while (cpu.GetStackSize() > 0 && arg.GetType() != ArgMarkerType) { args.Add(arg); // It's important to dereference with PopValue, not using PopStack, because the function // being called might not even be able to see the variable in scope anyway. // In other words, if calling a function like so: // declare foo to 3. // myfunc(foo). // The code inside myfunc needs to see that as being identical to just saying: // myfunc(3). // It has to be unaware of the fact that the name of the argument was 'foo'. It just needs to // see the contents that were inside foo. arg = cpu.PopValue(); } if (! direct) cpu.PopStack(); // throw away the delegate or KOSDelegate info - we already snarfed it by now. // Push the arg marker back on again. cpu.PushStack(new KOSArgMarkerType()); // Push the arguments back on again, which will invert their order: foreach (object item in args) cpu.PushStack(item); }
/// <summary> /// Performs the actual execution of a subroutine call, either from this opcode or externally from elsewhere. /// All "call a routine" logic should shunt through this code here, which handles all the complex cases, /// or at least it should. /// Note that in the case of a user function, this does not *ACTUALLY* execute the function yet. It just /// arranges the stack correctly for the call and returns the new location that the IP should be jumped to /// on the next instruction to begin the subroutine. For all built-in cases, it actually executes the /// call right now and doesn't return until it's done. But for User functions it can't do that - it can only /// advise on where to jump on the next instruction to begin the function. /// </summary> /// <param name="cpu">the cpu its running on</param> /// <param name="direct">same meaning as OpcodeCall.Direct</param> /// <param name="destination">if direct, then this is the function name</param> /// <param name="calledFromKOSDelegateCall">true if KOSDelegate.Call() brought us here. If true that /// means any pre-bound args are already on the stack. If false it means they aren't and this will have to /// put them there.</param> /// <returns>new IP to jump to, if this should be followed up by a jump. If -1 then it means don't jump.</returns> public static int StaticExecute(ICpu cpu, bool direct, object destination, bool calledFromKOSDelegateCall) { object functionPointer; object delegateReturn = null; int newIP = -1; // new instruction pointer to jump to, next, if any. if (direct) { functionPointer = cpu.GetValue(destination); if (functionPointer == null) throw new KOSException("Attempt to call function failed - Value of function pointer for " + destination + " is null."); } else // for indirect calls, dig down to find what's underneath the argument list in the stack and use that: { bool foundBottom = false; int digDepth; int argsCount = 0; for (digDepth = 0; (! foundBottom) && digDepth < cpu.GetStackSize() ; ++digDepth) { object arg = cpu.PeekValue(digDepth); if (arg != null && arg.GetType() == ArgMarkerType) foundBottom = true; else ++argsCount; } functionPointer = cpu.PeekValue(digDepth); if (! ( functionPointer is Delegate || functionPointer is KOSDelegate || functionPointer is ISuffixResult)) { // Indirect calls are meant to be delegates. If they are not, then that means the // function parentheses were put on by the user when they weren't required. Just dig // through the stack to the result of the getMember and skip the rest of the execute logic // If args were passed to a non-method, then clean them off the stack, and complain: if (argsCount>0) { for (int i=1 ; i<=argsCount; ++i) cpu.PopValue(); throw new KOSArgumentMismatchException( 0, argsCount, "\n(In fact in this case the parentheses are entirely optional)"); } cpu.PopValue(); // pop the ArgMarkerString too. return -1; } } // If it's a string it might not really be a built-in, it might still be a user func. // Detect whether it's built-in, and if it's not, then convert it into the equivalent // user func call by making it be an integer instruction pointer instead: if (functionPointer is string || functionPointer is StringValue) { string functionName = functionPointer.ToString(); if (functionName.EndsWith("()")) functionName = functionName.Substring(0, functionName.Length - 2); if (!(cpu.BuiltInExists(functionName))) { // It is not a built-in, so instead get its value as a user function pointer variable, despite // the fact that it's being called AS IF it was direct. if (!functionName.EndsWith("*")) functionName = functionName + "*"; if (!functionName.StartsWith("$")) functionName = "$" + functionName; functionPointer = cpu.GetValue(functionName); } } KOSDelegate kosDelegate = functionPointer as KOSDelegate; if (kosDelegate != null) { if (! calledFromKOSDelegateCall) kosDelegate.InsertPreBoundArgs(); } IUserDelegate userDelegate = functionPointer as IUserDelegate; if (userDelegate != null) functionPointer = userDelegate.EntryPoint; BuiltinDelegate builtinDel = functionPointer as BuiltinDelegate; if (builtinDel != null && (! calledFromKOSDelegateCall) ) functionPointer = builtinDel.Name; // If the IP for a jump location got encapsulated as a user int when it got stored // into the internal variable, then get the primitive int back out of it again: ScalarIntValue userInt = functionPointer as ScalarIntValue; if (userInt != null) functionPointer = userInt.GetIntValue(); // Convert to int instead of cast in case the identifier is stored // as an encapsulated ScalarValue, preventing an unboxing collision. if (functionPointer is int || functionPointer is ScalarValue) { CpuUtility.ReverseStackArgs(cpu, direct); var contextRecord = new SubroutineContext(cpu.InstructionPointer+1); newIP = Convert.ToInt32(functionPointer); cpu.PushAboveStack(contextRecord); if (userDelegate != null) { cpu.AssertValidDelegateCall(userDelegate); // Reverse-push the closure's scope record, just after the function return context got put on the stack. for (int i = userDelegate.Closure.Count - 1 ; i >= 0 ; --i) cpu.PushAboveStack(userDelegate.Closure[i]); } } else if (functionPointer is string) { // Built-ins don't need to dereference the stack values because they // don't leave the scope - they're not implemented that way. But later we // might want to change that. var name = functionPointer as string; string functionName = name; if (functionName.EndsWith("()")) functionName = functionName.Substring(0, functionName.Length - 2); cpu.CallBuiltinFunction(functionName); // If this was indirect, we need to consume the thing under the return value. // as that was the indirect BuiltInDelegate: if ((! direct) && builtinDel != null) { object topThing = cpu.PopStack(); cpu.PopStack(); // remove BuiltInDelegate object. cpu.PushStack(topThing); // put return value back. } } else if (functionPointer is ISuffixResult) { var result = (ISuffixResult) functionPointer; if (!result.HasValue) { result.Invoke(cpu); } delegateReturn = result.Value; } // TODO:erendrake This else if is likely never used anymore else if (functionPointer is Delegate) { throw new KOSYouShouldNeverSeeThisException("OpcodeCall unexpected function pointer delegate"); } else { throw new KOSNotInvokableException(functionPointer); } if (functionPointer is ISuffixResult) { if (! (delegateReturn is KOSPassThruReturn)) cpu.PushStack(delegateReturn); // And now leave the return value on the stack to be read. } return newIP; }
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 void Invoke(ICpu cpu) { MethodInfo methInfo = del.Method; ParameterInfo[] paramArray = methInfo.GetParameters(); var args = new List<object>(); var paramArrayArgs = new List<Structure>(); // Will be true iff the lastmost parameter of the delegate is using the C# 'param' keyword and thus // expects the remainder of the arguments marshalled together into one array object. bool isParamArrayArg = false; CpuUtility.ReverseStackArgs(cpu, false); for (int i = 0; i < paramArray.Length; ++i) { object arg = cpu.PopValue(); Type argType = arg.GetType(); ParameterInfo paramInfo = paramArray[i]; // If this is the lastmost parameter then it might be a 'param' array which expects all the rest of // the arguments to be collected together into one single array parameter when invoking the method: isParamArrayArg = (i == paramArray.Length - 1 && Attribute.IsDefined(paramInfo, typeof(ParamArrayAttribute))); if (arg != null && arg.GetType() == CpuUtility.ArgMarkerType) { if (isParamArrayArg) break; // with param arguments, you want to consume everything to the arg bottom - it's normal. else throw new KOSArgumentMismatchException(paramArray.Length, paramArray.Length - (i + 1)); } // Either the expected type of this one parameter, or if it's a 'param' array as the last arg, then // the expected type of that array's elements: Type paramType = (isParamArrayArg ? paramInfo.ParameterType.GetElementType() : paramInfo.ParameterType); // Parameter type-safe checking: bool inheritable = paramType.IsAssignableFrom(argType); if (!inheritable) { bool castError = false; // If it's not directly assignable to the expected type, maybe it's "castable" to it: try { arg = Convert.ChangeType(arg, Type.GetTypeCode(paramType)); } catch (InvalidCastException) { throw new KOSCastException(argType, paramType); } catch (FormatException) { castError = true; } if (castError) { throw new Exception(string.Format("Argument {0}({1}) to method {2} should be {3} instead of {4}.", (paramArray.Length - i), arg, methInfo.Name, paramType.Name, argType)); } } if (isParamArrayArg) { paramArrayArgs.Add(Structure.FromPrimitiveWithAssert(arg)); --i; // keep hitting the last item in the param list again and again until a forced break because of arg bottom marker. } else { args.Add(Structure.FromPrimitiveWithAssert(arg)); } } if (isParamArrayArg) { // collect the param array args that were at the end into the one single // array item that will be sent to the method when invoked: args.Add(paramArrayArgs.ToArray()); } // Consume the bottom marker under the args, which had better be // immediately under the args we just popped, or the count was off. if (!isParamArrayArg) // A param array arg will have already consumed the arg bottom mark. { bool foundArgMarker = false; int numExtraArgs = 0; while (cpu.GetStackSize() > 0 && !foundArgMarker) { object marker = cpu.PopValue(); if (marker != null && marker.GetType() == CpuUtility.ArgMarkerType) foundArgMarker = true; else ++numExtraArgs; } if (numExtraArgs > 0) throw new KOSArgumentMismatchException(paramArray.Length, paramArray.Length + numExtraArgs); } // Delegate.DynamicInvoke expects a null, rather than an array of zero length, when // there are no arguments to pass: object[] argArray = (args.Count > 0) ? args.ToArray() : null; try { // I could find no documentation on what DynamicInvoke returns when the delegate // is a function returning void. Does it return a null? I don't know. So to avoid the // problem, I split this into these two cases: if (methInfo.ReturnType == typeof(void)) { del.DynamicInvoke(argArray); value = ScalarValue.Create(0); // By adding this we can unconditionally assume all functionshave a return value // to be used or popped away, even if "void". In order to mainain consistency with // the void return value of functions, and to ensure that we don't accidentally pass // a value back to the user that they cannot interact with (null), we return zero. } else { // Convert a primitive return type to a structure. This is done in the opcode, since // the opcode calls the deligate directly and cannot be (quickly) intercepted value = Structure.FromPrimitiveWithAssert(del.DynamicInvoke(argArray)); } } catch (TargetInvocationException e) { // Annoyingly, calling DynamicInvoke on a delegate wraps any exceptions the delegate throws inside // this TargetInvocationException, which hides them from the kOS user unless we do this: if (e.InnerException != null) throw e.InnerException; throw; } }
public void Invoke(ICpu cpu) { MethodInfo methInfo = del.Method; ParameterInfo[] paramArray = methInfo.GetParameters(); var args = new List <object>(); var paramArrayArgs = new List <Structure>(); // Will be true iff the lastmost parameter of the delegate is using the C# 'param' keyword and thus // expects the remainder of the arguments marshalled together into one array object. bool isParamArrayArg = false; CpuUtility.ReverseStackArgs(cpu, false); for (int i = 0; i < paramArray.Length; ++i) { object arg = cpu.PopValue(); Type argType = arg.GetType(); ParameterInfo paramInfo = paramArray[i]; // If this is the lastmost parameter then it might be a 'param' array which expects all the rest of // the arguments to be collected together into one single array parameter when invoking the method: isParamArrayArg = (i == paramArray.Length - 1 && Attribute.IsDefined(paramInfo, typeof(ParamArrayAttribute))); if (arg != null && arg.GetType() == CpuUtility.ArgMarkerType) { if (isParamArrayArg) { break; // with param arguments, you want to consume everything to the arg bottom - it's normal. } else { throw new KOSArgumentMismatchException(paramArray.Length, paramArray.Length - (i + 1)); } } // Either the expected type of this one parameter, or if it's a 'param' array as the last arg, then // the expected type of that array's elements: Type paramType = (isParamArrayArg ? paramInfo.ParameterType.GetElementType() : paramInfo.ParameterType); // Parameter type-safe checking: bool inheritable = paramType.IsAssignableFrom(argType); if (!inheritable) { bool castError = false; // If it's not directly assignable to the expected type, maybe it's "castable" to it: try { arg = Convert.ChangeType(arg, Type.GetTypeCode(paramType)); } catch (InvalidCastException) { throw new KOSCastException(argType, paramType); } catch (FormatException) { castError = true; } if (castError) { throw new Exception(string.Format("Argument {0}({1}) to method {2} should be {3} instead of {4}.", (paramArray.Length - i), arg, methInfo.Name, paramType.Name, argType)); } } if (isParamArrayArg) { paramArrayArgs.Add(Structure.FromPrimitiveWithAssert(arg)); --i; // keep hitting the last item in the param list again and again until a forced break because of arg bottom marker. } else { args.Add(Structure.FromPrimitiveWithAssert(arg)); } } if (isParamArrayArg) { // collect the param array args that were at the end into the one single // array item that will be sent to the method when invoked: args.Add(paramArrayArgs.ToArray()); } // Consume the bottom marker under the args, which had better be // immediately under the args we just popped, or the count was off. if (!isParamArrayArg) // A param array arg will have already consumed the arg bottom mark. { bool foundArgMarker = false; int numExtraArgs = 0; while (cpu.GetStackSize() > 0 && !foundArgMarker) { object marker = cpu.PopValue(); if (marker != null && marker.GetType() == CpuUtility.ArgMarkerType) { foundArgMarker = true; } else { ++numExtraArgs; } } if (numExtraArgs > 0) { throw new KOSArgumentMismatchException(paramArray.Length, paramArray.Length + numExtraArgs); } } // Delegate.DynamicInvoke expects a null, rather than an array of zero length, when // there are no arguments to pass: object[] argArray = (args.Count > 0) ? args.ToArray() : null; try { // I could find no documentation on what DynamicInvoke returns when the delegate // is a function returning void. Does it return a null? I don't know. So to avoid the // problem, I split this into these two cases: if (methInfo.ReturnType == typeof(void)) { del.DynamicInvoke(argArray); value = ScalarValue.Create(0); // By adding this we can unconditionally assume all functionshave a return value // to be used or popped away, even if "void". In order to mainain consistency with // the void return value of functions, and to ensure that we don't accidentally pass // a value back to the user that they cannot interact with (null), we return zero. } else { // Convert a primitive return type to a structure. This is done in the opcode, since // the opcode calls the deligate directly and cannot be (quickly) intercepted value = Structure.FromPrimitiveWithAssert(del.DynamicInvoke(argArray)); } } catch (TargetInvocationException e) { // Annoyingly, calling DynamicInvoke on a delegate wraps any exceptions the delegate throws inside // this TargetInvocationException, which hides them from the kOS user unless we do this: if (e.InnerException != null) { throw e.InnerException; } throw; } }
/// <summary> /// Call this when executing a delegate function whose delegate object was stored on /// the stack underneath the arguments. The code here is using reflection and complex /// enough that it needed to be separated from the main Execute method. /// </summary> /// <param name="cpu">the cpu this opcode is being called on</param> /// <param name="dlg">the delegate object this opcode is being called for.</param> /// <returns>whatever object the delegate method returned</returns> public static object ExecuteDelegate(ICpu cpu, Delegate dlg) { MethodInfo methInfo = dlg.Method; ParameterInfo[] paramArray = methInfo.GetParameters(); var args = new List<object>(); // Iterating over parameter signature backward because stack: for (int i = paramArray.Length - 1 ; i >= 0 ; --i) { object arg = cpu.PopValue(); if (arg != null && arg.GetType() == ArgMarkerType) throw new KOSArgumentMismatchException(paramArray.Length, paramArray.Length - (i+1)); Type argType = arg.GetType(); ParameterInfo paramInfo = paramArray[i]; Type paramType = paramInfo.ParameterType; // Parameter type-safe checking: bool inheritable = paramType.IsAssignableFrom(argType); if (! inheritable) { bool castError = false; // If it's not directly assignable to the expected type, maybe it's "castable" to it: try { arg = Convert.ChangeType(arg, Type.GetTypeCode(paramType)); } catch (InvalidCastException) { throw new KOSCastException(argType, paramType); } catch (FormatException) { castError = true; } if (castError) { throw new Exception(string.Format("Argument {0}({1}) to method {2} should be {3} instead of {4}.", (paramArray.Length - i), arg, methInfo.Name, paramType.Name, argType)); } } args.Add(arg); } // Consume the bottom marker under the args, which had better be // immediately under the args we just popped, or the count was off: bool foundArgMarker = false; int numExtraArgs = 0; while (cpu.GetStackSize() > 0 && !foundArgMarker) { object marker = cpu.PopValue(); if (marker != null && marker.GetType() == ArgMarkerType) foundArgMarker = true; else ++numExtraArgs; } if (numExtraArgs > 0) throw new KOSArgumentMismatchException(paramArray.Length, paramArray.Length + numExtraArgs); args.Reverse(); // Put back in normal order instead of stack order. // Dialog.DynamicInvoke expects a null, rather than an array of zero length, when // there are no arguments to pass: object[] argArray = (args.Count>0) ? args.ToArray() : null; try { // I could find no documentation on what DynamicInvoke returns when the delegate // is a function returning void. Does it return a null? I don't know. So to avoid the // problem, I split this into these two cases: if (methInfo.ReturnType == typeof(void)) { dlg.DynamicInvoke(argArray); return null; // So that the compiler building the opcodes for a function call statement doesn't // have to know the function prototype to decide whether or // not it needs to pop a value from the stack for the return value. By adding this, // it can unconditionally assume there will be exactly 1 value left behind on the stack // regardless of what function it was that was being called. } return dlg.DynamicInvoke(argArray); } catch (TargetInvocationException e) { // Annoyingly, calling DynamicInvoke on a delegate wraps any exceptions the delegate throws inside // this TargetInvocationException, which hides them from the kOS user unless we do this: if (e.InnerException != null) throw e.InnerException; throw; } }
public override void Execute(ICpu cpu) { object functionPointer; object delegateReturn = null; if (Direct) { functionPointer = cpu.GetValue(Destination); if (functionPointer == null) throw new KOSException("Attempt to call function failed - Value of function pointer for " + Destination + " is null."); } else // for indirect calls, dig down to find what's underneath the argument list in the stack and use that: { bool foundBottom = false; int digDepth; int argsCount = 0; for (digDepth = 0; (! foundBottom) && digDepth < cpu.GetStackSize() ; ++digDepth) { object arg = cpu.PeekValue(digDepth); if (arg != null && arg.GetType() == ArgMarkerType) foundBottom = true; else ++argsCount; } functionPointer = cpu.PeekValue(digDepth); if (! ( functionPointer is Delegate)) { // Indirect calls are meant to be delegates. If they are not, then that means the // function parentheses were put on by the user when they weren't required. Just dig // through the stack to the result of the getMember and skip the rest of the execute logic // If args were passed to a non-method, then clean them off the stack, and complain: if (argsCount>0) { for (int i=1 ; i<=argsCount; ++i) cpu.PopValue(); throw new KOSArgumentMismatchException( 0, argsCount, "\n(In fact in this case the parentheses are entirely optional)"); } cpu.PopValue(); // pop the ArgMarkerString too. return; } } // If it's a string it might not really be a built-in, it might still be a user func. // Detect whether it's built-in, and if it's not, then convert it into the equivalent // user func call by making it be an integer instruction pointer instead: if (functionPointer is string) { string functionName = functionPointer as string; if (functionName.EndsWith("()")) functionName = functionName.Substring(0, functionName.Length - 2); if (!(cpu.BuiltInExists(functionName))) { // It is not a built-in, so instead get its value as a user function pointer variable, despite // the fact that it's being called AS IF it was direct. if (!functionName.EndsWith("*")) functionName = functionName + "*"; if (!functionName.StartsWith("$")) functionName = "$" + functionName; functionPointer = cpu.GetValue(functionName); } } IUserDelegate userDelegate = functionPointer as IUserDelegate; if (userDelegate != null) functionPointer = userDelegate.EntryPoint; if (functionPointer is int) { ReverseStackArgs(cpu); int currentPointer = cpu.InstructionPointer; DeltaInstructionPointer = (int)functionPointer - currentPointer; var contextRecord = new SubroutineContext(currentPointer+1); cpu.PushAboveStack(contextRecord); if (userDelegate != null) { cpu.AssertValidDelegateCall(userDelegate); // Reverse-push the closure's scope record, just after the function return context got put on the stack. for (int i = userDelegate.Closure.Count - 1 ; i >= 0 ; --i) cpu.PushAboveStack(userDelegate.Closure[i]); } } else if (functionPointer is string) { // Built-ins don't need to dereference the stack values because they // don't leave the scope - they're not implemented that way. But later we // might want to change that. var name = functionPointer as string; string functionName = name; if (functionName.EndsWith("()")) functionName = functionName.Substring(0, functionName.Length - 2); cpu.CallBuiltinFunction(functionName); } else if (functionPointer is Delegate) { delegateReturn = ExecuteDelegate(cpu, (Delegate)functionPointer); } else { // This is one of those "the user had better NEVER see this error" sorts of messages that's here to keep us in check: throw new Exception( string.Format("kOS internal error: OpcodeCall calling a function described using {0} which is of type {1} and kOS doesn't know how to call that.", functionPointer, functionPointer.GetType().Name) ); } if (! Direct) { cpu.PopValue(); // consume function name, branch index, or delegate } if (functionPointer is Delegate) { cpu.PushStack(delegateReturn); // And now leave the return value on the stack to be read. } }