code generator that automatically switches endianness
This commit is contained in:
		
							parent
							
								
									e603c154b9
								
							
						
					
					
						commit
						5532c4f5a8
					
				
					 8 changed files with 291 additions and 34 deletions
				
			
		| 
						 | 
					@ -22,6 +22,10 @@
 | 
				
			||||||
        <PackageReference Include="System.Text.Encoding.CodePages" Version="8.0.0"/>
 | 
					        <PackageReference Include="System.Text.Encoding.CodePages" Version="8.0.0"/>
 | 
				
			||||||
        <PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.1"/>
 | 
					        <PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.1"/>
 | 
				
			||||||
        <PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="8.0.1"/>
 | 
					        <PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="8.0.1"/>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <ProjectReference Include="../EndiannessSourceGenerator/EndiannessSourceGenerator.csproj"
 | 
				
			||||||
 | 
					                          OutputItemType="Analyzer"
 | 
				
			||||||
 | 
					                          ReferenceOutputAssembly="false"/>
 | 
				
			||||||
    </ItemGroup>
 | 
					    </ItemGroup>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
</Project>
 | 
					</Project>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -14,7 +14,7 @@ internal sealed class DisplayConnection(IOptions<DisplayConfiguration> options)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public ValueTask SendClearAsync()
 | 
					    public ValueTask SendClearAsync()
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        var header = new HeaderWindow { Command = DisplayCommand.Clear };
 | 
					        var header = new HeaderWindow { Command = (ushort)DisplayCommand.Clear };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return SendAsync(header, Memory<byte>.Empty);
 | 
					        return SendAsync(header, Memory<byte>.Empty);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
| 
						 | 
					@ -23,7 +23,7 @@ internal sealed class DisplayConnection(IOptions<DisplayConfiguration> options)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        var header = new HeaderWindow
 | 
					        var header = new HeaderWindow
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            Command = DisplayCommand.Cp437Data,
 | 
					            Command = (ushort)DisplayCommand.Cp437Data,
 | 
				
			||||||
            Height = grid.Height,
 | 
					            Height = grid.Height,
 | 
				
			||||||
            Width = grid.Width,
 | 
					            Width = grid.Width,
 | 
				
			||||||
            PosX = x,
 | 
					            PosX = x,
 | 
				
			||||||
| 
						 | 
					@ -37,7 +37,7 @@ internal sealed class DisplayConnection(IOptions<DisplayConfiguration> options)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        var header = new HeaderWindow
 | 
					        var header = new HeaderWindow
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            Command = DisplayCommand.CharBrightness,
 | 
					            Command = (ushort)DisplayCommand.CharBrightness,
 | 
				
			||||||
            PosX = x,
 | 
					            PosX = x,
 | 
				
			||||||
            PosY = y,
 | 
					            PosY = y,
 | 
				
			||||||
            Height = luma.Height,
 | 
					            Height = luma.Height,
 | 
				
			||||||
| 
						 | 
					@ -49,7 +49,7 @@ internal sealed class DisplayConnection(IOptions<DisplayConfiguration> options)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public async ValueTask SendBrightnessAsync(byte brightness)
 | 
					    public async ValueTask SendBrightnessAsync(byte brightness)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        var header = new HeaderWindow { Command = DisplayCommand.Brightness };
 | 
					        var header = new HeaderWindow { Command = (ushort)DisplayCommand.Brightness };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        var payloadBuffer = _arrayPool.Rent(1);
 | 
					        var payloadBuffer = _arrayPool.Rent(1);
 | 
				
			||||||
        var payload = payloadBuffer.AsMemory(0, 1);
 | 
					        var payload = payloadBuffer.AsMemory(0, 1);
 | 
				
			||||||
| 
						 | 
					@ -61,13 +61,13 @@ internal sealed class DisplayConnection(IOptions<DisplayConfiguration> options)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public ValueTask SendHardResetAsync()
 | 
					    public ValueTask SendHardResetAsync()
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        var header = new HeaderWindow { Command = DisplayCommand.HardReset };
 | 
					        var header = new HeaderWindow { Command = (ushort)DisplayCommand.HardReset };
 | 
				
			||||||
        return SendAsync(header, Memory<byte>.Empty);
 | 
					        return SendAsync(header, Memory<byte>.Empty);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public async ValueTask SendFadeOutAsync(byte loops)
 | 
					    public async ValueTask SendFadeOutAsync(byte loops)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        var header = new HeaderWindow { Command = DisplayCommand.FadeOut };
 | 
					        var header = new HeaderWindow { Command = (ushort)DisplayCommand.FadeOut };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        var payloadBuffer = _arrayPool.Rent(1);
 | 
					        var payloadBuffer = _arrayPool.Rent(1);
 | 
				
			||||||
        var payload = payloadBuffer.AsMemory(0, 1);
 | 
					        var payload = payloadBuffer.AsMemory(0, 1);
 | 
				
			||||||
| 
						 | 
					@ -81,8 +81,9 @@ internal sealed class DisplayConnection(IOptions<DisplayConfiguration> options)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        var header = new HeaderWindow
 | 
					        var header = new HeaderWindow
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            Command = DisplayCommand.BitmapLinearWin,
 | 
					            Command = (ushort)DisplayCommand.BitmapLinearWin,
 | 
				
			||||||
            PosX = x, PosY = y,
 | 
					            PosX = x,
 | 
				
			||||||
 | 
					            PosY = y,
 | 
				
			||||||
            Width = (ushort)(pixels.Width / 8),
 | 
					            Width = (ushort)(pixels.Width / 8),
 | 
				
			||||||
            Height = pixels.Height
 | 
					            Height = pixels.Height
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
| 
						 | 
					@ -113,7 +114,6 @@ internal sealed class DisplayConnection(IOptions<DisplayConfiguration> options)
 | 
				
			||||||
        var buffer = _arrayPool.Rent(messageSize);
 | 
					        var buffer = _arrayPool.Rent(messageSize);
 | 
				
			||||||
        var message = buffer.AsMemory(0, messageSize);
 | 
					        var message = buffer.AsMemory(0, messageSize);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        header.ChangeToNetworkOrder();
 | 
					 | 
				
			||||||
        MemoryMarshal.Write(message.Span, header);
 | 
					        MemoryMarshal.Write(message.Span, header);
 | 
				
			||||||
        payload.CopyTo(message[headerSize..]);
 | 
					        payload.CopyTo(message[headerSize..]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,17 +1,19 @@
 | 
				
			||||||
using System.Runtime.InteropServices;
 | 
					using System.Runtime.InteropServices;
 | 
				
			||||||
 | 
					using EndiannessSourceGenerator;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace DisplayCommands.Internals;
 | 
					namespace DisplayCommands.Internals;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[StructEndianness(IsLittleEndian = false)]
 | 
				
			||||||
[StructLayout(LayoutKind.Sequential, Pack = 16, Size = 10)]
 | 
					[StructLayout(LayoutKind.Sequential, Pack = 16, Size = 10)]
 | 
				
			||||||
internal struct HeaderBitmap
 | 
					internal partial struct HeaderBitmap
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    public DisplayCommand Command;
 | 
					    private ushort _command;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public ushort Offset;
 | 
					    private ushort _offset;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public ushort Length;
 | 
					    private ushort _length;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public DisplaySubCommand SubCommand;
 | 
					    private ushort _subCommand;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public ushort Reserved;
 | 
					    private ushort _reserved;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -1,29 +1,19 @@
 | 
				
			||||||
using System.Buffers.Binary;
 | 
					 | 
				
			||||||
using System.Runtime.InteropServices;
 | 
					using System.Runtime.InteropServices;
 | 
				
			||||||
 | 
					using EndiannessSourceGenerator;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace DisplayCommands.Internals;
 | 
					namespace DisplayCommands.Internals;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[StructEndianness(IsLittleEndian = false)]
 | 
				
			||||||
[StructLayout(LayoutKind.Sequential, Pack = 16, Size = 10)]
 | 
					[StructLayout(LayoutKind.Sequential, Pack = 16, Size = 10)]
 | 
				
			||||||
internal struct HeaderWindow
 | 
					internal partial struct HeaderWindow
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    public DisplayCommand Command;
 | 
					    private ushort _command;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public ushort PosX;
 | 
					    private ushort _posX;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public ushort PosY;
 | 
					    private ushort _posY;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public ushort Width;
 | 
					    private ushort _width;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public ushort Height;
 | 
					    private ushort _height;
 | 
				
			||||||
 | 
					 | 
				
			||||||
    public void ChangeToNetworkOrder()
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        if (!BitConverter.IsLittleEndian)
 | 
					 | 
				
			||||||
            return;
 | 
					 | 
				
			||||||
        Command = (DisplayCommand)BinaryPrimitives.ReverseEndianness((ushort)Command);
 | 
					 | 
				
			||||||
        PosX = BinaryPrimitives.ReverseEndianness(PosX);
 | 
					 | 
				
			||||||
        PosY = BinaryPrimitives.ReverseEndianness(PosY);
 | 
					 | 
				
			||||||
        Width = BinaryPrimitives.ReverseEndianness(Width);
 | 
					 | 
				
			||||||
        Height = BinaryPrimitives.ReverseEndianness(Height);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										227
									
								
								EndiannessSourceGenerator/EndiannessGenerator.cs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										227
									
								
								EndiannessSourceGenerator/EndiannessGenerator.cs
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,227 @@
 | 
				
			||||||
 | 
					using System;
 | 
				
			||||||
 | 
					using System.Collections.Generic;
 | 
				
			||||||
 | 
					using System.Linq;
 | 
				
			||||||
 | 
					using System.Text;
 | 
				
			||||||
 | 
					using Microsoft.CodeAnalysis;
 | 
				
			||||||
 | 
					using Microsoft.CodeAnalysis.CSharp;
 | 
				
			||||||
 | 
					using Microsoft.CodeAnalysis.CSharp.Syntax;
 | 
				
			||||||
 | 
					using Microsoft.CodeAnalysis.Text;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace EndiannessSourceGenerator;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					internal class DebugMeException(string message) : Exception(message);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					internal class InvalidUsageException(string message) : Exception(message);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[Generator]
 | 
				
			||||||
 | 
					public class StructEndiannessSourceGenerator : ISourceGenerator
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    private const string Namespace = "EndiannessSourceGenerator";
 | 
				
			||||||
 | 
					    private const string AttributeName = "StructEndiannessAttribute";
 | 
				
			||||||
 | 
					    private const string IsLittleEndianProperty = "IsLittleEndian";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private const string AttributeSourceCode =
 | 
				
			||||||
 | 
					        $$"""
 | 
				
			||||||
 | 
					          // <auto-generated/>
 | 
				
			||||||
 | 
					          namespace {{Namespace}}
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					              [System.AttributeUsage(System.AttributeTargets.Struct)]
 | 
				
			||||||
 | 
					              public class {{AttributeName}}: System.Attribute
 | 
				
			||||||
 | 
					              {
 | 
				
			||||||
 | 
					                  public required bool {{IsLittleEndianProperty}} { get; init; }
 | 
				
			||||||
 | 
					              }
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					          """;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private const string UsingDeclarations =
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        using System;
 | 
				
			||||||
 | 
					        using System.Buffers.Binary;
 | 
				
			||||||
 | 
					        """;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public void Initialize(GeneratorInitializationContext context)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        // Register the attribute source
 | 
				
			||||||
 | 
					        context.RegisterForPostInitialization(i => { i.AddSource($"{AttributeName}.g.cs", AttributeSourceCode); });
 | 
				
			||||||
 | 
					        // context.RegisterForSyntaxNotifications(() => new SyntaxCon);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private readonly SymbolDisplayFormat _namespacedNameFormat =
 | 
				
			||||||
 | 
					        new(typeQualificationStyle: SymbolDisplayTypeQualificationStyle.NameAndContainingTypesAndNamespaces);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // TODO: generate syntax tree with roslyn to get rid of string wrangling and so code is properly formatted
 | 
				
			||||||
 | 
					    public void Execute(GeneratorExecutionContext context)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        var treesWithStructsWithAttributes = context.Compilation.SyntaxTrees
 | 
				
			||||||
 | 
					            .Where(st => st.GetRoot().DescendantNodes()
 | 
				
			||||||
 | 
					                .OfType<StructDeclarationSyntax>()
 | 
				
			||||||
 | 
					                .Any(p => p.DescendantNodes()
 | 
				
			||||||
 | 
					                    .OfType<AttributeSyntax>()
 | 
				
			||||||
 | 
					                    .Any()))
 | 
				
			||||||
 | 
					            .ToList();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        foreach (var tree in treesWithStructsWithAttributes)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            var semanticModel = context.Compilation.GetSemanticModel(tree);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            var structsWithAttributes = tree.GetRoot().DescendantNodes()
 | 
				
			||||||
 | 
					                .OfType<StructDeclarationSyntax>()
 | 
				
			||||||
 | 
					                .Where(cd => cd.DescendantNodes()
 | 
				
			||||||
 | 
					                    .OfType<AttributeSyntax>()
 | 
				
			||||||
 | 
					                    .Any());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            foreach (var structDeclaration in structsWithAttributes)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                var foundAttribute = GetEndiannessAttribute(structDeclaration, semanticModel);
 | 
				
			||||||
 | 
					                // not my type
 | 
				
			||||||
 | 
					                if (foundAttribute == null)
 | 
				
			||||||
 | 
					                    continue;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                HandleStruct(context, structDeclaration, semanticModel, foundAttribute);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private static void HandleStruct(GeneratorExecutionContext context, StructDeclarationSyntax structDeclaration,
 | 
				
			||||||
 | 
					        SemanticModel semanticModel, AttributeSyntax foundAttribute)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        var isPartial = structDeclaration.Modifiers.Any(m => m.IsKind(SyntaxKind.PartialKeyword));
 | 
				
			||||||
 | 
					        if (!isPartial)
 | 
				
			||||||
 | 
					            throw new InvalidUsageException("struct is not marked partial");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        var accessibilityModifier = structDeclaration.Modifiers.Any(m => m.IsKind(SyntaxKind.InternalKeyword))
 | 
				
			||||||
 | 
					            ? "internal"
 | 
				
			||||||
 | 
					            : "public";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        var structType = semanticModel.GetDeclaredSymbol(structDeclaration);
 | 
				
			||||||
 | 
					        if (structType == null)
 | 
				
			||||||
 | 
					            throw new DebugMeException("struct type info is null");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        var structNamespace = structType.ContainingNamespace?.ToDisplayString();
 | 
				
			||||||
 | 
					        if (structNamespace == null)
 | 
				
			||||||
 | 
					            throw new InvalidUsageException("struct has to be contained in a namespace");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        var structIsLittleEndian = GetStructIsLittleEndian(foundAttribute);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        var generatedCode = new StringBuilder();
 | 
				
			||||||
 | 
					        generatedCode.AppendLine(UsingDeclarations);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        generatedCode.AppendLine($"namespace {structNamespace};");
 | 
				
			||||||
 | 
					        generatedCode.AppendLine($$"""{{accessibilityModifier}} partial struct {{structType.Name}} {""");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        var hasProperties = structDeclaration.Members
 | 
				
			||||||
 | 
					            .Any(m => m.IsKind(SyntaxKind.PropertyDeclaration));
 | 
				
			||||||
 | 
					        if (hasProperties)
 | 
				
			||||||
 | 
					            throw new InvalidUsageException("struct cannot have properties");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        var fieldDeclarations = structDeclaration.Members
 | 
				
			||||||
 | 
					            .Where(m => m.IsKind(SyntaxKind.FieldDeclaration)).OfType<FieldDeclarationSyntax>();
 | 
				
			||||||
 | 
					        GenerateStructProperties(generatedCode, fieldDeclarations, semanticModel, structIsLittleEndian);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        generatedCode.AppendLine("}"); // end of struct
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        context.AddSource($"{structNamespace}.{structType.Name}.g.cs",
 | 
				
			||||||
 | 
					            SourceText.From(generatedCode.ToString(), Encoding.UTF8));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private static void GenerateStructProperties(StringBuilder generatedCode,
 | 
				
			||||||
 | 
					        IEnumerable<FieldDeclarationSyntax> fieldDeclarations, SemanticModel semanticModel, bool structIsLittleEndian)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        foreach (var field in fieldDeclarations)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            if (!field.Modifiers.Any(m => m.IsKind(SyntaxKind.PrivateKeyword)))
 | 
				
			||||||
 | 
					                throw new InvalidUsageException("fields have to be private");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            var variableDeclaration = field.DescendantNodes()
 | 
				
			||||||
 | 
					                .OfType<VariableDeclarationSyntax>()
 | 
				
			||||||
 | 
					                .FirstOrDefault();
 | 
				
			||||||
 | 
					            if (variableDeclaration == null)
 | 
				
			||||||
 | 
					                throw new DebugMeException("variable declaration of field declaration null");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            var variableTypeInfo = semanticModel.GetTypeInfo(variableDeclaration.Type).Type;
 | 
				
			||||||
 | 
					            if (variableTypeInfo == null)
 | 
				
			||||||
 | 
					                throw new DebugMeException("variable type info of field declaration null");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            var typeName = variableTypeInfo.ToDisplayString();
 | 
				
			||||||
 | 
					            var fieldName = variableDeclaration.Variables.First().Identifier.ToString();
 | 
				
			||||||
 | 
					            var propertyName = GeneratePropertyName(fieldName);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            GenerateProperty(generatedCode, typeName, propertyName, structIsLittleEndian, fieldName);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private static void GenerateProperty(StringBuilder generatedCode, string typeName, string propertyName,
 | 
				
			||||||
 | 
					        bool structIsLittleEndian, string fieldName)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        generatedCode.AppendLine($$"""public {{typeName}} {{propertyName}} {""");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        var maybeNegator = structIsLittleEndian ? string.Empty : "!";
 | 
				
			||||||
 | 
					        var sameEndiannessExpression = $"{maybeNegator}BitConverter.IsLittleEndian";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        generatedCode.AppendLine($"get => {sameEndiannessExpression}");
 | 
				
			||||||
 | 
					        generatedCode.AppendLine($"    ? {fieldName}");
 | 
				
			||||||
 | 
					        generatedCode.AppendLine($"    : BinaryPrimitives.ReverseEndianness({fieldName});");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        generatedCode.AppendLine($"set => {fieldName} = {sameEndiannessExpression}");
 | 
				
			||||||
 | 
					        generatedCode.AppendLine("    ? value");
 | 
				
			||||||
 | 
					        generatedCode.AppendLine("    : BinaryPrimitives.ReverseEndianness(value);");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        generatedCode.AppendLine("}"); // end of property
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private static string GeneratePropertyName(string fieldName)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        var propertyName = fieldName;
 | 
				
			||||||
 | 
					        if (propertyName.StartsWith("_"))
 | 
				
			||||||
 | 
					            propertyName = propertyName.Substring(1);
 | 
				
			||||||
 | 
					        if (!char.IsLetter(propertyName, 0) || char.IsUpper(propertyName, 0))
 | 
				
			||||||
 | 
					            throw new InvalidUsageException("field names have to start with a lower case letter");
 | 
				
			||||||
 | 
					        propertyName = propertyName.Substring(0, 1).ToUpperInvariant()
 | 
				
			||||||
 | 
					                       + propertyName.Substring(1);
 | 
				
			||||||
 | 
					        return propertyName;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private static AttributeSyntax? GetEndiannessAttribute(StructDeclarationSyntax structDeclaration,
 | 
				
			||||||
 | 
					        SemanticModel semanticModel)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        AttributeSyntax? foundAttribute = null;
 | 
				
			||||||
 | 
					        foreach (var attributeSyntax in structDeclaration.DescendantNodes().OfType<AttributeSyntax>())
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            var attributeTypeInfo = semanticModel.GetTypeInfo(attributeSyntax).Type;
 | 
				
			||||||
 | 
					            if (attributeTypeInfo == null)
 | 
				
			||||||
 | 
					                throw new DebugMeException("attribute type info is null");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (attributeTypeInfo.ContainingNamespace?.Name != Namespace)
 | 
				
			||||||
 | 
					                continue;
 | 
				
			||||||
 | 
					            if (attributeTypeInfo.Name != AttributeName)
 | 
				
			||||||
 | 
					                continue;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            foundAttribute = attributeSyntax;
 | 
				
			||||||
 | 
					            break;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return foundAttribute;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private static bool GetStructIsLittleEndian(AttributeSyntax foundAttribute)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        var endiannessArguments = foundAttribute.ArgumentList;
 | 
				
			||||||
 | 
					        if (endiannessArguments == null)
 | 
				
			||||||
 | 
					            throw new InvalidUsageException("endianness attribute has no arguments");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        var isLittleEndianArgumentSyntax = endiannessArguments.Arguments
 | 
				
			||||||
 | 
					            .FirstOrDefault(argumentSyntax =>
 | 
				
			||||||
 | 
					                argumentSyntax.NameEquals?.Name.Identifier.ToString() == IsLittleEndianProperty);
 | 
				
			||||||
 | 
					        if (isLittleEndianArgumentSyntax == null)
 | 
				
			||||||
 | 
					            throw new InvalidUsageException("endianness attribute argument not found");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        bool? structIsLittleEndian = isLittleEndianArgumentSyntax.Expression.Kind() switch
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            SyntaxKind.FalseLiteralExpression => false,
 | 
				
			||||||
 | 
					            SyntaxKind.TrueLiteralExpression => true,
 | 
				
			||||||
 | 
					            SyntaxKind.DefaultLiteralExpression => false,
 | 
				
			||||||
 | 
					            _ => throw new InvalidUsageException($"{IsLittleEndianProperty} has to be set with a literal")
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					        return structIsLittleEndian.Value;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										23
									
								
								EndiannessSourceGenerator/EndiannessSourceGenerator.csproj
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								EndiannessSourceGenerator/EndiannessSourceGenerator.csproj
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,23 @@
 | 
				
			||||||
 | 
					<Project Sdk="Microsoft.NET.Sdk">
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <PropertyGroup>
 | 
				
			||||||
 | 
					        <TargetFramework>netstandard2.0</TargetFramework>
 | 
				
			||||||
 | 
					        <IsPackable>false</IsPackable>
 | 
				
			||||||
 | 
					        <Nullable>enable</Nullable>
 | 
				
			||||||
 | 
					        <LangVersion>latest</LangVersion>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
 | 
				
			||||||
 | 
					        <IsRoslynComponent>true</IsRoslynComponent>
 | 
				
			||||||
 | 
					        <PackageId>EndiannessSourceGenerator</PackageId>
 | 
				
			||||||
 | 
					    </PropertyGroup>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <ItemGroup>
 | 
				
			||||||
 | 
					        <PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4">
 | 
				
			||||||
 | 
					            <PrivateAssets>all</PrivateAssets>
 | 
				
			||||||
 | 
					            <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
 | 
				
			||||||
 | 
					        </PackageReference>
 | 
				
			||||||
 | 
					        <PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.9.2" PrivateAssets="all" />
 | 
				
			||||||
 | 
					        <PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="4.9.2"/>
 | 
				
			||||||
 | 
					    </ItemGroup>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					</Project>
 | 
				
			||||||
							
								
								
									
										5
									
								
								EndiannessSourceGenerator/Readme.md
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								EndiannessSourceGenerator/Readme.md
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,5 @@
 | 
				
			||||||
 | 
					# Endianness Source Generator
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					When annotating a struct with the `StructEndianness` attribute, this code generator will generate properties for the declared fields.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Each time a property is read or written, the endianness is converted from runtime endianness to struct endianness or vice-versa.
 | 
				
			||||||
| 
						 | 
					@ -4,6 +4,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TanksServer", "TanksServer\
 | 
				
			||||||
EndProject
 | 
					EndProject
 | 
				
			||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DisplayCommands", "DisplayCommands\DisplayCommands.csproj", "{B4B43561-7A2C-486B-99F7-E58A67BC370A}"
 | 
					Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DisplayCommands", "DisplayCommands\DisplayCommands.csproj", "{B4B43561-7A2C-486B-99F7-E58A67BC370A}"
 | 
				
			||||||
EndProject
 | 
					EndProject
 | 
				
			||||||
 | 
					Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EndiannessSourceGenerator", "EndiannessSourceGenerator\EndiannessSourceGenerator.csproj", "{D77FE880-F2B8-43B6-8B33-B6FA089CC337}"
 | 
				
			||||||
 | 
					EndProject
 | 
				
			||||||
Global
 | 
					Global
 | 
				
			||||||
	GlobalSection(SolutionConfigurationPlatforms) = preSolution
 | 
						GlobalSection(SolutionConfigurationPlatforms) = preSolution
 | 
				
			||||||
		Debug|Any CPU = Debug|Any CPU
 | 
							Debug|Any CPU = Debug|Any CPU
 | 
				
			||||||
| 
						 | 
					@ -18,5 +20,9 @@ Global
 | 
				
			||||||
		{B4B43561-7A2C-486B-99F7-E58A67BC370A}.Debug|Any CPU.Build.0 = Debug|Any CPU
 | 
							{B4B43561-7A2C-486B-99F7-E58A67BC370A}.Debug|Any CPU.Build.0 = Debug|Any CPU
 | 
				
			||||||
		{B4B43561-7A2C-486B-99F7-E58A67BC370A}.Release|Any CPU.ActiveCfg = Release|Any CPU
 | 
							{B4B43561-7A2C-486B-99F7-E58A67BC370A}.Release|Any CPU.ActiveCfg = Release|Any CPU
 | 
				
			||||||
		{B4B43561-7A2C-486B-99F7-E58A67BC370A}.Release|Any CPU.Build.0 = Release|Any CPU
 | 
							{B4B43561-7A2C-486B-99F7-E58A67BC370A}.Release|Any CPU.Build.0 = Release|Any CPU
 | 
				
			||||||
 | 
							{D77FE880-F2B8-43B6-8B33-B6FA089CC337}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
 | 
				
			||||||
 | 
							{D77FE880-F2B8-43B6-8B33-B6FA089CC337}.Debug|Any CPU.Build.0 = Debug|Any CPU
 | 
				
			||||||
 | 
							{D77FE880-F2B8-43B6-8B33-B6FA089CC337}.Release|Any CPU.ActiveCfg = Release|Any CPU
 | 
				
			||||||
 | 
							{D77FE880-F2B8-43B6-8B33-B6FA089CC337}.Release|Any CPU.Build.0 = Release|Any CPU
 | 
				
			||||||
	EndGlobalSection
 | 
						EndGlobalSection
 | 
				
			||||||
EndGlobal
 | 
					EndGlobal
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue