implemented parser
This commit is contained in:
+172
-11
@@ -1,29 +1,190 @@
|
||||
namespace ParadoxSaveParser.Lib;
|
||||
using System.Text;
|
||||
|
||||
public abstract class Parser
|
||||
namespace ParadoxSaveParser.Lib;
|
||||
|
||||
public class Parser
|
||||
{
|
||||
protected Stream _saveFile;
|
||||
|
||||
protected Parser(Stream savefile)
|
||||
private List<Token> _tokens = new();
|
||||
private int _tokenIndex;
|
||||
|
||||
public Parser(Stream savefile)
|
||||
{
|
||||
_saveFile = savefile;
|
||||
}
|
||||
|
||||
|
||||
protected enum TokenType
|
||||
{
|
||||
Invalid, String, Equals, BracketOpen, BracketClose,
|
||||
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 BuildAST()
|
||||
|
||||
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}")
|
||||
{}
|
||||
}
|
||||
|
||||
public abstract SaveData Parse();
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
namespace ParadoxSaveParser.Lib;
|
||||
|
||||
public class ParserEU4 : Parser
|
||||
{
|
||||
public ParserEU4(Stream savefile) : base(savefile)
|
||||
{
|
||||
}
|
||||
|
||||
public override SaveData Parse()
|
||||
{
|
||||
var saveData = new SaveData();
|
||||
|
||||
return saveData;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
namespace ParadoxSaveParser.Lib;
|
||||
|
||||
public class SaveData
|
||||
{
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user