diff --git a/csharp/Lib/SrcGen/Attributes/Generate.cs b/csharp/Lib/SrcGen/Attributes/Generate.cs new file mode 100644 index 000000000..f2161b573 --- /dev/null +++ b/csharp/Lib/SrcGen/Attributes/Generate.cs @@ -0,0 +1,16 @@ + +using System.Diagnostics.CodeAnalysis; + +namespace InnovEnergy.Lib.SrcGen.Attributes; + +[SuppressMessage("ReSharper", "UnusedTypeParameter")] +[AttributeUsage(AttributeTargets.Struct, AllowMultiple = true)] +public class Generate : Attribute +{ + public String[] Defines { get; } + + public Generate(params String[] defines) + { + Defines = defines; + } +} \ No newline at end of file diff --git a/csharp/Lib/SrcGen/Attributes/NestProperties.cs b/csharp/Lib/SrcGen/Attributes/NestProperties.cs new file mode 100644 index 000000000..afb6fb6a0 --- /dev/null +++ b/csharp/Lib/SrcGen/Attributes/NestProperties.cs @@ -0,0 +1,8 @@ +namespace InnovEnergy.Lib.SrcGen.Attributes; + +public class NestProperties : Attribute +{ + public String StructName { get; } + + public NestProperties(String structName) => StructName = structName; +} \ No newline at end of file diff --git a/csharp/Lib/SrcGen/ConfigConnectionStringCodeFixProvider.cs b/csharp/Lib/SrcGen/ConfigConnectionStringCodeFixProvider.cs new file mode 100644 index 000000000..0d9925e9a --- /dev/null +++ b/csharp/Lib/SrcGen/ConfigConnectionStringCodeFixProvider.cs @@ -0,0 +1,33 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CodeActions; +using Microsoft.CodeAnalysis.CodeRefactorings; + +namespace InnovEnergy.Lib.SrcGen; + + +[ExportCodeRefactoringProvider(LanguageNames.CSharp, Name = nameof(IeCodeRefactoringProvider))] +public sealed class IeCodeRefactoringProvider : CodeRefactoringProvider +{ + + // NOT WORKING + public override async Task ComputeRefactoringsAsync(CodeRefactoringContext context) + { + // var root = await context + // .Document + // .GetSyntaxRootAsync(context.CancellationToken) + // .ConfigureAwait(false); + + await File.WriteAllTextAsync("/home/eef/sync/work/Code/innovenergy/git/csharp/Lib/SrcGen/test.txt", context.Span.ToString()); + + var action = CodeAction.Create("IeAction", async token => + { + var text = await context.Document.GetTextAsync(token); + + var modified = text.Replace(0, 0, "// " + context.Span + "\n"); + + return context.Document.WithText(modified); + }); + + context.RegisterRefactoring(action); + } +} diff --git a/csharp/Lib/SrcGen/Editor.cs b/csharp/Lib/SrcGen/Editor.cs new file mode 100644 index 000000000..cc6cb4538 --- /dev/null +++ b/csharp/Lib/SrcGen/Editor.cs @@ -0,0 +1,81 @@ +using Microsoft.CodeAnalysis; + +namespace InnovEnergy.Lib.SrcGen; + +public readonly ref struct Rewriter where TRoot : SyntaxNode where TNode : SyntaxNode +{ + internal readonly TRoot Root; + internal readonly IEnumerable Descendants; + + internal Rewriter(TRoot root, IEnumerable descendants) + { + Root = root; + Descendants = descendants; + } + + public Rewriter Where(Func predicate) + { + return new Rewriter(Root, Descendants.Where(predicate)); + } + + public Rewriter OfType() where T : TNode + { + return new Rewriter(Root, Descendants.OfType()); + } + + public Rewriter HasAncestor() where T : SyntaxNode + { + return new Rewriter(Root, Descendants.Where(d => d.Ancestors().OfType().Any())); + } + + public Rewriter HasParent() where T : SyntaxNode + { + return new Rewriter(Root, Descendants.Where(d => d.Parent is T)); + } + + public Rewriter SelectNodes(Func> nodes) + { + return new Rewriter(Root, Descendants.SelectMany(nodes)); + } + + public Rewriter SelectNode(Func node) where T: SyntaxNode + { + return new Rewriter(Root, Descendants.Select(node)); + } + + public Rewriter GetAncestor() where T : SyntaxNode + { + return SelectNode(n => n.Ancestors().OfType().First()); + } + + public TRoot Replace(SyntaxNode syntaxNode) + { + return Root.ReplaceNodes(Descendants, (_, _) => syntaxNode); + } + + public TRoot Replace(Func map) + { + return Root.ReplaceNodes(Descendants, (_, n) => map(n)); + } + + public TRoot Remove() => Remove(SyntaxRemoveOptions.KeepNoTrivia); + + public TRoot Remove(SyntaxRemoveOptions options) => Root.RemoveNodes(Descendants, options)!; +} + +public static class Rewrite +{ + public static Rewriter EditNodes(this R root) where R : SyntaxNode + { + return new Rewriter(root, root.DescendantNodes()); + } +} + + + + + + + + + diff --git a/csharp/Lib/SrcGen/GenerateAttributes.cs b/csharp/Lib/SrcGen/GenerateAttributes.cs new file mode 100644 index 000000000..0afdcde63 --- /dev/null +++ b/csharp/Lib/SrcGen/GenerateAttributes.cs @@ -0,0 +1,74 @@ +using InnovEnergy.Lib.Utils; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace InnovEnergy.Lib.SrcGen; + +public static class GenerateAttributes +{ + 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.GetGenerateAttributes()) + .ForEach(GenerateFile); + + String GenerateFile(AttributeSyntax attribute) + { + var derivedType = attribute.GetAncestor(); + var defineArgs = attribute.ArgumentList?.Arguments ?? Enumerable.Empty(); + var filePath = derivedType.SyntaxTree.FilePath; + var derivedName = derivedType.Identifier.ValueText; + var baseRef = attribute.GetGenericArgument(); + var baseType = compilation.GetDefinition(baseRef); + var baseName = baseType.Identifier.ValueText; + var baseFile = baseType.GetAncestor(); + var generatedPath = filePath.GetGeneratedPath(baseName); + + var defines = defineArgs + .Select(n => n.ToString()) + .Select(s => s.Replace("\"", "").ToUpper()) + .Select(s => $"#define {s}"); + + Console.WriteLine($"Generating {generatedPath}"); + + var code = GenerateCode(); + + var fileContents = Utils.AutoGeneratedMessage(nameof(GenerateAttributes)) + + defines.JoinLines() + "\n" + + code; + + File.WriteAllText(generatedPath, fileContents); + + return generatedPath; + + + String GenerateCode() + { + try + { + return baseFile + .AddPartialModifier() + .ReplaceIdentifiers(baseName, derivedName) + .RemoveNotImplemented() + .GetText() + .ToString(); + } + catch (Exception e) + { + return $"Failed to generate source for {filePath}\n\n{e}" + .SplitLines() + .Select(l => $"// {l}") + .JoinLines(); + } + } + + } + } +} \ No newline at end of file diff --git a/csharp/Lib/SrcGen/NestProperties.cs b/csharp/Lib/SrcGen/NestProperties.cs new file mode 100644 index 000000000..bafcdd0d8 --- /dev/null +++ b/csharp/Lib/SrcGen/NestProperties.cs @@ -0,0 +1,251 @@ +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(); + var nameSpace = attribute.GetAncestor(); + var filePath = type.SyntaxTree.FilePath; + var generatedPath = filePath.GetGeneratedPath(nameof(NestProperties)); + + var recordProperties = type + .Members + .OfType() + .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(); + + prefixTree.Fold, 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() + .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)); + } + + private static RecordDeclarationSyntax CreateWrapperRecord(String structName, + String wrappedTypeName, + IEnumerable properties) + { + const String self = "Self"; + + var wrappedType = IdentifierName(wrappedTypeName); + var recId = Identifier(structName); + var modifiers = TokenList(Token(SyntaxKind.PublicKeyword), Token(ReadOnlyKeyword)); + + + + + //properties.Prepend(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)); + } +} + + + + + + + \ No newline at end of file diff --git a/csharp/Lib/SrcGen/Program.cs b/csharp/Lib/SrcGen/Program.cs new file mode 100644 index 000000000..be1903bb6 --- /dev/null +++ b/csharp/Lib/SrcGen/Program.cs @@ -0,0 +1,154 @@ +using InnovEnergy.Lib.SrcGen.Trees; +using Microsoft.Build.Locator; +using Microsoft.CodeAnalysis.MSBuild; +using Sawmill; + +namespace InnovEnergy.Lib.SrcGen; + + + +using S = IEnumerable; + +//public record Node(String Prefix, Node Children); + +public static class Program +{ + + private const String GeneratedCodeMsg = "// This file has been automatically generated, do not edit!\n\n"; + + public static async Task Main(String[] args) + { + //Nest(); + + var projectPath = args.FirstOrDefault() + ?? "/home/eef/sync/work/Code/innovenergy/git/csharp/Lib/Devices/Battery48TL/Battery48TL.csproj"; + + if (projectPath is null) + throw new ArgumentException(nameof(projectPath)); + + MSBuildLocator.RegisterDefaults(); // WTF + + using var workspace = MSBuildWorkspace.Create(); + await workspace.OpenProjectAsync(projectPath); + + var solution = workspace.CurrentSolution; + var project = solution.Projects.Single(p => p.FilePath == projectPath); + var compilation = await project.GetCompilationAsync(); + + if (compilation is null) + throw new Exception($"Project {projectPath} failed to build!"); + + await GenerateAttributes.Generate(project, compilation); + await NestProperties.Generate(project, compilation); + } + + + private static void Nest() + { + var strings = new[] + { + "AcCurrent", + "AcVoltage", + "AcPowerReactive", + "AcPowerActive", + "DcCurrent", + "DcVoltage", + "Temperature", + "Soc" + }; + + + var tree1 = new ForwardTree("", strings.Select(s => new ForwardTree(s)).ToList()); + + static ForwardTree GroupByPrefix(ForwardTree tree) + { + var newChildren = tree + .Children + .Where(s => s.Node.Length > 0) + .GroupBy(s => s.Node[..1], s => new ForwardTree(s.Node[1..])) + .Select(g => GroupByPrefix(new ForwardTree(g.Key, g.ToList()))) + .ToList(); + + return newChildren.Count == 0 + ? tree // leaf + : new ForwardTree(tree.Node, newChildren); + } + + + ForwardTree ConcatUnaryNodes(ForwardTree tree) + { + return tree.Children.Count == 1 && tree.Children[0] is var child + ? new ForwardTree(tree.Node + child.Node, child.Children) + : tree; + } + + var tree2 = tree1.Rewrite(GroupByPrefix); + var tree3 = tree2.Rewrite(ConcatUnaryNodes); + + + Console.WriteLine(tree1); + Console.WriteLine("\n======\n"); + Console.WriteLine(tree2); + Console.WriteLine("\n======\n"); + Console.WriteLine(tree3); + } + + + private static Func, ForwardTree> RewriteTree(Func, ForwardTree> rec) + => tree + => tree.RewriteIter(n => + { + if (n.Children.Count == 0 || n.Children.SelectMany(c => c.Children).Any()) + return n; + + var groups = n + .Children + .Where(s => s.Node.Length > 0) + .GroupBy(s => s.Node[..1], s => rec(new ForwardTree(s.Node[1..]))) + .Select(g => new ForwardTree(g.Key, g.ToList())) + .ToList(); + + if (groups.Count == 0) + return n; + + return new ForwardTree(n.Node, groups); + }); +} + + + + + // static IReadOnlyList GetChildren(IEnumerable strings) + // { + // return strings + // .Where(s => s.Length > 0) + // .GroupBy(s => s[..1], s => s[1..]) + // .Select(l => + // { + // var children = GetChildren(l); + // return children.Count == 1 + // ? new StringTree(l.Key + children[0].Node, children[0].Children) + // : new StringTree(l.Key, children); + // }) + // .ToList(); + // } + //var t2 = new Tree<(String prefix, String[] strings)>(stringNode, ); + + // var strs = Func (Func rec) + // => S (ss) + // => ss.Where(s => s.Length > 0) + // .GroupBy(s => s[..1], s => s[1..]) + // .SelectMany(g=> !g.Any() ? new[] { g.Key } : rec(g)) + // + // ; + // + // var g = Func (Func h) + // => Int32 (Int32 m) + // => m > 1 ? h(m - 1) + h(m - 2) : m; + // + // var fib = Combinator.Y(g); + // var fib2 = Combinator.Y(strs); + // + // var y = fib(5); + // var x = fib2(strings).ToList(); + // \ No newline at end of file diff --git a/csharp/Lib/SrcGen/SyntaxUtils.cs b/csharp/Lib/SrcGen/SyntaxUtils.cs new file mode 100644 index 000000000..bac279f52 --- /dev/null +++ b/csharp/Lib/SrcGen/SyntaxUtils.cs @@ -0,0 +1,182 @@ +using System.Diagnostics.CodeAnalysis; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace InnovEnergy.Lib.SrcGen; + +public static class SyntaxUtils +{ + // const String DebugFile = "/home/eef/sync/work/Code/innovenergy/git/csharp/Lib/SrcGen/Debug.txt"; + // + // static SyntaxUtils() => File.WriteAllText(DebugFile, ""); + + public static Boolean HasExtendsAttribute(this TypeDeclarationSyntax td) + { + return td + .GetAttributes() + .Select(a => a.Name) + .OfType() + .Any(n => n.Identifier.ValueText == "Extends"); + } + + + public static T GetAncestor(this SyntaxNode sn) where T: SyntaxNode + { + return sn + .Ancestors() + .OfType() + .First(); + } + + + public static IEnumerable GetGenerateAttributes(this SyntaxNode sn) + { + return sn + .DescendantNodes() + .Where(n => n is AttributeSyntax { Name : GenericNameSyntax { Identifier.ValueText: "Generate" } }) + .OfType(); + } + + + public static IEnumerable GetNestPropertiesAttributes(this SyntaxNode sn) + { + return sn + .DescendantNodes() + .Where(n => n is AttributeSyntax { Name : IdentifierNameSyntax { Identifier.ValueText: "NestProperties" } }) + .OfType(); + } + + + + + public static IEnumerable GetAttributes(this MemberDeclarationSyntax member) + { + return member + .AttributeLists + .SelectMany(al => al.Attributes); + } + + // public static T Debug(this T t, String? title = null) + // { + // var prefix = title is null ? "" : title + ": "; + // var contents = t is null ? "" : t.ToString(); + // + // File.AppendAllText(DebugFile, prefix + contents + '\n'); + // return t; + // } + + public static IEnumerable GetAttributes(this SyntaxNode node, String attributeName) + { + return node + .DescendantTokens() + .Where(st => st.ValueText == attributeName) + .Where(st => st.IsKind(SyntaxKind.IdentifierToken)) + .Select(st => st.Parent?.Parent) + .OfType(); + } + + public static IEnumerable ChildNodes(this SyntaxNode node) where T : SyntaxNode + { + return node.ChildNodes().OfType(); + } + + public static IEnumerable DescendantNodes(this SyntaxNode node) where T : SyntaxNode + { + return node.DescendantNodes().OfType(); + } + + [SuppressMessage("ReSharper", "PossibleMultipleEnumeration")] + public static T ReplaceIdentifiers(this T root, String idToReplace, String replacementId) where T: SyntaxNode + { + var newIdentifier = SyntaxFactory.Identifier(replacementId); + + return root + .EditNodes() + .OfType() + .Where(n => n.Identifier.ValueText == idToReplace) + .Replace(n => n.WithIdentifier(newIdentifier).WithTriviaFrom(n)) + + .EditNodes() + .OfType() + .Where(n => n.Identifier.ValueText == idToReplace) + .Replace(n => n.WithIdentifier(newIdentifier).WithTriviaFrom(n)) + + .EditNodes() + .OfType() + .Where(n => n.Identifier.ValueText == idToReplace) + .Replace(n => n.WithIdentifier(newIdentifier.WithTrailingTrivia(NewLine))); + } + + + private static readonly SyntaxTrivia NewLine = SyntaxFactory.SyntaxTrivia(SyntaxKind.EndOfLineTrivia, "\n"); + private static readonly SyntaxTrivia WhiteSpace = SyntaxFactory.SyntaxTrivia(SyntaxKind.WhitespaceTrivia, " "); + + private static readonly SyntaxToken PartialKeyword = SyntaxFactory + .Token(SyntaxKind.PartialKeyword) + .WithTrailingTrivia(WhiteSpace); + + public static CompilationUnitSyntax AddPartialModifier(this CompilationUnitSyntax baseFile) + { + return baseFile + .EditNodes() + .OfType() + .Where(s => !s.Modifiers.Contains(PartialKeyword)) + .Replace(s => s.AddModifiers(PartialKeyword)); + } + + + public static T GetDefinition(this Compilation compilation, TypeSyntax baseTypeDeclaration) where T : SyntaxNode + { + return compilation + .GetDefinitions(baseTypeDeclaration) + .OfType() + .Single(); + } + + public static IEnumerable GetDefinitions(this Compilation compilation, TypeSyntax baseTypeDeclaration) where T : SyntaxNode + { + return compilation + .GetDefinitions(baseTypeDeclaration) + .OfType(); + } + + public static IEnumerable GetDefinitions(this Compilation compilation, TypeSyntax baseTypeDeclaration) + { + return compilation + .GetSemanticModel(baseTypeDeclaration.SyntaxTree) + .GetTypeInfo(baseTypeDeclaration) + .Type? + .DeclaringSyntaxReferences + .Select(r => r.GetSyntax()) + ?? Enumerable.Empty(); + } + + public static IEnumerable GetGenericArguments(this AttributeSyntax attribute) => attribute.Name switch + { + GenericNameSyntax gns => gns.TypeArgumentList.Arguments, + _ => Enumerable.Empty() + }; + + public static TypeSyntax GetGenericArgument(this AttributeSyntax attribute) => attribute.GetGenericArguments().Single(); + + public static CompilationUnitSyntax RemoveNotImplemented(this CompilationUnitSyntax baseFile) + { + return baseFile + .EditNodes() + .OfType() + .Where(i => i.Identifier.ValueText == nameof(NotImplementedException)) + .HasAncestor() + .GetAncestor() + .Remove(); + } + + public static String GetGeneratedPath(this String path, String postfix = "generated") + { + var ext = Path.GetExtension(path); + var name = Path.GetFileNameWithoutExtension(path); + var dir = Path.GetDirectoryName(path); + + return $"{dir}/{name}.{postfix}{ext}"; + } +} \ No newline at end of file diff --git a/csharp/Lib/SrcGen/Trees/ForwardTree.cs b/csharp/Lib/SrcGen/Trees/ForwardTree.cs new file mode 100644 index 000000000..3776856c7 --- /dev/null +++ b/csharp/Lib/SrcGen/Trees/ForwardTree.cs @@ -0,0 +1,46 @@ +using InnovEnergy.Lib.Utils; +using Sawmill; + +namespace InnovEnergy.Lib.SrcGen.Trees; + + +public class ForwardTree : IRewritable> where T : notnull +{ + public T Node { get; } + public IReadOnlyList> Children { get; } + public Boolean IsLeaf => Children.Count == 0; + + public ForwardTree(T node) : this(node, Array.Empty>()) + { + } + + public ForwardTree(T node, IReadOnlyList> children) + { + Node = node; + Children = children; + } + + public ForwardTree WithNode(T node) => new ForwardTree(node, Children); + public ForwardTree WithNode(Func makeNode) => new ForwardTree(makeNode(Node), Children); + + public override String ToString() + { + var node = Node.ToString()!; + return Children.Aggregate(node, (s, c) => s + "\n" + c.ToString().Indent(node.Length)); + } + + Int32 IRewritable>.CountChildren() => Children.Count; + + void IRewritable>.GetChildren(Span> childrenReceiver) + { + for (var i = 0; i < Children.Count; i++) + childrenReceiver[i] = Children[i]; + } + + ForwardTree IRewritable>.SetChildren(ReadOnlySpan> newChildren) + { + return new ForwardTree(Node, newChildren.ToArray()); + } + + +} \ No newline at end of file diff --git a/csharp/Lib/SrcGen/Trees/PrefixTree.cs b/csharp/Lib/SrcGen/Trees/PrefixTree.cs new file mode 100644 index 000000000..40a50d421 --- /dev/null +++ b/csharp/Lib/SrcGen/Trees/PrefixTree.cs @@ -0,0 +1,41 @@ +using Sawmill; + +namespace InnovEnergy.Lib.SrcGen.Trees; + +using T = ForwardTree; + +public static class PrefixTree +{ + public static Tree Create(IEnumerable strings, String root = "") + { + var children = strings.Select(s => new T(s)).ToList(); + var tree = new T(root, children); + + var forwardTree = tree + .Rewrite(GroupByPrefix) + .Rewrite(ConcatUnaryNodes); + + return new Tree(forwardTree); + } + + private static T GroupByPrefix(T tree) + { + var newChildren = tree + .Children + .Where(s => s.Node.Length > 0) + .GroupBy(s => s.Node[..1], s => new T(s.Node[1..])) + .Select(g => GroupByPrefix(new T(g.Key, g.ToList()))) + .ToList(); + + return newChildren.Count == 0 + ? tree // leaf + : new T(tree.Node, newChildren); + } + + private static T ConcatUnaryNodes(ForwardTree tree) + { + return tree.Children.Count == 1 && tree.Children[0] is var child + ? new T(tree.Node + child.Node, child.Children) + : tree; + } +} diff --git a/csharp/Lib/SrcGen/Trees/Tree.cs b/csharp/Lib/SrcGen/Trees/Tree.cs new file mode 100644 index 000000000..3e13a5bbf --- /dev/null +++ b/csharp/Lib/SrcGen/Trees/Tree.cs @@ -0,0 +1,47 @@ +using InnovEnergy.Lib.Utils; +using Sawmill; + +namespace InnovEnergy.Lib.SrcGen.Trees; + +public class Tree :IRewritable> where T : notnull +{ + private readonly ForwardTree _Tree; + public Tree? Parent { get; } + + public Boolean IsRoot => Parent is null; + public Boolean IsLeaf => !Children.Any(); + + public Tree(ForwardTree tree, Tree? parent = default) + { + _Tree = tree; + Parent = parent; + } + + public T Node => _Tree.Node; + + public IEnumerable> Children => _Tree + .Children + .Select(c => new Tree(c, this)); + + public IEnumerable> Ancestors => Parent.Unfold(t => t.Parent); + + public override String ToString() => _Tree.ToString(); + + public Tree Root => this.Unfold(t => t.Parent).Last(); + + Int32 IRewritable>.CountChildren() => _Tree.Children.Count; + + void IRewritable>.GetChildren(Span> childrenReceiver) + { + var i = 0; + foreach (var child in Children) + childrenReceiver[i++] = child; + } + + Tree IRewritable>.SetChildren(ReadOnlySpan> newChildren) + { + var forwardTree = new ForwardTree(Node, _Tree.Children.ToArray()); + + return new Tree(forwardTree, Parent); + } +} \ No newline at end of file diff --git a/csharp/Lib/SrcGen/Trees/TreeStruct.cs b/csharp/Lib/SrcGen/Trees/TreeStruct.cs new file mode 100644 index 000000000..b69be66e4 --- /dev/null +++ b/csharp/Lib/SrcGen/Trees/TreeStruct.cs @@ -0,0 +1,34 @@ + +using InnovEnergy.Lib.Utils; + +namespace InnovEnergy.Lib.SrcGen.Trees; + +public readonly struct TreeStruct where T : notnull +{ + [Obsolete] public TreeStruct() => throw new Exception("Forbidden"); + + public TreeStruct(T node, Func> getChildren) + { + Node = node; + _GetChildren = getChildren; + } + + public IEnumerable> Children + { + get + { + var getChildren = _GetChildren; + return _GetChildren(Node).Select(c => new TreeStruct(c, getChildren)); + } + } + + public T Node { get; } + + private readonly Func> _GetChildren; + + public IEnumerable TraverseDepthFirstPostOrder() => TreeTraversal.TraverseDepthFirstPostOrder(Node, _GetChildren); + public IEnumerable TraverseDepthFirstPreOrder() => TreeTraversal.TraverseDepthFirstPreOrder(Node, _GetChildren); + public IEnumerable TraverseBreadthFirst() => TreeTraversal.TraverseBreadthFirst(Node, _GetChildren); + + +} \ No newline at end of file diff --git a/csharp/Lib/SrcGen/Utils.cs b/csharp/Lib/SrcGen/Utils.cs new file mode 100644 index 000000000..e75daf65c --- /dev/null +++ b/csharp/Lib/SrcGen/Utils.cs @@ -0,0 +1,9 @@ +namespace InnovEnergy.Lib.SrcGen; + +public static class Utils +{ + public static String AutoGeneratedMessage(String name) + { + return $"// This file has been automatically generated by {name}, do not edit!\n\n"; + } +} \ No newline at end of file