Innovenergy_trunk/csharp/Lib/SrcGen/NestProperties.cs

251 lines
8.6 KiB
C#

using InnovEnergy.Lib.SrcGen.Trees;
using InnovEnergy.Lib.Utils;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Sawmill;
using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
using static Microsoft.CodeAnalysis.CSharp.SyntaxKind;
namespace InnovEnergy.Lib.SrcGen;
public static class NestProperties
{
private static readonly SyntaxToken PublicKeyword = Token(SyntaxKind.PublicKeyword);
public static async Task Generate(Project project, Compilation compilation)
{
var syntaxTrees = await project
.Documents
.Where(d => d.Project == project)
.Where(d => d.SupportsSyntaxTree)
.Select(async d => await d.GetSyntaxRootAsync())
.WhenAll();
syntaxTrees.NotNull()
.SelectMany(c => c.GetNestPropertiesAttributes())
.ForEach(GenerateFile);
String GenerateFile(AttributeSyntax attribute)
{
var typeName = attribute.ArgumentList!.Arguments.Single().ToString().Replace("\"", "");
var type = attribute.GetAncestor<TypeDeclarationSyntax>();
var nameSpace = attribute.GetAncestor<NamespaceDeclarationSyntax>();
var filePath = type.SyntaxTree.FilePath;
var generatedPath = filePath.GetGeneratedPath(nameof(NestProperties));
var recordProperties = type
.Members
.OfType<PropertyDeclarationSyntax>()
.Where(m => m.Modifiers.Any(mod => mod.IsKind(SyntaxKind.PublicKeyword)))
.ToList();
var propNames = recordProperties.Select(m => m.Identifier.ValueText);
var prefixTree = PrefixTree.Create(propNames);
prefixTree.WriteLine();
Console.WriteLine("$$$$$$$$$$$$$");
var wrappedTypeName = type.Identifier.ValueText;
var records = new List<RecordDeclarationSyntax>();
prefixTree.Fold<Tree<String>, MemberDeclarationSyntax>((memory, tree) =>
{
var path = tree
.Ancestors
.Prepend(tree)
.Aggregate("", (a, b) => b.Node + a);
if (tree.IsLeaf)
{
var prop = recordProperties.First(p => p.Identifier.ValueText == path);
var writable = prop
.DescendantNodes()
.OfType<AccessorDeclarationSyntax>()
.Any(d => d.IsKind(SetAccessorDeclaration)
&& !d.DescendantTokens().Any(t => t.IsKind(PrivateKeyword)));
return CreateAccessorProperty
(
prop.Type,
tree.Node,
path,
writable
);
}
else
{
var children = memory.ToArray();
var rec = CreateWrapperRecord(typeName + path, wrappedTypeName, children);
records.Add(rec);
return CreateWrapperProperty(IdentifierName(typeName + path), Identifier(tree.Node));
}
});
//x.NormalizeWhitespace().GetText().WriteLine();
records.Reverse();
records.ForEach(r => r.NormalizeWhitespace().WriteLine("\n"));
Console.WriteLine($"Generating {generatedPath}");
return "";
}
}
private static CompilationUnitSyntax CreateCompilationUnit(NameSyntax nameSpaceName, params MemberDeclarationSyntax[] members)
{
var nameSpace = NamespaceDeclaration(nameSpaceName)
.WithMembers(members.Apply(List));
return CompilationUnit().WithMembers(nameSpace.Apply(SingletonList<MemberDeclarationSyntax>));
}
private static RecordDeclarationSyntax CreateWrapperRecord(String structName,
String wrappedTypeName,
IEnumerable<MemberDeclarationSyntax> properties)
{
const String self = "Self";
var wrappedType = IdentifierName(wrappedTypeName);
var recId = Identifier(structName);
var modifiers = TokenList(Token(SyntaxKind.PublicKeyword), Token(ReadOnlyKeyword));
//properties.Prepend<MemberDeclarationSyntax>(selfProperty).Prepend(constructor).Apply(List);
// var structMembers = new MemberDeclarationSyntax[] { selfProperty, constructor }
// .Apply(List);
var recMembers = properties.Apply(List);
var recParams = self
.Apply(Identifier)
.Apply(Parameter)
.WithType(wrappedType)
.Apply(SingletonSeparatedList)
.Apply(ParameterList);
return RecordDeclaration(Token(RecordKeyword), recId)
.WithModifiers(modifiers)
.WithClassOrStructKeyword(Token(StructKeyword))
.WithParameterList(recParams)
.WithModifiers(modifiers)
.WithOpenBraceToken(Token(OpenBraceToken))
.WithMembers(recMembers)
.WithCloseBraceToken(Token(CloseBraceToken));
//return /*StructDeclaration(structId)*/
}
private static PropertyDeclarationSyntax CreateAccessorProperty(TypeSyntax propertyTypeName,
String propertyName,
String parentPropertyName,
Boolean writeable)
{
const String self = "Self";
const String value = "value";
var propertyModifier = SyntaxKind
.PublicKeyword
.Apply(Token)
.Apply(TokenList);
var semicolonToken = Token(SemicolonToken);
var accessExpression = MemberAccessExpression
(
SimpleMemberAccessExpression,
IdentifierName(self),
IdentifierName(parentPropertyName)
);
var getterBody = ArrowExpressionClause(accessExpression);
var getter = GetAccessorDeclaration
.Apply(AccessorDeclaration)
.WithExpressionBody(getterBody)
.WithSemicolonToken(semicolonToken);
var setterBody = AssignmentExpression(SimpleAssignmentExpression, accessExpression, IdentifierName(value))
.Apply(ArrowExpressionClause);
var setter = SetAccessorDeclaration
.Apply(AccessorDeclaration)
.WithExpressionBody(setterBody)
.WithSemicolonToken(semicolonToken);
var accessors = writeable
? new[] { getter, setter }
: new[] { getter };
var accessorsList = accessors
.Apply(List)
.Apply(AccessorList);
var property = PropertyDeclaration
(
type : propertyTypeName,
identifier: Identifier(propertyName)
);
return property
.WithModifiers(propertyModifier)
.WithAccessorList(accessorsList);
}
private static PropertyDeclarationSyntax CreateWrapperProperty(TypeSyntax type, SyntaxToken identifier)
{
var modifiers = SyntaxKind
.PublicKeyword
.Apply(Token)
.Apply(TokenList);
var self = IdentifierName("Self")
.Apply(Argument)
.Apply(SingletonSeparatedList)
.Apply(ArgumentList);
var body = ObjectCreationExpression(type)
.WithArgumentList(self)
.Apply(ArrowExpressionClause);
return PropertyDeclaration(type, identifier)
.WithModifiers(modifiers)
.WithExpressionBody(body)
.WithSemicolonToken(Token(SemicolonToken));
}
}