Beispiel #1
0
        public TokenList(
            string sourceText,
            string sourceNameForErrorMessages,
            Dictionary <string, Setting> settings)
        {
            // Tokens only get created as part of this list of tokens.
            char pushedLetter = '\0';
            char gottenLetter;
            int  letterIndex;
            var  specialIds = new List <string> {
                "John", "Jane", "Smith", "he", "He", "him", "Him", "his", "His", "himself", "Himself", "man", "Man", "boy", "Boy", "Mr", "Mrs", "she", "She", "her", "Her", "hers", "Hers", "herself", "Herself", "woman", "Woman", "girl", "Girl", "Ms"
            };

            TheList = new List <Token>();
            string textAccumulator = "";
            int    lineNumber      = 1;

            letterIndex = 0;

            // This outer loop is for accumulating text strings
            while (true)
            {
                if (!GetLetter())
                {
                    if (textAccumulator.Length != 0)
                    {
                        TheList.Add(new Token(TokenType.Characters, textAccumulator, lineNumber));
                    }
                    TheList.Add(new Token(TokenType.EndOfSourceText, "", lineNumber));
                    return;
                }

                switch (gottenLetter)
                {
                case '\n':
                    // @ lets you explicitly put in a paragraph break. We'll clean up any extra spaces later. This lets you break contiguous text up into multiple lines within 'if' groups without having it affect formatting.
                    textAccumulator += "\n";
                    ++lineNumber;
                    break;

                case '\r':
                    // Ignore the CR in CRLF.
                    break;

                case '\t':
                    textAccumulator += "\t";
                    break;

                // Didn't want to put this way at the bottom--gets lost.
                default:
                    textAccumulator += gottenLetter;
                    break;

                case '[':
                    if (!GetLetter())
                    {
                        TheList.Add(new Token(TokenType.EndOfSourceText, "", lineNumber));
                        return;
                    }

                    // Check for a [[ comment starter.
                    if (gottenLetter != '[')
                    {
                        // No comment--pretend this never happened.
                        UngetLetter();
                    }
                    else
                    {
                        //  [[ This is an example comment. ]]
                        if (!GetComment())
                        {
                            // If it returns false, it means you've reached the end.
                            TheList.Add(new Token(TokenType.EndOfSourceText, "", lineNumber));
                            return;
                        }
                        // We got a text mode comment, so we break back out to the outer loop.
                        break; // switch
                    }

                    // If we have accumulated a text string, record it as the next token before going into code mode.
                    if (textAccumulator.Length != 0)
                    {
                        TheList.Add(new Token(TokenType.Characters, textAccumulator, lineNumber));
                        textAccumulator = "";
                    }

                    // This inner loop is for breaking out control codes
                    for (bool gotClosingBracket = false; !gotClosingBracket;)
                    {
                        if (!GetLetter())
                        {
                            TheList.Add(new Token(TokenType.EndOfSourceText, "", lineNumber));
                            return;
                        }

                        switch (gottenLetter)
                        {
                        case ' ':
                        case '\r':
                            break;

                        case '\n':
                            ++lineNumber;
                            break;

                        case ']':
                            // Leave code mode
                            gotClosingBracket = true;
                            break;

                        case '=':
                            TheList.Add(new Token(TokenType.Equal, "=", lineNumber));
                            break;

                        case '.':
                            TheList.Add(new Token(TokenType.Period, ".", lineNumber));
                            break;

                        case ',':
                            TheList.Add(new Token(TokenType.Comma, ",", lineNumber));
                            break;

                        case '[':
                            // Must be a comment.
                            if (!GetLetter())
                            {
                                TheList.Add(new Token(TokenType.EndOfSourceText, "", lineNumber));
                                return;
                            }
                            if (gottenLetter != '[')
                            {
                                throw new InvalidOperationException(string.Format($"file {sourceNameForErrorMessages} line {lineNumber}: expected '[[' but got '[{gottenLetter}' in\n{sourceText}"));
                            }

                            if (!GetComment())
                            {
                                // If it returns false, it means you've reached the end.
                                TheList.Add(new Token(TokenType.EndOfSourceText, "", lineNumber));
                                return;
                            }
                            // Done skipping comment. Move on to the next token.
                            break;

                        default:
                            if (!char.IsLetterOrDigit(gottenLetter) || gottenLetter == '_')
                            {
                                throw new InvalidOperationException(string.Format($"file {sourceNameForErrorMessages} line {lineNumber}: unexpected character '{gottenLetter}' in\n{sourceText}"));
                            }

                            string id = "";
                            do
                            {
                                id += gottenLetter;

                                if (!GetLetter())
                                {
                                    break;
                                }
                            } while (Char.IsLetterOrDigit(gottenLetter) || gottenLetter == '_' || gottenLetter == '.');

                            UngetLetter();
                            if (id == "if")
                            {
                                TheList.Add(new Token(TokenType.If, id, lineNumber));
                            }
                            else if (id == "else")
                            {
                                TheList.Add(new Token(TokenType.Else, id, lineNumber));
                            }
                            else if (id == "or")
                            {
                                TheList.Add(new Token(TokenType.Or, id, lineNumber));
                            }
                            else if (id == "not")
                            {
                                TheList.Add(new Token(TokenType.Not, id, lineNumber));
                            }
                            else if (id == "end")
                            {
                                TheList.Add(new Token(TokenType.End, id, lineNumber));
                            }
                            else if (id == "when")
                            {
                                TheList.Add(new Token(TokenType.When, id, lineNumber));
                            }
                            else if (id == "set")
                            {
                                TheList.Add(new Token(TokenType.Set, id, lineNumber));
                            }
                            else if (id == "score")
                            {
                                TheList.Add(new Token(TokenType.Score, id, lineNumber));
                            }
                            else if (id == "sort")
                            {
                                TheList.Add(new Token(TokenType.Sort, id, lineNumber));
                            }
                            else if (id == "text")
                            {
                                TheList.Add(new Token(TokenType.Text, id, lineNumber));
                            }
                            else if (id == "merge")
                            {
                                TheList.Add(new Token(TokenType.Merge, id, lineNumber));
                            }
                            else if (id == "return")
                            {
                                TheList.Add(new Token(TokenType.Return, id, lineNumber));
                            }
                            else if (id == "scene")
                            {
                                TheList.Add(new Token(TokenType.Scene, id, lineNumber));
                            }
                            else if (specialIds.Contains(id))
                            {
                                TheList.Add(new Token(TokenType.SpecialId, id, lineNumber));
                            }
                            else
                            {
                                var type = settings.ContainsKey(id) ?
                                           settings[id] switch
                                {
                                    ScoreSetting _ => TokenType.ScoreId,
                                    StringSetting _ => TokenType.StringId,
                                    BooleanSetting _ => TokenType.BooleanId,
                                    _ => throw new NotImplementedException(), // Just making the compiler happy. There is no other case.
                                }
                              :
                                TokenType.Id;
                                TheList.Add(new Token(type, id, lineNumber));
                            }
                            break;
                        }
                    }
                    break;
                }
Beispiel #2
0
        public static Page TryLoad(
            StreamReader reader,
            Dictionary <string, Unit> unitsByUniqueId,
            Dictionary <string, ReactionArrow> reactionArrowsByUniqueId)
        {
            var result = new Page();

            result.ActionText = null;
            result.Reactions  = new Dictionary <string, ScoredReactionArrow>();

            // End of file right at the beginning (maybe after an 'x' operation) indicates a valid end of the file.
            var line = reader.ReadLine();

            if (line == null)
            {
                return(null);
            }

            while (true)
            {
                var parts = line.Split(SaveFileDelimiter);
                switch (parts[0])
                {
                case "s":
                    Setting setting;
                    switch (parts[2])
                    {
                    case "s":
                        setting = new StringSetting(parts[3]);
                        break;

                    case "c":
                        if (!int.TryParse(parts[3], out var chosen))
                        {
                            throw new InvalidOperationException(string.Format($"Can't parse int chosen '{parts[3]}'."));
                        }
                        if (!int.TryParse(parts[4], out var opportunity))
                        {
                            throw new InvalidOperationException(string.Format($"Can't parse int opportunity '{parts[4]}'."));
                        }
                        setting = new ScoreSetting(chosen, opportunity);
                        break;

                    case "b":
                        switch (parts[3])
                        {
                        case "0":
                            setting = new BooleanSetting(false);
                            break;

                        case "1":
                            setting = new BooleanSetting(true);
                            break;

                        default:
                            throw new InvalidOperationException(string.Format($"Unexpected boolean value '{parts[3]}'."));
                        }
                        break;

                    default:
                        throw new InvalidOperationException(string.Format($"Unexpected setting type '{parts[2]}'."));
                    }
                    result.Settings.Add(parts[1], setting);
                    break;

                case "n":
                    result.NextTargetUnitOnReturn.Push(unitsByUniqueId[parts[1]]);
                    break;

                case "a":
                    result.ActionText = parts[1];
                    break;

                case "r":
                    if (!double.TryParse(parts[2], out var score))
                    {
                        throw new InvalidOperationException(string.Format($"Can't parse double score '{parts[2]}'."));
                    }
                    result.Reactions.Add(parts[3], new ScoredReactionArrow(score, reactionArrowsByUniqueId[parts[1]]));
                    break;

                case "x":
                    // Flip the stack so it's going the right way. When you copy a stack, it flips it.
                    result.NextTargetUnitOnReturn = new Stack <Unit>(result.NextTargetUnitOnReturn);
                    return(result);

                default:
                    throw new InvalidOperationException(string.Format($"Unexpected operation '{parts[0]}'."));
                }
                line = reader.ReadLine();
                if (line == null)
                {
                    throw new InvalidOperationException(string.Format($"Unexpected end of save file."));
                }
            }
        }
        public static Page?TryLoad(
            TextReader reader,
            World world)
        {
            // Loads saved story state data and links it to the static world description. Either loads and returns a valid Page or returns null if it reaches the end of the reader. Run this multiple times to read multiple Pages from the reader.

            var    settings               = new Dictionary <string, Setting>(world.Settings);
            string actionText             = "";
            var    reactions              = new Dictionary <string, ScoredReactionArrow>();
            var    nextTargetNodeOnReturn = new Stack <Node>();

            // End of file right at the beginning (maybe after an 'x' operation) indicates a valid end of the file. That means we're done reading all the pages in the file.
            var line = reader.ReadLine();

            if (line == null)
            {
                return(null);
            }

            while (true)
            {
                var parts = line.Split(SaveFileDelimiter);
                switch (parts[0])
                {
                case "s":
                    Setting setting;
                    switch (parts[2])
                    {
                    case "s":
                        setting = new StringSetting(parts[3]);
                        break;

                    case "c":
                        if (!int.TryParse(parts[3], out var chosen))
                        {
                            throw new InvalidOperationException(string.Format($"Can't parse int chosen '{parts[3]}'."));
                        }
                        if (!int.TryParse(parts[4], out var opportunity))
                        {
                            throw new InvalidOperationException(string.Format($"Can't parse int opportunity '{parts[4]}'."));
                        }
                        setting = new ScoreSetting(chosen, opportunity);
                        break;

                    case "b":
                        switch (parts[3])
                        {
                        case "0":
                            setting = new BooleanSetting(false);
                            break;

                        case "1":
                            setting = new BooleanSetting(true);
                            break;

                        default:
                            throw new InvalidOperationException(string.Format($"Unexpected boolean value '{parts[3]}'."));
                        }
                        break;

                    default:
                        throw new InvalidOperationException(string.Format($"Unexpected setting type '{parts[2]}'."));
                    }
                    settings[parts[1]] = setting;
                    break;

                case "n":
                    nextTargetNodeOnReturn.Push(world.NodesByUniqueId[parts[1]]);
                    break;

                case "a":
                    actionText = parts[1];
                    break;

                case "r":
                    if (!double.TryParse(parts[2], out var score))
                    {
                        throw new InvalidOperationException(string.Format($"Can't parse double score '{parts[2]}'."));
                    }
                    reactions.Add(parts[3], new ScoredReactionArrow(score, world.ReactionArrowsByUniqueId[parts[1]]));
                    break;

                case "x":
                    // Flip the stack so it's going the right way. When you copy a stack, it flips it.
                    nextTargetNodeOnReturn = new Stack <Node>(nextTargetNodeOnReturn);
                    return(new Page(actionText, reactions, settings, nextTargetNodeOnReturn));

                default:
                    throw new InvalidOperationException(string.Format($"Unexpected operation '{parts[0]}'."));
                }
                line = reader.ReadLine();
                if (line == null)
                {
                    throw new InvalidOperationException(string.Format($"Unexpected end of save file."));
                }
            }
        }