InkParser(string str, string inkFilename = null, Ink.ErrorHandler externalErrorHandler = null, InkParser rootParser = null) : base(str) { _filename = inkFilename; RegisterExpressionOperators (); GenerateStatementLevelRules (); this.errorHandler = OnError; _externalErrorHandler = externalErrorHandler; if (rootParser == null) { _rootParser = this; _openFilenames = new HashSet<string> (); if (inkFilename != null) { var workingDir = Directory.GetCurrentDirectory(); var fullRootInkPath = Path.Combine (workingDir, inkFilename); _openFilenames.Add (fullRootInkPath); } } else { _rootParser = rootParser; } }
protected object IncludeStatement() { Whitespace (); if (ParseString ("INCLUDE") == null) return null; Whitespace (); var filename = (string) Expect(() => ParseUntilCharactersFromString ("\n\r"), "filename for include statement"); filename = filename.TrimEnd (' ', '\t'); // Working directory should already have been set up relative to the root ink file. var workingDirectory = Directory.GetCurrentDirectory (); var fullFilename = System.IO.Path.Combine (workingDirectory, filename); if (FilenameIsAlreadyOpen (fullFilename)) { Error ("Recursive INCLUDE detected: '" + fullFilename + "' is already open."); ParseUntilCharactersFromString("\r\n"); return new IncludedFile(null); } else { AddOpenFilename (fullFilename); } Parsed.Story includedStory = null; string includedString = null; try { includedString = File.ReadAllText(fullFilename); } catch { Error ("Failed to load: '"+filename+"' (relative to directory: "+workingDirectory+")"); } if (includedString != null ) { InkParser parser = new InkParser(includedString, filename, _externalErrorHandler, _rootParser); includedStory = parser.Parse(); if( includedStory == null ) { // This error should never happen: if the includedStory isn't // returned, then it should've been due to some error that // has already been reported, so this is a last resort. if( !parser.hadError ) { Error ("Failed to parse included file '" + filename); } } } RemoveOpenFilename (fullFilename); // Return valid IncludedFile object even when story failed to parse and we have a null story: // we don't want to attempt to re-parse the include line as something else return new IncludedFile (includedStory); }
public void TestEmptyChoice() { int warningCount = 0; InkParser parser = new InkParser("*", null, (string message, ErrorType errorType) => { if (errorType == ErrorType.Warning) { warningCount++; Assert.IsTrue(message.Contains("completely empty")); } else { Assert.Fail("Shouldn't have had any errors"); } }); parser.Parse(); Assert.AreEqual(1, warningCount); }
protected Ink.Parsed.Story CompileStringWithoutRuntime(string str, bool testingErrors = false) { _testingErrors = testingErrors; _errorMessages.Clear(); _warningMessages.Clear(); InkParser parser = new InkParser(str, null, TestErrorHandler); var parsedStory = parser.Parse(); Assert.IsFalse(parsedStory.hadError); parsedStory.ExportRuntime(TestErrorHandler); return parsedStory; }
// Helper compile function protected Story CompileString(string str, bool countAllVisits = false, bool testingErrors = false) { _testingErrors = testingErrors; _errorMessages.Clear(); _warningMessages.Clear(); InkParser parser = new InkParser(str, null, TestErrorHandler); var parsedStory = parser.Parse(); parsedStory.countAllVisits = countAllVisits; Story story = parsedStory.ExportRuntime(TestErrorHandler); Assert.AreNotEqual(null, story); // Convert to json and back again if (_mode == TestMode.JsonRoundTrip) { var jsonStr = story.ToJsonString(); story = new Story(jsonStr); } return story; }
public void TestReturnTextWarning() { InkParser parser = new InkParser("== test ==\n return something", null, (string message, ErrorType errorType) => { if (errorType == ErrorType.Warning) { throw new TestWarningException(); } }); Assert.Throws<TestWarningException>(() => parser.Parse()); }
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); } } }
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; Runtime.Path userDivertedPath = null; // autoPlay: Pick random choice if (autoPlay) { choiceIdx = rand.Next () % choices.Count; } // Normal: Ask user for choice number else { 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++; } do { // Prompt Console.Write("?> "); string userInput = Console.ReadLine (); var inputParser = new InkParser (userInput); var input = inputParser.CommandLineUserInput(); // Choice if (input.choiceInput != null) { choiceIdx = ((int)input.choiceInput) - 1; if (choiceIdx < 0 || choiceIdx >= choices.Count) { Console.WriteLine ("Choice out of range"); } else { choiceIsValid = true; } } // Help else if (input.isHelp) { Console.WriteLine ("Type a choice number, a divert (e.g. '-> myKnot'), an expression, or a variable assignment (e.g. 'x = 5')"); } // Quit else if (input.isExit) { return; } // Request for debug source line number else if (input.debugSource != null) { var offset = (int)input.debugSource; var dm = DebugMetadataForContentAtOffset (offset); if (dm != null) Console.WriteLine ("DebugSource: "+dm.ToString ()); else Console.WriteLine ("DebugSource: Unknown source"); } // User entered some ink else if (input.userImmediateModeStatement != null ) { var parsedObj = input.userImmediateModeStatement as Parsed.Object; // Variable assignment: create in Parsed.Story as well as the Runtime.Story // so that we don't get an error message during reference resolution if( parsedObj is Parsed.VariableAssignment ) { var varAssign = (Parsed.VariableAssignment) parsedObj; if( varAssign.isNewTemporaryDeclaration ) { parsedStory.TryAddNewVariableDeclaration(varAssign); } } parsedObj.parent = parsedStory; var runtimeObj = parsedObj.runtimeObject; parsedObj.ResolveReferences(parsedStory); if( !parsedStory.hadError ) { // Divert if( parsedObj is Parsed.Divert ) { var parsedDivert = parsedObj as Parsed.Divert; userDivertedPath = parsedDivert.runtimeDivert.targetPath; } // Expression or variable assignment else if( parsedObj is Parsed.Expression || parsedObj is Parsed.VariableAssignment ) { var result = story.EvaluateExpression((Container)runtimeObj); if( result != null ) { Console.WriteLine(result.ToString()); } } } else { parsedStory.ResetError(); } } else { Console.WriteLine ("Unexpected input. Type 'help' or a choice number."); } } while(!choiceIsValid && userDivertedPath == null); } Console.ResetColor (); if (choiceIsValid) { story.ChooseChoiceIndex (choiceIdx); } else if (userDivertedPath != null) { story.ChoosePath (userDivertedPath); userDivertedPath = null; } EvaluateStory (); } }
static void Main(string[] args) { if (args.Length != 1) { Console.WriteLine("Expected the root ink filename as an argument"); return; } var filename = args[0]; // Change working directory to wherever the main ink file is string absRootFilename; if (!Path.IsPathRooted(filename)) { absRootFilename = Path.Combine(Directory.GetCurrentDirectory(), filename); } else { absRootFilename = filename; } Directory.SetCurrentDirectory(Path.GetDirectoryName(absRootFilename)); filename = Path.GetFileName(absRootFilename); // Load up the ink var inkText = File.ReadAllText(filename); // No error handler, will just have an exception if it doesn't compile var parser = new Ink.InkParser(inkText, filename, fileHandler: new InkFileHandler()); var story = parser.Parse(); if (!story) { Console.WriteLine("ERROR: No story was produced?"); } // Find all the text content var allStrings = story.FindAll <Ink.Parsed.Text>(); // Count all the words across all strings var totalWords = 0; foreach (var text in allStrings) { var wordsInThisStr = 0; var wasWhiteSpace = true; foreach (var c in text.text) { if (c == ' ' || c == '\t' || c == '\n' || c == '\r') { wasWhiteSpace = true; } else if (wasWhiteSpace) { wordsInThisStr++; wasWhiteSpace = false; } } totalWords += wordsInThisStr; } Console.WriteLine("Total words: " + totalWords); }
InkParser(string str, string inkFilename = null, Ink.ErrorHandler externalErrorHandler = null, InkParser rootParser = null, IFileHandler fileHandler = null) : base(str) { _filename = inkFilename; RegisterExpressionOperators(); GenerateStatementLevelRules(); // Built in handler for all standard parse errors and warnings this.errorHandler = OnStringParserError; // The above parse errors are then formatted as strings and passed // to the Ink.ErrorHandler, or it throws an exception _externalErrorHandler = externalErrorHandler; _fileHandler = fileHandler ?? new DefaultFileHandler(); if (rootParser == null) { _rootParser = this; _openFilenames = new HashSet <string> (); if (inkFilename != null) { var fullRootInkPath = _fileHandler.ResolveInkFilename(inkFilename); _openFilenames.Add(fullRootInkPath); } } else { _rootParser = rootParser; } }
public void Begin() { EvaluateStory(); var rand = new Random(); while (story.currentChoices.Count > 0) { var choices = story.currentChoices; var choiceIdx = 0; bool choiceIsValid = false; Runtime.Path userDivertedPath = null; // autoPlay: Pick random choice if (autoPlay) { choiceIdx = rand.Next() % choices.Count; } // Normal: Ask user for choice number else { Console.ForegroundColor = ConsoleColor.Blue; int i = 1; foreach (ChoiceInstance choice in choices) { Console.WriteLine("{0}: {1}", i, choice.choiceText); i++; } do { string userInput = Console.ReadLine(); var inputParser = new InkParser(userInput); object evaluatedInput = inputParser.CommandLineUserInput(); // Choice if (evaluatedInput is int?) { choiceIdx = ((int)evaluatedInput) - 1; if (choiceIdx < 0 || choiceIdx >= choices.Count) { Console.WriteLine("Choice out of range"); } else { choiceIsValid = true; } } // Help else if (evaluatedInput is string && (string)evaluatedInput == "help") { Console.WriteLine("Type a choice number, a divert (e.g. '==> myKnot'), an expression, or a variable assignment (e.g. 'x = 5')"); } // User entered some ink else if (evaluatedInput is Parsed.Object) { // Variable assignment: create in Parsed.Story as well as the Runtime.Story // so that we don't get an error message during reference resolution if (evaluatedInput is Parsed.VariableAssignment) { var varAssign = (Parsed.VariableAssignment)evaluatedInput; if (varAssign.isNewTemporaryDeclaration) { parsedStory.TryAddNewVariableDeclaration(varAssign); } } var parsedObj = (Parsed.Object)evaluatedInput; parsedObj.parent = parsedStory; parsedObj.GenerateRuntimeObject(); parsedObj.ResolveReferences(parsedStory); var runtimeObj = parsedObj.runtimeObject; if (!parsedStory.hadError) { // Divert if (evaluatedInput is Parsed.Divert) { userDivertedPath = ((Parsed.Divert)evaluatedInput).runtimeDivert.targetPath; } // Expression or variable assignment else if (evaluatedInput is Parsed.Expression || evaluatedInput is Parsed.VariableAssignment) { var result = story.EvaluateExpression((Container)runtimeObj); if (result != null) { Console.WriteLine(result); } } } else { parsedStory.ResetError(); } } else { Console.WriteLine("Unexpected input. Type 'help' or a choice number."); } } while(!choiceIsValid && userDivertedPath == null); } Console.ResetColor(); if (choiceIsValid) { story.ChooseChoiceIndex(choiceIdx); } else if (userDivertedPath != null) { story.ChoosePath(userDivertedPath); userDivertedPath = null; } EvaluateStory(); } }
public CommandLineInputResult ReadCommandLineInput(string userInput) { var inputParser = new InkParser(userInput); var inputResult = inputParser.CommandLineUserInput(); var result = new CommandLineInputResult(); // Choice if (inputResult.choiceInput != null) { result.choiceIdx = ((int)inputResult.choiceInput) - 1; } // Help else if (inputResult.isHelp) { result.output = "Type a choice number, a divert (e.g. '-> myKnot'), an expression, or a variable assignment (e.g. 'x = 5')"; } // Quit else if (inputResult.isExit) { result.requestsExit = true; } // Request for debug source line number else if (inputResult.debugSource != null) { var offset = (int)inputResult.debugSource; var dm = DebugMetadataForContentAtOffset(offset); if (dm != null) { result.output = "DebugSource: " + dm.ToString(); } else { result.output = "DebugSource: Unknown source"; } } // Request for runtime path lookup (to line number) else if (inputResult.debugPathLookup != null) { var pathStr = inputResult.debugPathLookup; var contentResult = _runtimeStory.ContentAtPath(new Runtime.Path(pathStr)); var dm = contentResult.obj.debugMetadata; if (dm != null) { result.output = "DebugSource: " + dm.ToString(); } else { result.output = "DebugSource: Unknown source"; } } // User entered some ink else if (inputResult.userImmediateModeStatement != null) { var parsedObj = inputResult.userImmediateModeStatement as Parsed.Object; // Variable assignment: create in Parsed.Story as well as the Runtime.Story // so that we don't get an error message during reference resolution if (parsedObj is Parsed.VariableAssignment) { var varAssign = (Parsed.VariableAssignment)parsedObj; if (varAssign.isNewTemporaryDeclaration) { _parsedStory.TryAddNewVariableDeclaration(varAssign); } } parsedObj.parent = _parsedStory; var runtimeObj = parsedObj.runtimeObject; parsedObj.ResolveReferences(_parsedStory); if (!_parsedStory.hadError) { // Divert if (parsedObj is Parsed.Divert) { var parsedDivert = parsedObj as Parsed.Divert; result.divertedPath = parsedDivert.runtimeDivert.targetPath.ToString(); } // Expression or variable assignment else if (parsedObj is Parsed.Expression || parsedObj is Parsed.VariableAssignment) { var evalResult = _runtimeStory.EvaluateExpression((Runtime.Container)runtimeObj); if (evalResult != null) { result.output = evalResult.ToString(); } } } else { _parsedStory.ResetError(); } } else { result.output = "Unexpected input. Type 'help' or a choice number."; } return(result); }
public Parsed.Story Parse() { _parser = new InkParser(_inputString, _options.sourceFilename, OnParseError, _options.fileHandler); _parsedStory = _parser.Parse(); return(_parsedStory); }