/// <summary> /// Call after just pushing or popping from the scope stack. Show it the /// item that was popped, and if it is a trigger context, then it will adjust /// the triggerContextCount by the diff you give it. /// </summary> /// <param name="item">Item.</param> /// <param name="diff">Diff.</param> private void AdjustTriggerCountIfNeeded(object item, int diff) { SubroutineContext sr = item as SubroutineContext; if (sr != null && sr.IsTrigger) { triggerContextCount += diff; } }
/// <summary> /// Check if the call-stack has evidence that we are currently inside /// a trigger. /// </summary> /// <returns>True if the current call stack indicates that either we are /// inside a trigger, or are inside code that was in turn indirectly called /// from a trigger. False if we are in mainline code instead.</returns> public bool HasTriggerContexts() { for (int index = stackPointer + 1; index < stack.Count; ++index) { SubroutineContext contextRecord = stack[index] as SubroutineContext; if (contextRecord != null) { if (contextRecord.IsTrigger) { return(true); } } } return(false); }
public void Push(object item) { ThrowIfInvalid(item); stackPointer++; if (stack.Count < MAX_STACK_SIZE) { stack.Insert(stackPointer, ProcessItem(item)); SubroutineContext sr = item as SubroutineContext; if (sr != null && sr.IsTrigger) { ++triggerContextCount; } } else { // TODO: make an IKOSException for this: throw new Exception("Stack overflow!!"); } }
public object Pop() { object item = null; if (stack.Count > 0) { item = stack[stackPointer]; stack.RemoveAt(stackPointer); stackPointer--; } if (triggerContextCount > 0) { SubroutineContext sr = item as SubroutineContext; if (sr != null && sr.IsTrigger) { --triggerContextCount; } } return(item); }
private void ProcessTriggers() { if (currentContext.ActiveTriggerCount() <= 0) return; int oldCount = currentContext.Program.Count; int currentInstructionPointer = currentContext.InstructionPointer; var triggersToBeExecuted = new List<int>(); // To ensure triggers execute in the same order in which they // got added (thus ensuring the system favors trying the least // recently added trigger first in a more fair round-robin way), // the nested function calls get built in stack order, so that // they execute in normal order. Thus the backward iteration // order used in the loop below: for (int index = currentContext.ActiveTriggerCount() - 1 ; index >= 0 ; --index) { int triggerPointer = currentContext.GetTriggerByIndex(index); // If the program is ended from within a trigger, the trigger list will be empty and the pointer // will be invalid. Only execute the trigger if it still exists. if (currentContext.ContainsTrigger(triggerPointer)) { // Insert a faked function call as if the trigger had been called from just // before whatever opcode was about to get executed, by pusing a context // record like OpcodeCall would do, and moving the IP to the // first line of the trigger, like OpcodeCall would do. Because // triggers can't take arguments, most of the messy work of // OpcodeCall.Execute isn't needed: SubroutineContext contextRecord = new SubroutineContext(currentInstructionPointer, triggerPointer); PushAboveStack(contextRecord); PushStack(new KOSArgMarkerType()); // to go with the faked function call of zero arguments. triggersToBeExecuted.Add(triggerPointer); currentInstructionPointer = triggerPointer; // Triggers can chain in this loop if more than one fire off at once - the second trigger // will look like it was a function that was called from the start of the first trigger. // The third trigger will look like a function that was called from the start of the second, etc. } } // Remove all triggers that will fire. Any trigger that wants to // re-enable itself will do so by returning a boolean true, which // will tell OpcodeReturn that it needs to re-add the trigger. foreach (int triggerPointer in triggersToBeExecuted) RemoveTrigger(triggerPointer); currentContext.InstructionPointer = currentInstructionPointer; }
/// <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) { 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. } }