public void WriteJson(SimpleJson.Writer writer) { writer.WriteObjectStart(); writer.WriteProperty("callstack", callStack.WriteJson); writer.WriteProperty("outputStream", w => Json.WriteListRuntimeObjs(w, outputStream)); // choiceThreads: optional // Has to come BEFORE the choices themselves are written out // since the originalThreadIndex of each choice needs to be set bool hasChoiceThreads = false; foreach (Choice c in currentChoices) { c.originalThreadIndex = c.threadAtGeneration.threadIndex; if (callStack.ThreadWithIndex(c.originalThreadIndex) == null) { if (!hasChoiceThreads) { hasChoiceThreads = true; writer.WritePropertyStart("choiceThreads"); writer.WriteObjectStart(); } writer.WritePropertyStart(c.originalThreadIndex); c.threadAtGeneration.WriteJson(writer); writer.WritePropertyEnd(); } } if (hasChoiceThreads) { writer.WriteObjectEnd(); writer.WritePropertyEnd(); } writer.WriteProperty("currentChoices", w => { w.WriteArrayStart(); foreach (var c in currentChoices) { Json.WriteChoice(w, c); } w.WriteArrayEnd(); }); writer.WriteObjectEnd(); }
public void WriteJson(SimpleJson.Writer writer) { writer.WriteObjectStart(); foreach (var keyVal in _globalVariables) { var name = keyVal.Key; var val = keyVal.Value; if (dontSaveDefaultValues) { // Don't write out values that are the same as the default global values Runtime.Object defaultVal; if (_defaultGlobalVariables.TryGetValue(name, out defaultVal)) { if (RuntimeObjectsEqual(val, defaultVal)) { continue; } } } writer.WritePropertyStart(name); Json.WriteRuntimeObject(writer, val); writer.WritePropertyEnd(); } writer.WriteObjectEnd(); }
public static void WriteRuntimeContainer(SimpleJson.Writer writer, Container container, bool withoutName = false) { writer.WriteArrayStart(); foreach (var c in container.content) { WriteRuntimeObject(writer, c); } // Container is always an array [...] // But the final element is always either: // - a dictionary containing the named content, as well as possibly // the key "#" with the count flags // - null, if neither of the above var namedOnlyContent = container.namedOnlyContent; var countFlags = container.countFlags; var hasNameProperty = container.name != null && !withoutName; bool hasTerminator = namedOnlyContent != null || countFlags > 0 || hasNameProperty; if (hasTerminator) { writer.WriteObjectStart(); } if (namedOnlyContent != null) { foreach (var namedContent in namedOnlyContent) { var name = namedContent.Key; var namedContainer = namedContent.Value as Container; writer.WritePropertyStart(name); WriteRuntimeContainer(writer, namedContainer, withoutName: true); writer.WritePropertyEnd(); } } if (countFlags > 0) { writer.WriteProperty("#f", countFlags); } if (hasNameProperty) { writer.WriteProperty("#n", container.name); } if (hasTerminator) { writer.WriteObjectEnd(); } else { writer.WriteNull(); } writer.WriteArrayEnd(); }
static void WriteInkList(SimpleJson.Writer writer, ListValue listVal) { var rawList = listVal.value; writer.WriteObjectStart(); writer.WritePropertyStart("list"); writer.WriteObjectStart(); foreach (var itemAndValue in rawList) { var item = itemAndValue.Key; int itemVal = itemAndValue.Value; writer.WritePropertyNameStart(); writer.WritePropertyNameInner(item.originName ?? "?"); writer.WritePropertyNameInner("."); writer.WritePropertyNameInner(item.itemName); writer.WritePropertyNameEnd(); writer.Write(itemVal); writer.WritePropertyEnd(); } writer.WriteObjectEnd(); writer.WritePropertyEnd(); if (rawList.Count == 0 && rawList.originNames != null && rawList.originNames.Count > 0) { writer.WritePropertyStart("origins"); writer.WriteArrayStart(); foreach (var name in rawList.originNames) { writer.Write(name); } writer.WriteArrayEnd(); writer.WritePropertyEnd(); } writer.WriteObjectEnd(); }
public static void WriteDictionaryRuntimeObjs(SimpleJson.Writer writer, Dictionary <string, Runtime.Object> dictionary) { writer.WriteObjectStart(); foreach (var keyVal in dictionary) { writer.WritePropertyStart(keyVal.Key); WriteRuntimeObject(writer, keyVal.Value); writer.WritePropertyEnd(); } writer.WriteObjectEnd(); }
public void WriteJson(SimpleJson.Writer writer) { writer.WriteObjectStart(); // callstack writer.WritePropertyStart("callstack"); writer.WriteArrayStart(); foreach (CallStack.Element el in callstack) { writer.WriteObjectStart(); if (!el.currentPointer.isNull) { writer.WriteProperty("cPath", el.currentPointer.container.path.componentsString); writer.WriteProperty("idx", el.currentPointer.index); } writer.WriteProperty("exp", el.inExpressionEvaluation); writer.WriteProperty("type", (int)el.type); if (el.temporaryVariables.Count > 0) { writer.WritePropertyStart("temp"); Json.WriteDictionaryRuntimeObjs(writer, el.temporaryVariables); writer.WritePropertyEnd(); } writer.WriteObjectEnd(); } writer.WriteArrayEnd(); writer.WritePropertyEnd(); // threadIndex writer.WriteProperty("threadIndex", threadIndex); if (!previousPointer.isNull) { writer.WriteProperty("previousContentObject", previousPointer.Resolve().path.ToString()); } writer.WriteObjectEnd(); }
void WriteJson(SimpleJson.Writer writer) { writer.WriteObjectStart(); // Flows writer.WritePropertyStart("flows"); writer.WriteObjectStart(); // Multi-flow if (_namedFlows != null) { foreach (var namedFlow in _namedFlows) { writer.WriteProperty(namedFlow.Key, namedFlow.Value.WriteJson); } } // Single flow else { writer.WriteProperty(_currentFlow.name, _currentFlow.WriteJson); } writer.WriteObjectEnd(); writer.WritePropertyEnd(); // end of flows writer.WriteProperty("currentFlowName", _currentFlow.name); writer.WriteProperty("variablesState", variablesState.WriteJson); writer.WriteProperty("evalStack", w => Json.WriteListRuntimeObjs(w, evaluationStack)); if (!divertedPointer.isNull) { writer.WriteProperty("currentDivertTarget", divertedPointer.path.componentsString); } writer.WriteProperty("visitCounts", w => Json.WriteIntDictionary(w, _visitCounts)); writer.WriteProperty("turnIndices", w => Json.WriteIntDictionary(w, _turnIndices)); writer.WriteProperty("turnIdx", currentTurnIndex); writer.WriteProperty("storySeed", storySeed); writer.WriteProperty("previousRandom", previousRandom); writer.WriteProperty("inkSaveVersion", kInkSaveStateVersion); // Not using this right now, but could do in future. writer.WriteProperty("inkFormatVersion", Story.inkVersionCurrent); writer.WriteObjectEnd(); }
void PrintAllMessages() { // { "issues": ["ERROR: blah", "WARNING: blah"] } if (opts.jsonOutput) { var writer = new Runtime.SimpleJson.Writer(); writer.WriteObjectStart(); writer.WritePropertyStart("issues"); writer.WriteArrayStart(); foreach (string msg in _authorMessages) { writer.Write(msg); } foreach (string msg in _warnings) { writer.Write(msg); } foreach (string msg in _errors) { writer.Write(msg); } writer.WriteArrayEnd(); writer.WritePropertyEnd(); writer.WriteObjectEnd(); Console.Write(writer.ToString()); } // Human consumption else { PrintIssues(_authorMessages, ConsoleColor.Green); PrintIssues(_warnings, ConsoleColor.Blue); PrintIssues(_errors, ConsoleColor.Red); } _authorMessages.Clear(); _warnings.Clear(); _errors.Clear(); }
void WriteJson(SimpleJson.Writer writer) { writer.WriteObjectStart(); bool hasChoiceThreads = false; foreach (Choice c in _currentChoices) { c.originalThreadIndex = c.threadAtGeneration.threadIndex; if (callStack.ThreadWithIndex(c.originalThreadIndex) == null) { if (!hasChoiceThreads) { hasChoiceThreads = true; writer.WritePropertyStart("choiceThreads"); writer.WriteObjectStart(); } writer.WritePropertyStart(c.originalThreadIndex); c.threadAtGeneration.WriteJson(writer); writer.WritePropertyEnd(); } } if (hasChoiceThreads) { writer.WriteObjectEnd(); writer.WritePropertyEnd(); } writer.WriteProperty("callstackThreads", callStack.WriteJson); writer.WriteProperty("variablesState", variablesState.WriteJson); writer.WriteProperty("evalStack", w => Json.WriteListRuntimeObjs(w, evaluationStack)); writer.WriteProperty("outputStream", w => Json.WriteListRuntimeObjs(w, _outputStream)); writer.WriteProperty("currentChoices", w => { w.WriteArrayStart(); foreach (var c in _currentChoices) { Json.WriteChoice(w, c); } w.WriteArrayEnd(); }); if (!divertedPointer.isNull) { writer.WriteProperty("currentDivertTarget", divertedPointer.path.componentsString); } writer.WriteProperty("visitCounts", w => Json.WriteIntDictionary(w, _visitCounts)); writer.WriteProperty("turnIndices", w => Json.WriteIntDictionary(w, _turnIndices)); writer.WriteProperty("turnIdx", currentTurnIndex); writer.WriteProperty("storySeed", storySeed); writer.WriteProperty("previousRandom", previousRandom); writer.WriteProperty("inkSaveVersion", kInkSaveStateVersion); // Not using this right now, but could do in future. writer.WriteProperty("inkFormatVersion", Story.inkVersionCurrent); writer.WriteObjectEnd(); }
public void Begin() { EvaluateStory(); var rand = new Random(); while (story.currentChoices.Count > 0 || this.keepOpenAfterStoryFinish) { var choices = story.currentChoices; var choiceIdx = 0; bool choiceIsValid = false; string userDivertedPath = null; // autoPlay: Pick random choice if (autoPlay) { choiceIdx = rand.Next() % choices.Count; } // Normal: Ask user for choice number else { if (!_jsonOutput) { Console.ForegroundColor = ConsoleColor.Blue; // Add extra newline to ensure that the choice is // on a separate line. Console.WriteLine(); int i = 1; foreach (Choice choice in choices) { Console.WriteLine("{0}: {1}", i, choice.text); i++; } } else { var writer = new Runtime.SimpleJson.Writer(); writer.WriteObjectStart(); writer.WritePropertyStart("choices"); writer.WriteArrayStart(); foreach (var choice in choices) { writer.Write(choice.text); } writer.WriteArrayEnd(); writer.WritePropertyEnd(); writer.WriteObjectEnd(); Console.WriteLine(writer.ToString()); } do { if (!_jsonOutput) { // Prompt Console.Write("?> "); } else { // Johnny Five, he's alive! Console.Write("{\"needInput\": true}"); } string userInput = Console.ReadLine(); // If we have null user input, it means that we're // "at the end of the stream", or in other words, the input // stream has closed, so there's nothing more we can do. // We return immediately, since otherwise we get into a busy // loop waiting for user input. if (userInput == null) { if (_jsonOutput) { Console.WriteLine("{\"close\": true}"); } else { Console.WriteLine("<User input stream closed.>"); } return; } var result = _compiler.ReadCommandLineInput(userInput); if (result.output != null) { if (_jsonOutput) { var writer = new Runtime.SimpleJson.Writer(); writer.WriteObjectStart(); writer.WriteProperty("cmdOutput", result.output); writer.WriteObjectEnd(); Console.WriteLine(writer.ToString()); } else { Console.WriteLine(result.output); } } if (result.requestsExit) { return; } if (result.divertedPath != null) { userDivertedPath = result.divertedPath; } if (result.choiceIdx >= 0) { if (result.choiceIdx >= choices.Count) { if (!_jsonOutput) // fail silently in json mode { Console.WriteLine("Choice out of range"); } } else { choiceIdx = result.choiceIdx; choiceIsValid = true; } } } while(!choiceIsValid && userDivertedPath == null); } Console.ResetColor(); if (choiceIsValid) { story.ChooseChoiceIndex(choiceIdx); } else if (userDivertedPath != null) { story.ChoosePathString(userDivertedPath); userDivertedPath = null; } EvaluateStory(); } }
void EvaluateStory() { while (story.canContinue) { story.Continue(); _compiler.RetrieveDebugSourceForLatestContent(); if (_jsonOutput) { var writer = new Runtime.SimpleJson.Writer(); writer.WriteObjectStart(); writer.WriteProperty("text", story.currentText); writer.WriteObjectEnd(); Console.WriteLine(writer.ToString()); } else { Console.Write(story.currentText); } var tags = story.currentTags; if (tags.Count > 0) { if (_jsonOutput) { var writer = new Runtime.SimpleJson.Writer(); writer.WriteObjectStart(); writer.WritePropertyStart("tags"); writer.WriteArrayStart(); foreach (var tag in tags) { writer.Write(tag); } writer.WriteArrayEnd(); writer.WritePropertyEnd(); writer.WriteObjectEnd(); Console.WriteLine(writer.ToString()); } else { Console.WriteLine("# tags: " + string.Join(", ", tags)); } } Runtime.SimpleJson.Writer issueWriter = null; if (_jsonOutput && (_errors.Count > 0 || _warnings.Count > 0)) { issueWriter = new Runtime.SimpleJson.Writer(); issueWriter.WriteObjectStart(); issueWriter.WritePropertyStart("issues"); issueWriter.WriteArrayStart(); if (_errors.Count > 0) { foreach (var errorMsg in _errors) { issueWriter.Write(errorMsg); } } if (_warnings.Count > 0) { foreach (var warningMsg in _warnings) { issueWriter.Write(warningMsg); } } issueWriter.WriteArrayEnd(); issueWriter.WritePropertyEnd(); issueWriter.WriteObjectEnd(); Console.WriteLine(issueWriter.ToString()); } if (_errors.Count > 0 && !_jsonOutput) { foreach (var errorMsg in _errors) { Console.WriteLine(errorMsg, ConsoleColor.Red); } } if (_warnings.Count > 0 && !_jsonOutput) { foreach (var warningMsg in _warnings) { Console.WriteLine(warningMsg, ConsoleColor.Blue); } } _errors.Clear(); _warnings.Clear(); } if (story.currentChoices.Count == 0 && keepOpenAfterStoryFinish) { if (_jsonOutput) { Console.WriteLine("{\"end\": true}"); } else { Console.WriteLine("--- End of story ---"); } } }
CommandLineTool(string[] args) { // Set console's output encoding to UTF-8 Console.OutputEncoding = System.Text.Encoding.UTF8; if (ProcessArguments(args) == false) { ExitWithUsageInstructions(); } if (opts.inputFile == null) { ExitWithUsageInstructions(); } string inputString = null; string workingDirectory = Directory.GetCurrentDirectory(); if (opts.outputFile == null) { opts.outputFile = Path.ChangeExtension(opts.inputFile, ".ink.json"); } if (!Path.IsPathRooted(opts.outputFile)) { opts.outputFile = Path.Combine(workingDirectory, opts.outputFile); } try { string fullFilename = opts.inputFile; if (!Path.IsPathRooted(fullFilename)) { fullFilename = Path.Combine(workingDirectory, fullFilename); } // Make the working directory the directory for the root ink file, // so that relative paths for INCLUDE files are correct. workingDirectory = Path.GetDirectoryName(fullFilename); Directory.SetCurrentDirectory(workingDirectory); // Now make the input file relative to the working directory, // but just getting the file's actual name. opts.inputFile = Path.GetFileName(fullFilename); inputString = File.ReadAllText(opts.inputFile); } catch { Console.WriteLine("Could not open file '" + opts.inputFile + "'"); Environment.Exit(ExitCodeError); } var inputIsJson = opts.inputFile.EndsWith(".json", StringComparison.InvariantCultureIgnoreCase); if (inputIsJson && opts.stats) { Console.WriteLine("Cannot show stats for .json, only for .ink"); Environment.Exit(ExitCodeError); } Parsed.Story parsedStory = null; Runtime.Story story = null; Compiler compiler = null; // Loading a normal ink file (as opposed to an already compiled json file) if (!inputIsJson) { compiler = new Compiler(inputString, new Compiler.Options { sourceFilename = opts.inputFile, pluginDirectories = pluginDirectories, countAllVisits = opts.countAllVisits, errorHandler = OnError }); // Only want stats, don't need to code-gen if (opts.stats) { parsedStory = compiler.Parse(); // Print any errors PrintAllMessages(); // Generate stats, then print as JSON var stats = Ink.Stats.Generate(compiler.parsedStory); if (opts.jsonOutput) { var writer = new Runtime.SimpleJson.Writer(); writer.WriteObjectStart(); writer.WritePropertyStart("stats"); writer.WriteObjectStart(); writer.WriteProperty("words", stats.words); writer.WriteProperty("knots", stats.knots); writer.WriteProperty("stitches", stats.stitches); writer.WriteProperty("functions", stats.functions); writer.WriteProperty("choices", stats.choices); writer.WriteProperty("gathers", stats.gathers); writer.WriteProperty("diverts", stats.diverts); writer.WriteObjectEnd(); writer.WritePropertyEnd(); writer.WriteObjectEnd(); Console.WriteLine(writer.ToString()); } else { Console.WriteLine("Words: " + stats.words); Console.WriteLine("Knots: " + stats.knots); Console.WriteLine("Stitches: " + stats.stitches); Console.WriteLine("Functions: " + stats.functions); Console.WriteLine("Choices: " + stats.choices); Console.WriteLine("Gathers: " + stats.gathers); Console.WriteLine("Diverts: " + stats.diverts); } return; } // Full compile else { story = compiler.Compile(); } } // Opening up a compiled json file for playing else { story = new Runtime.Story(inputString); // No purpose for loading an already compiled file other than to play it opts.playMode = true; } var compileSuccess = !(story == null || _errors.Count > 0); if (opts.jsonOutput) { if (compileSuccess) { Console.WriteLine("{\"compile-success\": true}"); } else { Console.WriteLine("{\"compile-success\": false}"); } } PrintAllMessages(); if (!compileSuccess) { Environment.Exit(ExitCodeError); } // Play mode if (opts.playMode) { _playing = true; // Always allow ink external fallbacks story.allowExternalFunctionFallbacks = true; var player = new CommandLinePlayer(story, false, compiler, opts.keepOpenAfterStoryFinish, opts.jsonOutput); //Capture a CTRL+C key combo so we can restore the console's foreground color back to normal when exiting Console.CancelKeyPress += OnExit; try { player.Begin(); } catch (Runtime.StoryException e) { if (e.Message.Contains("Missing function binding")) { OnError(e.Message, ErrorType.Error); PrintAllMessages(); } else { throw e; } } catch (System.Exception e) { string storyPath = "<END>"; var path = story.state.currentPathString; if (path != null) { storyPath = path.ToString(); } throw new System.Exception(e.Message + " (Internal story path: " + storyPath + ")", e); } } // Compile mode else { var jsonStr = story.ToJson(); try { File.WriteAllText(opts.outputFile, jsonStr, new System.Text.UTF8Encoding(false)); if (opts.jsonOutput) { Console.WriteLine("{\"export-complete\": true}"); } } catch { Console.WriteLine("Could not write to output file '" + opts.outputFile + "'"); Environment.Exit(ExitCodeError); } } }
public string RequestExternalFunctionResult(string functionName, object[] args) { var arguments = ""; for (var i = 0; i < args.Length; i++) { if (args[i] is string) { args[i] = "\"" + args[i] + "\""; } arguments += args[i]; if (i + 1 < args.Length) { arguments += ", "; } } if (!_jsonOutput) { Console.ForegroundColor = ConsoleColor.Blue; Console.Write("Enter result for " + functionName + "(" + arguments + ") > "); Console.ResetColor(); } else { var writer = new Runtime.SimpleJson.Writer(); writer.WriteObjectStart(); writer.WriteProperty("external-function", functionName); if (arguments != "") { writer.WritePropertyStart("arguments"); writer.WriteArrayStart(); writer.Write(arguments); writer.WriteArrayEnd(); writer.WritePropertyEnd(); } writer.WriteObjectEnd(); Console.WriteLine(writer.ToString()); } string userInput = Console.ReadLine(); // If we have null user input, it means that we're // "at the end of the stream", or in other words, the input // stream has closed, so there's nothing more we can do. // We return immediately, since otherwise we get into a busy // loop waiting for user input. if (userInput == null) { if (_jsonOutput) { Console.WriteLine("{\"close\": true}"); } else { Console.WriteLine("<User input stream closed.>"); } } return(userInput); }