Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"dotnet.defaultSolution": "wham.sln"
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ public async Task RunAsync(DirectoryInfo source, DirectoryInfo output, string ve
var node = await datafile.GetDataAsync()
?? throw new NotSupportedException($"Failed to retrieve data node from {datafile.Filepath}");
Log.Verbose("- Loading finished. Saving XML file...");
var extension = node.GetXmlDocumentKindOrUnknown().GetXmlFileExtension();
var extension = node.Kind.GetXmlDocumentKindOrUnknown().GetXmlFileExtension();
var filename = Path.Combine(output.FullName, fileDir.Name + extension);
using (var fileStream = File.Create(filename))
{
Expand Down
12 changes: 6 additions & 6 deletions src/WarHub.ArmouryModel.ProjectModel/DatafileInfo`1.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using System.IO;
using System.Threading;
using System.Threading;
using System.Threading.Tasks;
using WarHub.ArmouryModel.Source;

Expand All @@ -8,13 +7,14 @@ namespace WarHub.ArmouryModel.ProjectModel
public sealed record DatafileInfo<TNode>(string Filepath, TNode Node) : IDatafileInfo<TNode>
where TNode : SourceNode
{
public SourceKind DataKind => Node.Kind;
private Task<SourceNode>? lazyTask;

public string GetStorageName() => Path.GetFileNameWithoutExtension(Filepath);
public SourceKind DataKind => Node.Kind;

public Task<SourceNode?> GetDataAsync(CancellationToken cancellationToken = default) => Task.FromResult<SourceNode?>(Node);
public Task<SourceNode> GetDataAsync(CancellationToken cancellationToken = default) =>
lazyTask ??= Task.FromResult<SourceNode>(Node);

public SourceNode? GetData(CancellationToken cancellationToken = default) => Node;
public SourceNode GetData(CancellationToken cancellationToken = default) => Node;

public bool TryGetData(out SourceNode node)
{
Expand Down
22 changes: 17 additions & 5 deletions src/WarHub.ArmouryModel.ProjectModel/IDatafileInfo.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using System.Threading;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using WarHub.ArmouryModel.Source;

Expand All @@ -24,27 +26,37 @@ public interface IDatafileInfo
/// Retrieves root <see cref="SourceNode"/> of the data file. May cause parsing.
/// </summary>
/// <returns>Retrieved root node.</returns>
Task<SourceNode?> GetDataAsync(CancellationToken cancellationToken = default);
Task<SourceNode> GetDataAsync(CancellationToken cancellationToken = default);

/// <summary>
/// Retrieves root <see cref="SourceNode"/> of the data file. May cause parsing.
/// Blocking method.
/// </summary>
/// <returns>Retrieved root node.</returns>
SourceNode? GetData(CancellationToken cancellationToken = default);
SourceNode GetData(CancellationToken cancellationToken = default);

/// <summary>
/// Attempts to retrieve root <see cref="SourceNode"/> of the data file
/// without causing parsing. Returns true if successful.
/// </summary>
/// <param name="node">Retrieved node if method returned <see langword="true"/>, <see langword="null"/> otherwise.</param>
/// <returns> <see langword="true"/> when successful, false otherwise.</returns>
bool TryGetData(out SourceNode? node);
bool TryGetData([NotNullWhen(true)] out SourceNode? node);

/// <summary>
/// Gets a name usable in file storage, with no extensions.
/// </summary>
/// <returns></returns>
string GetStorageName();
string GetStorageName() => Path.GetFileNameWithoutExtension(Filepath);

/// <summary>
/// Create a tree for root node contained in this datafile.
/// </summary>
/// <remarks>
/// Default implementation creates a lazy tree that will parse the data
/// only when it's requested.
/// </remarks>
/// <returns>Created source tree.</returns>
SourceTree CreateTree() => new LazyDatafileSourceTree(this);
}
}
2 changes: 1 addition & 1 deletion src/WarHub.ArmouryModel.ProjectModel/IDatafileInfo`1.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,6 @@ public interface IDatafileInfo<out TNode> : IDatafileInfo where TNode : SourceNo
/// Type-parametrized and synchronous version of <see cref="IDatafileInfo.GetDataAsync"/>.
/// </summary>
/// <returns>Retrieved root node.</returns>
TNode? Node { get; }
TNode Node { get; }
}
}
86 changes: 86 additions & 0 deletions src/WarHub.ArmouryModel.ProjectModel/LazyDatafileSourceTree.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.Threading;
using System.Threading.Tasks;
using WarHub.ArmouryModel.Source;

namespace WarHub.ArmouryModel.ProjectModel
{
public sealed class LazyDatafileSourceTree : SourceTree
{
private SourceNode? lazyRoot;

public LazyDatafileSourceTree(IDatafileInfo datafile)
{
Datafile = datafile;
}

public IDatafileInfo Datafile { get; }

public override string? FilePath => Datafile.Filepath;

public override FileLinePositionSpan GetLineSpan(TextSpan span)
{
return default;
}

public override Location GetLocation(TextSpan span)
{
return Location.Create(this, span);
}

public override bool TryGetRoot([NotNullWhen(true)] out SourceNode? root)
{
if (Datafile.TryGetData(out var data))
{
root = NodeForThisTree(data);
return true;
}
root = null;
return false;
}

public override SourceNode GetRoot(CancellationToken cancellationToken = default)
{
try
{
var data = Datafile.GetData(cancellationToken);
return GetAssociatedInterlockedNode(data);
}
catch (Exception ex) when (!(ex is OperationCanceledException && cancellationToken.IsCancellationRequested))
{
return ThrowNoData(ex);
}
}

public override async Task<SourceNode> GetRootAsync(CancellationToken cancellationToken = default)
{
try
{
var data = await Datafile.GetDataAsync(cancellationToken);
return GetAssociatedInterlockedNode(data);
}
catch (Exception ex) when (!(ex is OperationCanceledException && cancellationToken.IsCancellationRequested))
{
return ThrowNoData(ex);
}
}

private SourceNode GetAssociatedInterlockedNode(SourceNode data)
{
var result = NodeForThisTree(data ?? ThrowNoData());
var previous = Interlocked.CompareExchange(ref lazyRoot, result, null);
return previous is null ? result : previous;
}

public override SourceTree WithRoot(SourceNode root)
{
return CreateForRoot(root, FilePath);
}

private SourceNode ThrowNoData(Exception? inner = null)
{
throw new InvalidOperationException($"Failed to retrieve data from datafile with path '{FilePath}'.", inner);
}
}
}
21 changes: 11 additions & 10 deletions src/WarHub.ArmouryModel.ProjectModel/UnknownTypeDatafileInfo.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.IO;
using System;
using System.Diagnostics.CodeAnalysis;
using System.Threading;
using System.Threading.Tasks;
using WarHub.ArmouryModel.Source;
Expand All @@ -7,22 +8,22 @@ namespace WarHub.ArmouryModel.ProjectModel
{
public record UnknownTypeDatafileInfo(string Filepath) : IDatafileInfo, IDatafileInfo<SourceNode>
{
private static readonly Task<SourceNode?> nullTask = Task.FromResult<SourceNode?>(null);

public SourceKind DataKind => SourceKind.Unknown;

public SourceNode? Node => null;

public SourceNode? GetData(CancellationToken cancellationToken = default) => null;
public SourceNode Node => throw new InvalidOperationException("There is no data for Unknown datafile.");

public Task<SourceNode?> GetDataAsync(CancellationToken cancellationToken = default) => nullTask;
public SourceNode GetData(CancellationToken cancellationToken = default) => Node;

public string GetStorageName() => Path.GetFileNameWithoutExtension(Filepath);
public Task<SourceNode> GetDataAsync(CancellationToken cancellationToken = default)
{
var node = Node;
return Task.FromResult(node);
}

public bool TryGetData(out SourceNode? node)
public bool TryGetData([NotNullWhen(true)] out SourceNode? node)
{
node = null;
return true;
return false;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
using System.Collections.Generic;
using System.Threading;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;

namespace WarHub.ArmouryModel.Source.CodeGeneration
{
internal class CoreSpanCalculationPartialGenerator : CorePartialGeneratorBase
{
protected CoreSpanCalculationPartialGenerator(CoreDescriptor descriptor, CancellationToken cancellationToken) : base(descriptor, cancellationToken)
{
}

public static TypeDeclarationSyntax Generate(CoreDescriptor descriptor, CancellationToken cancellationToken)
{
var generator = new CoreSpanCalculationPartialGenerator(descriptor, cancellationToken);
return generator.GenerateTypeDeclaration();
}

protected override IEnumerable<MemberDeclarationSyntax> GenerateMembers()
{
yield return
MethodDeclaration(IdentifierName("int"), Names.CalculateDescendantSpanLength)
.AddModifiers(SyntaxKind.ProtectedKeyword, SyntaxKind.OverrideKeyword)
.WithBody(
Block(GetStatements()));
IEnumerable<StatementSyntax> GetStatements()
{
var countVar = IdentifierName(Identifier("count"));
var literalOne = LiteralExpression(SyntaxKind.NumericLiteralExpression, Literal(1));
yield return
LocalDeclarationStatement(
VariableDeclaration(IdentifierName("var"))
.AddVariables(
VariableDeclarator(
countVar.Identifier,
argumentList: null,
initializer: EqualsValueClause(LiteralExpression(SyntaxKind.NumericLiteralExpression, Literal(0))))));
foreach (var child in Descriptor.Entries)
{
if (child is CoreValueChild)
{
yield return
ExpressionStatement(
AssignmentExpression(SyntaxKind.AddAssignmentExpression, countVar, literalOne))
/*.WithLeadingTrivia(Comment("for " + child.Identifier.Text))*/;
}
else if (child is CoreObjectChild)
{
// count += ({child}?.GetSpanLength()) ?? 0;
var callGetSpan =
child.IdentifierName.QuestionDot(Names.GetSpanLength).Invoke().WrapInParens().QuestionQuestion(Zero);
yield return
ExpressionStatement(
AssignmentExpression(
SyntaxKind.AddAssignmentExpression,
countVar,
callGetSpan));
}
else if (child is CoreListChild)
{
// foreach (var item in {child})
// count += item.GetSpanLength();
var itemVar = IdentifierName("item");
var callGetSpan =
itemVar.Dot(Names.GetSpanLength).Invoke();
var sumExpression =
ExpressionStatement(
AssignmentExpression(SyntaxKind.AddAssignmentExpression, countVar, callGetSpan));
yield return
ForEachStatement(IdentifierName("var"), itemVar.Identifier, child.IdentifierName, sumExpression);

}
}
yield return ReturnStatement(countVar);
}
}
}
}
6 changes: 5 additions & 1 deletion src/WarHub.ArmouryModel.Source.CodeGeneration/Names.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
namespace WarHub.ArmouryModel.Source.CodeGeneration
using Microsoft.CodeAnalysis;

namespace WarHub.ArmouryModel.Source.CodeGeneration
{
internal static class Names
{
Expand All @@ -8,6 +10,8 @@ internal static class Names
public const string ListSuffix = "List";
public const string CoreSuffix = "Core";
public const string Empty = "Empty";
public const string CalculateDescendantSpanLength = "CalculateDescendantSpanLength";
public const string GetSpanLength = "GetSpanLength";
public const string Add = nameof(System.Collections.IList.Add);
public const string AddRange = nameof(System.Collections.Generic.List<int>.AddRange);
public const string AddRangeAsBuilders = "AddRangeAsBuilders";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ private static IEnumerable<TypeDeclarationSyntax> GenerateCorePartials(CoreDescr
yield break;
}
yield return CoreEmptyPropertyPartialGenerator.Generate(descriptor, default);
yield return CoreSpanCalculationPartialGenerator.Generate(descriptor, default);
}

private static IEnumerable<TypeDeclarationSyntax> GenerateNodePartials(CoreDescriptor descriptor)
Expand Down
18 changes: 18 additions & 0 deletions src/WarHub.ArmouryModel.Source/Foundation/NodeCore.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,29 @@
/// </summary>
public abstract record NodeCore : ICore<SourceNode>
{
private int spanLength;

/// <summary>
/// Create a Node from this Core.
/// </summary>
/// <param name="parent">Optional parent of the created Node.</param>
/// <returns>The newly created Node.</returns>
public abstract SourceNode ToNode(SourceNode? parent = null);

/// <summary>
/// Returns span length this node represents.
/// </summary>
public int GetSpanLength()
{
if (spanLength > 0) return spanLength;
return spanLength = 1 + CalculateDescendantSpanLength();
}

/// <summary>
/// Returns a calculated span length of all of this node's descendants.
/// This doesn't include span of the node itself.
/// </summary>
/// <returns></returns>
protected abstract int CalculateDescendantSpanLength();
}
}
Loading