diff --git a/SystemExtensions.NetStandard.Generators/BlittableStringGenerator.cs b/SystemExtensions.NetStandard.Generators/BlittableStringGenerator.cs new file mode 100644 index 0000000..8844c53 --- /dev/null +++ b/SystemExtensions.NetStandard.Generators/BlittableStringGenerator.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 BlittableStringGenerator : IIncrementalGenerator +{ + private const string UsingSystem = "System"; + private const string UsingSystemRuntimeInterop = "System.Runtime.InteropServices"; + private const string AttributeNamespace = "System.Extensions"; + private const string AttributeName = "GenerateBlittableStringAttribute"; + private const string AttributeShortName = "GenerateBlittableString"; + private const string String = "String"; + private const string Public = "public"; + private const string Required = "required"; + private const string Size = "Size"; + private const string Int = "int"; + + public void Initialize(IncrementalGeneratorInitializationContext context) + { + context.RegisterPostInitializationOutput(context => + { + var compilationUnitBuilder = SyntaxBuilder.CreateCompilationUnit() + .WithNamespace( + SyntaxBuilder.CreateNamespace(AttributeNamespace) + .WithClass(SyntaxBuilder.CreateClass(AttributeName) + .WithModifier(Public) + .WithConstructor(SyntaxBuilder.CreateConstructor(AttributeName) + .WithModifier(Public)) + .WithAttribute(SyntaxBuilder.CreateAttribute("AttributeUsage") + .WithArgument(AttributeTargets.Assembly) + .WithArgument("Inherited", false) + .WithArgument("AllowMultiple", true)) + .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 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, (int)fixedSize); + } + }); + } + + private static void Execute(SourceProductionContext sourceProductionContext, int size) + { + var structName = $"{String}{size}"; + 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 partial struct {structName}"); + builder.AppendLine("{"); + builder.AppendLine($" public const int Size = {size};"); + builder.AppendLine(); + builder.AppendLine(" public fixed char Value[Size];"); + builder.AppendLine(); + builder.AppendLine(" public Span AsSpan()"); + builder.AppendLine(" {"); + builder.AppendLine(" fixed (char* ptr = this.Value)"); + builder.AppendLine(" {"); + builder.AppendLine(" return MemoryMarshal.CreateSpan(ref *ptr, Size);"); + builder.AppendLine(" }"); + builder.AppendLine(" }"); + builder.AppendLine(); + builder.AppendLine(" public readonly ReadOnlySpan AsReadOnlySpan()"); + builder.AppendLine(" {"); + builder.AppendLine(" fixed (char* ptr = this.Value)"); + builder.AppendLine(" {"); + builder.AppendLine(" return new ReadOnlySpan(ptr, Size);"); + builder.AppendLine(" }"); + builder.AppendLine(" }"); + builder.AppendLine(); + builder.AppendLine(" public void Set(ReadOnlySpan value)"); + builder.AppendLine(" {"); + builder.AppendLine(" fixed (char* ptr = this.Value)"); + builder.AppendLine(" {"); + builder.AppendLine(" var span = MemoryMarshal.CreateSpan(ref *ptr, Size);"); + builder.AppendLine(" span.Clear();"); + builder.AppendLine(" value[..Math.Min(value.Length, Size - 1)].CopyTo(span);"); + builder.AppendLine(" }"); + builder.AppendLine(" }"); + builder.AppendLine(); + builder.AppendLine(" public override readonly string ToString()"); + builder.AppendLine(" {"); + builder.AppendLine(" var span = this.AsReadOnlySpan();"); + builder.AppendLine(" var nullIndex = span.IndexOf('\\0');"); + builder.AppendLine(" return nullIndex >= 0 ? new string(span[..nullIndex]) : new string(span);"); + builder.AppendLine(" }"); + builder.AppendLine(); + builder.AppendLine($" public static implicit operator string({structName} value) => value.ToString();"); + builder.AppendLine(); + builder.AppendLine($" public static implicit operator {structName}(string value)"); + builder.AppendLine(" {"); + builder.AppendLine($" var result = default({structName});"); + builder.AppendLine(" result.Set(value);"); + builder.AppendLine(" return result;"); + builder.AppendLine(" }"); + builder.AppendLine(); + builder.AppendLine($" public static implicit operator ReadOnlySpan({structName} value) => value.AsReadOnlySpan();"); + builder.AppendLine("}"); + + sourceProductionContext.AddSource($"{structName}.g.cs", builder.ToString()); + } +} +#nullable disable diff --git a/SystemExtensions.NetStandard.Generators/FixedArrayGenerator.cs b/SystemExtensions.NetStandard.Generators/FixedArrayGenerator.cs index 8d1b48e..18623ca 100644 --- a/SystemExtensions.NetStandard.Generators/FixedArrayGenerator.cs +++ b/SystemExtensions.NetStandard.Generators/FixedArrayGenerator.cs @@ -56,7 +56,7 @@ public class FixedArrayGenerator : IIncrementalGenerator compilation.Assembly.GetAttributes().Where(a => a.AttributeClass != null && (a.AttributeClass.Name == AttributeName || a.AttributeClass.Name == AttributeShortName)); - foreach(var attr in assemblyAttributes) + foreach (var attr in assemblyAttributes) { // Extract the type argument (T) from GenerateFixedArrayAttribute var namedTypeSymbol = attr.AttributeClass as INamedTypeSymbol; @@ -96,47 +96,69 @@ public class FixedArrayGenerator : IIncrementalGenerator builder.AppendLine($"using {UsingSystem};"); builder.AppendLine($"using {UsingSystemRuntimeInterop};"); builder.AppendLine(); - builder.AppendLine($"namespace {AttributeNamespace}"); + builder.AppendLine($"namespace {AttributeNamespace};"); + builder.AppendLine(); + builder.AppendLine("[StructLayout(LayoutKind.Sequential, Pack = 1)]"); + builder.AppendLine($"public unsafe struct {structName}"); builder.AppendLine("{"); - builder.AppendLine(" [StructLayout(LayoutKind.Sequential, Pack = 1)]"); - builder.AppendLine($" public unsafe struct {structName}"); + 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($" 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(" get"); 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(" if ((uint)index >= Size) throw new IndexOutOfRangeException();"); + builder.AppendLine($" return ref this.elements[index];"); builder.AppendLine(" }"); + builder.AppendLine(" }"); builder.AppendLine(); - builder.AppendLine($" public Span<{targetType}> AsSpan()"); + builder.AppendLine($" public Span<{targetType}> AsSpan()"); + builder.AppendLine(" {"); + builder.AppendLine($" fixed ({targetType}* ptr = this.elements)"); builder.AppendLine(" {"); - builder.AppendLine($" fixed ({targetType}* ptr = this.elements)"); - builder.AppendLine(" {"); - builder.AppendLine($" return new Span<{targetType}>(ptr, Size);"); - builder.AppendLine(" }"); + builder.AppendLine($" return MemoryMarshal.CreateSpan(ref *ptr, Size);"); builder.AppendLine(" }"); + builder.AppendLine(" }"); builder.AppendLine(); - builder.AppendLine($" public static {structName} From{capitalizedType}Array({targetType}[] array)"); + builder.AppendLine($" public readonly ReadOnlySpan<{targetType}> AsReadOnlySpan()"); + builder.AppendLine(" {"); + builder.AppendLine($" fixed ({targetType}* ptr = this.elements)"); builder.AppendLine(" {"); - builder.AppendLine($" var arr = default({structName});"); - builder.AppendLine(" array.AsSpan().CopyTo(arr.AsSpan());"); - builder.AppendLine(" return arr;"); + builder.AppendLine($" return new ReadOnlySpan<{targetType}>(ptr, Size);"); builder.AppendLine(" }"); builder.AppendLine(" }"); + builder.AppendLine(); + builder.AppendLine($" public void Set(ReadOnlySpan<{targetType}> value)"); + builder.AppendLine(" {"); + builder.AppendLine($" fixed ({targetType}* ptr = this.elements)"); + builder.AppendLine(" {"); + builder.AppendLine($" var span = MemoryMarshal.CreateSpan(ref *ptr, Size);"); + builder.AppendLine(" span.Clear();"); + builder.AppendLine(" value[..Math.Min(value.Length, Size)].CopyTo(span);"); + builder.AppendLine(" }"); + builder.AppendLine(" }"); + builder.AppendLine(); + builder.AppendLine($" public void Clear()"); + builder.AppendLine(" {"); + builder.AppendLine($" fixed ({targetType}* ptr = this.elements)"); + builder.AppendLine(" {"); + builder.AppendLine($" MemoryMarshal.CreateSpan(ref *ptr, Size).Clear();"); + builder.AppendLine(" }"); + builder.AppendLine(" }"); + builder.AppendLine(); + builder.AppendLine($" public static {structName} From{capitalizedType}Array({targetType}[] array)"); + builder.AppendLine(" {"); + builder.AppendLine($" var arr = default({structName});"); + builder.AppendLine(" arr.Set(array);"); + builder.AppendLine(" return arr;"); + builder.AppendLine(" }"); builder.AppendLine("}"); sourceProductionContext.AddSource($"{structName}.g.cs", builder.ToString()); } } -#nullable disable +#nullable disable \ No newline at end of file diff --git a/SystemExtensions.NetStandard.Generators/SystemExtensions.NetStandard.Generators.csproj b/SystemExtensions.NetStandard.Generators/SystemExtensions.NetStandard.Generators.csproj index 7a454eb..ed541c1 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.6 + 0.1.7 latest true true