diff --git a/src/Kafa.Generators/Kafa.Generators.csproj b/src/Kafa.Generators/Kafa.Generators.csproj
index f558ae9..705186e 100644
--- a/src/Kafa.Generators/Kafa.Generators.csproj
+++ b/src/Kafa.Generators/Kafa.Generators.csproj
@@ -1,9 +1,18 @@
- netstandard2.0
+ net7.0;netstandard2.0
+ 11.0
+
+ true
+ false
+ true
+ true
+ true
+
+
all
diff --git a/src/Kafa.Generators/KafaSourceGenerator.Attribute.cs b/src/Kafa.Generators/KafaSourceGenerator.Attribute.cs
new file mode 100644
index 0000000..6c43e39
--- /dev/null
+++ b/src/Kafa.Generators/KafaSourceGenerator.Attribute.cs
@@ -0,0 +1,27 @@
+namespace nyingi.Kafa.Generators;
+
+internal sealed partial class KafaSourceGenerator
+{
+ private static readonly string GeneratedAttribute = $@"[global::System.CodeDom.Compiler.GeneratedCodeAttribute(""{typeof(KafaSourceGenerator).Assembly.GetName().Name}"", ""0.1.0"")]";
+
+ //lang=c#
+ private static readonly string KafaSerializableAttribute = $@"
+//
+
+#nullable enable
+
+namespace nyingi.Kafa.Generators;
+
+{GeneratedAttribute}
+[global::System.AttributeUsage(global::System.AttributeTargets.Class, AllowMultiple =true, Inherited = false)]
+internal sealed class KafaSerializableAttribute : global::System.Attribute
+{{
+ public global::System.Type Type {{ get; }}
+
+ public KafaSerializableAttribute(global::System.Type type)
+ {{
+ Type = type;
+ }}
+}}
+";
+}
\ No newline at end of file
diff --git a/src/Kafa.Generators/KafaSourceGenerator.Context.cs b/src/Kafa.Generators/KafaSourceGenerator.Context.cs
new file mode 100644
index 0000000..6de4900
--- /dev/null
+++ b/src/Kafa.Generators/KafaSourceGenerator.Context.cs
@@ -0,0 +1,17 @@
+using System.Collections.Immutable;
+using Microsoft.CodeAnalysis;
+
+namespace nyingi.Kafa.Generators
+{
+ internal sealed partial class KafaSourceGenerator
+ {
+ private record struct KafaGeneratorContext(string? actualnamespace, string name, TypeContext typeContext);
+
+ private record struct TypeContext(bool isReferenceType, string name, ImmutableArray propertyContext);
+
+ private record struct PropertyContext(string type, string name);
+
+ private static readonly SymbolDisplayFormat Format = SymbolDisplayFormat.FullyQualifiedFormat.WithMiscellaneousOptions(SymbolDisplayMiscellaneousOptions.UseSpecialTypes | SymbolDisplayMiscellaneousOptions.ExpandNullable | SymbolDisplayMiscellaneousOptions.IncludeNullableReferenceTypeModifier);
+
+ }
+}
\ No newline at end of file
diff --git a/src/Kafa.Generators/KafaSourceGenerator.Emitter.cs b/src/Kafa.Generators/KafaSourceGenerator.Emitter.cs
deleted file mode 100644
index d63f7c3..0000000
--- a/src/Kafa.Generators/KafaSourceGenerator.Emitter.cs
+++ /dev/null
@@ -1,19 +0,0 @@
-using Microsoft.CodeAnalysis;
-
-namespace Kafa.Generators
-{
-
- [Generator]
- public class KafaSourceGenerator : ISourceGenerator
- {
- public void Execute(GeneratorExecutionContext context)
- {
- throw new System.NotImplementedException();
- }
-
- public void Initialize(GeneratorInitializationContext context)
- {
- throw new System.NotImplementedException();
- }
- }
-}
diff --git a/src/Kafa.Generators/KafaSourceGenerator.cs b/src/Kafa.Generators/KafaSourceGenerator.cs
new file mode 100644
index 0000000..3f97d29
--- /dev/null
+++ b/src/Kafa.Generators/KafaSourceGenerator.cs
@@ -0,0 +1,171 @@
+using System;
+using System.Collections.Immutable;
+using System.Diagnostics.CodeAnalysis;
+using System.Linq;
+using System.Runtime.CompilerServices;
+using System.Text;
+using System.Threading;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+using Microsoft.CodeAnalysis.Text;
+
+namespace nyingi.Kafa.Generators
+{
+
+ [Generator]
+ internal sealed partial class KafaSourceGenerator : IIncrementalGenerator
+ {
+ private const string _kafaAttributeName = "nyingi.Kafa.Generators.KafaSerializableAttribute";
+ public void Initialize(IncrementalGeneratorInitializationContext context)
+ {
+ context.RegisterPostInitializationOutput(PostInitializationCallback);
+
+
+ IncrementalValuesProvider provider = context.SyntaxProvider
+ .CreateSyntaxProvider(SyntacticPredicate, SemanticTransformer)
+ .Where(static ((INamedTypeSymbol, INamedTypeSymbol)? types) => types.HasValue)
+ .Select(static ((INamedTypeSymbol, INamedTypeSymbol)? types, CancellationToken cancellationToken) =>
+ TransformType(types!.Value));
+
+ }
+
+ private static void PostInitializationCallback(IncrementalGeneratorPostInitializationContext context)
+ {
+ context.AddSource("KafaSerializableAttribute.g.cs", SourceText.From(KafaSerializableAttribute, Encoding.UTF8));
+ }
+ private static bool SyntacticPredicate(SyntaxNode syntaxNode, CancellationToken cancellationToken)
+ {
+ return syntaxNode is ClassDeclarationSyntax
+ {
+ AttributeLists.Count: > 0,
+ } candidate
+ && candidate.Modifiers.Any(SyntaxKind.PartialKeyword)
+ && !candidate.Modifiers.Any(SyntaxKind.StaticKeyword);
+ }
+
+ private static (INamedTypeSymbol, INamedTypeSymbol)? SemanticTransformer(GeneratorSyntaxContext context, CancellationToken cancellationToken)
+ {
+ var classDeclarationSyntax = (ClassDeclarationSyntax)context.Node;
+
+ INamedTypeSymbol? namedTypeSymbol = context.SemanticModel.GetDeclaredSymbol(classDeclarationSyntax, cancellationToken);
+
+
+ if (namedTypeSymbol is not null
+ && TryGetAttribute(classDeclarationSyntax, _kafaAttributeName, context.SemanticModel, cancellationToken,
+ out AttributeSyntax? attributeSyntax)
+ && TryGetType(attributeSyntax, context.SemanticModel, cancellationToken,
+ out INamedTypeSymbol? typeSymbol))
+ {
+ return (namedTypeSymbol, typeSymbol);
+ }
+
+ return null;
+ }
+
+
+ private static bool TryGetAttribute(ClassDeclarationSyntax classDeclarationSyntax, string attributeName,
+ SemanticModel semanticModel, CancellationToken cancellationToken,
+ out AttributeSyntax? value)
+ {
+ foreach (AttributeListSyntax attributeList in classDeclarationSyntax.AttributeLists)
+ {
+ foreach (AttributeSyntax attributeSyntax in attributeList.Attributes)
+ {
+ SymbolInfo info = semanticModel.GetSymbolInfo(attributeSyntax, cancellationToken);
+ ISymbol? symbol = info.Symbol;
+
+ if (symbol is IMethodSymbol methodSymbol
+ && methodSymbol.ContainingType.ToDisplayString()
+ .Equals(attributeName, StringComparison.Ordinal))
+ {
+ value = attributeSyntax;
+ return true;
+ }
+ }
+ }
+
+ value = null;
+ return false;
+ }
+
+
+ private static bool TryGetType(AttributeSyntax attributeSyntax, SemanticModel semanticModel,
+ CancellationToken cancellationToken, out INamedTypeSymbol? namedTypeSymbol)
+ {
+ var argumentList = attributeSyntax.ArgumentList;
+ if (argumentList!.Arguments.Count > 1) ;
+ {
+ AttributeArgumentSyntax argumentSyntax = argumentList.Arguments[0];
+
+ if (argumentSyntax.Expression is TypeOfExpressionSyntax typeOf)
+ {
+ SymbolInfo info = semanticModel.GetSymbolInfo(typeOf.Type, cancellationToken);
+ ISymbol? symbol = info.Symbol;
+
+ if (symbol is INamedTypeSymbol typeSymbol)
+ {
+ namedTypeSymbol = typeSymbol;
+ return true;
+ }
+ }
+ }
+
+ namedTypeSymbol = null;
+ return false;
+ }
+
+ private static KafaGeneratorContext TransformType((INamedTypeSymbol kafaPartialGeneratorType, INamedTypeSymbol targetType) types)
+ {
+ string? @namespace = types.kafaPartialGeneratorType.ContainingNamespace.IsGlobalNamespace
+ ? null
+ : types.kafaPartialGeneratorType.ContainingNamespace.ToDisplayString();
+
+ string name = types.kafaPartialGeneratorType.Name;
+
+ bool isReferenceType = types.kafaPartialGeneratorType.IsReferenceType;
+ string targetTypeName = types.targetType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
+
+ bool areInternalSymbolsAccessible =
+ types.targetType.ContainingAssembly.GivesAccessTo((types.kafaPartialGeneratorType.ContainingAssembly));
+
+ ImmutableArray propertyContexts = types.targetType.GetTypeAndSubtypes()
+ .Reverse()
+ .SelectMany(static type => type.GetMembers())
+ .Where(member => FilterProperty(member, areInternalSymbolsAccessible))
+ .Select(static member => TransformProperty(member))
+ .Distinct()
+ .ToImmutableArray();
+
+
+ return new KafaGeneratorContext(@namespace, name,
+ new TypeContext(isReferenceType, targetTypeName, propertyContexts));
+ }
+
+
+ private static bool FilterProperty(ISymbol member, bool areInernalSymbolsAccessible)
+ {
+ if (!member.IsStatic && member.Kind == SymbolKind.Property)
+ {
+ var property = (IPropertySymbol)member;
+
+ return property.GetMethod is { } get
+ && (get.DeclaredAccessibility is Accessibility.Public
+ || (areInernalSymbolsAccessible &&
+ (get.DeclaredAccessibility is Accessibility.Internal
+ or Accessibility.ProtectedOrInternal)));
+ }
+
+ return false;
+ }
+
+ private static PropertyContext TransformProperty(ISymbol member)
+ {
+ var property = (IPropertySymbol)member;
+ string type = property.Type.ToDisplayString(Format);
+ string name = property.Name;
+
+ return new PropertyContext(type, name);
+ }
+ }
+}
diff --git a/src/Kafa.Generators/NameTypeSymbolExtensions.cs b/src/Kafa.Generators/NameTypeSymbolExtensions.cs
new file mode 100644
index 0000000..a3c5587
--- /dev/null
+++ b/src/Kafa.Generators/NameTypeSymbolExtensions.cs
@@ -0,0 +1,19 @@
+using System.Collections.Generic;
+using Microsoft.CodeAnalysis;
+
+namespace nyingi.Kafa.Generators;
+
+internal static class NameTypeSymbolExtensions
+{
+
+ public static IEnumerable GetTypeAndSubtypes(this INamedTypeSymbol? type)
+ {
+ INamedTypeSymbol? current = type;
+ while (current is not null)
+ {
+ yield return current;
+ current = current.BaseType;
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/src/Kafa.sln b/src/Kafa.sln
index ae10311..19fa62a 100644
--- a/src/Kafa.sln
+++ b/src/Kafa.sln
@@ -7,6 +7,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Kafa", "Kafa\Kafa.csproj",
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "KafaTests", "KafaTests\KafaTests.csproj", "{BB863E7D-755B-4626-90C3-6AAA1A8B4FAD}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Kafa.Generators", "Kafa.Generators\Kafa.Generators.csproj", "{65208E1E-7289-451E-8C66-578B98F4CC90}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -21,6 +23,10 @@ Global
{BB863E7D-755B-4626-90C3-6AAA1A8B4FAD}.Debug|Any CPU.Build.0 = Debug|Any CPU
{BB863E7D-755B-4626-90C3-6AAA1A8B4FAD}.Release|Any CPU.ActiveCfg = Release|Any CPU
{BB863E7D-755B-4626-90C3-6AAA1A8B4FAD}.Release|Any CPU.Build.0 = Release|Any CPU
+ {65208E1E-7289-451E-8C66-578B98F4CC90}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {65208E1E-7289-451E-8C66-578B98F4CC90}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {65208E1E-7289-451E-8C66-578B98F4CC90}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {65208E1E-7289-451E-8C66-578B98F4CC90}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/src/Kafa/IKafaFormatter.cs b/src/Kafa/IKafaFormatter.cs
new file mode 100644
index 0000000..0142cb2
--- /dev/null
+++ b/src/Kafa/IKafaFormatter.cs
@@ -0,0 +1,10 @@
+using nyingi.Kafa.Reader;
+using nyingi.Kafa.Writer;
+
+namespace nyingi.Kafa;
+
+public interface IKafaFormatter
+{
+ public static abstract void Serialize(ref KafaWriter writer, scoped ref T? entity);
+ public static abstract void Deserialize(ref KafaReader reader, scoped ref T? entity);
+}
\ No newline at end of file
diff --git a/src/Kafa/Kafa.csproj b/src/Kafa/Kafa.csproj
index 5cba791..68c1d16 100644
--- a/src/Kafa/Kafa.csproj
+++ b/src/Kafa/Kafa.csproj
@@ -5,6 +5,11 @@
enable
enable
true
- Generated
+
+
+
+
+
diff --git a/src/Kafa/Writer/KafaWriter.cs b/src/Kafa/Writer/KafaWriter.cs
index 0523b68..d9263c8 100644
--- a/src/Kafa/Writer/KafaWriter.cs
+++ b/src/Kafa/Writer/KafaWriter.cs
@@ -12,7 +12,7 @@ public class KafaWriter : IDisposable
private readonly byte[] _winNewLine = new byte[2] {(byte)'\r',(byte)'\n'};
private readonly KafaOptions _options;
-
+ private readonly byte[] _cachedNewLine;
private byte[] _separator = new byte[1];
public KafaWriter(in IBufferWriter bufferWriter, KafaOptions options)
@@ -20,12 +20,14 @@ public KafaWriter(in IBufferWriter bufferWriter, KafaOptions options)
_bufferWriter = bufferWriter ?? throw new NullReferenceException(nameof(bufferWriter));
_options = options;
_separator[0] = (byte)_options.Separator;
+ _cachedNewLine = Environment.OSVersion.Platform == PlatformID.Unix ? _unixNewLine : _winNewLine;
}
public KafaWriter(in Stream stream, KafaOptions options)
{
_stream = stream;
_options = options;
_kafaPooledWriter = new KafaPooledWriter(); // use the default 65k
+ _cachedNewLine = Environment.OSVersion.Platform == PlatformID.Unix ? _unixNewLine : _winNewLine;
}
public void WriteSeparator()
@@ -35,8 +37,7 @@ public void WriteSeparator()
public void WriteLine()
{
- var newLine = Environment.OSVersion.Platform == PlatformID.Unix ? _unixNewLine : _winNewLine;
- Write(newLine);
+ Write(_cachedNewLine);
}
public void Write(string str)