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; }
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.")); } } }