Skip to content

Commit

Permalink
codegen and building improvements (#13)
Browse files Browse the repository at this point in the history
codegen, diagnostics, linking and post processing improvements
  • Loading branch information
malstraem authored Sep 23, 2024
1 parent 6fa0d14 commit 8501753
Show file tree
Hide file tree
Showing 172 changed files with 1,601 additions and 1,212 deletions.
34 changes: 21 additions & 13 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
@@ -1,19 +1,27 @@
<Project>
<ItemGroup>
<PackageVersion Include="CommunityToolkit.Mvvm" Version="8.2.2" />
<PackageVersion Include="CommunityToolkit.Mvvm" Version="8.3.2" />

<PackageVersion Include="Docfx.App" Version="2.77.0" />
<PackageVersion Include="BenchmarkDotNet" Version="0.13.13-nightly.20240601.156" />

<PackageVersion Include="BenchmarkDotNet" Version="0.14.0" />

<PackageVersion Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4" PrivateAssets="all" />
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.10.0" PrivateAssets="all" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.10.0" />
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.11.0" PrivateAssets="all" />

<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.11.1" />

<PackageVersion Include="xunit" Version="2.9.0" />
<PackageVersion Include="xunit.runner.visualstudio" Version="2.8.2" PrivateAssets="all" IncludeAssets="runtime; build; native; contentfiles; analyzers; buildtransitive" />
<PackageVersion Include="Avalonia" Version="11.1.0" />
<PackageVersion Include="Avalonia.Desktop" Version="11.1.0" />
<PackageVersion Include="Avalonia.Diagnostics" Version="11.0.11" />
<PackageVersion Include="Avalonia.Fonts.Inter" Version="11.1.0" />
<PackageVersion Include="Semi.Avalonia" Version="11.1.0-rc2.1" />
<PackageVersion Include="Irihi.Ursa" Version="1.0.0-rc1" />
<PackageVersion Include="Irihi.Ursa.Themes.Semi" Version="1.0.0-rc1" />
<PackageVersion Include="xunit.runner.visualstudio" Version="2.8.2"
PrivateAssets="all" IncludeAssets="runtime; build; native; contentfiles; analyzers; buildtransitive" />

<PackageVersion Include="Avalonia" Version="11.1.3" />
<PackageVersion Include="Avalonia.Desktop" Version="11.1.3" />
<PackageVersion Include="Avalonia.Diagnostics" Version="11.1.3" />
<PackageVersion Include="Avalonia.Fonts.Inter" Version="11.1.3" />

<PackageVersion Include="Semi.Avalonia" Version="11.1.0.4" />
<PackageVersion Include="Irihi.Ursa" Version="1.3.0" />
<PackageVersion Include="Irihi.Ursa.Themes.Semi" Version="1.3.0" />
</ItemGroup>
</Project>
</Project>
3 changes: 2 additions & 1 deletion codegen/Arinc424.Generators.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" />
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers"
PrivateAssets="all" IncludeAssets="runtime; build; native; contentfiles; analyzers; buildtransitive"/>
</ItemGroup>
</Project>
43 changes: 42 additions & 1 deletion codegen/CharGenerator.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,47 @@
using System.Text;

using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;

namespace Arinc424.Generators;

using static Constants;

[Generator]
public class CharGenerator() : ConverterGenerator(Constants.CharAttribute);
public class CharGenerator : ConverterGenerator
{
private protected override StringBuilder WriteTarget(StringBuilder builder, BaseTarget target)
{
var (members, blank) = ((Target)target).GetMembersWithBlank();

_ = builder.Append($@"
public static bool TryConvert({CharArg}, out {target.Symbol.Name} value)
{{
switch ({Char})
{{
case (char)32:
value = {blank}; return true;");

foreach (var member in members)
{
var (name, argument) = member;

_ = builder.Append($@"
case {argument}:
value = {name}; return true;");
}
return builder.Append(@$"
default:
value = {target.Unknown}; return false;
}}").Append($@"
}}");
}

private protected override bool IsMatch(EnumDeclarationSyntax @enum) => @enum.HaveAttribute(CharAttribute);

public CharGenerator()
{
@base = CharConverter;
qualifier = CharAttributeQualifier;
}
}
15 changes: 9 additions & 6 deletions codegen/Constants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,22 @@ namespace Arinc424.Generators;

internal static class Constants
{
internal const string CharAttribute = "Arinc424.Attributes.CharAttribute";
internal const string StringAttribute = "Arinc424.Attributes.StringAttribute";
internal const string CharAttributeQualifier = "Arinc424.Attributes.CharAttribute";
internal const string StringAttributeQualifier = "Arinc424.Attributes.StringAttribute";

internal const string MapAttribute = "Map";
internal const string CharAttribute = "Char";
internal const string FlagsAttribute = "Flags";
internal const string OffsetAttribute = "Offset";
internal const string StringAttribute = "String";

internal const string StringConverter = "IStringConverter";
internal const string CharConverter = "ICharConverter";
internal const string StringConverter = "IStringConverter";

internal const string String = "@string";
internal const string Char = "@char";
internal const string String = "@string";
internal const string Problem = "problem";

internal const string StringSignature = "ReadOnlySpan<char> @string";
internal const string CharSignature = "char @char";
internal const string CharArg = "char @char";
internal const string StringArg = "ReadOnlySpan<char> @string";
}
117 changes: 37 additions & 80 deletions codegen/ConverterGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,109 +9,66 @@ namespace Arinc424.Generators;

using static Constants;

public abstract class ConverterGenerator(string qualifier) : IIncrementalGenerator
public abstract class ConverterGenerator : IIncrementalGenerator
{
internal virtual StringBuilder WriteTarget(StringBuilder builder, Target target)
{
var members = target.Members.FirstOrDefault();

if (members is null)
return builder;

string offset = target.IsChar
? Char
: target.IsFlags ? $"{String}[0]" : String;

var blank = members.FirstOrDefault(x => x.IsBlank);
#pragma warning disable CS8618
protected string @base, qualifier;
#pragma warning restore CS8618
private protected abstract StringBuilder WriteTarget(StringBuilder builder, BaseTarget target);

if (blank is not null)
private void Process(SourceProductionContext context, ImmutableArray<BaseTarget> targets)
{
foreach (var target in targets)
{
var (member, argument) = blank;

string check = target.IsChar ? $"char.IsWhiteSpace({Char})" : $"{String}.IsWhiteSpace()";

_ = builder.Append($"{check} ? {member} : ");
var (name, text) = Generate(target);

members = members.Except([blank]).ToArray();
context.AddSource(name, SourceText.From(text, Encoding.UTF8));
}

return builder.WriteOffset(offset).WriteMembers(members, target.Unknown).Append("\n }");
}

private (string name, string text) Generate(Target target)
private (string name, string text) Generate(BaseTarget target)
{
var symbol = target.Symbol;

var (converter, signature, @return) = target.IsChar
? (CharConverter, CharSignature, symbol.Name)
: (StringConverter, StringSignature, $"Result<{symbol.Name}>");

string name = $"{symbol.Name}Converter";

var builder = new StringBuilder().Append($@"using {symbol.ContainingNamespace};
var builder = new StringBuilder().Append($$"""
using {{symbol.ContainingNamespace}};
namespace Arinc424.Converters;
internal abstract class {name} : {converter}<{symbol.Name}>
{{
public static {@return} Convert({signature}) => ");

_ = WriteTarget(builder, target).Append(";\n}\n");

return ($"{name}.gen.cs", builder.ToString());
}
namespace Arinc424.Converters;
private void Process(SourceProductionContext context, ImmutableArray<Target> targets)
{
foreach (var target in targets)
{
var (name, text) = Generate(target);
internal abstract class {{name}} : {{@base}}<{{symbol.Name}}>
{
""");

context.AddSource(name, SourceText.From(text, Encoding.UTF8));
}
return ($"{name}.gen.cs", WriteTarget(builder, target).Append("\n}").ToString());
}

private static bool HaveAttribute(MemberDeclarationSyntax member, string attributeName) =>
member.AttributeLists.Any(x => x.Attributes.Any(x => x.Name.ToString() == attributeName));

private static bool TryAttribute(MemberDeclarationSyntax member, string attributeName, out AttributeSyntax? attribute)
{
attribute = member.AttributeLists.SelectMany(x => x.Attributes).FirstOrDefault(x => x.Name.ToString() == attributeName);
return attribute is not null;
}
private protected virtual bool IsMatch(EnumDeclarationSyntax @enum) => true;

private IncrementalValueProvider<ImmutableArray<Target>> CreateProvider(IncrementalGeneratorInitializationContext incrementalContext)
private protected virtual BaseTarget CreateTarget(GeneratorAttributeSyntaxContext context, CancellationToken _)
{
return incrementalContext.SyntaxProvider.ForAttributeWithMetadataName
(
qualifier,
(node, _) => node is EnumDeclarationSyntax,
(context, _) =>
{
var enumSyntax = (EnumDeclarationSyntax)context.TargetNode;

var symbol = (INamedTypeSymbol)context.TargetSymbol;
var enumSyntax = (EnumDeclarationSyntax)context.TargetNode;

List<Member> members = [];
List<Member[]> offsetMembers = [];
var symbol = (INamedTypeSymbol)context.TargetSymbol;

foreach (var member in enumSyntax.Members)
{
if (HaveAttribute(member, OffsetAttribute))
{
offsetMembers.Add([.. members]);
members = [];
}
List<Member> members = [];

if (TryAttribute(member, MapAttribute, out var attribute))
members.Add(new($"{symbol.Name}.{member.Identifier}", attribute!.ArgumentList?.Arguments.First().ToString() ?? string.Empty));
}
offsetMembers.Add([.. members]);

return new Target((INamedTypeSymbol)context.TargetSymbol, [.. offsetMembers], qualifier == CharAttribute, HaveAttribute(enumSyntax, FlagsAttribute));
}
).Collect();
foreach (var member in enumSyntax.Members)
{
if (member.TryAttribute(MapAttribute, out var attribute))
members.Add(new($"{symbol.Name}.{member.Identifier}", attribute!.ArgumentList?.Arguments.First().ToString() ?? string.Empty));
}
return new Target((INamedTypeSymbol)context.TargetSymbol, [.. members]);
}

private IncrementalValueProvider<ImmutableArray<BaseTarget>> CreateProvider(IncrementalGeneratorInitializationContext incrementalContext)
=> incrementalContext.SyntaxProvider
.ForAttributeWithMetadataName(
qualifier,
(node, _) => node is EnumDeclarationSyntax @enum && IsMatch(@enum),
CreateTarget
).Collect();

public void Initialize(IncrementalGeneratorInitializationContext context) => context.RegisterSourceOutput(CreateProvider(context), Process);
}
94 changes: 94 additions & 0 deletions codegen/FlagsGenerator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
using System.Text;

using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis;

namespace Arinc424.Generators;

using static Constants;

[Generator]
public class FlagsGenerator : ConverterGenerator
{
private protected override StringBuilder WriteTarget(StringBuilder builder, BaseTarget target)
{
_ = builder.Append($@"
public static Result<{target.Symbol.Name}> Convert({StringArg})
{{
bool valid = true;
var value = {target.Unknown};").Append("\n");

var offsetMembers = ((FlagsTarget)target).GetMembersWithBlank();

string[] charDeclarations = new string[offsetMembers.Length];

for (int i = 0; i < offsetMembers.Length; i++)
charDeclarations[i] = $"{Char}{i} = {String}[{i}]";

_ = builder.Append($@"
char {string.Join(", ", charDeclarations)};").Append("\n");

for (int i = 0; i < offsetMembers.Length; i++)
{
_ = builder.Append(@$"
switch ({Char}{i})
{{");

var (members, blank) = offsetMembers[i];

_ = builder.Append(@$"
case (char)32:
value |= {blank}; break;");

foreach (var member in members)
{
var (fullName, argument) = member;

_ = builder.Append($@"
case {argument}:
value |= {fullName}; break;");
}
_ = builder.Append(@$"
default:
valid = false; break;
}}");
}
return builder.Append($@"
return valid ? value : {String};
}}");
}

private protected override BaseTarget CreateTarget(GeneratorAttributeSyntaxContext context, CancellationToken _)
{
var enumSyntax = (EnumDeclarationSyntax)context.TargetNode;

var symbol = (INamedTypeSymbol)context.TargetSymbol;

List<Member> members = [];
List<Member[]> offsetMembers = [];

foreach (var member in enumSyntax.Members)
{
if (member.HaveAttribute(OffsetAttribute))
{
offsetMembers.Add([.. members]);
members = [];
}

if (member.TryAttribute(MapAttribute, out var attribute))
members.Add(new($"{symbol.Name}.{member.Identifier}", attribute!.ArgumentList?.Arguments.First().ToString() ?? string.Empty));
}
offsetMembers.Add([.. members]);

return new FlagsTarget((INamedTypeSymbol)context.TargetSymbol, [.. offsetMembers]);
}

private protected override bool IsMatch(EnumDeclarationSyntax @enum) => @enum.HaveAttribute(FlagsAttribute);

public FlagsGenerator()
{
@base = StringConverter;
qualifier = StringAttributeQualifier;
}
}
13 changes: 10 additions & 3 deletions codegen/Member.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,17 @@
namespace Arinc424.Generators;
using System.Diagnostics;

internal class Member(string fullName, string argument)
namespace Arinc424.Generators;

[DebuggerDisplay($"{{{nameof(name)},nq}}, {{{nameof(argument)},nq}}")]
internal class Member(string name, string argument)
{
private readonly string name = name;

private readonly string argument = argument;

internal void Deconstruct(out string member, out string value)
{
member = fullName;
member = name;
value = argument;
}

Expand Down
Loading

0 comments on commit 8501753

Please sign in to comment.