From bae28b25523c1871237eaf8230b32882852c8edb Mon Sep 17 00:00:00 2001 From: Alex Macocian Date: Sat, 17 May 2025 17:50:29 +0200 Subject: [PATCH] Implement FixedArrayGenerator --- .../FixedArrayGenerator.cs | 142 ++++++++++++++++++ ...emExtensions.NetStandard.Generators.csproj | 4 +- 2 files changed, 144 insertions(+), 2 deletions(-) create mode 100644 SystemExtensions.NetStandard.Generators/FixedArrayGenerator.cs diff --git a/SystemExtensions.NetStandard.Generators/FixedArrayGenerator.cs b/SystemExtensions.NetStandard.Generators/FixedArrayGenerator.cs new file mode 100644 index 0000000..89dc928 --- /dev/null +++ b/SystemExtensions.NetStandard.Generators/FixedArrayGenerator.cs @@ -0,0 +1,142 @@ +using Microsoft.CodeAnalysis; +using Sybil; +using System.Text; +using System.Linq; + +namespace System.Extensions; +#nullable enable +[Generator(LanguageNames.CSharp)] +public class FixedArrayGenerator : IIncrementalGenerator +{ + private const string UsingSystem = "System"; + private const string UsingSystemRuntimeInterop = "System.Runtime.InteropServices"; + private const string AttributeNamespace = "System.Extensions"; + private const string AttributeName = "GenerateFixedArrayAttribute"; + private const string AttributeShortName = "GenerateFixedArray"; + private const string Array = "Array"; + private const string Public = "public"; + private const string Struct = "struct"; + private const string Required = "required"; + private const string Size = "Size"; + private const string Int = "int"; + private const string TType = "T"; + + public void Initialize(IncrementalGeneratorInitializationContext context) + { + context.RegisterPostInitializationOutput(context => + { + var compilationUnitBuilder = SyntaxBuilder.CreateCompilationUnit() + .WithNamespace( + SyntaxBuilder.CreateNamespace(AttributeNamespace) + .WithClass(SyntaxBuilder.CreateClass(AttributeName) + .WithModifier(Public) + .WithTypeParameter(SyntaxBuilder.CreateTypeParameter(TType)) + .WithTypeParameterConstraint(SyntaxBuilder.CreateTypeParameterConstraint(TType) + .WithType(Struct)) + .WithConstructor(SyntaxBuilder.CreateConstructor(AttributeName) + .WithModifier(Public)) + .WithAttribute(SyntaxBuilder.CreateAttribute("AttributeUsage") + .WithArgument(AttributeTargets.Assembly) + .WithArgument("Inherited", false) + .WithArgument("AllowMultiple", false)) + .WithProperty(SyntaxBuilder.CreateProperty(Int, Size) + .WithModifier(Public) + .WithModifier(Required) + .WithAccessor(SyntaxBuilder.CreateGetter()) + .WithAccessor(SyntaxBuilder.CreateSetter())) + .WithBaseClass(nameof(Attribute)))); + var compilationUnitSyntax = compilationUnitBuilder.Build(); + var source = compilationUnitSyntax.ToFullString(); + context.AddSource($"{AttributeName}.g", source); + }); + + context.RegisterSourceOutput(context.CompilationProvider, (sourceProductionContext, compilation) => + { + var assemblyAttributes = + compilation.Assembly.GetAttributes().Where(a => a.AttributeClass != null && + (a.AttributeClass.Name == AttributeName || a.AttributeClass.Name == AttributeShortName)); + + foreach(var attr in assemblyAttributes) + { + // Extract the type argument (T) from GenerateFixedArrayAttribute + var namedTypeSymbol = attr.AttributeClass as INamedTypeSymbol; + if (namedTypeSymbol is null || namedTypeSymbol.TypeArguments.Length != 1) + { + continue; + } + + var targetType = namedTypeSymbol.TypeArguments[0].ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat); + // Extract the Size parameter from the named arguments. + int? fixedSize = default; + foreach (var arg in attr.NamedArguments) + { + if (arg.Key == Size && arg.Value.Value is int intValue) + { + fixedSize = intValue; + break; + } + } + + if (fixedSize is null) + { + // As Size is required, skip if not provided. + continue; + } + + Execute(sourceProductionContext, targetType, (int)fixedSize); + } + }); + } + + private static void Execute(SourceProductionContext sourceProductionContext, string targetType, int size) + { + var capitalizedType = targetType.ToUpperInvariant()[0] + targetType.Substring(1); + var structName = $"{Array}{size}{capitalizedType}"; + var builder = new StringBuilder(); + builder.AppendLine($"using {UsingSystem};"); + builder.AppendLine($"using {UsingSystemRuntimeInterop};"); + builder.AppendLine(); + builder.AppendLine($"namespace {AttributeNamespace}"); + builder.AppendLine("{"); + builder.AppendLine(" [StructLayout(LayoutKind.Sequential, Pack = 1)]"); + builder.AppendLine($" public unsafe struct {structName}"); + builder.AppendLine(" {"); + builder.AppendLine($" private const int Size = {size};"); + builder.AppendLine(); + builder.AppendLine($" private fixed {targetType} elements[Size];"); + builder.AppendLine(); + builder.AppendLine(" public int Length => Size;"); + builder.AppendLine(); + builder.AppendLine($" public ref {targetType} this[int index]"); + builder.AppendLine(" {"); + builder.AppendLine(" get"); + builder.AppendLine(" {"); + builder.AppendLine(" if (index >= Size) throw new IndexOutOfRangeException();"); + builder.AppendLine($" fixed ({targetType}* ptr = this.elements)"); + builder.AppendLine(" {"); + builder.AppendLine(" return ref ptr[index];"); + builder.AppendLine(" }"); + builder.AppendLine(" }"); + builder.AppendLine(" }"); + builder.AppendLine(); + builder.AppendLine($" public Span<{targetType}> AsSpan()"); + builder.AppendLine(" {"); + builder.AppendLine($" fixed ({targetType}* ptr = this.elements)"); + builder.AppendLine(" {"); + builder.AppendLine($" return new Span<{targetType}>(ptr, Size);"); + builder.AppendLine(" }"); + builder.AppendLine(" }"); + builder.AppendLine(); + builder.AppendLine($" public static {structName} FromBytes(byte[] array)"); + builder.AppendLine(" {"); + builder.AppendLine($" var arr = default({structName});"); + builder.AppendLine(" array.AsSpan().CopyTo(arr.AsSpan());"); + builder.AppendLine(" return arr;"); + builder.AppendLine(" }"); + builder.AppendLine(" }"); + builder.AppendLine("}"); + + sourceProductionContext.AddSource($"{structName}.g.cs", builder.ToString()); + } +} +#nullable disable diff --git a/SystemExtensions.NetStandard.Generators/SystemExtensions.NetStandard.Generators.csproj b/SystemExtensions.NetStandard.Generators/SystemExtensions.NetStandard.Generators.csproj index 36d3928..54411dc 100644 --- a/SystemExtensions.NetStandard.Generators/SystemExtensions.NetStandard.Generators.csproj +++ b/SystemExtensions.NetStandard.Generators/SystemExtensions.NetStandard.Generators.csproj @@ -5,7 +5,7 @@ true Alexandru Macocian Source generators extensions for netstandard2.0. - 0.1.2 + 0.1.3 latest true true @@ -18,7 +18,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - +