190 lines
5.3 KiB
C#
190 lines
5.3 KiB
C#
using System.Text;
|
|
|
|
namespace ParadoxSaveParser.Lib;
|
|
|
|
public class Parser
|
|
{
|
|
protected Stream _saveFile;
|
|
private List<Token> _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<object> ParseList()
|
|
{
|
|
List<object> 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<string, List<object>> ParseDict()
|
|
{
|
|
Dictionary<string, List<object>> 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<object>? list))
|
|
{
|
|
list = new List<object>();
|
|
dict.Add(key, list);
|
|
}
|
|
list.Add(value);
|
|
}
|
|
|
|
return dict;
|
|
}
|
|
|
|
public Dictionary<string, List<object>> Parse()
|
|
{
|
|
Lex();
|
|
if (_tokens.Count == 0)
|
|
throw new Exception("Save file is empty");
|
|
|
|
_tokenIndex = 0;
|
|
var root = ParseDict();
|
|
return root;
|
|
}
|
|
} |