Skip to content

Commit

Permalink
Metadata properties for SaveGame (#4)
Browse files Browse the repository at this point in the history
* Add helper properties for metadata in SaveGame

* Bump version and remove preview package dependency
  • Loading branch information
jzebedee authored Apr 5, 2019
1 parent 75f45f2 commit d0b4b7c
Show file tree
Hide file tree
Showing 7 changed files with 284 additions and 103 deletions.
33 changes: 33 additions & 0 deletions src/LibCK2/Game/GameDate.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;

namespace LibCK2.Game
{
public readonly struct GameDate
{
public int Year { get; }
public int Month { get; }
public int Day { get; }

public GameDate(int year, int month, int day) => (Year, Month, Day) = (year, month, day);

public override string ToString() => $"{Year}.{Month}.{Day}";

private static readonly Regex r_tDate = new Regex(@"(\d+)\.(\d+)\.(\d+)", RegexOptions.Compiled);
public static GameDate Parse(string value)
{
if (value == null)
throw new ArgumentNullException(nameof(value));

var m = r_tDate.Match(value);
if (!m.Success)
throw new FormatException($"Value was not in the correct format");

return Parse(m);
}
internal static GameDate Parse(Match m)
=> new GameDate(year: int.Parse(m.Groups[1].Value), month: int.Parse(m.Groups[2].Value), day: int.Parse(m.Groups[3].Value));
}
}
6 changes: 3 additions & 3 deletions src/LibCK2/LibCK2.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,13 @@
<Description>LibCK2 is a .NET Core library for viewing and manipulating Crusader Kings 2 game data and save games</Description>
<PackageLicenseUrl>https://github.com/scorpdx/LibCK2/blob/master/LICENSE</PackageLicenseUrl>
<PackageProjectUrl>https://github.com/scorpdx/LibCK2</PackageProjectUrl>
<VersionPrefix>2.0</VersionPrefix>
<VersionPrefix>2.1</VersionPrefix>
<LangVersion>8.0</LangVersion>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="System.IO.Pipelines" Version="4.6.0-preview.19073.11" />
<PackageReference Include="System.Text.Encoding.CodePages" Version="4.6.0-preview.19073.11" />
<PackageReference Include="System.IO.Pipelines" Version="4.5.0" />
<PackageReference Include="System.Text.Encoding.CodePages" Version="4.5.0" />
</ItemGroup>

</Project>
20 changes: 20 additions & 0 deletions src/LibCK2/Parsing/AsyncEnumerableExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;

namespace LibCK2.Parsing
{
internal static class AsyncEnumerableExtensions
{
public static async Task<List<T>> ToListAsync<T>(this IAsyncEnumerable<T> asyncEnumerable)
{
var ret = new List<T>();
await foreach(var val in asyncEnumerable)
{
ret.Add(val);
}
return ret;
}
}
}
234 changes: 143 additions & 91 deletions src/LibCK2/Parsing/CK2Parsing.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,59 +25,22 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/

using LibCK2.Game;
using System;
using System.Buffers;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.Json;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using TokenTypes = LibCK2.Parsing.Scanner.TokenTypes;
using static LibCK2.SaveGame;

namespace LibCK2.Parsing
{
internal static class CK2Parsing
{
public enum TokenTypes
{
Value,
Equal,
Start,
End
}

internal static async Task<List<(string token, TokenTypes stoppedBy)>> ParseTokensAsync(Stream stream)
{
var tokens = new List<(string token, TokenTypes stoppedBy)>();

var scanner = new Scanner(new AsyncStreamPipe(stream).Input, SaveGameEncoding);
var stopBytes = SaveGameEncoding.GetBytes("\r\n\t{}=");

await foreach (var (token, stoppedBy) in scanner.ReadTokensAsync(stopBytes))
{
switch ((char)stoppedBy)
{
case '{':
tokens.Add((token, TokenTypes.Start));
break;
case '}':
tokens.Add((token, TokenTypes.End));
break;
case '=':
tokens.Add((token, TokenTypes.Equal));
break;
case '\r':
case '\n':
case '\t':
default:
tokens.Add((token, TokenTypes.Value));
break;
}
}

return tokens;
}

private static readonly Regex r_tString = new Regex(@"""(.*?)""", RegexOptions.Compiled);
private static readonly Regex r_tDate = new Regex(@"(\d+)\.(\d+)\.(\d+)", RegexOptions.Compiled);
private static readonly Regex r_tFloat = new Regex(@"(-?\d+\.\d+)", RegexOptions.Compiled);
Expand All @@ -93,10 +56,7 @@ internal static object ParseTokenType(string c)
}
else if ((m = r_tDate.Match(c)).Success)
{
var year = m.Groups[1].Value;
var month = m.Groups[2].Value;
var day = m.Groups[3].Value;
return new DateTime(int.Parse(year), int.Parse(month), int.Parse(day));
return GameDate.Parse(m);
}
else if ((m = r_tFloat.Match(c)).Success)
{
Expand All @@ -123,6 +83,101 @@ internal static object ParseTokenType(string c)
}
}

internal static async IAsyncEnumerable<(string key, object value)> TokensToTypesAsync(IAsyncEnumerable<(string token, TokenTypes stoppedBy)> tokens)
{
await using var tokenIterator = tokens.GetAsyncEnumerator();
(string token, TokenTypes stoppedBy) prev = default;
while (await tokenIterator.MoveNextAsync())
{
(string token, TokenTypes stoppedBy) = tokenIterator.Current;
//System.Diagnostics.Debug.Assert((prev.token != null && stoppedBy != TokenTypes.Equal) ? prev.stoppedBy == TokenTypes.Equal : true);
switch (stoppedBy)
{
case TokenTypes.Value when token == CK2Header:
case TokenTypes.Open:
var ll = new List<(string token, TokenTypes stoppedBy)>();
int depth = 1;
while (depth > 0 && await tokenIterator.MoveNextAsync())
{
switch (tokenIterator.Current.stoppedBy)
{
case TokenTypes.Open: depth++; break;
case TokenTypes.Close: depth--; break;
}
ll.Add(tokenIterator.Current);
}
yield return (token == CK2Header ? token : prev.token, ll);
//{
// //var prev = parsedTokens[i - 1];
// var next = parsedTokens[i + 1];
// switch (next.stoppedBy)
// {
// case TokenTypes.Value:
// case TokenTypes.End:
// json.WriteStartArray(prev.token);
// subitems.Push(false); //array
// break;
// default:
// json.WriteStartObject(prev.token);
// subitems.Push(true); //object
// break;
// }
//}
break;
case TokenTypes.Close:
//throw new InvalidOperationException("no end");
//yield return (2, prev.token, null);
//{
// var isObject = subitems.Pop();
// if (isObject)
// {
// json.WriteEndObject();
// }
// else
// {
// if (!string.IsNullOrWhiteSpace(token))
// {
// WriteArray(ref json, token);
// }
// json.WriteEndArray();
// }
//}
break;
//case TokenTypes.Equal:
// {
// var key = token;
// var next = parsedTokens[i + 1];
// if (next.stoppedBy == TokenTypes.Value)
// {
// WriteObject(ref json, key, next.token);
// i++;
// }
// }
// break;
case TokenTypes.Equal: break;
//case TokenTypes.Value when string.IsNullOrEmpty(token): break;
case TokenTypes.Value:
yield return (prev.token, ParseTokenType(token));
break;
default:
// if (string.IsNullOrWhiteSpace(token))
// {
// //
// }
// else if (subitems.Peek() == false) //array
// {
// WriteArray(ref json, token);
// }
// else
// {
throw new InvalidOperationException("Unexpected token type");
// }
// break;
}
prev = (token, stoppedBy);
}
}

internal static void TokensToJson(IReadOnlyList<(string token, TokenTypes stoppedBy)> parsedTokens, IBufferWriter<byte> writer)
{
var json = new Utf8JsonWriter(writer);
Expand All @@ -146,8 +201,8 @@ void WriteArray(ref Utf8JsonWriter in_json, string value)
case bool b:
in_json.WriteBooleanValue(b);
break;
case DateTime d:
in_json.WriteStringValue($"{d.Year}.{d.Month}.{d.Day}");
case GameDate d:
in_json.WriteStringValue(d.ToString());
break;
}
}
Expand All @@ -169,71 +224,68 @@ void WriteObject(ref Utf8JsonWriter in_json, string key, string value)
case bool b:
in_json.WriteBoolean(key, b);
break;
case DateTime d:
in_json.WriteString(key, $"{d.Year}.{d.Month}.{d.Day}");
case GameDate d:
in_json.WriteString(key, d.ToString());
break;
}
}

Stack<bool> subitems = new Stack<bool>();
for (int i = 0; i < parsedTokens.Count; i++)
if (parsedTokens[0].token == CK2Header)
{
json.WriteStartObject(CK2Header);
subitems.Push(true);
}
else throw new InvalidOperationException("Unknown header");

for (int i = 1; i < parsedTokens.Count; i++)
{
var prev = i > 1 ? parsedTokens[i - 1] : default;
var next = i < parsedTokens.Count - 1 ? parsedTokens[i + 1] : default;
var (token, stoppedBy) = parsedTokens[i];
switch (stoppedBy)
{
case TokenTypes.Equal:
case TokenTypes.Comment when next.stoppedBy == TokenTypes.Value:
json.WriteCommentValue(next.token);
i++;
break;
case TokenTypes.Equal when next.stoppedBy == TokenTypes.Value:
WriteObject(ref json, token, next.token);
i++;
break;
case TokenTypes.Equal when next.stoppedBy == TokenTypes.Open:
break;
case TokenTypes.Open:
switch (next.stoppedBy)
{
var key = token;
var next = parsedTokens[i + 1];
if (next.stoppedBy == TokenTypes.Value)
{
WriteObject(ref json, key, next.token);
i++;
}
case TokenTypes.Value:
case TokenTypes.Close:
json.WriteStartArray(prev.token);
subitems.Push(false); //array
break;
default:
json.WriteStartObject(prev.token);
subitems.Push(true); //object
break;
}
break;
case TokenTypes.Start:
case TokenTypes.Close:
var isObject = subitems.Pop();
if (isObject)
{
var prev = parsedTokens[i - 1];
var next = parsedTokens[i + 1];
switch (next.stoppedBy)
{
case TokenTypes.Value:
case TokenTypes.End:
json.WriteStartArray(prev.token);
subitems.Push(false); //array
break;
default:
json.WriteStartObject(prev.token);
subitems.Push(true); //object
break;
}
json.WriteEndObject();
}
break;
case TokenTypes.End:
else
{
var isObject = subitems.Pop();
if (isObject)
{
json.WriteEndObject();
}
else
if (!string.IsNullOrWhiteSpace(token))
{
if (!string.IsNullOrWhiteSpace(token))
{
WriteArray(ref json, token);
}
json.WriteEndArray();
WriteArray(ref json, token);
}
json.WriteEndArray();
}
break;
default:
if (i == 0 && token == CK2Header)
{
json.WriteStartObject(token);
subitems.Push(true);
}
else if (string.IsNullOrWhiteSpace(token))
if (string.IsNullOrWhiteSpace(token))
{
//
}
Expand Down
2 changes: 1 addition & 1 deletion src/LibCK2/Parsing/PipeHelpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ internal static ArraySegment<byte> GetArray(this Memory<byte> buffer)

internal static ArraySegment<byte> GetArray(this ReadOnlyMemory<byte> buffer)
{
if (!MemoryMarshal.TryGetArray<byte>(buffer, out var segment))
if (!MemoryMarshal.TryGetArray(buffer, out var segment))
throw new InvalidOperationException("MemoryMarshal.TryGetArray<byte> could not provide an array");

return segment;
Expand Down
Loading

0 comments on commit d0b4b7c

Please sign in to comment.