void Emit(Node node, ByteCode code, object operandA = null, object operandB = null) { var instruction = new Instruction(); instruction.operation = code; instruction.operandA = operandA; instruction.operandB = operandB; node.instructions.Add (instruction); if (code == ByteCode.Label) { // Add this label to the label table node.labels.Add ((string)instruction.operandA, node.instructions.Count - 1); } }
internal void RunInstruction(Instruction i) { switch (i.operation) { case ByteCode.Label: // No-op; used as a destination for JumpTo and Jump. break; case ByteCode.JumpTo: // Jumps to a named label state.programCounter = FindInstructionPointForLabel ((string)i.operandA); break; case ByteCode.RunLine: // Looks up a string from the string table and // passes it to the client as a line var lineText = program.GetString ((string)i.operandA); lineHandler (new Dialogue.LineResult (lineText)); break; case ByteCode.RunCommand: // Passes a string to the client as a custom command commandHandler ( new Dialogue.CommandResult ((string)i.operandA) ); break; case ByteCode.PushString: // Pushes a string value onto the stack; the operand // is an index into the string table, so that's looked up // first. state.PushValue (program.GetString ((string)i.operandA)); break; case ByteCode.PushNumber: // Pushes a number onto the stack. state.PushValue (Convert.ToSingle(i.operandA)); break; case ByteCode.PushBool: // Pushes a boolean value onto the stack. state.PushValue (Convert.ToBoolean(i.operandA)); break; case ByteCode.PushNull: // Pushes a null value onto the stack. state.PushValue (Value.NULL); break; case ByteCode.JumpIfFalse: // Jumps to a named label if the value on the top of the stack // evaluates to the boolean value 'false'. if (state.PeekValue ().AsBool == false) { state.programCounter = FindInstructionPointForLabel ((string)i.operandA); } break; case ByteCode.Jump: // Jumps to a label whose name is on the stack. var jumpDestination = state.PeekValue ().AsString; state.programCounter = FindInstructionPointForLabel (jumpDestination); break; case ByteCode.Pop: // Pops a value from the stack. state.PopValue (); break; case ByteCode.CallFunc: // Call a function, whose parameters are expected to // be on the stack. Pushes the function's return value, // if it returns one. var functionName = (string)i.operandA; var function = dialogue.library.GetFunction (functionName); { var paramCount = function.paramCount; // If this function takes "-1" parameters, it is variadic. // Expect the compiler to have placed the number of parameters // actually passed at the top of the stack. if (paramCount == -1) { paramCount = (int)state.PopValue ().AsNumber; } // Get the parameters, which were pushed in reverse Value[] parameters = new Value[paramCount]; for (int param = paramCount - 1; param >= 0; param--) { parameters [param] = state.PopValue (); } // Invoke the function var result = function.InvokeWithArray (parameters); // If the function returns a value, push it if (function.returnsValue) { state.PushValue (result); } } break; case ByteCode.PushVariable: // Get the contents of a variable, push that onto the stack. var variableName = (string)i.operandA; var loadedValue = dialogue.continuity.GetValue (variableName); state.PushValue (loadedValue); break; case ByteCode.StoreVariable: // Store the top value on the stack in a variable. var topValue = state.PeekValue (); var destinationVariableName = (string)i.operandA; dialogue.continuity.SetValue (destinationVariableName, topValue); break; case ByteCode.Stop: // Immediately stop execution, and report that fact. nodeCompleteHandler (new Dialogue.NodeCompleteResult (null)); executionState = ExecutionState.Stopped; break; case ByteCode.RunNode: string nodeName; if (string.IsNullOrEmpty((string) i.operandA)) { // Get a string from the stack, and jump to a node with that name. nodeName = state.PeekValue ().AsString; } else { // jump straight to the node nodeName = (string)i.operandA; } nodeCompleteHandler (new Dialogue.NodeCompleteResult (nodeName)); SetNode (nodeName); break; case ByteCode.AddOption: // Add an option to the current state. state.currentOptions.Add (new KeyValuePair<string, string> ((string)i.operandA, (string)i.operandB)); break; case ByteCode.ShowOptions: // If we have no options to show, immediately stop. if (state.currentOptions.Count == 0) { executionState = ExecutionState.Stopped; nodeCompleteHandler (new Dialogue.NodeCompleteResult (null)); break; } // If we have a single option, and it has no label, select it immediately and continue // execution if (state.currentOptions.Count == 1 && state.currentOptions[0].Key == null) { var destinationNode = state.currentOptions[0].Value; state.PushValue(destinationNode); state.currentOptions.Clear(); break; } if (dialogue.continuity.GetValue(SpecialVariables.ShuffleOptions).boolValue) { // Shuffle the dialog options if needed var n = state.currentOptions.Count; for (int opt1 = 0; opt1 < n; opt1++) { int opt2 = opt1 + (int)(random.NextDouble () * (n - opt1)); // r.Next(0, state.currentOptions.Count-1); var temp = state.currentOptions [opt2]; state.currentOptions [opt2] = state.currentOptions [opt1]; state.currentOptions [opt1] = temp; } } // Otherwise, present the list of options to the user and let them pick var optionStrings = new List<string> (); foreach (var option in state.currentOptions) { optionStrings.Add (program.GetString (option.Key)); } // We can't continue until our client tell us which option to pick executionState = ExecutionState.WaitingOnOptionSelection; // Pass the options set to the client, as well as a delegate for them to call when the // user has made a selection optionsHandler (new Dialogue.OptionSetResult (optionStrings, delegate (int selectedOption) { // we now know what number option was selected; push the corresponding node name // to the stack var destinationNode = state.currentOptions[selectedOption].Value; state.PushValue(destinationNode); // We no longer need the accumulated list of options; clear it so that it's // ready for the next one state.currentOptions.Clear(); // We can now also keep running executionState = ExecutionState.Running; })); break; default: // Whoa, no idea what bytecode this is. Stop the program // and throw an exception. executionState = ExecutionState.Stopped; throw new ArgumentOutOfRangeException (); } }