Пример #1
0
        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;
            }
        }
Пример #2
0
        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);
        }
Пример #3
0
        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);
        }
Пример #4
0
        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;
        }
Пример #5
0
        // 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;
        }
Пример #6
0
        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());
        }
Пример #7
0
        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);
                }
            }
        }
Пример #8
0
		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 ();
			}
		}
Пример #9
0
    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;
            }
        }
Пример #11
0
        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();
            }
        }
Пример #12
0
        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);
        }
Пример #13
0
 public Parsed.Story Parse()
 {
     _parser      = new InkParser(_inputString, _options.sourceFilename, OnParseError, _options.fileHandler);
     _parsedStory = _parser.Parse();
     return(_parsedStory);
 }