Generator (Experiment)

This commit is contained in:
ig 2023-06-13 13:03:09 +02:00
parent cdaed39301
commit 62ff63c0bb
13 changed files with 976 additions and 0 deletions

View File

@ -0,0 +1,16 @@
using System.Diagnostics.CodeAnalysis;
namespace InnovEnergy.Lib.SrcGen.Attributes;
[SuppressMessage("ReSharper", "UnusedTypeParameter")]
[AttributeUsage(AttributeTargets.Struct, AllowMultiple = true)]
public class Generate<T> : Attribute
{
public String[] Defines { get; }
public Generate(params String[] defines)
{
Defines = defines;
}
}

View File

@ -0,0 +1,8 @@
namespace InnovEnergy.Lib.SrcGen.Attributes;
public class NestProperties : Attribute
{
public String StructName { get; }
public NestProperties(String structName) => StructName = structName;
}

View File

@ -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);
}
}

View File

@ -0,0 +1,81 @@
using Microsoft.CodeAnalysis;
namespace InnovEnergy.Lib.SrcGen;
public readonly ref struct Rewriter<TRoot, TNode> where TRoot : SyntaxNode where TNode : SyntaxNode
{
internal readonly TRoot Root;
internal readonly IEnumerable<TNode> Descendants;
internal Rewriter(TRoot root, IEnumerable<TNode> descendants)
{
Root = root;
Descendants = descendants;
}
public Rewriter<TRoot, TNode> Where(Func<TNode, Boolean> predicate)
{
return new Rewriter<TRoot, TNode>(Root, Descendants.Where(predicate));
}
public Rewriter<TRoot, T> OfType<T>() where T : TNode
{
return new Rewriter<TRoot, T>(Root, Descendants.OfType<T>());
}
public Rewriter<TRoot, TNode> HasAncestor<T>() where T : SyntaxNode
{
return new Rewriter<TRoot, TNode>(Root, Descendants.Where(d => d.Ancestors().OfType<T>().Any()));
}
public Rewriter<TRoot, TNode> HasParent<T>() where T : SyntaxNode
{
return new Rewriter<TRoot, TNode>(Root, Descendants.Where(d => d.Parent is T));
}
public Rewriter<TRoot, TNode> SelectNodes(Func<TNode, IEnumerable<TNode>> nodes)
{
return new Rewriter<TRoot, TNode>(Root, Descendants.SelectMany(nodes));
}
public Rewriter<TRoot, T> SelectNode<T>(Func<TNode, T> node) where T: SyntaxNode
{
return new Rewriter<TRoot, T>(Root, Descendants.Select(node));
}
public Rewriter<TRoot, T> GetAncestor<T>() where T : SyntaxNode
{
return SelectNode(n => n.Ancestors().OfType<T>().First());
}
public TRoot Replace(SyntaxNode syntaxNode)
{
return Root.ReplaceNodes(Descendants, (_, _) => syntaxNode);
}
public TRoot Replace(Func<TNode, SyntaxNode> 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<R, SyntaxNode> EditNodes<R>(this R root) where R : SyntaxNode
{
return new Rewriter<R, SyntaxNode>(root, root.DescendantNodes());
}
}

View File

@ -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<StructDeclarationSyntax>();
var defineArgs = attribute.ArgumentList?.Arguments ?? Enumerable.Empty<SyntaxNode>();
var filePath = derivedType.SyntaxTree.FilePath;
var derivedName = derivedType.Identifier.ValueText;
var baseRef = attribute.GetGenericArgument();
var baseType = compilation.GetDefinition<StructDeclarationSyntax>(baseRef);
var baseName = baseType.Identifier.ValueText;
var baseFile = baseType.GetAncestor<CompilationUnitSyntax>();
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();
}
}
}
}
}

View File

@ -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<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));
}
}

View File

@ -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<String>;
//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<String>("", strings.Select(s => new ForwardTree<String>(s)).ToList());
static ForwardTree<String> GroupByPrefix(ForwardTree<String> tree)
{
var newChildren = tree
.Children
.Where(s => s.Node.Length > 0)
.GroupBy(s => s.Node[..1], s => new ForwardTree<String>(s.Node[1..]))
.Select(g => GroupByPrefix(new ForwardTree<String>(g.Key, g.ToList())))
.ToList();
return newChildren.Count == 0
? tree // leaf
: new ForwardTree<String>(tree.Node, newChildren);
}
ForwardTree<String> ConcatUnaryNodes(ForwardTree<String> tree)
{
return tree.Children.Count == 1 && tree.Children[0] is var child
? new ForwardTree<String>(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<String>, ForwardTree<String>> RewriteTree(Func<ForwardTree<String>, ForwardTree<String>> 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<String>(s.Node[1..])))
.Select(g => new ForwardTree<String>(g.Key, g.ToList()))
.ToList();
if (groups.Count == 0)
return n;
return new ForwardTree<String>(n.Node, groups);
});
}
// static IReadOnlyList<StringTree> GetChildren(IEnumerable<String> 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<S, S> (Func<S, S> 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<Int32, Int32> (Func<Int32, Int32> 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();
//

View File

@ -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<GenericNameSyntax>()
.Any(n => n.Identifier.ValueText == "Extends");
}
public static T GetAncestor<T>(this SyntaxNode sn) where T: SyntaxNode
{
return sn
.Ancestors()
.OfType<T>()
.First();
}
public static IEnumerable<AttributeSyntax> GetGenerateAttributes(this SyntaxNode sn)
{
return sn
.DescendantNodes()
.Where(n => n is AttributeSyntax { Name : GenericNameSyntax { Identifier.ValueText: "Generate" } })
.OfType<AttributeSyntax>();
}
public static IEnumerable<AttributeSyntax> GetNestPropertiesAttributes(this SyntaxNode sn)
{
return sn
.DescendantNodes()
.Where(n => n is AttributeSyntax { Name : IdentifierNameSyntax { Identifier.ValueText: "NestProperties" } })
.OfType<AttributeSyntax>();
}
public static IEnumerable<AttributeSyntax> GetAttributes(this MemberDeclarationSyntax member)
{
return member
.AttributeLists
.SelectMany(al => al.Attributes);
}
// public static T Debug<T>(this T t, String? title = null)
// {
// var prefix = title is null ? "" : title + ": ";
// var contents = t is null ? "<null>" : t.ToString();
//
// File.AppendAllText(DebugFile, prefix + contents + '\n');
// return t;
// }
public static IEnumerable<AttributeSyntax> 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<AttributeSyntax>();
}
public static IEnumerable<T> ChildNodes<T>(this SyntaxNode node) where T : SyntaxNode
{
return node.ChildNodes().OfType<T>();
}
public static IEnumerable<T> DescendantNodes<T>(this SyntaxNode node) where T : SyntaxNode
{
return node.DescendantNodes().OfType<T>();
}
[SuppressMessage("ReSharper", "PossibleMultipleEnumeration")]
public static T ReplaceIdentifiers<T>(this T root, String idToReplace, String replacementId) where T: SyntaxNode
{
var newIdentifier = SyntaxFactory.Identifier(replacementId);
return root
.EditNodes()
.OfType<IdentifierNameSyntax>()
.Where(n => n.Identifier.ValueText == idToReplace)
.Replace(n => n.WithIdentifier(newIdentifier).WithTriviaFrom(n))
.EditNodes()
.OfType<ConstructorDeclarationSyntax>()
.Where(n => n.Identifier.ValueText == idToReplace)
.Replace(n => n.WithIdentifier(newIdentifier).WithTriviaFrom(n))
.EditNodes()
.OfType<TypeDeclarationSyntax>()
.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<StructDeclarationSyntax>()
.Where(s => !s.Modifiers.Contains(PartialKeyword))
.Replace(s => s.AddModifiers(PartialKeyword));
}
public static T GetDefinition<T>(this Compilation compilation, TypeSyntax baseTypeDeclaration) where T : SyntaxNode
{
return compilation
.GetDefinitions(baseTypeDeclaration)
.OfType<T>()
.Single();
}
public static IEnumerable<T> GetDefinitions<T>(this Compilation compilation, TypeSyntax baseTypeDeclaration) where T : SyntaxNode
{
return compilation
.GetDefinitions(baseTypeDeclaration)
.OfType<T>();
}
public static IEnumerable<SyntaxNode> GetDefinitions(this Compilation compilation, TypeSyntax baseTypeDeclaration)
{
return compilation
.GetSemanticModel(baseTypeDeclaration.SyntaxTree)
.GetTypeInfo(baseTypeDeclaration)
.Type?
.DeclaringSyntaxReferences
.Select(r => r.GetSyntax())
?? Enumerable.Empty<SyntaxNode>();
}
public static IEnumerable<TypeSyntax> GetGenericArguments(this AttributeSyntax attribute) => attribute.Name switch
{
GenericNameSyntax gns => gns.TypeArgumentList.Arguments,
_ => Enumerable.Empty<TypeSyntax>()
};
public static TypeSyntax GetGenericArgument(this AttributeSyntax attribute) => attribute.GetGenericArguments().Single();
public static CompilationUnitSyntax RemoveNotImplemented(this CompilationUnitSyntax baseFile)
{
return baseFile
.EditNodes()
.OfType<IdentifierNameSyntax>()
.Where(i => i.Identifier.ValueText == nameof(NotImplementedException))
.HasAncestor<ThrowExpressionSyntax>()
.GetAncestor<MemberDeclarationSyntax>()
.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}";
}
}

View File

@ -0,0 +1,46 @@
using InnovEnergy.Lib.Utils;
using Sawmill;
namespace InnovEnergy.Lib.SrcGen.Trees;
public class ForwardTree<T> : IRewritable<ForwardTree<T>> where T : notnull
{
public T Node { get; }
public IReadOnlyList<ForwardTree<T>> Children { get; }
public Boolean IsLeaf => Children.Count == 0;
public ForwardTree(T node) : this(node, Array.Empty<ForwardTree<T>>())
{
}
public ForwardTree(T node, IReadOnlyList<ForwardTree<T>> children)
{
Node = node;
Children = children;
}
public ForwardTree<T> WithNode(T node) => new ForwardTree<T>(node, Children);
public ForwardTree<T> WithNode(Func<T, T> makeNode) => new ForwardTree<T>(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<ForwardTree<T>>.CountChildren() => Children.Count;
void IRewritable<ForwardTree<T>>.GetChildren(Span<ForwardTree<T>> childrenReceiver)
{
for (var i = 0; i < Children.Count; i++)
childrenReceiver[i] = Children[i];
}
ForwardTree<T> IRewritable<ForwardTree<T>>.SetChildren(ReadOnlySpan<ForwardTree<T>> newChildren)
{
return new ForwardTree<T>(Node, newChildren.ToArray());
}
}

View File

@ -0,0 +1,41 @@
using Sawmill;
namespace InnovEnergy.Lib.SrcGen.Trees;
using T = ForwardTree<String>;
public static class PrefixTree
{
public static Tree<String> Create(IEnumerable<String> 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<String>(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<String> tree)
{
return tree.Children.Count == 1 && tree.Children[0] is var child
? new T(tree.Node + child.Node, child.Children)
: tree;
}
}

View File

@ -0,0 +1,47 @@
using InnovEnergy.Lib.Utils;
using Sawmill;
namespace InnovEnergy.Lib.SrcGen.Trees;
public class Tree<T> :IRewritable<Tree<T>> where T : notnull
{
private readonly ForwardTree<T> _Tree;
public Tree<T>? Parent { get; }
public Boolean IsRoot => Parent is null;
public Boolean IsLeaf => !Children.Any();
public Tree(ForwardTree<T> tree, Tree<T>? parent = default)
{
_Tree = tree;
Parent = parent;
}
public T Node => _Tree.Node;
public IEnumerable<Tree<T>> Children => _Tree
.Children
.Select(c => new Tree<T>(c, this));
public IEnumerable<Tree<T>> Ancestors => Parent.Unfold(t => t.Parent);
public override String ToString() => _Tree.ToString();
public Tree<T> Root => this.Unfold(t => t.Parent).Last();
Int32 IRewritable<Tree<T>>.CountChildren() => _Tree.Children.Count;
void IRewritable<Tree<T>>.GetChildren(Span<Tree<T>> childrenReceiver)
{
var i = 0;
foreach (var child in Children)
childrenReceiver[i++] = child;
}
Tree<T> IRewritable<Tree<T>>.SetChildren(ReadOnlySpan<Tree<T>> newChildren)
{
var forwardTree = new ForwardTree<T>(Node, _Tree.Children.ToArray());
return new Tree<T>(forwardTree, Parent);
}
}

View File

@ -0,0 +1,34 @@
using InnovEnergy.Lib.Utils;
namespace InnovEnergy.Lib.SrcGen.Trees;
public readonly struct TreeStruct<T> where T : notnull
{
[Obsolete] public TreeStruct() => throw new Exception("Forbidden");
public TreeStruct(T node, Func<T, IEnumerable<T>> getChildren)
{
Node = node;
_GetChildren = getChildren;
}
public IEnumerable<TreeStruct<T>> Children
{
get
{
var getChildren = _GetChildren;
return _GetChildren(Node).Select(c => new TreeStruct<T>(c, getChildren));
}
}
public T Node { get; }
private readonly Func<T, IEnumerable<T>> _GetChildren;
public IEnumerable<T> TraverseDepthFirstPostOrder() => TreeTraversal.TraverseDepthFirstPostOrder(Node, _GetChildren);
public IEnumerable<T> TraverseDepthFirstPreOrder() => TreeTraversal.TraverseDepthFirstPreOrder(Node, _GetChildren);
public IEnumerable<T> TraverseBreadthFirst() => TreeTraversal.TraverseBreadthFirst(Node, _GetChildren);
}

View File

@ -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";
}
}