using System.Text; namespace ParadoxSaveParser.Lib; public class Parser { protected Stream _saveFile; private List _tokens = new(); private int _tokenIndex; public Parser(Stream savefile) { _saveFile = savefile; } protected enum TokenType { Invalid, String, Equals, BracketOpen, BracketClose, } protected struct Token { public TokenType type; public string? value; public override string ToString() { switch (type) { case TokenType.Invalid: return "INVALID_TOKEN"; case TokenType.String: return value ?? "NULL"; case TokenType.Equals: return "="; case TokenType.BracketOpen: return "{"; case TokenType.BracketClose: return "}"; default: throw new ArgumentOutOfRangeException(type.ToString()); } } } protected void Lex() { _tokens.Clear(); StringBuilder str = new(); void CompleteStringToken() { if (str.Length > 0 && str[0] != '#') { _tokens.Add(new Token { type = TokenType.String, value = str.ToString() }); str.Clear(); } } while (_saveFile.CanRead) { int c = _saveFile.ReadByte(); switch (c) { case -1: CompleteStringToken(); return; case ' ': case '\t': case '\n': case '\r': CompleteStringToken(); break; case '=': CompleteStringToken(); _tokens.Add(new Token { type = TokenType.Equals }); break; case '{': CompleteStringToken(); _tokens.Add(new Token { type = TokenType.BracketOpen }); break; case '}': CompleteStringToken(); _tokens.Add(new Token { type = TokenType.BracketClose }); break; default: str.Append((char)c); break; } } } protected class UnexpectedTokenException : Exception { public UnexpectedTokenException(Token token, int tokenIndex) : base($"Unexpected token at index {tokenIndex}: {token}") {} } private object? ParseValue() { Token tok = _tokens[_tokenIndex++]; switch (tok.type) { case TokenType.String: return tok.value!; case TokenType.BracketOpen: return ParseListOrDict(); case TokenType.BracketClose: return null; default: throw new UnexpectedTokenException(tok, _tokenIndex - 1); } } private object ParseListOrDict() { Token first = _tokens[_tokenIndex]; Token second = _tokens[_tokenIndex + 1]; if (first.type == TokenType.String && second.type == TokenType.Equals) return ParseDict(); return ParseList(); } private List ParseList() { List list = new(); Token tok = _tokens[_tokenIndex]; while (tok.type != TokenType.BracketClose) { object? value = ParseValue(); if (value == null) break; list.Add(value); } return list; } private Dictionary> ParseDict() { Dictionary> dict = new(); // root is a dict without closing bracket, so this method must check _tokenIndex < _tokens.Count while (_tokenIndex < _tokens.Count) { Token tok = _tokens[_tokenIndex++]; if (tok.type == TokenType.BracketClose) break; if(tok.type != TokenType.String) throw new UnexpectedTokenException(tok, _tokenIndex - 1); string key = tok.value!; tok = _tokens[_tokenIndex++]; if(tok.type != TokenType.Equals) throw new UnexpectedTokenException(tok, _tokenIndex - 1); object? value = ParseValue(); if (value == null) throw new UnexpectedTokenException(_tokens[_tokenIndex - 1], _tokenIndex - 1); if(!dict.TryGetValue(key, out List? list)) { list = new List(); dict.Add(key, list); } list.Add(value); } return dict; } public Dictionary> Parse() { Lex(); if (_tokens.Count == 0) throw new Exception("Save file is empty"); _tokenIndex = 0; var root = ParseDict(); return root; } }