public void PostExport(Parsed.Story parsedStory, Runtime.Story runtimeStory) { foreach (var plugin in _plugins) { plugin.PostExport(parsedStory, runtimeStory); } }
public void PostExport(Parsed.Fiction parsedFiction, Runtime.Story runtimeStory) { foreach (var plugin in _plugins) { plugin.PostExport(parsedFiction, runtimeStory); } }
private Runtime.Story CreateStory(Parsed.Fiction parsedFiction) { Runtime.Story runtimeStory = null; if (_pluginManager != null) { _pluginManager.PostParse(parsedFiction); } if (parsedFiction != null && !_hadParseError) { parsedFiction.countAllVisits = _options.countAllVisits; runtimeStory = parsedFiction.ExportRuntime(); if (_pluginManager != null) { _pluginManager.PostExport(parsedFiction, runtimeStory); } } else { runtimeStory = null; } return(runtimeStory); }
public Runtime.Story PostExport(Parsed.Story parsedStory, Runtime.Story runtimeStory) { object[] args = new object[] { parsedStory, runtimeStory }; foreach (var plugin in _plugins) { typeof(IPlugin).InvokeMember("PostExport", BindingFlags.InvokeMethod, null, plugin, args); } return((Runtime.Story)args[1]); }
CommandLineTool(string[] args) { // Set console's output encoding to UTF-8 Console.OutputEncoding = System.Text.Encoding.UTF8; if (ProcessArguments(args) == false) { ExitWithUsageInstructions(); } if (opts.testMode) { opts.inputFile = "test.ink"; } 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); } if (opts.stressTest) { StressTestContentGenerator stressTestContent = null; TimeOperation("Generating test content", () => { stressTestContent = new StressTestContentGenerator(100); }); Console.WriteLine("Generated ~{0}k of test ink", stressTestContent.sizeInKiloChars); inputString = stressTestContent.content; } else { 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); } } InkParser parser = null; Parsed.Story parsedStory = null; Runtime.Story story = null; errors = new List <string> (); warnings = new List <string> (); authorMessages = new List <string> (); var pluginManager = new PluginManager(pluginNames); var inputIsJson = opts.inputFile.EndsWith(".json"); // Loading a normal ink file (as opposed to an already compiled json file) if (!inputIsJson) { TimeOperation("Creating parser", () => { parser = new InkParser(inputString, opts.inputFile, OnError); }); TimeOperation("Parsing", () => { parsedStory = parser.Parse(); }); TimeOperation("PostParsePlugins", () => { pluginManager.PostParse(parsedStory); }); if (parsedStory != null && errors.Count == 0) { if (opts.countAllVisits) { parsedStory.countAllVisits = true; } TimeOperation("Exporting runtime", () => { story = parsedStory.ExportRuntime(OnError); }); TimeOperation("PostParsePlugins", () => { pluginManager.PostExport(parsedStory, story); }); } } // 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; } PrintAllMessages(); if (story == null || errors.Count > 0) { Environment.Exit(ExitCodeError); } // JSON round trip testing //if (opts.testMode) { // var jsonStr = story.ToJsonString (); // Console.WriteLine (jsonStr); // Console.WriteLine ("---------------------------------------------------"); // var reloadedStory = new Runtime.Story (jsonStr); // var newJsonStr = reloadedStory.ToJsonString (); // Console.WriteLine (newJsonStr); // story = reloadedStory; //} // Play mode // Test mode may use "-tp" in commmand line args to specify that // the test script is also played if (opts.playMode) { _playing = true; // Always allow ink external fallbacks story.allowExternalFunctionFallbacks = true; var player = new CommandLinePlayer(story, false, parsedStory, opts.keepOpenAfterStoryFinish); //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.currentPath; if (path != null) { storyPath = path.ToString(); } throw new System.Exception(e.Message + " (Internal story path: " + storyPath + ")", e); } } // Compile mode else { var jsonStr = story.ToJsonString(); try { File.WriteAllText(opts.outputFile, jsonStr, System.Text.Encoding.UTF8); } catch { Console.WriteLine("Could not write to output file '" + opts.outputFile + "'"); Environment.Exit(ExitCodeError); } } }
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, pluginNames = pluginNames, countAllVisits = opts.countAllVisits, errorHandler = OnError, fileHandler = this }); // 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); var writer = new Runtime.SimpleJson.Writer(); 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(); Console.WriteLine(writer.ToString()); 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; } PrintAllMessages(); if (story == null || _errors.Count > 0) { 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); //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, System.Text.Encoding.UTF8); } catch { Console.WriteLine("Could not write to output file '" + opts.outputFile + "'"); Environment.Exit(ExitCodeError); } } }
public Runtime.Story ExportRuntime(ErrorHandler errorHandler = null) { _errorHandler = errorHandler; // Find all constants before main export begins, so that VariableReferences know // whether to generate a runtime variable reference or the literal value constants = new Dictionary <string, Expression> (); foreach (var constDecl in FindAll <ConstantDeclaration> ()) { // Check for duplicate definitions Parsed.Expression existingDefinition = null; if (constants.TryGetValue(constDecl.constantName, out existingDefinition)) { if (!existingDefinition.Equals(constDecl.expression)) { var errorMsg = string.Format("CONST '{0}' has been redefined with a different value. Multiple definitions of the same CONST are valid so long as they contain the same value. Initial definition was on {1}.", constDecl.constantName, existingDefinition.debugMetadata); Error(errorMsg, constDecl, isWarning: false); } } constants [constDecl.constantName] = constDecl.expression; } // List definitions are treated like constants too - they should be usable // from other variable declarations. _listDefs = new Dictionary <string, ListDefinition> (); foreach (var listDef in FindAll <ListDefinition> ()) { _listDefs [listDef.identifier?.name] = listDef; } externals = new Dictionary <string, ExternalDeclaration> (); // Resolution of weave point names has to come first, before any runtime code generation // since names have to be ready before diverts start getting created. // (It used to be done in the constructor for a weave, but didn't allow us to generate // errors when name resolution failed.) ResolveWeavePointNaming(); // Get default implementation of runtimeObject, which calls ContainerBase's generation method var rootContainer = runtimeObject as Runtime.Container; // Export initialisation of global variables // TODO: We *could* add this as a declarative block to the story itself... var variableInitialisation = new Runtime.Container(); variableInitialisation.AddContent(Runtime.ControlCommand.EvalStart()); // Global variables are those that are local to the story and marked as global var runtimeLists = new List <Runtime.ListDefinition> (); foreach (var nameDeclPair in variableDeclarations) { var varName = nameDeclPair.Key; var varDecl = nameDeclPair.Value; if (varDecl.isGlobalDeclaration) { if (varDecl.listDefinition != null) { _listDefs[varName] = varDecl.listDefinition; variableInitialisation.AddContent(varDecl.listDefinition.runtimeObject); runtimeLists.Add(varDecl.listDefinition.runtimeListDefinition); } else { varDecl.expression.GenerateIntoContainer(variableInitialisation); } var runtimeVarAss = new Runtime.VariableAssignment(varName, isNewDeclaration: true); runtimeVarAss.isGlobal = true; variableInitialisation.AddContent(runtimeVarAss); } } variableInitialisation.AddContent(Runtime.ControlCommand.EvalEnd()); variableInitialisation.AddContent(Runtime.ControlCommand.End()); if (variableDeclarations.Count > 0) { variableInitialisation.name = "global decl"; rootContainer.AddToNamedContentOnly(variableInitialisation); } // Signal that it's safe to exit without error, even if there are no choices generated // (this only happens at the end of top level content that isn't in any particular knot) rootContainer.AddContent(Runtime.ControlCommand.Done()); // Replace runtimeObject with Story object instead of the Runtime.Container generated by Parsed.ContainerBase var runtimeStory = new Runtime.Story(rootContainer, runtimeLists); runtimeObject = runtimeStory; if (_hadError) { return(null); } // Optimisation step - inline containers that can be FlattenContainersIn(rootContainer); // Now that the story has been fulled parsed into a hierarchy, // and the derived runtime hierarchy has been built, we can // resolve referenced symbols such as variables and paths. // e.g. for paths " -> knotName --> stitchName" into an INKPath (knotName.stitchName) // We don't make any assumptions that the INKPath follows the same // conventions as the script format, so we resolve to actual objects before // translating into an INKPath. (This also allows us to choose whether // we want the paths to be absolute) ResolveReferences(this); if (_hadError) { return(null); } runtimeStory.ResetState(); return(runtimeStory); }
CommandLineTool(string[] args) { // Set console's output encoding to UTF-8 Console.OutputEncoding = System.Text.Encoding.UTF8; if (ProcessArguments (args) == false) { ExitWithUsageInstructions (); } if (opts.testMode) { opts.inputFile = "test.ink"; } 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); if (opts.stressTest) { StressTestContentGenerator stressTestContent = null; TimeOperation ("Generating test content", () => { stressTestContent = new StressTestContentGenerator (100); }); Console.WriteLine ("Generated ~{0}k of test ink", stressTestContent.sizeInKiloChars); inputString = stressTestContent.content; } else { 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); } } InkParser parser = null; Parsed.Story parsedStory = null; Runtime.Story story = null; errors = new List<string> (); warnings = new List<string> (); authorMessages = new List<string> (); var pluginManager = new PluginManager (pluginNames); var inputIsJson = opts.inputFile.EndsWith (".json"); // Loading a normal ink file (as opposed to an already compiled json file) if (!inputIsJson) { TimeOperation ("Creating parser", () => { parser = new InkParser (inputString, opts.inputFile, OnError); }); TimeOperation ("Parsing", () => { parsedStory = parser.Parse (); }); TimeOperation ("PostParsePlugins", () => { pluginManager.PostParse(parsedStory); }); if (parsedStory != null) { if (opts.countAllVisits) { parsedStory.countAllVisits = true; } TimeOperation ("Exporting runtime", () => { story = parsedStory.ExportRuntime (OnError); }); TimeOperation ("PostParsePlugins", () => { pluginManager.PostExport(parsedStory, story); }); } } // 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; } PrintAllMessages (); if (story == null || errors.Count > 0) { Environment.Exit (ExitCodeError); } // JSON round trip testing // if (opts.testMode) { // var jsonStr = story.ToJsonString (indented:true); // Console.WriteLine (jsonStr); // // Console.WriteLine ("---------------------------------------------------"); // // var reloadedStory = new Runtime.Story (jsonStr); // var newJsonStr = reloadedStory.ToJsonString (indented: true); // Console.WriteLine (newJsonStr); // // story = reloadedStory; // } // Play mode // Test mode may use "-tp" in commmand line args to specify that // the test script is also played if (opts.playMode) { _playing = true; // Always allow ink external fallbacks story.allowExternalFunctionFallbacks = true; var player = new CommandLinePlayer (story, false, parsedStory, opts.keepOpenAfterStoryFinish); //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; } } } // Compile mode else { var jsonStr = story.ToJsonString (); try { File.WriteAllText (opts.outputFile, jsonStr, System.Text.Encoding.UTF8); } catch { Console.WriteLine ("Could not write to output file '" + opts.outputFile+"'"); Environment.Exit (ExitCodeError); } } }
CommandLineTool(string[] args) { if (ProcessArguments(args) == false) { ExitWithUsageInstructions(); } if (opts.testMode) { opts.inputFile = "test.ink"; } if (opts.inputFile == null) { ExitWithUsageInstructions(); } if (opts.outputFile == null) { opts.outputFile = Path.ChangeExtension(opts.inputFile, ".ink.json"); } string inputString = null; string rootDirectory = System.IO.Directory.GetCurrentDirectory(); if (opts.workingDirectory != null) { rootDirectory = Path.GetFullPath(opts.workingDirectory); } if (opts.stressTest) { StressTestContentGenerator stressTestContent = null; TimeOperation("Generating test content", () => { stressTestContent = new StressTestContentGenerator(100); }); Console.WriteLine("Generated ~{0}k of test ink", stressTestContent.sizeInKiloChars); inputString = stressTestContent.content; } else { try { string fullFilename = opts.inputFile; if (!Path.IsPathRooted(fullFilename)) { fullFilename = Path.Combine(rootDirectory, fullFilename); } inputString = File.ReadAllText(fullFilename); } catch { Console.WriteLine("Could not open file '" + opts.inputFile + "'"); Environment.Exit(ExitCodeError); } } InkParser parser = null; Parsed.Story parsedStory = null; Runtime.Story story = null; errors = new List <string> (); warnings = new List <string> (); authorMessages = new List <string> (); var pluginManager = new PluginManager(pluginNames); var inputIsJson = opts.inputFile.EndsWith(".json"); // Loading a normal ink file (as opposed to an already compiled json file) if (!inputIsJson) { TimeOperation("Creating parser", () => { parser = new InkParser(inputString, opts.inputFile, rootDirectory, OnError); }); TimeOperation("Parsing", () => { parsedStory = parser.Parse(); }); TimeOperation("PostParsePlugins", () => { pluginManager.PostParse(parsedStory); }); if (parsedStory != null) { if (opts.countAllVisits) { parsedStory.countAllVisits = true; } TimeOperation("Exporting runtime", () => { story = parsedStory.ExportRuntime(OnError); }); TimeOperation("PostParsePlugins", () => { pluginManager.PostExport(parsedStory, story); }); } } // Opening up a compiled json file for playing else { story = Runtime.Story.CreateWithJson(inputString); // No purpose for loading an already compiled file other than to play it opts.playMode = true; } PrintMessages(authorMessages, ConsoleColor.Green); PrintMessages(warnings, ConsoleColor.Blue); PrintMessages(errors, ConsoleColor.Red); if (story == null) { Environment.Exit(ExitCodeError); } // JSON round trip testing // if (opts.testMode) { // var jsonStr = story.ToJsonString (indented:true); // Console.WriteLine (jsonStr); // // Console.WriteLine ("---------------------------------------------------"); // // var reloadedStory = Runtime.Story.CreateWithJson (jsonStr); // var newJsonStr = reloadedStory.ToJsonString (indented: true); // Console.WriteLine (newJsonStr); // // story = reloadedStory; // } // Play mode // Test mode may use "-tp" in commmand line args to specify that // the test script is also played if (opts.playMode) { // Always allow ink external fallbacks story.allowExternalFunctionFallbacks = true; var player = new CommandLinePlayer(story, false, parsedStory); player.Begin(); } // Compile mode else { var jsonStr = story.ToJsonString(opts.indentedJson); try { File.WriteAllText(opts.outputFile, jsonStr, System.Text.Encoding.UTF8); } catch { Console.WriteLine("Could write to output file '" + opts.outputFile + "'"); Environment.Exit(ExitCodeError); } } }
public Runtime.Story ExportRuntime(ErrorHandler errorHandler = null) { _errorHandler = errorHandler; // Find all constants before main export begins, so that VariableReferences know // whether to generate a runtime variable reference or the literal value constants = new Dictionary<string, Expression> (); foreach (var constDecl in FindAll<ConstantDeclaration> ()) { // Check for duplicate definitions Parsed.Expression existingDefinition = null; if (constants.TryGetValue (constDecl.constantName, out existingDefinition)) { if (!existingDefinition.Equals (constDecl.expression)) { var errorMsg = string.Format ("CONST '{0}' has been redefined with a different value. Multiple definitions of the same CONST are valid so long as they contain the same value. Initial definition was on {1}.", constDecl.constantName, existingDefinition.debugMetadata); Error (errorMsg, constDecl, isWarning:false); } } constants [constDecl.constantName] = constDecl.expression; } externals = new Dictionary<string, ExternalDeclaration> (); // Get default implementation of runtimeObject, which calls ContainerBase's generation method var rootContainer = runtimeObject as Runtime.Container; // Export initialisation of global variables // TODO: We *could* add this as a declarative block to the story itself... var variableInitialisation = new Runtime.Container (); variableInitialisation.AddContent (Runtime.ControlCommand.EvalStart ()); // Global variables are those that are local to the story and marked as global foreach (var nameDeclPair in variableDeclarations) { var varName = nameDeclPair.Key; var varDecl = nameDeclPair.Value; if (varDecl.isGlobalDeclaration) { varDecl.expression.GenerateIntoContainer (variableInitialisation); var runtimeVarAss = new Runtime.VariableAssignment (varName, isNewDeclaration:true); runtimeVarAss.isGlobal = true; variableInitialisation.AddContent (runtimeVarAss); } } variableInitialisation.AddContent (Runtime.ControlCommand.EvalEnd ()); variableInitialisation.AddContent (Runtime.ControlCommand.End ()); if (variableDeclarations.Count > 0) { variableInitialisation.name = "global decl"; rootContainer.AddToNamedContentOnly (variableInitialisation); } // Signal that it's safe to exit without error, even if there are no choices generated // (this only happens at the end of top level content that isn't in any particular knot) rootContainer.AddContent (Runtime.ControlCommand.Done ()); // Replace runtimeObject with Story object instead of the Runtime.Container generated by Parsed.ContainerBase var runtimeStory = new Runtime.Story (rootContainer); runtimeObject = runtimeStory; if (hadError) return null; // Optimisation step - inline containers that can be FlattenContainersIn (rootContainer); // Now that the story has been fulled parsed into a hierarchy, // and the derived runtime hierarchy has been built, we can // resolve referenced symbols such as variables and paths. // e.g. for paths " -> knotName --> stitchName" into an INKPath (knotName.stitchName) // We don't make any assumptions that the INKPath follows the same // conventions as the script format, so we resolve to actual objects before // translating into an INKPath. (This also allows us to choose whether // we want the paths to be absolute) ResolveReferences (this); if (hadError) return null; runtimeStory.ResetState (); return runtimeStory; }
public Runtime.Story ExportRuntime(ErrorHandler errorHandler = null) { _errorHandler = errorHandler; // Find all constants before main export begins, so that VariableReferences know // whether to generate a runtime variable reference or the literal value constants = new Dictionary <string, Expression> (); foreach (var constDecl in FindAll <ConstantDeclaration> ()) { constants [constDecl.constantName] = constDecl.expression; } externals = new Dictionary <string, ExternalDeclaration> (); // Get default implementation of runtimeObject, which calls ContainerBase's generation method var rootContainer = runtimeObject as Runtime.Container; // Export initialisation of global variables // TODO: We *could* add this as a declarative block to the story itself... var variableInitialisation = new Runtime.Container(); variableInitialisation.AddContent(Runtime.ControlCommand.EvalStart()); // Global variables are those that are local to the story and marked as global foreach (var nameDeclPair in variableDeclarations) { var varName = nameDeclPair.Key; var varDecl = nameDeclPair.Value; if (varDecl.isGlobalDeclaration) { varDecl.expression.GenerateIntoContainer(variableInitialisation); var runtimeVarAss = new Runtime.VariableAssignment(varName, isNewDeclaration: true); runtimeVarAss.isGlobal = true; variableInitialisation.AddContent(runtimeVarAss); } } variableInitialisation.AddContent(Runtime.ControlCommand.EvalEnd()); variableInitialisation.AddContent(Runtime.ControlCommand.End()); if (variableDeclarations.Count > 0) { variableInitialisation.name = "global decl"; rootContainer.AddToNamedContentOnly(variableInitialisation); } // Signal that it's safe to exit without error, even if there are no choices generated // (this only happens at the end of top level content that isn't in any particular knot) rootContainer.AddContent(Runtime.ControlCommand.Done()); // Replace runtimeObject with Story object instead of the Runtime.Container generated by Parsed.ContainerBase var runtimeStory = new Runtime.Story(rootContainer); runtimeObject = runtimeStory; if (hadError) { return(null); } // Now that the story has been fulled parsed into a hierarchy, // and the derived runtime hierarchy has been built, we can // resolve referenced symbols such as variables and paths. // e.g. for paths " -> knotName --> stitchName" into an INKPath (knotName.stitchName) // We don't make any assumptions that the INKPath follows the same // conventions as the script format, so we resolve to actual objects before // translating into an INKPath. (This also allows us to choose whether // we want the paths to be absolute) ResolveReferences(this); if (hadError) { return(null); } runtimeStory.ResetState(); return(runtimeStory); }