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 ((int)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 ((int)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 (new Value ()); 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.GetNumber (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; // TODO: Handle storing values other than numbers dialogue.continuity.SetNumber (destinationVariableName, topValue.AsNumber); break; case ByteCode.Stop: // Immediately stop execution, and report that fact. executionState = ExecutionState.Stopped; nodeCompleteHandler (new Dialogue.NodeCompleteResult (null)); 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<int, string> ((int)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 == -1) { var destinationNode = state.currentOptions[0].Value; state.PushValue(destinationNode); state.currentOptions.Clear(); break; } if (dialogue.continuity.GetNumber(SpecialVariables.ShuffleOptions) != 0.0f) { // Shuffle the dialog options if needed var r = new Random(); var n = state.currentOptions.Count; for (int opt1 = 0; opt1 < n; opt1++) { int opt2 = opt1 + (int)(r.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 (); } }
public string ToString(Program p, Library l) { // Labels are easy: just dump out the name if (operation == ByteCode.Label) { return(operandA + ":"); } // Convert the operands to strings var opAString = operandA != null?operandA.ToString() : ""; var opBString = operandB != null?operandB.ToString() : ""; // Generate a comment, if the instruction warrants it string comment = ""; // Stack manipulation comments var pops = 0; var pushes = 0; switch (operation) { // These operations all push a single value to the stack case ByteCode.PushBool: case ByteCode.PushNull: case ByteCode.PushNumber: case ByteCode.PushString: case ByteCode.PushVariable: case ByteCode.ShowOptions: pushes = 1; break; // Functions pop 0 or more values, and pop 0 or 1 case ByteCode.CallFunc: var function = l.GetFunction((string)operandA); pops = function.paramCount; if (function.returnsValue) { pushes = 1; } break; // Pop always pops a single value case ByteCode.Pop: pops = 1; break; // Switching to a different node will always clear the stack case ByteCode.RunNode: comment += "Clears stack"; break; } // If we had any pushes or pops, report them if (pops > 0 && pushes > 0) { comment += string.Format("Pops {0}, Pushes {1}", pops, pushes); } else if (pops > 0) { comment += string.Format("Pops {0}", pops); } else if (pushes > 0) { comment += string.Format("Pushes {0}", pushes); } // String lookup comments switch (operation) { case ByteCode.PushString: case ByteCode.RunLine: case ByteCode.AddOption: // Add the string for this option, if it has one if ((string)operandA != null) { var text = p.GetString((string)operandA); comment += string.Format("\"{0}\"", text); } break; } if (comment != "") { comment = "; " + comment; } return(string.Format("{0,-15} {1,-10} {2,-10} {3, -10}", operation.ToString(), opAString, opBString, comment)); }
public string ToString(Program p, Library l) { // Labels are easy: just dump out the name if (operation == ByteCode.Label) { return operandA + ":"; } // Convert the operands to strings var opAString = operandA != null ? operandA.ToString () : ""; var opBString = operandB != null ? operandB.ToString () : ""; // Generate a comment, if the instruction warrants it string comment = ""; // Stack manipulation comments var pops = 0; var pushes = 0; switch (operation) { // These operations all push a single value to the stack case ByteCode.PushBool: case ByteCode.PushNull: case ByteCode.PushNumber: case ByteCode.PushString: case ByteCode.PushVariable: case ByteCode.ShowOptions: pushes = 1; break; // Functions pop 0 or more values, and pop 0 or 1 case ByteCode.CallFunc: var function = l.GetFunction ((string)operandA); pops = function.paramCount; if (function.returnsValue) pushes = 1; break; // Pop always pops a single value case ByteCode.Pop: pops = 1; break; // Switching to a different node will always clear the stack case ByteCode.RunNode: comment += "Clears stack"; break; } // If we had any pushes or pops, report them if (pops > 0 && pushes > 0) comment += string.Format ("Pops {0}, Pushes {1}", pops, pushes); else if (pops > 0) comment += string.Format ("Pops {0}", pops); else if (pushes > 0) comment += string.Format ("Pushes {0}", pushes); // String lookup comments switch (operation) { case ByteCode.PushString: case ByteCode.RunLine: case ByteCode.AddOption: // Add the string for this option, if it has one if ((string)operandA != null) { var text = p.GetString((string)operandA); comment += string.Format ("\"{0}\"", text); } break; } if (comment != "") { comment = "; " + comment; } return string.Format ("{0,-15} {1,-10} {2,-10} {3, -10}", operation.ToString (), opAString, opBString, comment); }
internal void RunInstruction(Instruction i) { switch (i.operation) { case ByteCode.Label: /// - Label /** No-op, used as a destination for JumpTo and Jump. */ break; case ByteCode.JumpTo: /// - JumpTo /** Jumps to a named label */ state.programCounter = FindInstructionPointForLabel((string)i.operandA); break; case ByteCode.RunLine: /// - 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); if (lineText == null) { dialogue.LogErrorMessage("No loaded string table includes line " + i.operandA); break; } lineHandler(new Dialogue.LineResult(lineText)); break; case ByteCode.RunCommand: /// - RunCommand /** Passes a string to the client as a custom command */ commandHandler( new Dialogue.CommandResult((string)i.operandA) ); break; case ByteCode.PushString: /// - 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: /// - PushNumber /** Pushes a number onto the stack. */ state.PushValue(Convert.ToSingle(i.operandA)); break; case ByteCode.PushBool: /// - PushBool /** Pushes a boolean value onto the stack. */ state.PushValue(Convert.ToBoolean(i.operandA)); break; case ByteCode.PushNull: /// - PushNull /** Pushes a null value onto the stack. */ state.PushValue(Value.NULL); break; case ByteCode.JumpIfFalse: /// - 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: /// - Jump /** Jumps to a label whose name is on the stack. */ var jumpDestination = state.PeekValue().AsString; state.programCounter = FindInstructionPointForLabel(jumpDestination); break; case ByteCode.Pop: /// - Pop /** Pops a value from the stack. */ state.PopValue(); break; case ByteCode.CallFunc: /// - 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; } Value result; if (paramCount == 0) { result = function.Invoke(); } else { // 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 result = function.InvokeWithArray(parameters); } // If the function returns a value, push it if (function.returnsValue) { state.PushValue(result); } } break; case ByteCode.PushVariable: /// - 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: /// - 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: /// - Stop /** Immediately stop execution, and report that fact. */ nodeCompleteHandler(new Dialogue.NodeCompleteResult(null)); executionState = ExecutionState.Stopped; break; case ByteCode.RunNode: /// - RunNode /** Run a node */ 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: /// - 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: /// - ShowOptions /** If we have no options to show, immediately stop. */ if (state.currentOptions.Count == 0) { nodeCompleteHandler(new Dialogue.NodeCompleteResult(null)); executionState = ExecutionState.Stopped; 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).AsBool) { // 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; case ByteCode.Concat: /// = Concat /** Pop two items off the stack, concat them, and push the result onto the stack. */ { var second = state.PopValue(); var first = state.PopValue(); state.PushValue(first.AsString + second.AsString); } break; case ByteCode.RunLineFromStack: /// - RunLineFromStack /** Pop a string from the stack and pass to client as a line. */ lineHandler(new Dialogue.LineResult(state.PopValue().AsString)); break; default: /// - default /** Whoa, no idea what bytecode this is. Stop the program * and throw an exception. */ executionState = ExecutionState.Stopped; throw new ArgumentOutOfRangeException(); } }